跳至主要內容

圆圈中最后剩下的数字

soulballad算法剑指Offer剑指Offer约 875 字大约 3 分钟

题目:

​ 0,1,2…n-1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字,求出这个圆圈里面剩下的最后一个数字。例如 0,1,2,3,4 这5个数字组成一个圆圈,从数字 0 开始每次删除第 3个数字,则删除的前 4 个数字依次是 2,0,4,1,因此最后剩下的数字是 3。

分析:

  1. 第一种:

    经典的解法, 用环形链表模拟圆圈。
    创建一个总共有 n 个结点的环形链表,然后每次在这个链表中删除第 m 个结点。

    代码:

    public static int getLastRemainByList(int n, int m) {
        if (n < 1 || m < 1) {
            return -1;
        }
    
        List<Integer> list = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            list.add(i);
        }
    
        // 计数,每次删除第m个数字
        int count = 0;
        for (int index = 0; list.size() > 1; index++) {
    
            count++;
            // 只剩最后一个元素
            index = index == list.size() ? 0 : index;
            if (count % m == 0) {
                list.remove(index);
                index--;
            }
        }
    
        return list.get(0);
    }
    
  2. 方法二:

    ​ 首先我们定义一个关于 n 和 m 的方程 f(n, m) 时,表示每次在 n 个数字 0,1, … ,n-1中每次删除第 m 个数字最后剩下的数字。

    ​ 在这 n个数字中, 第一个被删除的数字是 (m-1)%n。为了简单起见,我们把 (m- 1)%n 记为 k,那么删除k 之后剩下的 n-1 个数字为 0,1,… ,k-1,k+1,… ,n-1,并且下一次删除从数字 k+1 开始计数。相当于在剩下的序列中, k+1 排在最前面,从而形成 k+1,... ,n- 1,0,I,… ,k-1 。该序列最后剩下的数字也应该是关于 n 和 m 的函数。由于这个序列的规律和前面最初的序列不一样(最初的序列是从 0 开始的连续序列),因此该函数不同于前面的函数,记为 f’(n-1,m)。最初序列最后剩下的数字 f(n, m)一定是删除一个数字之后的序列最后剩下的数字,即 f(n, m)=f’(n-1, m)。

    ​ 接下来我们把剩下的这 n-1 个数字的序列 k-1, …,n-1,0,1,… ,k-1 做一个映射,映射的结果是形成一个从 0 到 n-2 的序列:

    k+1 -> 0
    k+2 -> 1
    ...
    n-1 -> n-k-2
    0 -> n-k-1
    1 -> n-k
    ...
    k-1 -> n-2
    

    我们把映射定义为 p,则 p(x) = (x - k - 1) % n,它表示如果映射前的数字是 x,那么映射后的数字是 (x-k-1)%n。该映射的逆映射是 p’(x) = (x + k + 1) % n 。

    ​ 由于映射之后的序列和最后的序列具有同样的形式,即都是从 0 开始的连续序列,因此仍然可以用函数 f 来表示,记为 f(n-1, m)。根据我们的映射规则,映射之前的序列中最后剩下的数字 f'(n-1, m)=p'[f(n-1, m)]=[f(n-1, m)+k+1]%n,把 k=(m-1)%n 带入得到 f(m, n)=f'(n-1, m)=[f(n-1, m) + m] %n。

    ​ 由此可以得到一个公式:
    $$
    f(n) =
    \begin{cases}
    0, & \text{n=1} \
    [f(n-1, m)+m]%n, & \text{n>1}
    \end{cases}
    $$

    代码:

    基于循环实现

    public static int getLastRemainByMath(int n, int m) {
        if (n < 1 || m < 1) {
            return -1;
        }
        int last = 0;
        for (int i = 2; i <= n; i++) {
            last = (last + m) % i;
        }
        return last;
    }
    
上次编辑于:
贡献者: soulballad