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中存在循环,返回trye;否则返回false。
示例1:
输入:
nums = [2, -1, 1, 2, 2]
输出:
true
解释:
存在循环,按下标0->2->3->0->…进入循环。循环长度为3。
示例2
输入:
nums = [-1, 2]
输出:
false
解释:
按下标1->1->…进行循环,由于循环长度为1,但是根据定义,循环的长度必须大于1,所以不构成循环。
示例3:
输入:
nums = [-2, 1, -1, -2, -2]
输出:
false
解释:
按下标1->2->1->…进行循环,但是nums[1]是正数,nums[2]是负数,按照所有nums[seq[j]]应当全正或全负的规则,所以不构成循环,返回false。
思路:快慢指针
快慢指针:
将环形数组理解为图中的n个点,nums[i]表示i号点向[(i+nums[i]) mod n]号点连有一条单向的边。
这张图中每个点有且仅有一条出边,这样从某个点出发,沿着单向边不断移动,最终必然会进入一个环中。根据题意,要检查图中是否存在一个所有单向边方向一致的环。可以采用快慢指针解决本题。
具体而言,检查每个节点,令快指针从当前节点出发,每次移动两步,慢指针每次移动一步。每次移动,都检查方向是否与初始方向一致,若不一致,则结束遍历返回false。
遍历过程中,如果快慢指针相遇,或者移动方向改变,停止遍历。
当nums[i]为n的整数倍,即i的后继结点为i本身时,循环长度k=1,不满足题意,需要跳过此种情况。
对于遍历过的节点,如果仍未成环,则将这些节点对应数组中的值置为0,再继续遍历其他节点,以免重复访问。
Python代码
class Solution:
def circularArrayLoop(self, nums: List[int]) -> bool:
n = len(nums)
# 找到x的下一个位置
nextnode = lambda x: (x+nums[x]) % n
for i in range(n):
if nums[i] == 0:
continue
slow = i
fast = nextnode(i)
# 判断同向移动
while nums[slow]*nums[fast] > 0 and nums[fast] * nums[nextnode(fast)] > 0:
# 如果快慢指针相等,说明有环
if fast == slow:
# 如果是自环,退出循环
if slow == nextnode(slow):
break
else:
return True
# 快指针一次走两步,慢指针一次走一步
slow = nextnode(slow)
fast = nextnode(nextnode(fast))
# 访问过的节点设置为0
# 如果此前没有return,则此前遍历过的元素都不成环,为避免再次无效查找,将这些节点设置为0
while nums[i] > 0:
# 先找到下一个元素的index,将当前元素置为0,向下走
tmp = nextnode(i)
nums[i] = 0
i = tmp
# 当所有节点都遍历后,未成环返回False
return False