You are given a circular array nums of positive and negative integers. If a number k at an index is positive, then move forward k steps. Conversely, if it's negative (-k), move backward k steps. Since the array is circular, you may assume that the last element's next element is the first element, and the first element's previous element is the last element.

Determine if there is a loop (or a cycle) in nums. A cycle must start and end at the same index and the cycle's length > 1. Furthermore, movements in a cycle must all follow a single direction. In other words, a cycle must not consist of both forward and backward movements.

Example 1:

Input: [2,-1,1,2,2]
Output: true
Explanation: There is a cycle, from index 0 -> 2 -> 3 -> 0. The cycle's length is 3.

Example 2:

Input: [-1,2]
Output: false
Explanation: The movement from index 1 -> 1 -> 1 ... is not a cycle, because the cycle's length is 1. By definition the cycle's length must be greater than 1.

Example 3:

Input: [-2,1,-1,-2,-2]
Output: false
Explanation: The movement from index 1 -> 2 -> 1 -> ... is not a cycle, because movement from index 1 -> 2 is a forward movement, but movement from index 2 -> 1 is a backward movement. All movements in a cycle must follow a single direction.

Note:

  1. -1000 ≤ nums[i] ≤ 1000
  2. nums[i] ≠ 0
  3. 1 ≤ nums.length ≤ 5000

Follow up:

Could you solve it in O(n) time complexity and O(1) extra space complexity?

说实话,这道题描述的并不是很清楚,比如题目中有句话说循环必须是 forward 或是 backward 的,如果不给例子说明,不太容易能 get 到 point。所谓的循环必须是一个方向的就是说不能跳到一个数,再反方向跳回来,这不算一个 loop。比如 [1, -1] 就不是一个 loop,而 [1, 1] 是一个正确的 loop。看到论坛中一半的帖子都是各种需要 clarify 和不理解 test case 就感觉很好笑~ 当然博主也成功踩坑了。弄清楚了题意后来考虑如何做,由于从一个位置只能跳到一个别的位置,而不是像图那样一个点可以到多个位置,所以这里我们就可以用个 HashMap 根据坐标建立一对一的映射,一旦某个达到的坐标已经有映射了,说明环存在,当然我们还需要进行一系列条件判断。首先我们需要一个 visited 数组,来记录访问过的数字,然后我们遍历原数组,如果当前数字已经访问过了,直接跳过,否则就以当前位置坐标为起始点开始查找,进行 while 循环,计算下一个位置,计算方法是当前位置坐标加上对应的数字,由于是循环数组,所以结果可能会超出数组的长度,所以我们要对数组长度取余。当然上面的数字也可能是负数,加完以后可能也是负数,所以在取余之前还得再补上一个n,使其变为正数,但是 若这个负数远大于n的话,取余之前只加上一个n,可能是不够的,所以正确的方法是应该先对n取余,再加上n。为了同时把正数的情况也包含进来,最终我们的处理方法是先对n取余,再加上n,再对n取余,这样不管正数还是负数,大小如何,都可以成功的旋转跳跃了。此时我们判断,如果 next 和 cur 相等,说明此时是一个数字的循环,不符合题意,再有就是检查二者的方向,数字是正数表示 forward,若是负数表示 backward,在一个 loop 中必须同正或同负,我们只要让二者相乘,如果结果是负数的话,说明方向不同,直接 break 掉。此时如果 next 已经有映射了,说明我们找到了合法的 loop,返回 true,否则建立一个这样的映射,将 next 位置在 visited 数组中标记 true,继续循环,参见代码如下:

解法一:

class Solution {
public:
bool circularArrayLoop(vector<int>& nums) {
int n = nums.size();
vector<bool> visited(n);
for (int i = ; i < n; ++i) {
if (visited[i]) continue;
visited[i] = true;
unordered_map<int, int> m;
int cur = i;
while (true) {
int next = ((cur + nums[cur]) % n + n) % n;
if (next == cur || nums[next] * nums[cur] < ) break;
if (m.count(next)) return true;
m[cur] = next;
cur = next;
visited[next] = true;
}
}
return false;
}
};

