文章目录
- 问题描述
- 思路
- 代码实现
问题描述
有 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;
}