力扣457:环形数组是否存在循环
- 题目
- 思路
- 代码
题目
存在一个不含 0 的 环形 数组 nums ,每个 nums[i] 都表示位于下标 i 的角色应该向前或向后移动的下标个数:
- 如果 nums[i] 是正数,向前(下标递增方向)移动 |nums[i]| 步
- 如果 nums[i] 是负数,向后(下标递减方向)移动 |nums[i]| 步
因为数组是 环形 的,所以可以假设从最后一个元素向前移动一步会到达第一个元素,而第一个元素向后移动一步会到达最后一个元素。
数组中的 循环 由长度为 k 的下标序列 seq 标识:
- 遵循上述移动规则将导致一组重复下标序列 seq[0] -> seq[1] -> … -> seq[k - 1] -> seq[0] -> …
- 所有 nums[seq[j]] 应当不是 全正 就是 全负
- k > 1
如果 nums 中存在循环,返回 true ;否则,返回 false 。
思路
我们先解析一下题目的意思是什么:一个数组中存储的值的绝对值就是移动的步数,正负则是移动的方向。想要判断一个数组有环必须满足两个条件:一是环状的也就是会重复进行,二是这个环的每个位置的移动方向都是相同的。也就是要么全部为正形成一个环要么全部为负形成一个环。有正有负的不算是一个环。
在了解了题目真正的意思后我们来思考一下要怎么做出这道题目,有环比较满足两个条件那么我们解法的重点也是这两个条件,判断是环状的所以我们需要计算出下一个位置在哪,移动方向要相同的所以我们需要设一个标志位来进行判断。
标志位好说,我们可以设置一个bool类型当作标志位flag,值大于0就为true小于0就为负,每次得到下一个位置时我们都需要进行判断flag和nums[next]的值是否是相反的如果相反就说明环不成立。接下来就是如何计算下一个位置了如果全为正值那么就好计算了我们只需要让当前下标加上当前下标所代表的值再取模整个数组的长度即可,但是这道题中是存在负数的如果全为负数我们要怎么计算下一个位置呢?我们要在正数的基础上加上数组的长度如何再取模一次数组的长度也就是(x+n)%n,这是因为在原本的计算方式下在第一次取模完数组的长度值的大小就被控制在了(-n,n)这个区间里所以我们再加上一个数组的长度这样范围就变成了(0,n)这个区间里。这样无论是正数还是负数都可以得到下一个位置。
在有了这两个条件后大体的框架就出来了但是我们还需要注意一个点,有没有可能一个数组没有环但是它的移动方向全部也是相同的呢?完全有可能,所以我们还需要一个判断条件就是我们移动了几次,如果移动次数大于数组的长度了判断出有环就说明这个数组是没有环的并且移动方向还都是相同的。
代码
class Solution {
public:bool check(int start,vector<int> & nums){int cur = start;int n = nums.size();//开头位置是向后的还是向前的bool flag = nums[start] > 0;//处理的数字数量int k = 1;while(true){//如果数量超过长度//说明有数字被处理两次即没有环if(k > n){return false;}//下一个位置int next = ((cur + nums[cur]) % n + n) % n;//如果下一个位置和当前位置前进方向是相反的说明没有环if(flag && nums[next] < 0 ){return false;}if(!flag && nums[next] > 0){return false;}//如果下一个位置和开始位置相同说明有环if(next == start){//同时还需要判断位置是否移动了return k >1;}cur = next;k++;}}bool circularArrayLoop(vector<int>& nums) {int n = nums.size();for(int i = 0;i < n ;i++){//把每个数字都当做起点来进行判断是否有环if(check(i,nums)){return true;}}return false;}
};