Leetcode 刷题记录 Day3
链表基础理论
链表是一种以指针模式串在一起的节点, 每个节点有两个部分(数据和指针), 指针区域存放下一个节点的位置, 最后一个节点指向None
链表类型:
- 单链表(如上图所示)
- 双链表: 与单链表不同的是有两个指针, 可以指向前一个位置。既可以向上查询也可以向下。
- 循环链表: 链表头尾相连。
单链表定义:
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
删除节点:
将前一个节点的next连接到删除节点的next即可(如图删除节点D)
添加节点:
将插入位置前一个节点的next连接到添加节点, 添加节点的next连接至前一个节点原来的next.(如图添加节点F)
性能分析:
数组: 插入/删除时间复杂度O(n), 查询时间复杂度O(1)
链表: 插入/删除时间复杂度O(1), 查询时间复杂度O(n)
203. Remove Linked List Elements
学会使用虚拟节点, 使得后续链表操作一致性(处理头结点), 否则需要额外做删除头结点操作
首先要判断head不为空
从虚拟节点开始而不是head, 因为是单项链表, 如果要删除节点需要让前一个节点指向后面的节点。判断curr.next, 进行删除, curr.next = curr.next.next即可
最后return的是dummyhead.next 而不是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]:
dummy_head = ListNode(next = head)
curr = dummy_head
# 即使头结点为空 不会报错
while curr.next:
if curr.next.val == val:
curr.next = curr.next.next
else:
curr = curr.next
return dummy_head.next
707. Design Linked List
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
def get(self, index: int) -> int:
curr = self.dummy_head
for i in range(index + 1):
if curr.next:
curr = curr.next
else:
return -1
return curr.val
def addAtHead(self, val: int) -> None:
new_head = ListNode(val=val, next = self.dummy_head.next)
self.dummy_head.next = new_head
def addAtTail(self, val: int) -> None:
cur = self.dummy_head
while cur.next:
cur = cur.next
cur.next = ListNode(val = val)
def addAtIndex(self, index: int, val: int) -> None:
cur = self.dummy_head
i = 0
while cur.next and i < index:
cur = cur.next
i += 1
if cur and i == index:
cur.next = ListNode(val = val, next = cur.next)
def deleteAtIndex(self, index: int) -> None:
cur = self.dummy_head
i = 0
while cur.next and i < index:
cur = cur.next
i += 1
if cur and cur.next:
cur.next = cur.next.next
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
206. Reverse Linked List
双指针法:
prev, curr 两个指针, 初始化curr为head, prev为None
循环中, 记录curr.next作为tmp用于后续更新curr, 让curr指向prev, 后续更新prev为curr, curr为tmp。循环直至curr == None停止s
# 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: Optional[ListNode]) -> Optional[ListNode]:
curr = head
prev = None
while curr:
# 记录下一个节点用于后续更新curr
tmp = curr.next
# 更新curr指针
curr.next = prev
# 更新对应curr和prev,县移动prev,否则找不到curr
prev = curr
curr = tmp
return prev
Recursion递归法:
# 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: Optional[ListNode]) -> Optional[ListNode]:
def reverse(curr, prev):
if curr == None:
return prev
tmp = curr.next
curr.next = prev
return reverse(tmp, curr)
return reverse(head, None)
Leetcode 刷题记录 Day4
24. Swap Nodes in Pairs
A -> B -> C -> D -> E ==========> B -> A -> D -> C -> E
使用虚拟头节点方式操作(才能操作head节点, 让dummyhead指向B);
用tmp1记录A的位置, 否则让dummyhead指向B后找不到A;
用tmp2记录C的位置, 否则让B指向A后找不到C;
遍历链表需要看后续是否还有两个节点进行交换, 没有节点或者只有一个节点则停止;
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
curr = dummy_head
while curr.next and curr.next.next:
tmp1 = curr.next
tmp2 = curr.next.next.next
curr.next = tmp1.next
curr.next.next = tmp1
tmp1.next = tmp2
curr = tmp1
return dummy_head.next
19. Remove Nth Node From End of List
同样适用dummy_head进行操作保持一致性
使用快慢指针找到倒数第N+1个节点, 因为删除倒数第N个点需要在倒数第N+1个节点进行操作.next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head = ListNode(next = head)
slow, fast = dummy_head, dummy_head
for i in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy_head.next
160. Intersection of Two Linked List
若两个链表相交, 后续链表长度相同, 先裁剪长度较长的链表。然后一一确认收个intersection位置。
注意: 是找相同节点 而不是相同value
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
def get_len(head):
'''
Get length of linked list
'''
count = 0
while head:
head = head.next
count += 1
return count
lenA, lenB = get_len(headA), get_len(headB)
currA, currB = ListNode(next = headA), ListNode(next = headB)
# 让后续两个list长度相同
if lenA >= lenB:
for _ in range(lenA - lenB):
currA = currA.next
else:
for _ in range(lenB - lenA):
currB = currB.next
while currA != currB and currA:
currA = currA.next
currB = currB.next
# if currA == currB, return currA, if currA == None, return None
return currA
142. Linked List Cycle II
如何判断环: 快慢指针, 如果慢指针追上了快指针, 说明环内相遇了;
快指针一次+2, 慢指针一次+1, 快指针相对于慢指针一次走一个节点,一定相遇;
如何找环的入口:
slow走了: x + y
fast走了: x + y + n(y + z), n代表fast在里边转了几圈(n >= 1)
2(x + y) = x + y + n(y + z) ======> x = n(y + z) - y ======> x = (n - 1)(y + z) + z
(图中x+y那个点假设叫B点,开始点叫A点)
A点出发一个指针,B点出发一个指针,相同速度, 一定会在环形入口相遇(因为B点出发指针转了n - 1圈后又走了z, 在环形入口相遇)
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummyhead = ListNode(next = head)
fast, slow = dummyhead, dummyhead
# 为了防止没有cycle, fast/fast.next 为None
while fast and fast.next:
fast = fast.next.next
slow = slow.next
# 快慢指针相遇
if fast == slow:
while fast != dummyhead:
# 头部和相遇点同时移动
fast = fast.next
dummyhead = dummyhead.next
return fast
return None