今天来看下leetcode的每日一题
索引从0开始长度为N的数组A,包含0到N - 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 数组嵌套
解题思路
- 一开始,暴力法,计算以每个数字开头的环的最大长度,复杂度为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;
};