NC132 环形链表的约瑟夫问题

题目描述

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

1. 暴力解法

没什么好说的,用一个数组模拟整个过程,n-1轮循环,每轮报一次数,然后每次出局的时候剩下的数据向前平移,直到剩一个人。

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @return int整型
     */
    int ysf(int n, int m) {
        vector<int> nums(n);
        
        for (int i = 0; i < n; i++) nums[i] = i;
        
        int start = 0; // 数组下标为0的,存的是1号人
        while (nums.size() != 1) {
            // 哪个玩家该出局
            int id_to_away = (start + m - 1) % nums.size();
            
            // 数组中删掉
            nums.erase(nums.begin() + id_to_away);
            
            // 这个人下次开始报1
            start = id_to_away;            
        }
        
        // 题目编号从1开始,代码的下标从0开始,因此加1修正
        return nums[0] + 1;
    }
};
  • 时间复杂度:O(n2)O(n^2)O(n2), 两重循环。其中erase方法也需要对数组进行一次遍历,故总的复杂度达到了O(n2)O(n^2)O(n2)
  • 空间复杂度:O(n)O(n)O(n), 维护了长度为n的数组。

2. 动态规划

方法1的时间复杂度太高了,我们看下有没有规律可以优化。

正向推导似乎很难找到规律,那么正难则反, 我们从后往前分析。

f(n,m)f(n,m)f(n,m)nnn个人玩游戏,报mmm的人退出的游戏,最后剩下的人的编号。我们看看是不是跟f(n1,m)f(n-1,m)f(n1,m)f(n+1,m)f(n+1,m)f(n+1,m)有关系,如果是的话,就可以dp了。

假设f(n,m)f(n,m)f(n,m)已知, 那么我们进行一轮游戏后,从被淘汰的那个人开始,进行游戏f(n1,m)f(n-1, m)f(n1,m),应该得到同一个人。

换句话说,我们只要求出游戏f(n,m)f(n,m)f(n,m)在第一轮结束后,谁被淘汰,就得到了f(n,m)f(n,m)f(n,m)f(n1,m)f(n-1, m)f(n1,m)的关系。

第一轮被淘汰的编号显然可以用m%nm\%nm%n表示,那么我们就得出了动态规划转移式:

f(n,m)=(m%n+f(n1,m))%nf(n,m) = (m\%n + f(n-1,m)) \% nf(n,m)=(m%n+f(n1,m))%n

最终的初始值肯定是f(1,m)=0f(1, m) = 0f(1,m)=0. 因为只有1个人玩游戏的时候,最后剩下的人肯定是0号玩家。

但是这个式子还是比较难算,我们进一步挖掘规律。

f(1,m)=0f(2,m)=(f(1,m)+m)%2f(3,m)=(f(2,m)+m)%3...f(1, m) = 0 \\ f(2, m) = (f(1, m) + m) \% 2 \\ f(3, m) = (f(2, m) + m) \% 3 \\ ...f(1,m)=0f(2,m)=(f(1,m)+m)%2f(3,m)=(f(2,m)+m)%3...

发现只要维护前一项就可以,因此我们只需要一个变量维护即可。

class Solution {
public:
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @return int整型
     */
    int ysf(int n, int m) {
        // write code here
        int res = 0;
        for(int i=2; i <=n;i++)
        {
            res = (res + m) % i;
        }
        // 题目编号从1开始,代码的下标从0开始,因此加1修正
        return res + 1;
    }
};
  • 时间复杂度:O(n)O(n)O(n), 一重循环。
  • 空间复杂度:O(1)O(1)O(1), 常数个变量