题目来源
题目描述
class Solution {
public:
int arrayNesting(vector<int>& nums) {
}
};
题目解析
题意
不断地执行i=A[i],每次取出A[i],直到出现重复,显然,这会形成循环
- 循环1:A[5]=6,A[6]=2,A[2]=0,A[0]=5
- 循环2:A[4]=1,A[1]=4
- 循环3(自循环):A[3]=3
共三组循环,可以把它们拆分开来,变成下图的样子
可以总结出一些性质:
- 对每一个循环,无论从哪里进入,结果都是一样的
- 由于不存在重复数字,也就是说不可能两个数字指向同一个地方,所以每个循环不存在交叉,因此查找过的元素一定不会被再次查找
思路
- 根据题意模拟,顺序遍历整个数组,尝试以它为开头找到循环。
- 对我们经过的每一个元素:
- 首先,不应再用它当开头——这会造成重复查找;
- 其次,其他循环不会经过它----因为没有交叉。
- 也就是说,一旦经过某个元素,以后就不可能再用上它,所以可以把它抹去,可以原地地将它置为-1,或者是n等等不会出现的数。
- 对我们经过的每一个元素:
- 不断查找,直到回到开头,这个循环结束,size记录了循环的长度。返回最大的size即可
问题:为什么一定会在入口处成环
- 先证明一定成环:
- 在没有回头指向遍历过的节点时,这个结构是线性的,但是最终状态不可能是线性的,因为每个节点必然指向某个节点,也必然被某个节点所指,所以它不可能真正的有头有尾。
- 最长的可能就是遍历完整个数组,最后返回到某个值并成环。
- 证明必然在遍历开始处成环:
- 关键在于没有相同元素。
- 根据上面的证明,一定成环,假如不在开头处成环,就一定返回到中间某个节点,但这必然造成重复。
- 为什么呢?
- 首先,在遍历时,已经有一个元素指向它了,现在又返回到它,不就有两个相同的值了吗?
- 类似于下图的情况。所以必须返回到还没有被指向的节点,也就是遍历的开头。
class Solution {
public:
// 1 <= nums.length <= 10^5
// 0 <= nums[i] < nums.length
int arrayNesting(vector<int>& nums) {
int maxLen = 0;
for (int i = 0; i < nums.size(); ++i) {
if(nums[i] < 0){
continue;
}
int currLen = 1;
int nextIdx = nums[i]; // 获取下一个位置的下标
nums[i] = -1; // 当前环标记为已经使用过
while (nextIdx != i){ // 下一个位置是否指向起始节点
++currLen; //环长度++, 现在nextIdx变为currIdx
int tmp = nums[nextIdx];// 获取下一个位置的下标
nums[nextIdx] = -1; //
nextIdx = tmp;
}
maxLen = std::max(maxLen, currLen);
}
return maxLen;
}
};