剑指 Offer 62. 圆圈中最后剩下的数字(约瑟夫环问题)

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

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2

限制:

1 <= n <= 10^5
1 <= m <= 10^6

方法一:算法思想:(数学解法,时间复杂度O(n),这个解法很牛)

阅前提示(全文最重要的点):
只关心最终活着那个人的序号变化

1 约瑟夫问题

这个问题实际上是约瑟夫问题,这个问题描述是

N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。

2 问题转换

既然约塞夫问题就是用人来举例的,那我们也给每个人一个编号(索引值),每个人用字母代替

下面这个例子是N=8 m=3的例子

我们定义F(n,m)表示最后剩下那个人的索引号,因此我们只关系最后剩下来这个人的索引号的变化情况即可

在这里插入图片描述

从8个人开始,每次杀掉一个人,去掉被杀的人,然后把杀掉那个人之后的第一个人作为开头重新编号

第一次C被杀掉,人数变成7,D作为开头,(最终活下来的G的编号从6变成3)
第二次F被杀掉,人数变成6,G作为开头,(最终活下来的G的编号从3变成0)
第三次A被杀掉,人数变成5,B作为开头,(最终活下来的G的编号从0变成3)
以此类推,当只剩一个人时,他的编号必定为0!(重点!)
3 最终活着的人编号的反推

现在我们知道了G的索引号的变化过程,那么我们反推一下
从N = 7 到N = 8 的过程

如何才能将N = 7 的排列变回到N = 8 呢?

我们先把被杀掉的C补充回来,然后右移m个人,发现溢出了,再把溢出的补充在最前面

神奇了 经过这个操作就恢复了N = 8 的排列了!
在这里插入图片描述
因此我们可以推出递推公式f(8,3) = [f(7, 3) + 3] % 8;
进行推广泛化,即f(n,m) = [f(n-1, m) + m] % n;

再把n=1这个最初的情况加上,就得到递推公式

在这里插入图片描述

最终代码:

class Solution {
public:
    int lastRemaining(int n, int m) {
        int pos = 0; // 最终活下来那个人的初始位置
        for(int i = 2; i <= n; i++){
            pos = (pos + m) % i;  // 每次循环右移
        }
        return pos;
    }
};

方法二:暴力法(时间复杂度O(mn))

class Solution {
    public int lastRemaining(int n, int m) {
		 if(m==1) {
			 return n-1;
		 }
		 int count=0;
		 
		 int nums[]=new int[n];
		 for(int i=0;i<n;i++) {
			 nums[i]=i;
		 }
		 int k=1;
		 while(count<n-1) {
			 for(int i=0;i<n;i++) {
				 if(nums[i]!=-1) {
					 if(k==m) {
						 System.out.println(nums[i]+" ");
						 nums[i]=-1;
						 count++;
						 k=0;
					 } 
					 k++;
				 }		
			 }
		 }
		 for(int i=0;i<n;i++) {
			 if(nums[i]!=-1) {
				 return nums[i];
			 }
		 }
		 return -1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯小帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值