• 【LeetCode】面试题62. 圆圈中最后剩下的数字


    题目:面试题62. 圆圈中最后剩下的数字

    这题很有意思,也很巧妙,故记录下来。

    官方题解思路,是约瑟夫环的数学解法:


    我们将上述问题建模为函数 f(n, m),该函数的返回值为最终留下的元素的序号。

    首先,长度为n的序列会先删除第m % n个元素,然后剩下一个长度为n - 1的序列。那么,我们可以递归地求解f(n - 1, m),就可以知道对于剩下的n - 1个元素,最终会留下第几个元素,我们设答案为x = f(n - 1, m)

    由于我们删除了第m % n个元素,将序列的长度变为n - 1。当我们知道了f(n - 1, m)对应的答案x之后,我们也就可以知道,长度为n的序列最后一个删除的元素,应当是从 m % n开始数的第x个元素。因此有f(n - 1, m) = (m % n + x) % n = (m + x) % n

    我们递归计算f(n, m),f(n - 1, m),f(n - 2, m), ... 直到递归的终点f(1, m)。当序列长度为1时,一定会留下唯一的那个元素,它的编号为0

    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-by-lee/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


    需要注意的是下标的细节(老生常谈了):这里的数组 index = number,但 m 是从1开始计数。f() 的返回值 x 应该是从0开始计数的number。上文中“应当是从m % n开始数的第x个元素。”这句话里的m % n是被删除的元素的下一个元素的index,从它开始的index为x的元素正是下一步要删除的元素在原数组里的index。

    Java递归代码如下:

    class Solution {
        public int lastRemaining(int n, int m) {
            return f(n, m);
        }
    
        private int f(int n, int m) {
            if (n == 1) return 0;
            int x = f(n - 1, m);
            return (x + m) % n;
        }
    }
    // 13 ms / 41.8 MB   37.71% / -%
    
    • 时间复杂度:O(n),需要求解的函数值有n个。
    • 空间复杂度:O(n),函数的递归深度为n,需要使用O(n)的栈空间。

    写成迭代,代码如下:

    class Solution {
        public int lastRemaining(int n, int m) {
            int index = 0;
            for (int i = 2; i <= n; i++) {
                index = (index + m) % i;
            }
            return index;
        }
    }
    // 7 ms / 36.6 MB   99.88% / -%
    
    • 时间复杂度:O(n),需要求解的函数值有n个。
    • 空间复杂度:O(1),只使用常数个变量。

    总的来说迭代的时间和空间都更少。而且迭代的写法其实有点像动态规划了。


    以下是踩坑过程:

    我首先想的是,建一个首尾相连的链表,然后暴力跑,可想而知的超时,只通过了26/36个测试用例。

    然后想了下优化策略,每次访问所需的步数可以通过模运算减少,然后调整了一下每次访问的过程,虽然多通过了一个测试用例,但还是超时。

    class Solution {
        public int lastRemaining(int n, int m) {
            Node head = new Node(-1);
            Node p = head;
            Node q = head;
            for (int i = 0; i < n; i++) {
                p.next = new Node(i);
                p = p.next;
            }
            int step = 0;
            while (n > 1) {
                step = (step + m - 1) % n + 1;
    
                q = head;
                p = head;
                for (int i = 0; i < step; i++) {
                    q = p;
                    p = p.next;
                }
                //System.out.println(p.value);
                q.next = p.next;
    
                step--;
                n--;
            }
            return head.next.value;
        }
    }
    
    class Node {
        int value;
        Node next;
    
        Node(int value) {
            this.value = value;
        }
    }
    

    然后把数据存到 LinkedList 中去试试,发现效果差不多,还是超时。结果没想到换成 ArrayList 之后居然能通过,看来在数据访问和数据移动的性能取舍之间有一个微妙的平衡,本题是倾向于需要更快的访问速度。

    class Solution {
        public int lastRemaining(int n, int m) {
            // List<Integer> list = new LinkedList<>();
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                list.add(i);
            }
            int index = 0;
            while (list.size() != 1) {
                index = (index + m - 1) % list.size();
                // System.out.println(list.get(index));
                list.remove(index);
            }
            // System.out.println(Arrays.toString(list.toArray()));
            return list.get(0);
        }
    }
    // 1122 ms / 43.1 MB    16.61% / -%
    
  • 相关阅读:
    Linux 间网线直连
    Ubuntu 14.04安装配置NFS
    Android Native IPC 方案支持情况
    使用WindowsAPI获取录音音频
    Ubuntu 64编译32位程序
    TensorFlow 安装 Ubuntu14.04
    纯css实现表单输入验证
    安装ELectron失败解决方案
    正则学习
    常见web攻击
  • 原文地址:https://www.cnblogs.com/caophoenix/p/12598046.html
Copyright © 2020-2023  润新知