思路1:链表模拟
普通解法,用链表的方法去模拟这个过程,N个人看作是N个链表节点,节点1指向节点2,节点2指向节点3,……,节点N-1指向节点N,节点N指向节点1,这样就形成了一个环。然后从节点1开始1、2、3……往下报数,每报到M,就把那个节点从环上删除。下一个节点接着从1开始报数。最终链表仅剩一个节点。它就是最终的胜利者。
缺点:
要模拟整个游戏过程,时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。
思路2:递推式
考虑下面的情景:
假如进行第10次游戏的时候某玩家胜利了,他记住了自己在开始时的位置为X,那下一轮开始时他会选择站在哪?
因为他知道在第11次游戏第一轮后肯定有一个人会淘汰,这时候就变成了10人的游戏,所以只要在一轮过后他站在了上一轮自己开始时候的位置就稳赢了!
由于游戏每一轮结束后每个人的位置相当于向左移动了M步,所以第11次开始他就直接站在上一轮开始位置靠右M个的位置,这样第11次游戏第一轮结束后每个玩家向左移动M位正好站在了他第10次游戏胜利的位置,这时候游戏人数也变成了10人,相当于完全变成了上一轮的游戏,所以这一次游戏他也肯定赢定了!
根据上面场景可以推出递推式:
因为求出的结果是数组中的下标,最终的编号还要加1
直接根据递推式写出的代码:
/*
* 直接用递推式求出胜利者编号!
*/
public int yuesefu2(int totalNum,int countNum) {
int p = 0;
for(int i =2;i<=totalNum;i++) {
p = (p+countNum)%i;
}
return p+1;
}
@Test
public void test1() {
System.out.println(yuesefu2(5, 2));//直接返回最终胜利玩家的编号3
}
}
利用公式求出每一轮移除的元素:
/**
* 最后剩下的数字
*/
import java.util.ArrayList;
import java.util.List;
public class LastNumberInCircle {
public void lastRemaining(int totalNum,int index){
//初始化人数
List<Integer> start = new ArrayList<Integer>();
for(int i = 0;i< totalNum;i++){
start.add(i);
}
//从第k个开始计数
int k = 0;
while(start.size() > 0){
k = k+ index;
//第m个人的索引位置
k = k %(start.size()) -1;
//判断是否到队尾
if(k < 0){
System.out.println(start.get(start.size()-1));
start.remove(start.size()-1);
k = 0;
}else{
System.out.println(start.get(k));
start.remove(k);
}
}
}
}
运行结果如下: