[路飞]_Leetcode 565数组嵌套

86 阅读3分钟

今天来看下leetcode的每日一题

索引从0开始长度为N的数组A,包含0N - 1的所有整数。找到最大的集合S并返回其大小,其中 S[i] = {A[i], A[A[i]], A[A[A[i]]], ... }且遵守以下的规则。

假设选择索引为i的元素A[i]S的第一个元素,S的下一个元素应该是A[A[i]],之后是A[A[A[i]]]... 以此类推,不断添加直到S出现重复的元素。

 

示例 1:

输入: A = [5,4,0,3,1,6,2]
输出: 4
解释: 
A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.

其中一种最长的 S[K]:
S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}

题目链接在这里 leetcode 565 数组嵌套

解题思路

  1. 一开始,暴力法,计算以每个数字开头的环的最大长度,复杂度为n^2,由于题目的数据量是10^5,遇到数据量大的会超时
var arrayNesting = function (nums) {
    let n = nums.length
    function f(i) {
        let s = new Set()
        let c = nums[i]
        s.add(c)
        while (true) {
            if (s.has(nums[c])) return s.size
            s.add(nums[c])
            c = nums[c]
        }
    }
    let res = 0
    for (let i = 0; i < n; i++) {
        res = Math.max(res, f(i))
    }
    return res
};

2.然后,仔细分析了一下实例1 将数组 5 4 0 3 1 6 2中, 把以每个数字开头的环画出图来

 0   5->6->2->0
     ↑________|   

 1   4->1
     ↑__|   

 2   0->5->6->2   0在第1次遍历时已经出现过,继续遍历只会重复计算出第1次的结果
     ↑________|

 3   3
     ↑|

 4   1->4    1在第2次遍历时已经出现过,继续遍历只会重复计算出第2次的结果
     ↑__|

5   6->2->0->5
    ↑________|

- 6   2->0->5->6
      ↑________|  

画完上面的图之后,我们突然发现最开始的方法其实存在着大量重复的运算,例如在计算以第0个位置开头的环的最大长度时。 我们已经知道了此时的最大环长度为4 上面的数字有5620,那么,如果在后续的计算中,如果遇到这些元素会发生什么呢? 他们显然会走向同一条环中,因为每个元素的下个元素的指向只有一个。因为每个数字肯定会出现在某个环上,这也就代表着, 如果我们在遍历过程中,将遇到的数组记录下来,后续计算过程中,遇到之前出现过的数字,这时我们可以肯定这个数字所在的环的长度我们已经计算过,这个数字就可以直接跳过,这样带来的好处是,最终的代码时间复杂度之只有O(n),在10^5的数据量情况下,完全可以通过。

代码

/**
 * @param {number[]} nums
 * @return {number}
 */

var arrayNesting = function(nums) {
    let ans = 0, n = nums.length;
        let vis = new Array(n).fill(0);
    for (let i = 0; i < n; ++i) {
        let cnt = 0;
        while (!vis[i]) {
            vis[i] = true;
            i = nums[i];
            ++cnt;
        }
        ans = Math.max(ans, cnt);
    }
    return ans;
};

3.更进一步,我们可以直接在原数组上打标记,来记录每个数字是否出现过,可以省去O(n)的空间复杂度

例如

- 0   5->6->2->0  0562四个位置构成的环已经计算完了,将每个位置打上标记,后续遇到之后直接跳过,数组变为[-1,4,-1,3,1,-1,-1]
      ↑________|   

- 1   4->1  将第1 4两个位置打上标记,数组变为[-1,-1,-1,3,-1,-1,-1]
      ↑__|   

- 2   0->5->6->2  0在第1次遍历时已经出现过,继续遍历只会重复计算出第1次的结果
      ↑________|

- 3   3
      ↑|

- 4   1->4  1在第2次遍历时已经出现过,继续遍历只会重复计算出第2次的结果
      ↑__|

- 5   6->2->0->5
      ↑________|

- 6   2->0->5->6
      ↑________|  

至此,就将算法的空间复杂度优化到O(1)的量级

/**
 * @param {number[]} nums
 * @return {number}
 */

var arrayNesting = function(nums) {
    let ans = 0, n = nums.length;
    for (let i = 0; i < n; ++i) {
        let cnt = 0;
        while (nums[i] !=-1) {
            let t = nums[i]
            nums[i] = -1
            i = t;
            ++cnt;
        }
        ans = Math.max(ans, cnt);
    }
    return ans;
};