已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
由于求余的特殊性质(从0开始),建议把编号从0~n-1,最后结果+1即为原问题的解。
再次简化,将编号k的人定义为编号0,其他依次编号%k。
从而最后answer=(answer(简化后)+k)%n(这个好理解,就是编号要加回去嘛!!!!)
先说说程序猿(我)的思路(很遗憾写不漂亮):
当时第一反应就是循环链表,循环报数,去除结点得到结果,大概代码如下(仓促写的,也没好的逻辑性可言,大致略过就好,见谅)
node类:
public class Node {
public Node next;
public int value;
public Node(){};
public Node(Node next,int value){
this.next=next;
this.value=value;
}
}
实现类:
public class Function {
//测试用m,n
private static int testm=3;
private static int testn=8;
//生成循环链表并进行操作
public void function(int m,int n) {
Node head = new Node(null, 1);
Node node = head;
for (int i = 2; i <= n; i++) {
Node newnode = new Node(null, i);
node.next = newnode;
node = node.next;
}
node.next = head;
/*m-1的原因是为了使用报到m那个数的前一个数的结点,然后指向下下个结点及跳过报到m的那个数,其实感觉太仓促写的,比较简陋没考虑太多*/
Node nownode = head;
if (m == 1) {
System.out.println("answer:"+n);
} else {
for (int j = 1; j <= n - 1; j++) {
for (int i = 1; i <= m - 1; i++) {
if (i == m - 1) {
System.out.println("out:" + nownode.next.value);
nownode.next = nownode.next.next;
nownode = nownode.next;
} else {
nownode = nownode.next;
}
}
}
System.out.println("answer:" + nownode.value);
}
}
public static void main(String[] args){
Function f=new Function();
f.function(testm,testn);
}
}
一眼就能看出时间复杂度O(m*n),那么接下去就是优化咯\(^o^)/~
还是程序猿思维(比我厉害点的,我就不说名字了):
如果当前游戏中还有n1个人, 给这个n1个人编号 从1到n1 ,那么下一次出局的人会是编号 (m+n1-1)%n1+1 或者简化为(m-1)%n1+1(我觉得简化的好理解) 。
通过这样的优化可以快速找到每一轮需要出局的人,这种情况下如list来存储当前还在游戏中的人的功能就会存在一定的优势。顺带一提最后时间复杂度为O(n)。
开始渐渐接近数学家思维了:
如果当前游戏还有n个人,从1到n进行编号,并且这一轮编号为a 的人出局了
原始: 1 2 3 … a-1 a a+1 …n
a出局之后: 1 2 3 … a-1 a+1 …n
编号a-1的人的下一位人的从n变为了n+1
而报数顺序变成了如下结构 :
a+1 a+2 …n 1 2 3 …a-1
(啊哈,一个新的N-1约瑟夫环,这样就是开始做递推了不是么。)
给 a1+1,a1+2....1,2,...a1-1. 每个元素做映射:
a1+1 -> 1,
a1+2 -> 2,
.....
a1-1 -> n-1.
即F(n,m) -> F(n-1,m)
可以看出从n转换到n-1的时候,每个元素都进行了减少a1的循环映射,而且a1 =m%n(重要点!!!上个状态可以决定下个状态,动态规划!!!)
从而得到递推公式F(n,m) = ( F(n-1,m) + (m%n) -1 )%n + 1.(这边1的含义其实是为了实现程序求余结果直观化,重新编号了,都从0开始编号,但是数学思维为了说明方便都是以1开始!!!)
且F(1,m)=1;(其中F(n,m)为最终留下的数)
得到公式的你还有什么不会的呢?递归咯。。。
public int function2(int m,int n){
if (n==1){
return 1;
}else {
return (function2(m, n - 1) + (m % n) - 1) % n + 1;
}
}
(我和公式中的n,m位置不同,为了体现我和数学家不同的思维,哼。)
时间复杂度也降为O(n)了。
(说是转载,其实很大一部分都是我自己理解的,代码也是自己的,但是毕竟不是我研究出来的,就不算是原创了,祝君好运。)