题目
标题和出处
标题:数组嵌套
出处:565. 数组嵌套
难度
4 级
题目描述
要求
给你一个长度为 n \texttt{n} n 的整数数组 nums \texttt{nums} nums, nums \texttt{nums} nums 是 [0, n - 1] \texttt{[0, n - 1]} [0, n - 1] 范围内的数字的一个排列。
你需要建立集合 s[k] = {nums[k], nums[nums[k]], nums[nums[nums[k]]], … } \texttt{s[k] = \{nums[k], nums[nums[k]], nums[nums[nums[k]]], \ldots \}} s[k] = {nums[k], nums[nums[k]], nums[nums[nums[k]]], …} 且遵守以下的规则:
- s[k] \texttt{s[k]} s[k] 的第一个元素从选定的 nums[k] \texttt{nums[k]} nums[k] 开始;
- s[k] \texttt{s[k]} s[k] 的下一个元素是 nums[nums[k]] \texttt{nums[nums[k]]} nums[nums[k]],然后是 nums[nums[nums[k]]] \texttt{nums[nums[nums[k]]]} nums[nums[nums[k]]],以此类推;
- 在 s[k] \texttt{s[k]} s[k] 出现重复元素之前,停止添加元素。
返回集合 s[k] \texttt{s[k]} s[k] 的最大长度。
示例
示例 1:
输入:
nums
=
[5,4,0,3,1,6,2]
\texttt{nums = [5,4,0,3,1,6,2]}
nums = [5,4,0,3,1,6,2]
输出:
4
\texttt{4}
4
解释:
nums[0]
=
5,
nums[1]
=
4,
nums[2]
=
0,
nums[3]
=
3,
nums[4]
=
1,
nums[5]
=
6,
nums[6]
=
2
\texttt{nums[0] = 5, nums[1] = 4, nums[2] = 0, nums[3] = 3, nums[4] = 1, nums[5] = 6, nums[6] = 2}
nums[0] = 5, nums[1] = 4, nums[2] = 0, nums[3] = 3, nums[4] = 1, nums[5] = 6, nums[6] = 2
其中一种最长的
s[k]
\texttt{s[k]}
s[k]:
s[0]
=
{nums[0],
nums[5],
nums[6],
nums[2]}
=
{5,
6,
2,
0}
\texttt{s[0] = \{nums[0], nums[5], nums[6], nums[2]\} = \{5, 6, 2, 0\}}
s[0] = {nums[0], nums[5], nums[6], nums[2]} = {5, 6, 2, 0}
示例 2:
输入:
nums
=
[0,1,2]
\texttt{nums = [0,1,2]}
nums = [0,1,2]
输出:
1
\texttt{1}
1
数据范围
- 1 ≤ nums.length ≤ 10 5 \texttt{1} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 1≤nums.length≤105
- 0 ≤ nums[i] < nums.length \texttt{0} \le \texttt{nums[i]} < \texttt{nums.length} 0≤nums[i]<nums.length
- nums \texttt{nums} nums 中不含有重复的元素
解法
思路和算法
最朴素的思想是,对于从 0 0 0 到 n − 1 n-1 n−1 的每个下标,分别以每个下标作为开始下标,寻找对应的 s s s。最坏情况下,对于一个下标寻找 s s s 的时间复杂度是 O ( n ) O(n) O(n),总时间复杂度是 O ( n 2 ) O(n^2) O(n2),会超出时间限制。
换一个角度考虑。假设其中的一个集合 s s s 包含 k k k 个元素,其中 k ≤ n k \le n k≤n,每个元素分别位于下标 i 1 , i 2 , … , i k i_1, i_2, \ldots, i_k i1,i2,…,ik,且满足 nums [ i j ] = i j + 1 ( 1 ≤ j < k ) \textit{nums}[i_j]=i_{j+1}(1 \le j<k) nums[ij]=ij+1(1≤j<k) 和 nums [ i k ] = i 1 \textit{nums}[i_k]=i_1 nums[ik]=i1。
这 k k k 个元素形成一个闭环: i 1 → i 2 → i 3 → … → i k → i 1 i_1 \rightarrow i_2 \rightarrow i_3 \rightarrow \ldots \rightarrow i_k \rightarrow i_1 i1→i2→i3→…→ik→i1。无论从这 k k k 个元素中的哪一个元素开始访问,都会遍历这 k k k 个元素,最后回到开始的元素,即得到的集合 s s s 都是相同的,除了访问元素的顺序不同。因此,一旦这 k k k 个元素中的任意一个元素被访问过,其余 k − 1 k-1 k−1 个元素一定也被访问过,不需要对已经访问过的元素再次访问。由此可以得到一个优化的做法,数组 nums \textit{nums} nums 中的每个元素都只访问一次,时间复杂度可以优化到 O ( n ) O(n) O(n)。
具体做法是,从下标 0 0 0 开始,遍历数组 nums \textit{nums} nums 中的元素,根据开始下标得到对应的集合 s s s。如果当前下标尚未被访问,则以当前下标为开始下标,遍历元素得到集合 s s s,直到回到开始下标,并将所有访问过的下标记为被访问,同时更新当前集合 s s s 的大小,维护最大的集合 s s s 的大小。如果当前下标已经被访问,则当前下标所属的集合 s s s 中的每个元素都已经被访问,因此跳过当前下标。该做法确保数组 nums \textit{nums} nums 中的每个元素只会被访问一次,不会被访问第二次,因此时间复杂度是 O ( n ) O(n) O(n)。
为了记录数组 nums \textit{nums} nums 中的每个元素是否被访问过,需要新建一个长度为 n n n 的数组 visited \textit{visited} visited,记录每个元素是否被访问过。如果要节省空间,也可以原地修改数组 nums \textit{nums} nums 中的元素,已经被访问过的位置的元素值修改成一个小于 0 0 0 或大于 n − 1 n-1 n−1 的数。
代码
class Solution {
public int arrayNesting(int[] nums) {
int n = nums.length;
boolean[] visited = new boolean[n];
int maxLength = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
int curLength = 0;
int index = i;
while (!visited[index]) {
int num = nums[index];
visited[index] = true;
index = num;
curLength++;
}
maxLength = Math.max(maxLength, curLength);
}
}
return maxLength;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。数组 nums \textit{nums} nums 中的每个元素都只会访问一次。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要创建长度为 n n n 的数组 visited \textit{visited} visited 记录每个元素是否被访问过。如果原地修改 nums \textit{nums} nums 的元素值,则空间复杂度可以降低到 O ( 1 ) O(1) O(1)。