目录
剑指18:删除链表的节点/Leetcode237:删除链表中的节点
剑指22:链表中倒数第K个节点/Leetcode19:删除链表的倒数第N个节点
Leetcode141:环形链表 / Leetcode142:环形链表II / 剑指23:链表中环的入口节点
剑指52:两个链表的第一个公共节点 / Leetcode160:相交链表
剑指06:从尾到头打印链表
基本思路:顺序打印之后倒序即可;
class Solution06(object):
"""
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
方法1:顺序打印,再reverse;
总结:
时间复杂度,首先遍历一遍生成顺序list O(n);其次reverse过程时间复杂度O(n),总结来说O(n)
"""
def reversePrint(self, head):
if head == None:
return []
elif head.next == None:
return [head.val]
else:
p = head
ans = [p.val]
while(p.next is not None):
p = p.next
ans.append(p.val)
ans.reverse()
return ans
"""
方法2:
这种先进后出的场景一定要想到栈这种数据结构stack
时间复杂度:仍然先遍历一遍链表,得到顺序list,复杂度为O(n),倒叙输出的复杂度也为O(n)
空间复杂度不好判断,基于reverse操作是否用到了额外的空间;
"""
def reversePrint_02(self, head):
stack = []
while head:
stack.append(head.val)
head = head.next
return stack[::-1] # 倒叙输出,后进先出,即stack的思想
"""
方法3:
递归思想!!!!待继续研究!!!!!!
"""
def reversePrint_03(self, head):
"""
if head is None:
return []
return self.reversePrint_03(head.next) + [head.val]
"""
return self.reversePrint_03(head.next) + [head.val] if head else []
剑指18:删除链表的节点/Leetcode237:删除链表中的节点
题目:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点
示例:输入: head = [4,5,1,9], val = 5;输出: [4,1,9];解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
基本思路:遍历查找
class Solution18(object):
"""
方法1:
遍历查找删除
时间复杂度--最好:O(1); 最坏:O(n); 平均:O(n/2),其实也就是O(n)
空间复杂度--O(1)
"""
def deleteNode(self, head, val):
s = ListNode(0) # 哨兵节点
s.next = head
p = s
while p.next:
if p.next.val == val:
p.next = p.next.next
return s.next
else:
p = p.next
return s.next
剑指22:链表中倒数第K个节点/Leetcode19:删除链表的倒数第N个节点
基本思路:双指针
class Solution22(object):
"""
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
"""
"""
方法1:
首先遍历一遍,得到链表的长度,再根据len(ListNode)-k+1得到预期的链表位置
时间复杂度为O(n)
空间复杂度为O(1)
"""
def getKthFromEnd(self, head, k):
i = 0
p = head
while p:
i += 1
p = p.next
j = i - k
i = 0
while head:
if i == j:
return head
else:
i += 1
head = head.next
"""
方法2:
双指针, 设置双指针的距离,只用遍历一次即可;
时间复杂度为O(n)
空间复杂度为O(1)
"""
def getKthFromEnd_02(self, head, k):
lo, fa = head, head
while k > 0:
fa = fa.next
k -= 1
while fa:
fa = fa.next
lo = lo.next
return lo
Leetcode141:环形链表 / Leetcode142:环形链表II / 剑指23:链表中环的入口节点
基本思路:快慢指针
class Solution141():
"""
快慢指针:
如果有环的话,那么快指针一定可以喝慢指针重合,如果没有环的话,快指针就可以走到链表末端None
"""
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
pre = ListNode(0)
pre.next = head
sl, fa = pre, head
while fa and fa.next:
if fa == sl: return True
fa = fa.next.next
sl = sl.next
return False
class Solution(object):
def detectCycle(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head: return None
p = ListNode(0) # 设置哨兵node,后续sl ans fa必须从哨兵节点同时开始
p.next = head
sl, fa = head, head.next
while sl != fa:
if not fa or not fa.next: return None
fa = fa.next.next
sl = sl.next
fa = p # 这里其中一个指针要从哨兵节点开始走,因为设置起点是哨兵节点,fa and lo必须从同一起点开始走
while sl != fa:
fa, sl = fa.next, sl.next
return fa
剑指24/Leetcode206:链表反转
基本思路:双指针
class Solution(object):
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head: return head
cur = head.next
head.next = None
while cur:
tmp = cur.next
cur.next = head
head = cur
cur = tmp
return head
剑指35:复杂链表的赋值
剑指52:两个链表的第一个公共节点 / Leetcode160:相交链表
基本思路:a + b = b + a;如果相遇的node为None,则没有公共nodes;
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
pa, pb = headA, headB
while pa != pb:
if pa:
pa = pa.next
else:
pa = headB
if pb:
pb = pb.next
else:
pb = headA
return pa
class Solution160(object):
"""
思路:首先遍历求长度,长的先跑,跑到短的头部,逐一比较即可;
时间复杂度:O(n)
空间复杂度:O(1)
"""
def getIntersectionNode_01(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
m, n = headA, headB
l1, l2 = 0, 0
while m:
m = m.next
l1 += 1
while n:
n = n.next
l2 += 1
if l1 > l2:
while l1 > l2:
headA = headA.next
l1 -= 1
elif l2 > l1:
while l2 > l1:
headB = headB.next
l2 -= 1
while headA and headB:
if headA == headB:
return headA
else:
headA = headA.next
headB = headB.next
return None
"""
方法2:技巧消除长度差
首先遍历求长度,长的先跑,跑到短的头部,逐一比较即可;
headA = 不同部分a + 相同部分c
headB = 不同部分b + 相同部分c
a + c + b + c = b + c + a + c 在最后第4部分c一定会相交(if有相交)
if没有香蕉:then在最后的None相遇;
时间复杂度:O(n)
空间复杂度:O(1)
"""
def getIntersectionNode_02(self, headA, headB):
if not headA or not headB: return None
a = headA
b = headB
while(a != b):
a = a.next if a else headB
b = b.next if b else headA
return a
Leetcode21:合并两个有序链表
题目:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
基本思路:递归,直接递归即可,不用想太多。
时间复杂度O(m+n):m和n分别为两个链表的长度。因为每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O(n+m)。
空间复杂度O(m+n):调用函数栈的次数
class Solution(object):
"""
方法1:递归,直接理解
"""
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
if not l1: return l2
if not l2: 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
Leetcode23:合并K个有序链表(复杂度分析参考)
题目:合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
基本思路:
1. 两两合并 -- 时间复杂度 O(kn) ,空间复杂度 O(1)【不考虑递归调用栈】
2. 分而治之 -- 时间复杂度 O(logk * n),空间复杂度 O(1)【不考虑递归调用栈】
3. 优先队列 -- 空间复杂度 O(k),时间复杂度 O(nlog(k));n是所有节点个数,k是链表数
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[ListNode]
:rtype: ListNode
"""
def mergeTwoList(l1, l2):
if not l1: return l2
if not l2: return l1
if l1.val < l2.val:
l1.next = mergeTwoList(l1.next, l2)
return l1
else:
l2.next = mergeTwoList(l1, l2.next)
return l2
"""
方法1:两两合并 --会超时
"""
# tmp = None
# for l in lists:
# tmp = mergeTwoList(tmp, l)
# return tmp
"""
方法2:分而治之
"""
if len(lists) == 0: return None
if len(lists) == 1: return lists[0]
if len(lists) == 2: return mergeTwoList(lists[0], lists[1])
mid = len(lists) // 2
return mergeTwoList(self.mergeKLists(lists[: mid]), self.mergeKLists(lists[mid:]))
"""
方法3:小顶堆
"""
def mergeKLists(self, lists):
"""
:type lists: List[ListNode]
:rtype: ListNode
"""
import heapq
if not lists: return None
dummy = p = ListNode(0)
heap = []
for l in lists: # 将所有元素都放入堆中
while l:
heapq.heappush(heap, l.val)
l = l.next
while heap: # 将小顶堆中元素逐一pop出来,每次pop出来的都是最小值
val = heapq.heappop(heap)
p.next = ListNode(val)
p = p.next
p.next = None
return dummy.next
Leetcode24:两两交换链表中的节点
-->跟Leetcode25是完全相同的思路
题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:给定 1->2->3->4
, 你应该返回 2->1->4->3
.
基本思路:翻转区间之前pre指针;翻转区间内start指针<--pre.next;翻转区间内end指针<--pre.next.next;
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def swapPairs(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head: return head
dummy = ListNode(0)
dummy.next = head
pre = dummy
while pre.next and pre.next.next: # 如果pre.next(start) and pre.next.next(end)同时存在才执行翻转操作
# 基于pre,赋值start and end指针
start, end = pre.next, pre.next.next
# 以下3行执行翻转操作
start.next = end.next
pre.next = end
end.next = start
# 翻转之后移动更新pre
pre = start
return dummy.next
Leetcode25:K 个一组翻转链表
题目:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:给你这个链表:1->2->3->4->5;当 k = 2 时,应当返回: 2->1->4->3->5;当 k = 3 时,应当返回: 3->2->1->4->5
基本思路:见代码逻辑
class Solution(object):
def reverseKGroup(self, head, k):
"""
:type head: ListNode
:type k: int
:rtype: ListNode
"""
dummy = ListNode(0)
dummy.next = head
pre = end = dummy
while end.next:
i = 0
while i < k and end:
end = end.next
i += 1
if not end:
break # 说明到了链表的最后部分,不用进行接下来的反转操作
# 为区间链表反转做好准备以及链表反转
start = pre.next
nextNode = end.next
end.next = None
pre.next = self.reverseList(start)
# 进行下一轮循环
start.next = nextNode
pre = end = start # start被反转了之后到了区间最末端
return dummy.next
def reverseList(self, head):
if not head: return head
cur = head.next
head.next = None
while cur:
tmp = cur.next
cur.next = head
head = cur
cur = tmp
return head
Leetcode83:删除排序链表中的重复元素
题目:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例:输入: 1->1->2->3->3 输出: 1->2->3
基本思路:因为是已经排序的链表,直接遍历即可
class Solution83(object):
"""
方法1:暴力遍历一遍
时间复杂度:O(n)
空间复杂度:O(1)
"""
def deleteDuplicates(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
cur = head
if not cur or not cur.next: return head # None或者仅有一个element;
while cur.next:
if cur.next.val == cur.val:
cur.next = cur.next.next
else:
cur = cur.next
return head
Leetcode82:删除排序链表中的重复元素 II
题目:给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例:输入: 1->2->3->3->4->4->5 输出: 1->2->5;输入: 1->1->1->2->3 输出: 2->3
基本思路:利用区间指针start and end,同时也利用前驱pre指针;
class Solution(object):
def deleteDuplicates(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head: return None
dummy = ListNode(0)
dummy.next = head # 这个特别注意,在定义哨兵节点之后,一定要连接上head;
pre = dummy
start = end = dummy.next
while end and end.next:
while end and end.next: # 通过start and end node位置,判断该元素是否是重复的;
if end.next.val == end.val:
end = end.next
else:
break
if start != end: # 如果存在重复,执行删除操作
pre.next = end.next # 删除操作
start = end = pre.next # 更新start and end
else:
pre = end # 不重复,更新pre
start = end = end.next # 同时也更新start and end
return dummy.next
Leetcode147:对链表进行插入排序
示例:输入: 4->2->1->3 输出: 1->2->3->4
基本思路:插入排序的思路
class Solution:
def insertionSortList(self, head: ListNode) -> ListNode:
dummy = ListNode(float('-inf')) # 找个排头
pre = dummy
cur = head # 依次拿head节点
while cur:
tmp = cur.next # 把下一次节点保持下来
while pre.next and pre.next.val < cur.val: # 找到插入的位置
pre = pre.next
# 进行插入操作
cur.next = pre.next
pre.next = cur
pre= dummy
cur = tmp
return dummy.next
Leetcode203:移除链表元素
题目:删除链表中等于给定值 val 的所有节点。
示例:输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
class Solution203(object):
"""
方法1:
遍历一遍即可
"""
def removeElements(self, head, val):
"""
:type head: ListNode
:type val: int
:rtype: ListNode
"""
# create a 哨兵node
fh = ListNode(0)
fh.next = head
head = fh
while fh.next:
if fh.next.val == val:
fh.next = fh.next.next
else:
fh = fh.next
return head.next
Leetcode234:回文链表
基本思路:快慢指针找中点,然后开始比较--在前半部分遍历的时候,就同时反转操作
时间复杂度:遍历找中点O(n),再比较O(n)
空间复杂度:存指针O(1)
class Solution234(object):
"""
方法1:快慢指针找中点,然后开始比较
时间复杂度:遍历找中点O(n),再比较O(n)
空间复杂度:存指针O(1)
"""
def isPalindrome(self, head):
# 先找链表中点
fa, sl = head, head
while fa and fa.next:
fa = fa.next.next
sl = sl.next
# 求链表长度,如果是奇数,则设置中点的下一个节点为比较起点;
l = 0
m = head
while m:
m = m.next
l += 1
if l % 2 == 1:
sl = sl.next
# 反转后半部分链表
sl = self.reverseList(sl)
# 逐一比较nodes
while sl:
if sl.val == head.val:
sl = sl.next
head = head.next
else:
return False
return True
def reverseList(self, head):
if not head or not head.next: return head
cur = self.reverseList(head.next) # 1-> 2<-3<-4<-5, 此时cur = 5, 最后return也是cur
head.next.next = head # head为1,head.next.next要指向自己
head.next = None
return cur
"""
方法2优化版:快慢指针找中点,然后开始比较--在前半部分遍历的时候,就同时反转操作
时间复杂度:遍历找中点O(n),再比较O(n)
空间复杂度:存指针O(1)
"""
def isPalindrome_02(self, head):
# 先计算链表长度(后面需要基于长度为奇偶进行不同处理)
l = 0
m = head
while m:
m = m.next
l += 1
# 找链表中点同时反转
fa, sl = head, head
rh = None
while fa and fa.next:
fa = fa.next.next # 快指针往前两步;
tmp = sl # 慢指针走前把位置记一下,用于待会儿做反转指针的head
sl = sl.next # 慢指针往前一步;
tmp.next = rh
rh = tmp
if l % 2 == 1:
sl = sl.next
while sl and rh:
if sl.val != rh.val:
return False
else:
sl, rh = sl.next, rh.next
return True
Leetcode876:链表的中间节点
基本思路:快慢双指针
class Solution876(object):
def middleNode(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
fa, sl = head, head
while fa and fa.next:
fa = fa.next.next
sl = sl.next
return sl
Leetcode206:反转链表 || 反转双链表
"""
1. 反转单链表
"""
def reverseList(head):
if not head: return head
cur = head.next
head.next = None
while cur:
tmp = cur.next
cur.next = head
head = cur
cur = next
return head
"""
2. 反转双链表
"""
def reverseDoubleList(head):
if not head: return head
pre = None
while head:
tmp = head.next
head.next = pre
head.pre = tmp
pre = head
head = tmp
return pre
Leetcode426:将二叉搜索树转化为排序的双向链表(题解参考1 and 题解参考2)
题目:将一个 二叉搜索树 就地转化为一个 已排序的双向循环链表 。对于双向循环列表,你可以将左右孩子指针作为双向循环链表的前驱和后继指针,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。特别地,我们希望可以 就地 完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中最小元素的指针。
基本思路:递归
"""
方法1
"""
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):
if not cur: return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
if not root: return
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
"""
方法2
"""
class Solution:
def treeToDoublyListCore(self, root: 'Node') -> ('Node', 'Node'):
"""
:param root: 树的根节点
:return: 双向链表的 头节点 和 尾节点
"""
# 根为空,那么 对应的双向链表的 头节点 和 尾节点 也为空
if root is None:
return None, None
# 左子树 对应的 双向链表的头节点和尾节点
left_head, left_tail = self.treeToDoublyListCore(root.left)
# 右子树 对应的 双向链表的头节点和尾节点
right_head, right_tail = self.treeToDoublyListCore(root.right)
# 根的 左节点 与 左子树的尾节点 互相连接
# 根的 右节点 与 右子树的头节点 互相连接
root.left, root.right = left_tail, right_head
if left_tail:
left_tail.right = root
if right_head:
right_head.left = root
# 左子树的头节点 如果存在则作为当前 双向链表的头节点,否则使用 根节点。尾节点同理。
return left_head if left_head else root, right_tail if right_tail else root
def treeToDoublyList(self, root: 'Node') -> 'Node' or None:
"""
递归遍历
时间复杂度:O(N),所有节点遍历一次。
空间复杂度:O(N),当二叉搜索树退化为链表时,树的深度为 N.
"""
head, tail = self.treeToDoublyListCore(root)
# 改造成循环双向链表
if head and tail:
head.left, tail.right = tail, head
return head