目录
一、反转链表 - 引入
参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/749/
二、反转链表
2.1 题目要求
2.2 解决过程
注意事项
本题应返回经修改的单链表的头节点 head,而不是什么都不返回或返回别的 (尽管题目并未明说...)。
个人实现
法一:双指针。如第一节所述,按原始顺序迭代节点,并将它们逐个移动到链表头部。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/11 - 76.37%
# 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:
if not head: # 空链表
return
front = head # 靠前/左节点 - 总为原链表的头节点
rear = front.next # 靠后/右节点 - 作为 front 的后驱节点用于移动
while rear:
# 移动 - rear 移动到 head 前成为新 head (注意移动次序正确)
front.next = rear.next # 第一个指向第三个
rear.next = head # 第二个指向第一个
head = rear # 头节点重指定为当前首个节点
# 更新
rear = front.next # rear 重指定为 front 的后驱节点
return head
相关解说 (思想一致)
其他实现
法一:双指针迭代。此方法比个人实现还要简洁,原理如下所示:
class Solution(object):
def reverseList(self, head):
pre = None # 靠前/左节点
cur = head # 靠后/右节点
while cur:
tmp = cur.next # 记录当前节点的下一个节点
cur.next = pre # 然后将当前节点指向pre
pre = cur # pre 和 cur节点各后移一位
cur = tmp
return pre
2020/07/11 - 99.95% - 最优之一
法二:递归。递归方法通常 写法简洁,但难免相应地更加 抽象费解,需要多画图和理解。原理如下所示:
class Solution(object):
def reverseList(self, head):
# 递归终止条件
if not (head and head.next): # if (not head) or (not head.next):
return head
# 此 cur 为最后一个节点
cur = self.reverseList(head.next)
# 假设链表 1->2->3->4->5, 那么此时 cur 就是 5
# 而 head 是 4, head 的下一个是 5,下下一个为空
# 所以 head.next.next 就是 5->4
head.next.next = head # 反向引用
# 防止链表循环,需将 head.next 设为空
head.next = None # 被引用节点指向 None
# 每层递归函数都返回 cur, 即最后一个节点
return cur
2020/07/11 - 99.90% - 最优之一
参考文献
https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/750/
三、移除链表元素
3.1 题目要求
3.2 解决过程
注意事项
老规矩,结果返回经处理的新链表头节点即可。
个人实现
法一:哑节点/哨兵节点 + 双指针迭代。思想很直白,遍历链表找到目标节点即通过跳跃引用间接删除。同时为避免对头部节点等特殊情况特殊处理,使用哑节点/哨兵节 dummy 点作为伪头,使各节点操作一致。此外,注意需返回新链表的头节点,结果 应返回 dummy.next 而非 head。因为当头节点也是待删除节点时,即便“删去”了头节点,变量 head 仍保存了其引用,此时结果返回 head 只会得到最初的一个头节点,而非新链表。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/11 - 99.95% - 最优
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode("h") # 哑节点/哨兵节点, 使各节点能够得到一致处理
dummy.next = head # 作为伪头
slow = dummy # 慢指针
fast = head # 快指针 (不用 dummy.next 是因为可能链表为空)
while fast:
if fast.val == val:
slow.next = fast.next # 跳跃引用实现删除
else:
slow = slow.next # 否则慢指针移动一步
fast = fast.next # 不论如何快指针要移动一步
return dummy.next # 不能 return head, 例如特殊情况 [1], 1
法二:单指针迭代 + 特殊情况特殊处理,稍麻烦些。
2020/07/11 - 99.80% - 次优
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
if not head: # 特殊情况 - 空链表
return head
while head and head.val == val: # 特殊情况 - 要删除头节点
head = head.next
ptr = head #单指针
while ptr:
if ptr.next and ptr.next.val == val:
ptr.next = ptr.next.next
else:
ptr = ptr.next
return head
官方实现与说明
# 官方实现 - 和个人实现基本一模一样
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
sentinel = ListNode(0)
sentinel.next = head
prev, curr = sentinel, head
while curr:
if curr.val == val:
prev.next = curr.next
else:
prev = curr
curr = curr.next
return sentinel.next
其他实现
法一:递归。这就好理解了,元素不是 val 的节点就保留,否则跳跃引用间接删除。如下所示:
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
if not head:
return None
head.next = self.removeElements(head.next, val)
if head.val == val:
return head.next
else:
return head
参考文献
https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/752/
四、奇偶链表
4.1 题目要求
4.2 解决过程
个人实现
法一:双指针。如果不要求空间复杂度 O(1) (in-place),可以使用哈希表等额外空间轻易解决。而本方法则通过迭代链表,分别由 快指针 odd 和 慢指针 even 组成 奇数位链表 和 偶数位链表,并在遍历至尾结点时,令 偶数位链表尾结点 指向 奇数位链表头节点 half 实现奇偶链表。空间复杂度 O(1),时间复杂度 O(n)。
2020/07/11 - 99.95% - 最优
2020/11/13 - 100% (36ms) - 最佳
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head: # null linked list
return head
odd = head # odd pointer
even = half = head.next # even pointer and half pointer
while even and even.next:
# skip connection
odd.next = odd.next.next
even.next = even.next.next
# point to next node
odd = odd.next
even = even.next
odd.next = half # half connection
return head
参考文献
https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/753/
https://leetcode-cn.com/problems/odd-even-linked-list/submissions/
五、回文链表
5.1 题目要求
5.2 解决过程
失败测试
最先试了一个计数器方法,然而这只能判断中点左右元素之和是否相等,而不能判断是否回文,因为无法比较顺序 (如 1->2->3->1->2 或 1->2->1->2 等)。虽然不能解题 (多种步长/间隔组合另当别论),但思想不错,不妨记录一下。空间复杂度 O(1),时间复杂度 O(n)。
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if not head:
return True
# 计算链表长度
length = 0
curr = head # 第一次遍历指针
while curr:
length += 1
curr = curr.next
# 累加-累减判断回文与否
counter = 0 # 计数器
times = length // 2 # 半段长度
ptr = head # 第二次遍历指针
# 前半段节点元素累加
for _ in range(times):
counter += ptr.val
ptr = ptr.next
# 中间节点元素跳过
if length % 2 != 0:
ptr = ptr.next
# 后半段节点元素累减
for _ in range(times):
counter -= ptr.val
ptr = ptr.next
return counter == 0
个人实现
法一:遍历链表依次保存各节点元素,然后按通常方式判断回文字符串 (如逆序重排、双指针、递归等)。空间复杂度 O(n),时间复杂度 O(n)。
2020/10/23 - 98.65% (68ms)
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
index = []
curr = head
while curr:
index.append(curr.val) # 依次保存各节点元素
curr = curr.next
return True if index == index[::-1] else False # 判断正序逆序是否一致
官方实现与说明
// C++ implementation
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> vals;
while (head != nullptr) {
vals.emplace_back(head->val);
head = head->next;
}
for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {
if (vals[i] != vals[j]) {
return false;
}
}
return true;
}
};
# Python implementation
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
vals = []
current_node = head
while current_node is not None:
vals.append(current_node.val)
current_node = current_node.next
return vals == vals[::-1]
# 伪代码
function print_values_in_reverse(ListNode head)
if head is NOT null
print_values_in_reverse(head.next)
print head.val
# Python implementation
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
self.front_pointer = head # self 是必要的
def recursively_check(current_node=head):
if current_node is not None:
if not recursively_check(current_node.next):
return False
if self.front_pointer.val != current_node.val: # 前后对应元素对比
return False
self.front_pointer = self.front_pointer.next # 移向下一个对比元素
return True
return recursively_check()
# 简化表示, 加强理解!!!
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
self.left = head
def check(right=head):
if right:
if not check(right.next): # 右指针先递归到最后一个, 再逐渐回溯
return False
if right.val != self.left.val: # 比较对应位置节点值
return False
self.left = self.left.next # 左指针后移一位
return True # 本次比较相等. 将结果反馈回上一层
return check()
2020/10/23 - 52.17% (88ms)
递归实现虽然简洁高效,但也相应地十分抽象费解,需要努力理解才行。
// C++ implementation
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr) {
return true;
}
// 找到前半部分链表的尾节点并反转后半部分链表
ListNode* firstHalfEnd = endOfFirstHalf(head);
ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
// 判断是否回文
ListNode* p1 = head;
ListNode* p2 = secondHalfStart;
bool result = true;
while (result && p2 != nullptr) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
// 还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
ListNode* endOfFirstHalf(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
# 通过双指针定位链表中间节点, 不错的想法 (快指针走到末尾, 慢指针刚好到中间)
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if head is None:
return True
# Find the end of first half and reverse second half.
first_half_end = self.end_of_first_half(head) # 链表中间节点
second_half_start = self.reverse_list(first_half_end.next) # 链表后半部分反转
# Check whether or not there's a palindrome.
result = True
first_position = head
second_position = second_half_start
while result and second_position is not None:
if first_position.val != second_position.val: # 从链表开头和中间处一一对比
result = False
first_position = first_position.next # 指向下一个元素
second_position = second_position.next # 指向下一个元素
# Restore the list and return the result.
first_half_end.next = self.reverse_list(second_half_start) # 恢复链表后半部分
return result
def end_of_first_half(self, head):
""" 通过快慢指针, 使慢指针指向链表中间节点引用 """
fast = head
slow = head
while fast.next is not None and fast.next.next is not None:
fast = fast.next.next
slow = slow.next
return slow
def reverse_list(self, head):
""" 反转链表 """
previous = None
current = head
while current is not None:
next_node = current.next
current.next = previous
previous = current
current = next_node
return previous
当然,只反转前半个链表而不反转回去也可以,例如:
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow = head
fast = head
pre = head
prepre = None
while fast and fast.next:
#pre记录反转的前半个列表,slow一直是原表一步步走
pre = slow
slow = slow.next
fast = fast.next.next
pre.next = prepre
prepre = pre
if fast:#长度是奇数还是偶数对应不同情况
slow = slow.next
while slow and pre:
if slow.val != pre.val:
return False
slow = slow.next
pre = pre.next
return True
参考文献
https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/754/
https://leetcode-cn.com/problems/palindrome-linked-list/solution/hui-wen-lian-biao-by-leetcode/
六、小结 - 链表经典问题
参考文献:https://leetcode-cn.com/explore/learn/card/linked-list/195/classic-problems/755/