文章目录
- 问题描述
- 思路
- 代码实现
问题描述
有 1~N 个数字,从 1~m 依次报数,数到 m 的数字要被删掉,求最后剩下的数字是?
思路
| 第一次报数 | 第二次报数 |
|---|---|
| 1 | n-m+1 |
| 2 | n-m+2 |
| … | … |
| m-2 | n-2 |
| m-1 | n-1 |
| m | 被删掉了 |
| m+1 | 1 |
| m+2 | 2 |
| … | … |
| n-1 | n-1-m |
| n | n-m |
通过上面的表格,我们可以发现这样的规律:
将某数字第一次报数设为 first ,第二次报数设为 second 。那么存在这样的关系:first=(second+m−1)%n+1first = (second + m - 1) \% n + 1first=(second+m−1)%n+1(公式一)
为什么不是 first=(second+m)%nfirst = (second + m) \% nfirst=(second+m)%n (公式二)呢?其实一开始我确实总结出来的是公式二,但是发现有个漏洞,数字编号是从 1 开始的,而公式二的编号是从 0 开始的,具体来说,就是当 second = n-m 时,first = 0 。可以看到并不符合实际,first=n 才对。换言之,也就是如果我们从 0 开始计数,那么公式二是可用的,如何从 0 开始计数呢? 答案就是把数字序列存到数组里嘛~
因此,将公式二可以进化为 first=(second+m)%n+1first = (second + m) \% n + 1first=(second+m)%n+1(公式三),但是简单的为公式二的结果 +1 ,就导致在公式二中,本来只有 second = n-m 结果不符合实际,而在公式三中,变成了只有 second = n-m 的结果符合实际,原本没问题的都变得有问题了……这是因为我们没有做到加减均衡,只有 +1,而没有 -1 。因此公式一应运而生~
那么我们可以得到这样的规律:
当n>1时,f(n)=(f(n−1)+m−1)%n+1当 n>1 时,f(n) = (f(n-1) + m - 1) \% n + 1当n>1时,f(n)=(f(n−1)+m−1)%n+1
当n=1时,f(1)=1当 n=1 时,f(1) = 1当n=1时,f(1)=1
解释一下就是,剩最后一个数的时候直接返回最后一次报数为 1 的数,反之则需要继续删除一个数。
而当 n-1=1 时,f(n) 也就意味着,最后一次报数为 1 的数,倒数第二次报数的时候它报的是几。
那么对于 f(n-1) 的调用也就意味着,本次报数为 n 的数,下一次报数为几。
说到这里已经很清楚了,显而易见的递归思想。
代码实现
int m;
int yuesefu(int n){if(n == 1) return 1; // 最后一次报数为 1,开始回溯return (yuesefu(n-1) + m - 1) % n + 1; // f(n-1)==1的时候开始回溯,求最后一次报数为 1 的数,第一次报数为几?
}// 还可以再简化
int m;
int yuesefu(int n){return n == 1 ? 1 : (yuesefu(n-1) + m - 1) % n + 1;
}// 如果将数字序列存入数组中,则可用公式二
int m;
int yuesefu(int n){return n == 0 ? 0 : (yuesefu(n-1) + m) % n;
}