【数学--约瑟夫环】剑指 Offer 62. 圆圈中最后剩下的数字

题目描述

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

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

示例:

输入: n = 5, m = 3
输出: 3

解题思路

这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的一名犹太历史学家。他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中。他们讨论是自杀还是被俘,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人,他们将向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意,他不知道是哪一个。 —— 【约瑟夫问题】维基百科

首先尝试了直接模拟,考虑到其时间复杂度为 O ( n 2 ) O(n^2) O(n2),且 1 < = n < = 1 0 5 1<=n<=10 ^5 1<=n<=105,会导致超时。

class Solution {
    
    
public:
	//模拟
    int lastRemaining(int n, int m) {
    
    
        vector<int> vec(n);
        for(int i = 0; i < n; i++){
    
    
            vec[i] = i;
        }
        vector<int>::iterator it = vec.begin();
        int diff = m - 1;
        while(--n){
    
    
            diff = vec.erase(it + diff % (n + 1)) - it + m - 1;
        }
        return vec[0];
    }
};

那么约瑟夫环问题怎么解呢?参考官方的题解,分治递归相对迭代逻辑上更自然一些。

  1. 首先,将问题拆解的更小。每一次删除操作后,数列长度减一,对于下一次操作而言,就变为求在新数列下,最后剩下的数;
  2. 新数列的起点是上一层数列删除数的下一位;
  3. 问题是如何将最底层的答案向上返回。这里就需要数学、逻辑上的推导,依凭数字的下标,建立上下层之间的映射。之所以不返回具体的值,是求不出来。要注意的是,我们是依赖下标定位需要删除的数,也就是说各层的下标关系我们是有的。不是我们刻意选下标作为建立上下层连系的属性,是没得选。
  4. 假设下层返回值x,这里的x是拥有新起点的下层数列最后剩下数字的下标。而下层的起点,是上层删除位的下一位,删除位的下标为m % n - 1,因此x映射在上层数列下标即,(m % n + x) % n

再来看迭代如何实现,下图对应示例,图中数列用数组表示,为了反应环状特点,在加粗黑线后复制拼接一份。
在这里插入图片描述
有点倾向于找规律,最底层数组的最终答案为下标0,而上一层相对应的下标可用(ans + m) % i表示,其中ans为下层下标,i为上层数组大小。故i2一直遍历到题目提供的n

具体看下方代码。

代码实现

递归:

class Solution {
    
    
public:
    int lastRemaining(int n, int m) {
    
    
        if(n == 1) return 0;
        int x = lastRemaining(n - 1, m);
        return (m % n + x) % n;
    }
};

迭代:

class Solution {
    
    
public:
    int lastRemaining(int n, int m) {
    
    
        int ans = 0;
        for(int i = 2; i <= n; i++){
    
    
            ans = (ans + m) % i;
        }
        return ans;
    }
};

猜你喜欢

转载自blog.csdn.net/LogosTR_/article/details/125774153
今日推荐