算法笔记(2)

题目1

定义何为步骤和?
比如680,680 + 68 + 6 = 754,680的步骤和叫754
给定一个正数num,判断它是不是某个数的步骤和

思路

单调性。思路过程:首先想到如果直接从步骤和num倒推原来的数很麻烦,进而想到从1计算步骤和到num,找匹配的,但是这种枚举太多了,然后想到能不能二分,进而想到看看能不能找到单调性。有这么一个结论:x的步骤和是A,y的步骤和是B,如果x > y,则A > B。因为步骤和的定义是首先加上这个数本身,然后依次累加这个数除以10后向下取整的结果,x > y,x/10 >= y /10,x/100 >= y /100......所以如果x > y,则A > B。这样我们就可以二分了。首先num二分之后看看二分的数的步骤和是否是num,如果小于num则往右二分,大于num则往左二分,直到没有数字了,就返回-1。根据步骤和的定义我们可以知道步骤和num一定大于原来的数字,所以0~num的范围实际是包含了原来的数字的,所以不必担心二分的范围内不包含原来的数字。

代码

int main()
{
	int num;
	cin >> num;
	int L = 0;
	int R = num;
	int mid;
	int flag = false;
	while (L <= R)
	{
		mid = L + (R - L) / 2;
		int stepNum = getStepNum(mid);
		if (stepNum < num)
		{
			L = mid + 1;
		}
		else if (stepNum > num)
		{
			R = mid - 1;
		}
		else
		{
			flag = true;
			cout << mid << endl;
		}
	}
	if (!flag)
	{
		cout << -1 << endl;
	}

	return 0;
}

涉及知识

找单调性,或者人为构建单调性,然后二分

题目2

给定一个数组arr,每个位置上的值代表机器人在当前位置时,可以选择直接跳到下标为 当前位置+当前位置的值以内 的位置。机器人从0位置开始,请问走到最后一个位置最少需要跳几步

思路

准备三个变量,变量step为当前跳了多少步,变量cur为当前跳step步最远可以到达的位置,变量next为如果跳step+1步最远可以到达的位置。机器人起始位置为arr[0],step起始值为0,跳0步最远也只能为0,所以cur的起始值为0,跳0+1步可以到达的最远位置就是arr[0]+0,所以next起始值为arr[0]。准备一个指针变量i,开始遍历数组,同时更新step、cur、next,更新的策略是:当我来到i位置时,先看跳step步的最远位置能不能覆盖到i,即cur是否大于等于i,如果是,那么看看i+arr[i]是否大于next的值决定next是否更新,如果cur < i,那么step+1,因为next就是step+1时能跳到的最远的位置,所以cur=next,而next继续看i+arr[i]是否大于当前next的值决定next是否更新。直到i越界,返回step

代码

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

int main()
{
	int n;
	cin >> n;
	vector<int> arr(n);
	int step = 0;
	int cur = 0;
	int next = 0;
	for (int i = 0; i < n; i++)
	{
		cin >> arr[i];
		if (cur < i)
		{
			step++;
			cur = next;
		}
		next = max(next, arr[i] + i);
	}
	cout << step << endl;
	return 0;
}

题目3

谷歌面试题扩展版
面值为1~N的牌组成一组,
每次你从组里等概率的抽出1~N中的一张
下次抽会换一个新的组,有无限组
当累加和 < a时,你将一直抽牌
当累加和 >= a且 < b时,你将获胜
当累加和 >= b时,你将失败
返回获胜的概率,给定的参数为N,a,b

思路

递归。当我现在的值为i的时候,我的胜率是:

  1. 如果我的值 >= a且 < b,那么胜率是1.0;如果我的值 >= b,那么胜率是0.0
  2. 如果我的值小于a,那么我的胜率是的((i+1的胜率) + (i+2的胜率)+ (i+3的胜率) + ... + (i+N的胜率)) / N,因为每次抽到的数字概率都是1 / N。

代码

double f(int cur, int N, int a, int b)
{
	if (cur >= a && cur < b)
	{
		return 1.0;
	}
	if (cur >= b)
	{
		return 0.0;
	}
	double w = 0.0;
	for (int i = 1; i <= N; i++)
	{
		w += f(cur + i, N, a, b);
	}
	return w / 10;
}

int main() 
{
	int N, a, b;
	cin >> N;
	cin >> a;
	cin >> b;
	cout << f(0, N, a, b) << endl;
	return 0;
}

优化扩展

for (int i = 1; i <= N; i++)
{
    w += f(cur + i, N, a, b);
}

在递归函数中有这样一个枚举行为,其实它是可以被省去的。具体看视频https://www.bilibili.com/video/BV1g3411i7of?p=150&vd_source=77d06bb648c4cce91c6939baa0595bcd P150 20:33

题目4

约瑟夫环问题

思路

证明思路很复杂,是通过Y = X % i这个函数变换来的。假设当前链表长度为i,数到m杀人,前一轮编号 = (后一轮编号 + m - 1) % i + 1
最后活下来的人在最后一轮的编号一定是1,链表长度为1,所以从后往前推,每次求出最后活下来的人在前一轮的编号,最后求出第一轮的时候这个人的编号即可

代码

//递归
int lastRamaining1(int n, int m)
{
    return getLive(n, m) - 1;    //-1是因为leetcode的题目要求不同
}
int getLive(int n, int m)    //n:剩n个人。m:数到m杀人
{
    if(n == 1)
    {
        return 1;
    }
    return (getLive(n - 1, m) + m - 1) % n + 1;
}

//迭代
int lastRamaining1(int n, int m)
{
    int ans = 1;
    int r = 1;
    while(r <= n)
    {
        ans = (ans + m - 1) % r + 1;
        r++;
    }
    return ans - 1;     //-1是因为leetcode的题目要求不同
}

涉及知识

涉及到剃刀函数的就想函数在坐标系中的移动变换(左加右减,上加下减)

题目5

思路

代码

题目6

思路

代码

posted @ 2022-10-03 09:15  小肉包i  阅读(28)  评论(0)    收藏  举报