我们也可以不用 visited 数组,直接在 nums 中标记,由于题目中说了 nums 中不会有0,所以可以把访问过的位置标记为0。然后计算 next 位置,对于会超出数组的长度的正数,我们可以通过对n取余,但是对于负数,若这个负数远大于n的话,取余之前只加上一个n,可能是不够的,所以正确的方法是应该先对n取余,再加上n。为了同时把正数的情况也包含进来,最终我们的处理方法是先对n取余,再加上n,再对n取余,这样不管正数还是负数,大小如何,都可以成功的旋转跳跃了。接下来看,如果 next 和i相等,直接跳过,因为这表示循环只有一个数字,不合题意。然后开始循环,当 cur 和 nums[next] 的乘积为正时,说明此时是一个方向的,我们将 cur 赋值为 nums[next],将 nums[next] 赋值为0,表示已经访问过,然后再计算新的 next 位置。直到循环被打破,若此时 next 和i相等,说明有大于1个数字的环存在,返回 true。最终 for 循环退出后,返回 false 即可,代码参见评论区11楼。想法倒是不错,但是存在一个逻辑上的错误。

对于 [1, 1, 2] 这个例子,当 i=0 时,最后跳出 while 循环时 next 是等于1的,和 i 不相等,所以没法返回 true。但这个例子其实是有正确的循环的,是后两个数字1和2可以循环,那为什么我们没 catch 到呢?因为我们前面的思路默认为循环开始的位置就是i,但明显不一定成立的,就像在链表中找环一样,环的起点可以是任意位置啊,不一定是表头结点啊。那为啥上面的解法就可以呢?这是因为上面使用的是 HashMap,而且对于每个i,都使用了一个新的 HashMap,跟之前的并没有联系,而且把以i位置为起点,经过的位置都存到了 HashMap 中,这样是可以找出环的。而这里我们直接跟i相比肯定是不对的。那么既然说到了链表中找环,刷题老司机们大声告诉我该用什么?对了,就是快慢指针了。那么对于每个i位置,慢指针指向i,快指针指向下一个位置,这里调用子函数来计算下一个位置。此时慢指针指向的数要和快指针指向的数正负相同,这个不难理解。并且慢指针指向的数还要跟快指针的下一个位置上的数符号相同,这个原因后面再讲。上面这两个就是 while 循环的条件,我们直到当快慢指针相遇的时候,就是环出现的时候,但是这里有个坑,即便快慢指针相遇了,也不同立马返回 true,因为题目中说了了环的长度必须大于1,所以我们要用慢指针指向的数和慢指针下一个位置上的数比较,若相同,则说明环的长度为1,此时并不返回 false,而且 break 掉 while 循环。因为这只能说以i位置开始的链表无符合要求的环而已,后面可能还会出现符合要求的环。但是若二者不相同的话,则已经找到了符合要求的环,直接返回 true。若快慢指针还不相同的,则分别更新,慢指针走一步,快指针走两步。当 while 循环退出后,我们需要标记已经走过的结点,从而提高运算效率,方法就是将慢指针重置为i,再用一个 while 循环,条件是 nums[i] 和 慢指针指的数正负相同,然后计算下一个位置,并且 nums[slow] 标记为0,并且慢指针移动到 next 位置。最终 for 循环退出后,返回 false 即可,参见代码如下:

解法二:

class Solution {
public:
bool circularArrayLoop(vector<int>& nums) {
int n = nums.size();
for (int i = ; i < n; ++i) {
if (nums[i] == ) continue;
int slow = i, fast = getNext(nums, i), val = nums[i];
while (val * nums[fast] > && val * nums[getNext(nums, fast)] > ) {
if (slow == fast) {
if (slow == getNext(nums, slow)) break;
return true;
}
slow = getNext(nums, slow);
fast = getNext(nums, getNext(nums, fast));
}
slow = i;
while (val * nums[slow] > ) {
int next = getNext(nums, slow);
nums[slow] = ;
slow = next;
}
}
return false;
}
int getNext(vector<int>& nums, int i) {
int n = nums.size();
return (((nums[i] + i) % n) + n) % n;
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/457

参考资料:

https://leetcode.com/problems/circular-array-loop/

https://leetcode.com/problems/circular-array-loop/discuss/94148/Java-SlowFast-Pointer-Solution

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] Circular Array Loop 环形数组循环的更多相关文章

