# 按k个一组翻转链表 defreverseKGroup(self, head: ListNode, k: int) -> ListNode: start = head # start指向当前组的开始节点 end = self.teamEnd(start, k) # 找到第一组的结束节点 if end isNone: # 如果第一组的长度不足k,直接返回原链表头节点 return head # 第一组很特殊因为牵扯到换头的问题 # 翻转后,第一组的末尾节点end会成为整个链表的新头节点 head = end self.reverse(start, end) # 翻转第一组节点 # 翻转之后start变成了上一组的结尾节点 lastTeamEnd = start # lastTeamEnd记录上一组翻转后的尾节点 # 循环处理剩余的链表 while lastTeamEnd.nextisnotNone: start = lastTeamEnd.next# 下一组的开始节点 end = self.teamEnd(start, k) # 找到下一组的结束节点 if end isNone: # 如果剩余部分的长度不足k,直接返回头节点,不进行翻转 return head self.reverse(start, end) # 翻转当前组 lastTeamEnd.next = end # 将上一组的尾节点与当前组翻转后的头节点(即原来的end)连接起来 lastTeamEnd = start # 更新lastTeamEnd为当前组翻转后的尾节点(即原来的start) return head # 返回新的头节点
# 当前组的开始节点是s,往下数k个找到当前组的结束节点返回 defteamEnd(self, s: ListNode, k: int) -> ListNode: """找到从s开始第k个节点""" # 从s开始,向后移动k-1次 while k - 1 != 0and s isnotNone: s = s.next# s指针后移 k -= 1# 计数器减1 return s # 返回第k个节点,如果不足k个则返回None
# s -> a -> b -> c -> e -> 下一组的开始节点 # 上面的链表通过如下的reverse方法调整成 : e -> c -> b -> a -> s -> 下一组的开始节点 # 翻转从s到e的这一段链表 defreverse(self, s: ListNode, e: ListNode): """翻转从s到e的链表段""" e = e.next# e是当前组的结尾,e.next指向下一组的开头 pre = None# pre是前一个节点,初值为None cur = s # cur是当前节点,初值为s next_node = None# next是下一个节点 # 遍历当前组,直到cur到达下一组的开头 while cur != e: next_node = cur.next# 保存当前节点的下一个节点 cur.next = pre # 将当前节点的next指针指向前一个节点 pre = cur # pre, cur向后移动 cur = next_node s.next = e # 翻转后,原来的头节点s变成了尾节点,它的next应该指向下一组的开头
# 提交如下的方法 defisPalindrome(self, head: ListNode) -> bool: if head isNoneor head.nextisNone: # 空链表或只有一个节点的链表是回文结构 returnTrue slow = head # slow指针每次走一步,fast指针每次走两步 fast = head # 找中点,当fast到达链表末尾时,slow正好在中间位置 while fast.nextisnotNoneand fast.next.nextisnotNone: slow = slow.next# slow指针后移一步 fast = fast.next.next# fast指针后移两步 # 现在中点就是slow,从中点开始往后的节点逆序 pre = slow # pre是反转后的链表的头节点,初始是slow cur = pre.next# cur是当前要处理的节点,初始是slow的下一个 next_node = None# next_node用于保存cur的下一个节点 pre.next = None# 断开前半部分和后半部分的连接 # 循环反转后半部分链表 while cur isnotNone: next_node = cur.next# 保存下一个节点 cur.next = pre # 当前节点的next指向前一个节点(pre) pre = cur # pre和cur指针后移 cur = next_node # 上面的过程已经把链表调整成从左右两侧往中间指 # head -> ... -> slow <- ... <- pre ans = True# ans默认为True,即假设是回文 left = head # left指针从头开始 right = pre # right指针从后半部分的头(即反转前的尾)开始 # left往右、right往左,每一步比对值是否一样,如果某一步不一样答案就是false while left isnotNoneand right isnotNone: if left.val != right.val: # 如果左右两边的值不相等 ans = False# 那么不是回文 break# 退出循环 left = left.next# 移动指针 right = right.next # 本着不坑的原则,把链表调整回原来的样子再返回判断结果 # 再次反转后半部分,恢复原链表结构 cur = pre.next# cur是当前要处理的节点,初始是pre的下一个 pre.next = None# 断开连接 next_node = None# next_node用于保存cur的下一个节点 # 循环将后半部分链表反转回来 while cur isnotNone: next_node = cur.next# 保存下一个节点 cur.next = pre # 当前节点的next指向前一个节点(pre) pre = cur # pre和cur指针后移 cur = next_node return ans # 返回最终的判断结果
# 找链表中点 slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next # slow指向中点
# 判断链表是否有环 slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: returnTrue# 有环 returnFalse
2. 虚拟头节点技巧
当需要修改头节点时,使用虚拟头节点简化操作:
1 2 3 4
dummy = ListNode(0) dummy.next = head # 对dummy.next进行操作 return dummy.next
3. 双指针技巧
用于处理需要同时操作两个位置的问题:
1 2 3 4 5 6 7 8 9
# 删除倒数第n个节点 dummy = ListNode(0, head) first = second = dummy for _ inrange(n + 1): first = first.next while first: first = first.next second = second.next second.next = second.next.next
4. 链表翻转技巧
翻转是链表的基础操作:
1 2 3 4 5 6 7 8 9
defreverse(head): pre = None cur = head while cur: next_node = cur.next cur.next = pre pre = cur cur = next_node return pre