给定一个整数数组 nums 和一个目标值 target在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
创建空字典,将目标值与 nums 列表中的数值之差存储在字典中,同时与其他数值进行比对 。
字典中的键(key)对应的是目标值与列表数值的差(即是 target - nums[i]),而字典的值(value)对应的是当前 nums[i] 的索引位置 i。
如果 nums[i] 在字典中,(注意: i 是变化的值,为了防止跟前面的混淆,前面的 “target - nums[i]” 写作 “target - nums[k]” 以作区分),也就是说 target - nums[k] = nums[i] (表明前面字典存储的键当中,有跟当前的 nums[i] 相等的数值),此时返回 target - nums[k] 在字典中对应的值,也即是 nums[k] 的索引位置 k,以及当前 nums[i] 的索引位置 i。两者就是要求的结果。
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
# 创建空字典
# 存储目标值与 nums 列表其中一个数值之差,同时与其他数值进行比对
# 相等返回对应的索引
auxiliary_dict = {}
# 获取 nums 列表的长度
nums_len = len(nums)
for i in range(nums_len):
# 目标值与对应 nums 列表 i 索引中的数值之差
two_sub = target - nums[i]
# 如果 nums[i] 在 auxiliary_dict 列表中,返回对应的索引
# 这里 nums[i] 是变化的,i 值不断变化。此时的 num[i] 如果在字典中,表示前面某次当中,目标值与其中一个数值之差等于当前的 num[i]
if nums[i] in auxiliary_dict:
return auxiliary_dict[nums[i]], i
# 否则,往字典中添加键值对
auxiliary_dict[two_sub] = i
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例 1:
输入: 121
输出: true
示例 2:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
class Solution:
def isPalindrome(self, x: int) -> bool:
'''判断数字是否是回文数
Args:
x: 传入的待判断的数字
Returns:
返回是否是回文数的判断结果,结果类型为布尔型
'''
# 首先负数不是回文数,
# 当传进来的数字非负,但最后一位数为 0 时,
# 只有当数字为 0,才符合回文数的要求。
if (x < 0 ) or (x != 0 and x % 10 == 0):
return False
# 将传入的数字分为前后两部分,
# 将后半部分进行倒序,与前面部分进行比较,
# 相等则为回文数,否则不是回文数。
cur = 0 # 用于存储后半部分数字
while (x > cur):
cur = cur * 10 + x % 10
x //= 10
# 考虑数字位数为奇数的情况下,可以用 cur // 10 进行消除中间数
return (x == cur) or (x == (cur // 10))
给定一个只包括 '(',')','{','}','[',']'
的字符串,判断字符串是否有效。
有效字符串需满足:
注意空字符串可被认为是有效字符串。
示例1:
输入: "()"
输出: true
示例2:
输入: "()[]{}"
输出: true
示例3:
输入: "([)]"
输出: false
示例4:
输入: "{[]}"
输出: true
class Solution:
def isValid(self, s: str) -> bool:
'''判断是否是有效的括号
Args:
str: 包含括号的字符串
Returns:
返回判断的结果,满足条件:
1. 左括号必须用相同的类型的右括号闭合。
2. 左括号必须以正确的顺序闭合。
空字符串可以被认为是有效的字符串
返回类型为布尔型
'''
# 以栈形式存储左括号
stack = []
# 以右括号当成键映射对应类型的左括号
prths_mapping = {'}': '{', ']': '[', ')': '('}
# 遍历字符串,遇左括号则进行入栈
for ch in s:
# 对字符进行判断,是否为右括号
if ch in prths_mapping:
# 为右括号的情况下,判断 stack 栈顶是否是同类型的左括号
# pop 出栈顶的字符
# 若 stack 为空,用 '?' 进行标记
pop_prth = stack.pop() if stack else '?'
# 如果左右括号不成对,直接返回 False
if prths_mapping[ch] != pop_prth:
return False
else: # 左括号入栈
stack.append(ch)
# stack 最终为空,则表示为有效
return not stack
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
# # Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
'''将两个有序链表合并成一个链表然后返回
Args:
l1: 有序链表 1
l2: 有序链表 2
Returns:
返回合并后的有序链表
'''
# 定义一个哨兵节点,用以正确返回合并后的链表
pre_head = ListNode(None)
# 控制 cur 指针,比较节点大小
cur = pre_head
# 比较两个链表的节点大小,当两个链表任意一个为空,则终止
while l1 and l2:
# 当 l1 的节点较小,则指针指向 l1,同时向后移
if l1.val < l2.val:
cur.next, l1 = l1, l1.next
else: # 否则,指针指向 l2,同时向后移
cur.next, l2 = l2, l2.next
# 维护 cur 指针
cur = cur.next
# 考虑其中任意一个链表为空,将非空的链表拼接在合并链表后面
cur.next = l1 if l1 else l2
return pre_head.next
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
class Solution:
def merge(self, nums1, m: int, nums2, n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
合并两个有序数组为一个有序数组
Args:
num1: 有序数组 1
m: num1 的元素数量
num2: 有序数组 2
n:num2 的元素数量
Returns:
返回合并后的有序数组
"""
# 通过双指针的方法,从后向前遍历的方法、
# 定义指针 p1 指向 num1 的数字末尾
# 定义指针 p2 指向 num2 的数字末尾
# 定义指针 p 指向 num1 的最末尾
p1 = m - 1
p2 = n - 1
p = m + n - 1
# 当 p 小于 0,即是 p1 和 p2 其中一个小于 0,遍历结束
while p1 >= 0 and p2 >= 0:
if nums1[p1] < nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
# 两个数组都是有序的
# 若是 p1 先小于 0,将 num2 剩余部分放在 num1 数组前面
nums1[:p2 + 1] = nums2[:p2 + 1]
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
pre
指向 None,一个 cur
指向链表头部;next
指针指向 pre
,同时 pre
和 cur
指针都往前进一位(这里注意元素保存)cur
为None,迭代完毕,pre
此时指向链表头部,返回。# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
'''反转链表
Args:
head: 链表
Returns:
返回反转后的链表
'''
# 定义两个指针
# pre 指向 None,cur 指向原链表头部
pre = None
cur = head
# cur 为 None,迭代结束
while cur:
# 这里注意引用更改,节点的存储
# 下面的代码相当于
# 存储节点值
# tmp = cur.next
# 将 cur 的 next 指向 pre
# cur.next = pre
# 移动两个指针
# pre = cur
# cur = tmp
cur.next, pre, cur = pre, cur, cur.next
# 返回反转后的链表
return pre
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
注意:
假设字符串的长度不会超过 1010。
示例:
输入:
"abccccdd"
输出:
7
解释:
我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
回文串的概念:就是正反读都一样的字符串。
例如:abba,这个字符串是回文串,中心界限是 ab|ba 中间这条竖线的位置。abdba,而这个字符串也是回文串,中心界限在 ab(d)ba 中字符 d 的位置。
在这里我们可以看出,构成回文串,字符个数有可能是奇数,也有可能是偶数。
当构成回文串的字符个数为偶数的情况下,字符一定是成对出现的(如 abba,都是成对存在);当构成回文串的字符个数为奇数的情况下,剔除中间中心的字符,其他的字符也是成对存在的。(如 abdba,剔除中心字符 d,其他字符也是成对)
但是这样需要注意的是,题中所给出的字符,当某个字符个数为奇数的情况下,并不是说不能用来构建回文串,只要剔除一个,变为偶数,那么同样可构成回文串。
当某个字符个数为奇数的情况下,可以保留其中 1 个不剔除,用以作为回文串中心,所以需要看情况在成对字符构成回文串后,将保留的这 1 个字符添加进去。若是字符都是成对的情况,则不需要考虑这种情况。
class Solution:
def longestPalindrome(self, s: str) -> int:
from collections import Counter
# 导入 Counter 类,用以统计字母出现的次数
d = Counter(s)
ans = len(s)
# 统计字母出现次数为奇数有多少
odd = 0
# 遍历字母对应的个数
for value in d.values():
# 统计字母出现次数为奇数有多少
# 先对符合条件的进行统计 + 1
if value % 2 != 0:
odd += 1
# 当 odd 为 0 的情况下
# 也就是所有字符都成对存在的情况下,直接返回题目所给字符的长度即可
# 若 odd 不为 0,也就是有出现字母个数为奇数的情况下,
# 这里的思路是将所有的字符都添加进来,字符出现次数为奇数的情况,进行统计
# 将这些情况全部剔除,但同时需要保留 1 个字符作为中心,所以最后要 + 1
return ans if odd == 0 else ans - odd + 1
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
思路:双指针
这里要注意审题,题目中已经说明,数组是已经排过序的。那么根据这个前提,我们就能够得出一点,如果数组中,有重复项,那么重复项一定是相邻的。
根据这个前提,我们初始化两个指针,一个在前 front,一个在后 tail。front 先移动,如果 nums[front] == nums[tail],表示元素值重复,front 直接向后移动。
如果 nums[front] != nums[tail],那么表示两个元素不再是重复项,此时将 front 指针所对应的元素值复制给 nums[tail] 的下一个元素,也就是 nums[tail+1],同时 tail 也要向前移动 。
重复上面的过程,直到 front 到达数组尾部。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
length = len(nums)
if length == 0:
return 0
# 初始化双指针
# front 在前,tail 在后
tail = 0
front = 1
# 遍历数组
while front < length:
# 如果两指针所指的元素值不相同,
# 表示元素不重复,
# 这时候将 nums[front] 的值复制给 nums[tail+1]
if nums[front] != nums[tail]:
tail += 1
nums[tail] = nums[front]
# front 持续移动
front += 1
return tail + 1
给定一个 没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
思路:深度优化搜索
先看题目,以所给数组 [1, 2, 3] 的全排列为例:
以 1 开始,全排列有:[1,2,3], [1,3,2];
以 2 开始,全排列有 [2,1,3], [2,3,1];
以 3 开始,全排列有 [3,1,2], [3,2,1]。
从上面的情况,可以看出。枚举每个每一位可能出现的情况,已选择的数字在下面的选择则不能出现。按照这个做法,所有的情况将能够罗列出来。这里其实就是执行一次深度优先搜索,从根节点到叶子节点形成的路径就是一个全排列。
按照这种思路,沿用上面的例子,从空列表 [] 开始,以 1 开始为例。现在确定以 1 开始,则列表为 [1],现在选择 [2] 和 [3] 之中的一个,先选 2,最后剩下的只有数字 3,所以形成全排列 [1, 2, 3]。
已知还有一种情况,也就是 [1, 3, 2],那么如何实现从 [1, 2, 3] 到 [1, 3, 2] 的变化。深度优先搜索是如何实现的?其实是从 [1, 2, 3] 回到 [1, 2] 的情况,撤销数字 3,因为当前层只能选择 3,所以再撤销 2 的选择,这样后面的程序则能在选择 3 的时候后续也能选择 2。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def _dfs(nums, depth, pmt, be_selected, length, ans):
# 表示深度优化搜索的深度等于数组长度时,这个时候表示全排列已经生成
# 也就是符合情况的选择已经选择完毕
# 将这个全排列的情况添加到列表中
# 这里需要注意,pmt 在参数传递是引用传递,拷贝一份添加到结果中
if depth == length:
ans.append(pmt[:])
return
# 开始遍历
for i in range(length):
# be_selected,表示原数组中的元素的状态,是否被选择,是为 True,否为 False
if not be_selected[i]:
# 当元素被选择时,改变状态
be_selected[i] = True
# 将元素添加到 pmt 中,以构成后续
pmt.append(nums[i])
# 向下一层进行遍历
_dfs(nums, depth + 1, pmt, be_selected, length, ans)
# 遍历结束时,进行回溯,这个时候状态要进行重置
# 如上面说的 `[1, 2, 3]` 到 `[1, 3, 2]` 中变化,要撤销 3,再撤销 2,重新选择
# 状态改变
be_selected[i] = False
# 撤销
pmt.pop()
length = len(nums)
if length == 0:
return []
be_selected = [False] * length
ans = []
_dfs(nums, 0, [], be_selected, length, ans)
return ans
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
思路:异或,位运算
先说下异或的性质:二进制位同一位相同则为 0,不同则为 1。
再说说一下异或的规律:
若两数值相同,两者的异或结果为 0,(即是任何数与自身异或结果为 0)
任何数与 0 异或结果为本身
同时异或是满足交换律,结合律的(数学符合:⊕)
交换律: a ⊕ b = b ⊕ a
结合律: a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c
回来看本题,题目说明,【整型数组 nums 中,除两个数字之外,其他数字都出现了两次】。那么根据交换律、结合律将相同数字进行异或运算,那么相同数字都会变为 0,再根据第二条规律,那么剩下的出现一次的数字。
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
res = 0
# 全员进行异或
for num in nums:
res ^= num
# 找出不为 0 的最低位
# & 位运算的使用
div = 1
while (div & res == 0):
div <<= 1
# 进行分组
p, q = 0, 0
for num in nums:
if num & div:
p ^= num
else:
q ^= num
return [p, q]
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
思路:迭代、翻转链表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
if head == None and head.next == None and k < 2:
return head
# 定义哨兵节点
dummy = ListNode(0)
# 指向节点
dummy.next = head
# 定义前驱后继节点
pre = dummy
tail = dummy
# 控制 tail 到待翻转链表部分的末尾
while True:
count = k
while count > 0 and tail != None:
count -= 1
tail = tail.next
# 到达尾部时,长度不足 k 时,跳出循环
if tail == None:
break
# 这里用于下次循环
head = pre.next
# 开始进行翻转
while pre.next != tail:
tmp = pre.next
pre.next = tmp.next
tmp.next = tail.next
tail.next = tmp
# 重置指针
pre = head
tail = head
return dummy.next
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例:
输入: [4,1,2,1,2]
输出: 4
思路:位运算
说明位运算方法的实现前,我们先看下题目,在说明后面,题目要求【算法应具有线性事件复杂度,不需要额外的空间】。如果没有这个要求,我们能想到方法可能有以下几种:
以上方法都能够解决该问题,但是题意要求【算法应具有线性事件复杂度,不需要额外的空间】。那么这三个方法都不满足条件。
关于位运算的使用,这里再重新说一下异或的性质:二进制位同一位相同则为 0,不同则为 1。关于异或的规律:
任何数与自身异或结果为 0。
任何数与 0 异或结果为本身。
同时,异或满足交换律、结合律(数学符号:⊕)
交换律: a ⊕ b = b ⊕ a
结合律: a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c
先定义返回变量为 0,对数组变量依次进行异或,那么根据上面的规律,我们就可以计算出题目中所说那个不重复的数字。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
# 任何数与 0 异或结果为自身
# 这里定义变量 0,对数组所有元素进行异或时
# 任何数与自身异或结果为 0
# 那么最终剩下的就是单独的数字
res = 0
for x in nums:
res ^= x
return res
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
示例:
输入: [1,3,4,2,2]
输出: 2
说明:
思路:二分查找
这里需要注意,题意中说明,有 4 个提示。这里会限制一些方法,例如:
对数组排序,重复数相邻,根据这个就可以找到重复数(这里违背【不能更改原数组】)
使用哈希表,(这里违背【只能使用额外的 O(1) 算法】)
…
上面的方法,在没有限制的情况下,可以使用,但是在这里,由于题目给出了限制,所以暂不考虑。
先看本题,【给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n)】,这是题目中给定的前提。
根据这个前提,这里二分法的思路是先定一个数值(这里同样定 [left, right] 的中间值 mid),不过这里要统计的是原始数组中,小于等于这个 mid 的值的元素个数(这里定义为 count)。如果这个 count 严格大于 mid 的值的话,这个重复元素就会落在区间 [left, mid] 中。
这里涉及一个原理:抽屉原理。关于抽屉原理,大致是这样的一个现象:如果桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。
以题目中所给的示例 1 进行展开分析:
[1,3,4,2,2]
1
首先,我们知道,这里的数值 2 是重复值。现在,我们按照上面的思路来分析一下。
这里有 5 个数字,即是 n + 1 = 5,n = 4,那么这里数组的数值都在 1 到 4 之间。
现在先定一个值(根据上面所述的中间值 mid),这里定为 2,遍历数组,统计小于等于 2 的数字个数。这里可以看到,有 3 个值,严格大于 mid,所以重复值落在 [1, 2] 这个区间中。
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
size = len(nums)
left = 1
right = size - 1
while left < right:
# 先找中间值
mid = left + (right - left) // 2
# mid = (left + right) // 2
# 遍历数组
# 统计数组中小于等于中间值的元素个数
count = 0
for num in nums:
if num <= mid:
count += 1
# 如果统计数严格大于中间值时,那么重复值将落在 [left, mid] 这个区间
if count > mid:
# 将右边界缩小到 mid
right = mid
# 否则重复值落在 [mid + 1, right]
else:
left = mid + 1
return left
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
思路:栈
题目中,要查找有效括号,有可能需要从内往外扩展,这符合栈先入后出的特性。
我们先使用暴力解,来尝试解决这个问题。
有效的括号是成对出现的,那么我们尝试罗列所有可能的非空偶数长度子字符串,判断其是否有效。
当我们遇到左括号 ( 时,我们将它入栈。当遇到右括号 ) 时,从栈中弹出一个左括号 (,如果栈中没有了左括号,或者遍历完成后栈中还保留有元素,那么这个子字符串就是无效的。循环遍历,更新最大的长度。
class Solution:
def longestValidParentheses(self, s: str) -> int:
max_len = 0
stack = []
# 当先遇到 `)` 需要先弹出元素,这里可以防止报错
# 当遇到的 `()` 的字符时,-1 在计算长度的时候,发挥作用
stack.append(-1)
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
max_len = max(max_len, i - stack[-1])
return max_len
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
思路:哈希表、快慢指针
在这里,我们直接看示例,如果链表存在环,那么某一个结点被访问的次数将不止一次。
哈希表
那么,我们使用哈希表来存储访问的节点,判读依据如下:
当某个结点在被访问时,发现存在于哈希表中,那么我们可以判断该链表为环形链表。
否则继续访问,直至结点为空。
快慢指针
题目最后进阶部分,提出是否能够用常量空间复杂度解决问题。因为前面的方法,定义哈希表存储需要额外的空间。
在这里,我们用快慢指针的思路来解决。具体如下:
定义两个指针 p、q,其中一个快指针 p 每次移动两步,慢指针 q 每次移动一步;
如果不存在环,那么快指针会先一步到达尾部,那么我们就可以返回 False;
如果存在环,那么快指针将会再次到达链表的某个结点,最终快慢指针将会重合,返回 True。
# 哈希表
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
hash_map = {}
# 结点非空时
while head:
# 先判断结点是否已经存在于哈希表中
# 存在,则表示存在环
if head in hash_map:
return True
# 记录访问的节点,访问过都标记为 1
hash_map[head] = 1
head = head.next
return False
# 快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def hasCycle(self, head: ListNode) -> bool:
# 定义快慢指针
# 快指针 p 每次移动两步,慢指针 q 每次移动一步
p = head
q = head
while p and p.next:
q = q.next
p = p.next.next
if p == q:
return True
return False