  1. [LeetCode] 457. Circular Array Loop 环形数组循环

    You are given a circular array nums of positive and negative integers. If a number k at an index is ...

  2. 【LeetCode】457. Circular Array Loop 环形数组是否存在循环 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题思路 快慢指针 代码 日期 题目地址:https://le ...

  3. Leetcode: Circular Array Loop

    You are given an array of positive and negative integers. If a number n at an index is positive, the ...

  4. Leetcode 457.环形数组循环

    环形数组循环 给定一组含有正整数和负整数的数组.如果某个索引中的 n 是正数的,则向前移动 n 个索引.相反,如果是负数(-n),则向后移动 n 个索引. 假设数组首尾相接.判断数组中是否有环.环中至 ...

  5. Java实现 LeetCode 457 环形数组循环

    457. 环形数组循环 给定一个含有正整数和负整数的环形数组 nums. 如果某个索引中的数 k 为正数,则向前移动 k 个索引.相反,如果是负数 (-k),则向后移动 k 个索引.因为数组是环形的, ...

  6. LeetCode 457. Circular Array Loop

    原题链接在这里:https://leetcode.com/problems/circular-array-loop/ 题目: You are given a circular array nums o ...

  7. [Swift]LeetCode457. 环形数组循环 | Circular Array Loop

    You are given an array of positive and negative integers. If a number n at an index is positive, the ...

  8. [LeetCode] Design Circular Deque 设计环形双向队列

    Design your implementation of the circular double-ended queue (deque). Your implementation should su ...

  9. [LeetCode] Design Circular Queue 设计环形队列

    Design your implementation of the circular queue. The circular queue is a linear data structure in w ...

随机推荐

  1. Android Service基础

    Service Service 是一个组件,用来执行长时间的后台操作,不提供用户界面. 另一个应用组件可以启动一个Service,它将持续地在后台运行,即便是用户转移到另一个应用它也不会停止. 另外, ...

  2. runtime.getruntime.availableprocessors

    1:获取cpu核心数: Runtime.getRuntime().availableProcessors(); 创建线程池: Executors.newFixedThreadPool(nThreads ...

  3. 使用 Except 和 Intersect

    做了一个如下的小厕所,如果我需要得到返回是 d,f 那我需要用那组语句呢? A: ;WITH CA AS( SELECT * FROM (VALUES('a'),('b'),('c'),('d'))a ...

  4. python中Properties的一些小用法

    property最大的用处就是可以为一个属性制定getter,setter,delete和doc,他的函数原型为: def __init__(self, fget=None, fset=None, f ...

  5. tomcat超时、内存不足

    1.Tomcat 启动超过45s启动失败,报超时错误 可以Eclipse 下Tomcat中扩大tomcat启动时间,默认为45 ,修改为245 2 . tomcat启动内存不足 Run - Run C ...

  6. linux,windows,ubuntu下git安装与使用

    ubuntu下git安装与使用:首先应该检查本地是否已经安装了git ,如果没有安装的话,在命令模式下输入 sudo apt-get install git 进行安装 输入git命令查看安装状态及常用 ...

  7. DNS协议(一)

    在互联网上要想与另外一台主机通信,要知道对方的IP地址,但是IP地址是很难记忆的, 比如百度的一台服务器的IP地址为115.239.210.27,我们在浏览器中输入http://115.239.210 ...

  8. 事后诸葛亮——城市安全风险管理项目Postmortem结果

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 本系统希望实现快速识别危害因素,使工作人员对风险作出准确的评估.即让使用者熟悉潜在的危险因素,知道 ...

  9. mysql基础篇 - 数据库及表的修改和删除

    基础篇 - 数据库及表的修改和删除         修改和删除 一.实验简介 本节实验中,我们将学习并实践如何对数据库的内容做修改,删除,重命名等操作. 二.实验准备 在正式开始本实验内容之前,需要先 ...

  10. 视频聊天 Demo

    ESFramework Demo -- 入门Demo,简单的即时通讯系统(附源码) 是基于ESFramework实现的一个简单的文字聊天demo,现在,我们将在这个demo的基础上,使用OMCS为其增 ...