LeetCode链表
Leetcode链表刷题记录
链表性质
- 每个节点包含两部分:数据域存储数据;指针域指向下一个节点的地址
- 内存不连续
- 链表的入口节点:head
- 复杂度:增删复杂度为O(1),索引/查询1 元素复杂度为O(n)
构造链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
如果不写构造函数,就无法直接在创建节点的时候赋值
class ListNode:
def __init__(self, val, next=None): #定义构造函数
self.val = val
self.next = next
删除
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 先处理head
while head != None and head.val == val:
head = head.next
if head == None:
return head
pre = head
while pre.next != None:
if pre.next.val == val:
pre.next = pre.next.next
# pre.next.next 有可能是None
else:
pre = pre.next
return head
最开始写的版本,太麻烦了,其实可以建一个虚拟节点,这样处理head的问题和处理后面节点的问题可以统一起来
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head == None:
return head
virtualhead = ListNode(0, next = head)
pre = virtualhead
while pre.next != None:
if pre.next.val == val:
pre.next = pre.next.next
else:
pre = pre.next
return virtualhead.next
注意点:
if pre.next.val == val:
之后pre不直接更新为pre.next,需要经过判断pre.next != None
- pre一直是需要更改的前一个节点
- 删除要找的一定是前一个节点,失去对前一个节点的记录,就不能删除
- 遍历的时候要定义一个临时指针,不能改变头节点
递归的修改
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head == None:
return head
head.next = self.removeElements(head.next, val)
if head.val == val:
head = head.next
return head
注意:
head.next = self.removeElements(head.next, val)
是在head.val == val:
之前的,因为head.val == val:
可能会删除当前节点,如果在此之前不计算好当前节点的next,删除了之后再计算next就可能会解析空指针;而且这样先删除再计算next也不符合程序逻辑
自定义链表
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.head = None
def get(self, index: int) -> int:
if self.head == None:
return -1
count = 0
tmp = self.head
while tmp != None:
if count == index:
return tmp.val
tmp = tmp.next
count += 1
return -1
def addAtHead(self, val: int) -> None:
newhead = ListNode(val, next = self.head)
self.head = newhead
def addAtTail(self, val: int) -> None:
newtail = ListNode(val)
if self.head == None:
self.head = newtail
else:
tmp = self.head
while tmp.next != None:
tmp = tmp.next
tmp.next = newtail
def addAtIndex(self, index: int, val: int) -> None:
if index == 0:
self.addAtHead(val)
return
if self.head == None:
return
newnode = ListNode(val)
tmp = self.head
count = 0
while tmp != None:
if count == index - 1: #找index前一个节点
newnode.next = tmp.next
tmp.next = newnode
return
tmp = tmp.next
count += 1
if count == index:
self.addAtTail(val)
def deleteAtIndex(self, index: int) -> None:
virtualhead = ListNode(next = self.head)
pre = virtualhead
count = 0
while pre.next != None:
if count == index:
pre.next = pre.next.next
break
count += 1
pre = pre.next
self.head = virtualhead.next
注意:
- 插入节点时一定是先改newnode.next 再修改tmp.next 指向node
- 插入/删除 节点时,tmp指向的一定是index的前一个节点
反转列表
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None:
return head
pre = None #初始化为空指针, 因为头变成尾
cur = head
while cur != None:
nex = cur.next
cur.next = pre
pre = cur
cur = nex
# 交换顺序
return pre
递归的写法
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None or head.next == None:
return head
newhead = self.reverseList(head.next)
head.next.next = head
head.next = None
return newhead
注意边界条件的的写法:if head == None or head.next == None:
因为当链表为空或者链表只有一个元素的时候都不需要反转处理
节点交换
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None or head.next == None:
return head
pre = head
cur = head.next
newhead = head.next
while cur != None:
pre.next = cur.next
cur.next = pre
if pre.next != None and pre.next.next != None:
# 存在下一对节点
cur = pre.next
pre.next = pre.next.next
pre = cur
cur = cur.next
else:
break
return newhead
更清晰的思路,每次指针都指向要处理的那对的前驱节点;加入虚拟头节点,这样就不用单独处理头
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
virtualhead = ListNode(next = head)
pre = virtualhead
while pre.next != None and pre.next.next != None:
# 注意判断顺序,利用and的短路性,避免解引用空指针
ptr1 = pre.next
ptr2 = pre.next.next
# 节点1和节点2为互相要交换的节点
ptr1.next = ptr2.next
ptr2.next = ptr1
pre.next = ptr2
pre = ptr1
return virtualhead.next
递归解法
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
if head == None or head.next == None:
# 无节点/只有1个节点不需要交换
return head
nex = head.next.next
nex = self.swapPairs(nex)
# 交换好后面
newhead = head.next
head.next.next = head
head.next = nex
return newhead
删除倒数第N个节点
双指针解法运用:本题的关键在于找到什么时候是倒数第N个节点,第一种直白的想法是我先遍历一遍链表,算出总长度,这样我就知道倒数第N个节点的索引,然后再遍历一遍,但这样显然太浪费了。由此想到双指针的解法,块指针比慢指针块n个节点,这样当快指针走到链表的尾部的时候,慢指针刚好指向要被删除的节点
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
virtualhead = ListNode(next = head)
slow = virtualhead
fast = virtualhead
n += 1 # 找前驱,方便删除
while fast != None:
if n > 0:
n -= 1
else:
slow = slow.next
fast = fast.next
if n > 0:
return virtualhead.next
slow.next = slow.next.next
return virtualhead.next
寻找交点
通过求链表长度的差值来对齐两个链表
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if headA == None or headB == None:
return None
ptrA = headA
ptrB = headB
lenA = 0
lenB = 0
while ptrA != None:
lenA += 1
ptrA = ptrA.next
while ptrB != None:
lenB += 1
ptrB = ptrB.next
ptrA = headA
ptrB = headB
print(lenA, lenB)
if lenA < lenB :
for _ in range(lenB - lenA):
ptrB = ptrB.next
else:
bias = lenA - lenB
for _ in range(lenA - lenB):
ptrA = ptrA.next
while ptrA != ptrB:
# 不用担心链表尾部,A B会同时到达链表尾部
ptrA = ptrA.next
ptrB = ptrB.next
return ptrA
补充一种解法,因为两个相交的链表有个性质,就是交点之后的路径是完全一样的(等长),交点之前的路径不同,在第一种解法里,我们做的无非是把两个链表对齐,让两个指针对齐,然后寻找交点,我们也可以用相交链表本身的性质来保证这一点
如果指针A先走A链表,再走B链表,指针B先走B链表,再走A链表,它们走路的速度一样,如果链表相交,走到交点处它们刚好相遇,如果不想交,它们不相等,各自走到链表末尾,最后都等于None,与我们要返回的值一样
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
ptrA = headA
ptrB = headB
while ptrA != ptrB:
if ptrA == None:
#走到A得末尾,从B的开头重新开始走
ptrA = headB
else:
ptrA = ptrA.next
if ptrB == None:
ptrB = headA
else:
ptrB = ptrB.next
return ptrA
环形链表
- 定义快慢指针fast和slow slow走一步,fast走两步
- 如果有环,fast和slow一定会相遇,因为fast和slow总会进入环中,一旦进入环,fast相当于比slow快一步,在追赶slow(操场上赛跑,只要是环形跑道,两个人有速度差,总有相遇的一天)
- 如何寻找入口
- 快指针与慢指针相遇的时候慢指针一定没有走完环的第一圈
直观理解:A的跑步速度是B的一倍,B绕操场跑一圈A可以跑两圈,所以如果A B同时出发,那么A必然在B刚好到达终点的时候追上B,但现在A抢跑(快指针比慢指针先入环),所以A一定在B第一圈到达终点前追上B
数学证明:假设当慢指针到达环入口的时候,快指针离环入口有 K K K个节点的距离,环的长度为 c c c,所以快指针为了追上慢指针还需要走 c − k c-k c−k步,设两个指针经过 t t t步(可以理解为单位时间)相遇, 2 t − t = c − k 2t - t = c - k 2t−t=c−k 得到 t = c − k t = c-k t=c−k,所以此时慢指针距离环的入口为 c − k ≤ c c-k \leq c c−k≤c慢指针走过的距离小于一个环长,慢指针在重新到达入口循环之前就和快指针相遇了 - 假设起点到环入口的距离为 x x x 我们要求解的就是 x x x 假设两者相遇时到环入口的距离为 y y y(由于1得 y = c − k y = c-k y=c−k) 根据1,慢指针走过的距离为 x + y x+y x+y,快指针走过的距离为 x + y + n c x+y+nc x+y+nc 根据快慢指针速度关系,得到 2 × ( x + y ) = x + y + n c 2 \times (x + y) = x + y + nc 2×(x+y)=x+y+nc 改写成x的表达式 x = n c − y = n c − ( c − k ) = ( n − 1 ) c + k x = nc - y = nc - (c-k) = (n-1)c + k x=nc−y=nc−(c−k)=(n−1)c+k 这个表达式的含义是什么?先假设n=1,那么 x = k x=k x=k,就是说快慢指针的相遇点到入环点的距离和头节点到入环点的距离是一样的,如果 n ≠ 1 n \neq 1 n=1,那么头节点到入环点的距离,是快慢节点相遇点到入环点的距离加上环长的 n − 1 n-1 n−1倍,所以如果有一个指针在快慢指针相遇的时候从头节点出发,快慢指针和新指针都以每次一个节点的速度移动,最终它们必定在环的入口相遇
- 快指针与慢指针相遇的时候慢指针一定没有走完环的第一圈
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast != None and fast.next != None:
slow = slow.next
fast = fast.next.next #前进两步
if slow == fast:
break
if slow != fast or fast == None or fast.next == None:
return None
new = head
while new != slow:
new = new.next
slow = slow.next
return new
总结
我把查询定义为查找一个元素在不在链表里,索引定义为知道在第几个,得到这个元素的内容 ↩︎