给定一个含有正整数和负整数的环形数组 nums。 如果某个索引中的数 k 为正数,则向前移动 k 个索引。相反,如果是负数 (-k),则向后移动 k 个索引。因为数组是环形的,所以可以假设最后一个元素的下一个元素是第一个元素,而第一个元素的前一个元素是最后一个元素。
确定 nums 中是否存在循环(或周期)。循环必须在相同的索引处开始和结束并且循环长度 > 1。此外,一个循环中的所有运动都必须沿着同一方向进行。换句话说,一个循环中不能同时包括向前的运动和向后的运动。
示例 1:
输入:[2,-1,1,2,2]
输出:true
解释:存在循环,按索引 0 -> 2 -> 3 -> 0 。循环长度为 3 。
示例 2:输入:[-1,2]
输出:false
解释:按索引 1 -> 1 -> 1 … 的运动无法构成循环,因为循环的长度为 1 。根据定义,循环的长度必须大于 1 。
示例 3:输入:[-2,1,-1,-2,-2]
输出:false
解释:按索引 1 -> 2 -> 1 -> … 的运动无法构成循环,因为按索引 1 -> 2 的运动是向前的运动,而按索引 2 -> 1 的运动是向后的运动。一个循环中的所有运动都必须沿着同一方向进行。
题解:
对于这种求数组环形的题,首先应该想到的就是采用快慢指针。本题的基础思想也是采用快慢指针,不过增加了一些细节。
如果单纯用快慢指针,那么需要对每个数都是用快慢指针进行遍历,时间复杂度是o(n),所以需要进行剪枝。
剪枝的策略就是:
- 0不可能在环形中
- 环形中数的必须同号,如果在某个环中发现了不同号的,那么以这个数字开头的环的数字与当前数字同号的都不会出现在这个结果中
- 环形长度为1是不行的
有了这几点就可以剪枝了:本来还需要一个数组来记录哪些数字不能出现在数组中,如果每次不不会出现在数组中的数字都置为0,就可以不用这个部分的空间消耗了。
private void setZero(int[] nums, int i){
int j;
while (true) { // !(nums[j] == 0 || nums[i]*nums[j]<0)
j = (i + nums[i] + 5000*nums.length) % nums.length;
if (nums[j] == 0 || nums[i]*nums[j]<0) {
nums[i] = 0;
break;
}
nums[i] = 0;
i = j;
}
}
// beat 100%
public boolean circularArrayLoop(int[] nums) {
if (nums.length == 0) return false;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == 0) continue;
int lastJ, lastK;
int j=i, k=i;
while (true) {
lastJ = j;
j = (j + nums[j] + 5000*nums.length) % nums.length;
if (nums[lastJ]*nums[j] < 0 || nums[j] == 0 || lastJ == j) {
setZero(nums, i);
break;
}
lastK = k;
k = (k + nums[k] + 5000*nums.length) % nums.length;
if (nums[lastK]*nums[k] < 0 || nums[k] == 0 || lastK == k){
setZero(nums, i);
break;
}
lastK = k;
k = (k + nums[k] + 5000*nums.length) % nums.length;
if (nums[lastK]*nums[k] < 0 || nums[k] == 0 || lastK == k){
setZero(nums, i);
break;
}
if (j == k)
return true;
}
}
return false;
}
特别注意:看到数组或者链表求关于环的问题的时候,首先要想到使用双指针的方式