链表
链表问题很多可以采用递归进行操作。递归理清思路的时候,主要考虑问题最简单的情况。当只有一个节点或两个节点的时候,问题是如何解决的。理清递归退出的条件,可以更加清晰代码的作用。
1. 找出两个链表的交点 LeetCode 160 Easy
问题的核心思想是:两个链表被分成三个部分,a、b、c. 如果他们有共同的交点那么一定有公式 a + c + b = b + c + a a+c+b = b+c+a a+c+b=b+c+a,因此当a链表循环到末尾时,跳转到b链表上。那么他们一定会同时到达同一个节点上去。
如果不存在共同链c,那么有
a
+
b
=
b
+
a
a+b=b+a
a+b=b+a,他们会同时到达对方的末尾None
处,因此也会退出循环。
如果题目只是判断是否相交不需要给出相交节点的值,那么还有如下思路:
- 将A链表的尾巴连接到B链表的表头,如果B链表存在循环,那么相交
- 直接将A、B两个链表循环到末尾,如果末尾节点相同,那么相交
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
la, lb = headA, headB
if la == None or lb == None: return None
while la != lb:
la = la.next if la != None else headB
lb = lb.next if lb != None else headA
return la
//java代码
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1 = headA, l2 = headB;
if (l1 == null || l2 == null) return null;
while (l1 != l2) {
l1 = (l1 != null) ? l1.next : headB;
l2 = (l2 != null) ? l2.next : headA;
}
return l2;
}
}
2. 链表反转 LeetCode 206 Easy
- 递归(从只有两个节点的最简单情形入手思考)
- 头插法(需要创建一个全新的链表,空间复杂度 O ( n ) O(n) O(n))
- 直接指针修改(内存空间消耗小,空间复杂度 O ( 1 ) O(1) O(1))
头插法实现较为简单,这里给出递归实现的python代码。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
# 递归的出口
if head == None or head.next == None: return head
newHead = self.reverseList(head.next)
# 考虑只有2个节点的情形,就可以快速理解递归的思想(node1 -> node2 -> null)
head.next.next = head
head.next = None
return newHead
// 头插法:Java代码
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) return null;
// 创建一个全新的表头,使用头插法将节点插入次表头
ListNode resHead = new ListNode(0, null);
while (head != null) {
ListNode tmp = new ListNode(head.val);
tmp.next = resHead.next;
resHead.next = tmp;
head = head.next;
}
// 返回头结点的下一个节点
return resHead.next;
}
}
3. 归并两个有序链表 LeetCode 21 Easy
- 递归
- 依次合并
# 依次合并 56ms, 13.9MB
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
headNew = ListNode()
headP = headNew
while l1 and l2:
if l1.val < l2.val:
headP.next = l1
l1 = l1.next
headP = headP.next
else:
headP.next = l2
l2 = l2.next
headP = headP.next
if l1:
headP.next = l1
if l2:
headP.next = l2
return headNew.next
# 递归
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
if l1 == None: return l2
if l2 == None: return l1
if l1.val < l2.val:
l1.next = self.mergeTwoLists(l1.next, l2)
return l1
else:
l2.next = self.mergeTwoLists(l1, l2.next)
return l2
4. 有序链表删除重复节点 LeetCode 19 Medium
- 链表循环删除节点
- 递归(递归思想需要把问题简化:如果当前链表只包含两个节点,那么进行比较,返回删除节点,否则进行递归)
# while循环删除前后相同节点
# 44ms, 13.9MB
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head == None or head.next == None: return head
headP, headPP = head, head.next
while headPP.next:
if headP.val == headPP.val:
headP.next = headPP.next
else:
headP = headPP
headPP = headPP.next
if headP.val == headPP.val:
headP.next = None
return head
# 递归删除
# 80ms, 14.1MB
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
if head == None or head.next == None: return head
head.next = self.deleteDuplicates(head.next)
return head.next if head.val == head.next.val else head
5. 删除链表的倒数第n个节点 LeetCode 19 Medium
首先明确n <= len(LinkList)
,利用滑动窗口的思想,构建一个长度为n的窗口,两个指针一前一后的滑动行进。right
指针指到结尾的时候,left
指针位置就是应该删除的n的位置。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
right = head
left = head
while n > 0:
right = right.next
n -= 1
if right == None:
return head.next
while right.next != None:
right = right.next
left = left.next
left.next = left.next.next
return head
6. 交换链表的相邻两个节点 LeetCode 24 Medium
题目要求:不能修改节点val的值,O(1)的空间复杂度。
思路:创建一个新的头结点,用新的头结点不断修改其后面两个节点的顺序,并后移两位,知道循环到链的末尾。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
resHead = res = ListNode(0, head)
while res.next != None and res.next.next != None:
p = res.next
pp = p.next
next = pp.next
p.next = next
pp.next = p
res.next = pp
res = pp.next
return resHead.next
7. 链表求和 leetCode 445 Medium
题目给出提示:可不可以不修改链表的结构,换句话说就是不使用逆转链表的方法,直接计算结果。如果不能翻转链表,那么一定需要其他的数据结构来记录某些信息。这里使用的是栈。
方法一:逆序链表,然后遍历链表用普通的加法规则
方法二:使用栈存储两个链表中的值,栈的FILO(First In Last Out)特性,刚好可以把链表中的值倒序。
注意: 这里两个链表可以采用一个循环完成,只要一个链表没有到达结尾,循环即不停止。直接按照之前累加方法即可,循环开始部分添加两个if,判断空结果。
# 方法一
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# 下面代码新链表构成部分需要优化,可以直接生成新的表头,而不需要从第二个位置开始构建新的链表。
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
l1 = self.reverse(l1)
l2 = self.reverse(l2)
total = 0
resHead = ListNode(0, None)
while l1 != None or l2 != None:
if l1 != None:
total += l1.val
l1 = l1.next
if l2 != None:
total += l2.val
l2 = l2.next
if total > 9:
tmp = ListNode(total % 10, None)
total //= 10
if resHead.next:
tmp.next = resHead.next
resHead.next = tmp
else:
resHead.next = tmp
else:
tmp = ListNode(total, None)
total = 0
if resHead.next:
tmp.next = resHead.next
resHead.next = tmp
else:
resHead.next = tmp
if total != 0:
tmp = ListNode(total, None)
tmp.next = resHead.next
resHead.next = tmp
return resHead.next
def reverse(self, head):
if head == None or head.next == None: return head
newHead = self.reverse(head.next)
head.next.next = head
head.next = None
return newHead
# 方法二
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
stackL1 = []
stackL2 = []
while l1:
stackL1.append(l1.val)
l1 = l1.next
while l2:
stackL2.append(l2.val)
l2 = l2.next
total = 0
resFirst = ListNode(0, None)
while len(stackL1) > 0 or len(stackL2) > 0:
if len(stackL1) > 0:
total += stackL1.pop()
if len(stackL2) > 0:
total += stackL2.pop()
resFirst.val = total % 10
total //= 10
tmp = ListNode(total, resFirst)
resFirst = tmp
return resFirst.next if resFirst.val == 0 else resFirst
8. 回文链表
有多种思路
- 逆序构建一个相同的链表,进行比较,空间复杂度 O ( n ) O(n) O(n)
- 原来链表找到中间节点,前半段,后半段比较,空间复杂度 O ( 1 ) O(1) O(1)
Note: 难点在于理清三个指针逆序链表的顺序。剩下的判断就容易多了,详细描述可参考博主Hustudent20080101
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if head == None: return True
length = 0
point = head
while point:
length += 1
point = point.next
if length == 1:
return True
if length % 2 == 0: # 123 456 leaael
pre = length // 2
revert = length // 2
else:
pre = length // 2 + 1 # 123 4 567
revert = length // 2
point_head = head
point_prev = None
for i in range(pre):
point_head = point_head.next
while point_head != None:
point_next = point_head.next
point_head.next = point_prev
point_prev = point_head
point_head = point_next
while point_prev != None:
if head.val != point_prev.val:
return False
head = head.next
point_prev = point_prev.next
return True
9. 分隔链表
本题理解题意之后,难度不大。主要是两个指针prev, head
在链表上不停移动,构造结果的过程。如果能想到生成ans
数组,那么本题就很好理解了。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
total_len = 0
head = root
while head:
total_len += 1
head = head.next
ans = [None for _ in range(k)]
l, r = total_len // k, total_len % k
prev, head = None, root
for i in range(k):
ans[i] = head
for j in range(l + (1 if r > 0 else 0)):
prev, head = head, head.next
if prev: prev.next = None
r -= 1
return ans
10. 链表元素按奇偶顺序重新构造
改题目是较简单的链表操作。
创建两个用来单独存储奇数位和偶数位的空链表,遍历原始链表,当遍历到奇数位置时,添加到奇数位链表中,当遍历到偶数位置时,添加到偶数位置上。最会将两个链表整合,返回。
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
point_odd = odd = ListNode(0, None)
point_even = even = ListNode(0, None)
i = 1
while head != None:
if i % 2 == 1:
point_odd.next = head
point_odd = point_odd.next
head = head.next
i += 1
else:
point_even.next = head
point_even = point_even.next
head = head.next
i += 1
point_even.next = None
point_odd.next = even.next
return odd.next