约瑟夫环问题解决方法

题目

0,1,… ,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

第一种:递归方法

剑指offer书上的思路:首先定义了一个函数f(n,m),这个函数代表的是圆圈数字为n,每次从圆圈里删除第m个数时,最后剩下的一个数字的标号。

考虑递归,要想用递归,就要寻找两个条件1.base case 2.f(n,m)和f(n-1,m)的关系,即如何让问题规模减少。

第一个条件base case很容易,当圆圈中只有一个数字的时候,这个数字就是剩下的数字,即标号为0.

第二个条件就要开始分析删除过程,一开始圈中有n个数字,标号为0~n-1,此时要删除第m个数,容易得第一个被删除后的元素应该为(m-1)%n(此处第m个数的下标应为m-1)。令k = (m-1)%n,那么删除第一个数后,圆圈中剩余的数下标为0,1,2,…,k-1,k+1,…,n-1.此时要进行第二次删除,这个时候圆圈中的数字个数为n-1,要删除第m个数,这不就是我们要找的规模减少吗?那么第二次删除的数字下标是f(n-1,m)吗?显然不是,为什么呢?因为我们上边定义的函数f(n,m)的使用条件是从下标0开始到n-1的数中删除第m个数,而第二次删除的时候,我们是从下标k+1开始,这里就是这个算法最难的部分,映射转换。

既然f函数规定的是从0开始,那我们将k+1映射为0,相应的,k+2映射为1…依次类推,原来的最后一个下标n-1映射为n-k-2, 0映射为n-k-1, 1映射为n-k, k-1映射为当前圆圈的最后一个下标n-2。由此可得到上一轮与本轮的映射关系,定义为p,记上一轮的下标为x’,本轮下标为x,则有,x = p(x‘) = (x’-k-1)%n; 可以得到p的逆,记为p*,即根据本轮的下标推出上一轮的下标,x‘=p*(x)=(x+k+1)%n。此时就得到了相邻两轮删除时下标的映射关系。

再以第二轮为例,可以套用函数f,得到最后删除的数字为f(n-1,m),此时这个数字的下标是在第二轮第一个数从0开始的前提下计算出来的。根据上一段的映射关系,可以得到第一轮这个数字的下标应该是p*(f(n-1,m)),即(f(n-1,m)+k+1)%m,第一轮最后剩下的数字和第二轮应该是同一个,所以再对第一轮套用f函数,可得到剩下的数字下标为f(n,m). 即f(n,m) = (f(n-1,m)+k+1)%n, k = (m-1)%n代入,f(n,m) = (f(n-1,m)+m)%n,这样,递推关系就找到了。

public class Solution {
	public int LastRemaining_Solution(int n, int m){
		if ( n < 1 || m < 1 ) {
			return -1;
		}
		if ( n == 1 ) {
			return 0;
		} else {
			return (LastRemaining_Solution( n - 1, m ) + m ) % n;
		}
	}
}

第二种:用数组模拟链表环

剑指offer上说,用链表环结构,比较复杂。所以我们可以用数组来模拟,通过下标的转换来实现环结构。

public class Solution {
	public int LastRemaining_Solution(int n, int m){
		if ( n < 1 || m < 1 ) {
			return -1;
		}
		int[] arr = new int[n];
		int i = -1, step = 0, count = n;
		while ( count > 0 ) {
			i++;
			if ( i == n ) i = 0;
			if ( arr[i] == -1 ) continue;
			step++;
			if ( step == m ) {
				arr[i] = -1;
				step = 0;
				count--;
			}
		}
		return i;
	}
}

n为圈中的数字个数,m为要删除的数,arr数组用来模拟圆圈,i记录数组下标,当i达到n的时候,将其变为0,实现循环。step为进行的步数,当step等于m的时候,删去元素,这里删去元素的方式并不是从数组中删除,而是将对应的值置为-1来表示该元素已被删除,从而跳过链表中的删除元素的步骤,避开了数组增删慢的缺点。

数组下标i为什么要从-1开始呢?因为如果从0开始,0位置的数字要进行操作,那么i++语句就要放到最后,但是这样第11行判断跳过的语句就会在条件成立时,在while中形成死循环,所以i++一定要在continue之前,但是我们又不能跳过第一个数字,只能把i的初始值设为-1.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值