题目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的时候,我的胜率是:
- 如果我的值 >= a且 < b,那么胜率是1.0;如果我的值 >= b,那么胜率是0.0
- 如果我的值小于a,那么我的胜率是的((i+2的胜率) + (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的题目要求不同
}
涉及知识
涉及到剃刀函数的就想函数在坐标系中的移动变换(左加右减,上加下减)