一、剑指 Offer 18. 删除链表的节点
1.1 题求
1.2 求解
法一:哨兵节点 + 遍历节点
# 32ms - 96.24%
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
# 哑节点 / 哨兵节点
dummy = ListNode(-1)
dummy.next = head
# 辅助左 / 右指针
left = dummy
right = dummy.next
while right:
# 找到指定节点, 直接跳接
if right.val == val:
left.next = right.next
break
# 没找到指定节点, 指向下一组
else:
left = right
right = right.next
# 返回新链表头节点
return dummy.next
官方说明
# 36ms - 89.05%
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if head.val == val:
return head.next
pre, cur = head, head.next
while cur and cur.val != val:
pre, cur = cur, cur.next
if cur:
pre.next = cur.next
return head
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/505fc7/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/509cy5/
二、剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
2.1 题求
2.2 求解
法一:双指针
# 40ms - 94.54%
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
n = len(nums)
left = 0
right = n - 1
while left < right:
# 令 left 指向一个偶数
while nums[left] % 2 == 1:
left += 1
# 针对全奇数数组
if left == n:
return nums
# 令 right 指向一个奇数
while nums[right] % 2 == 0:
right -= 1
# 针对全偶数数组
if right == -1:
return nums
# 针对已排序数组
if left < right:
# 奇偶交换
nums[left], nums[right] = nums[right], nums[left]
# 继续移动
left += 1
right -= 1
return nums
法一:优化
# 36ms - 98.19%
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
left = 0
right = len(nums) - 1
while left < right:
# 令 left 指向一个偶数
while left < right and nums[left] & 1 == 1: # 使用 & 位运算代替 % 取余更快
left += 1
# 令 right 指向一个奇数
while left < right and nums[right] & 1 == 0: # 使用 & 位运算代替 % 取余更快
right -= 1
# 奇偶交换
nums[left], nums[right] = nums[right], nums[left]
return nums
官方说明
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
i, j = 0, len(nums) - 1
while i < j:
while i < j and nums[i] & 1 == 1:
i += 1
while i < j and nums[j] & 1 == 0:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
return nums
2.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v8a6t/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v3rqr/
三、剑指 Offer 22. 链表中倒数第 k 个节点
3.1 题求
3.2 求解
法一:数组 (以空间换时间)
# 32ms - 87.75
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
node_list = []
while head:
node_list.append(head)
head = head.next
return node_list[-k]
法二:双指针
# 28ms - 96.00%
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
# 快、慢指针
slow = fast = head
# 链表总长(快指针步数)、链表半长(慢指针步数)
len_list = half_index = 0
while fast:
# 慢指针走第一步
slow = slow.next
half_index += 1
# 快指针走第一步
fast = fast.next
len_list += 1
# 快指针走第二步
if fast:
fast = fast.next
len_list += 1
# 目标节点索引, 倒数第 k 个即正数第 len_list - k 个
target_index = len_list - k
# 目标节点在慢指针右侧, 则从慢指针开始
if target_index >= half_index:
# 从慢指针所指中部出发需要走的步数
steps = target_index - half_index
while steps:
slow = slow.next
steps -= 1 # 需走步数 -1
return slow
# 目标节点在慢指针左侧, 则从头节点开始
while target_index:
head = head.next
target_index -= 1
return head
官方说明
# 28ms - 95.89%
# 非常巧妙的解法!!!
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
former, latter = head, head
for _ in range(k): # 先走
former = former.next
while former: # 后走
former, latter = former.next, latter.next
return latter
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
former, latter = head, head
for _ in range(k):
if not former: # 考虑越界问题
return
former = former.next
while former:
former, latter = former.next, latter.next
return latter
3.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58tl52/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/588dhc/
四、剑指 Offer 25. 合并两个排序的链表
4.1 题求
4.2 求解
法一:双指针
# 40ms - 98.21%
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if not l1:
return l2
elif not l2:
return l1
head = dummy = ListNode(-1)
while l1 and l2:
if l1.val <= l2.val:
head.next = l1
l1 = l1.next
else:
head.next = l2
l2 = l2.next
head = head.next
head.next = l2 if l2 else l1
return dummy.next
官方说明
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
cur = dum = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
cur.next, l1 = l1, l1.next
else:
cur.next, l2 = l2, l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return dum.next
4.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vq98s/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5v6htd/
五、剑指 Offer 52. 两个链表的第一个公共节点
5.1 题求
5.2 求解
法一:双指针
# 120ms - 98.15%
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
# 起点
left, right = headA, headB
# 遍历后交换起点再遍历
while left != right:
left = left.next if left else headB
right = right.next if right else headA
# 交叉点处相等, 非空交叉, 空则平行
return left # right 也可
5.3 解答
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/oe5os3/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/oe95zm/
六、剑指 Offer 57. 和为 s 的两个数字
6.1 题求
6.2 求解
法一:哈希集合
# 120ms - 65.21%
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hashset = set()
for n in nums:
diff = target - n # 作差
if n in hashset: # 符合
return [diff, n]
hashset.add(diff) # 记录
官方说明
# 88ms - 97.77%
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对撞双指针
i, j = 0, len(nums)-1
while i < j:
s = nums[i] + nums[j] # 当前和
if s > target: # 当前和过大, 减小较大值
j -= 1
elif s < target: # 当前和过小, 增大较小值
i += 1
else:
return [nums[i], nums[j]] # 当前和满足
return []
6.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5832fi/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58qpje/
七、剑指 Offer 58 - I. 翻转单词顺序
7.1 题求
7.2 求解
法一:字符串方法
# 28ms - 96.58%
class Solution:
def reverseWords(self, s: str) -> str:
return ' '.join(s.strip().split()[::-1])
法二:迭代
# 44ms - 42.57%
class Solution:
def reverseWords(self, s: str) -> str:
# 左边界 + 检测全空字符串
start = 0
while start < len(s) and s[start] == ' ':
start += 1
if start == len(s):
return ''
# 右边界
index = len(s) - 1
while s[index] == ' ':
index -= 1
# 总字符串/单词 列表
string = []
word = []
# 从右往左遍历
while index >= start:
# 记录当前 word 的有效 char
if s[index] != ' ':
word.append(s[index])
# 遇到空格
else:
if word:
# 将当前有效的 word 逆序加入 string
string.append(''.join(word[::-1]))
word.clear()
# 左移一位
index -= 1
string.append(''.join(word[::-1]))
return ' '.join(string)
官方说明
# 40ms - 59.59%
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip() # 删除首尾空格
i = j = len(s) - 1
res = []
while i >= 0:
while i >= 0 and s[i] != ' ':
i -= 1 # 搜索首个空格
res.append(s[i+1: j+1]) # 添加单词
while i >= 0 and s[i] == ' ':
i -= 1 # 跳过单词间空格
j = i # j 指向下个单词的尾字符
return ' '.join(res) # 拼接并返回
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip() # 删除首尾空格
strs = s.split() # 分割字符串
strs.reverse() # 翻转单词列表
return ' '.join(strs) # 拼接为字符串并返回
class Solution:
def reverseWords(self, s: str) -> str:
return ' '.join(s.strip().split()[::-1])
7.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/586ecg/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/58kxyv/