单链表的基本技巧:
1、合并两个有序链表
这道题一般两种算法,一种是使用递归,每次的递归条件是:谁的头结点小,就把谁身后的数据以及另一个链表重新放入递归。结束条件是某一个链表为空。
非递归算法是重新找个空表,用p做表头,然后比较list1和list2的当前大小,将小的连接到p上,循环到某个表为空,就把另一个表剩余的都连接到p上。最后输出p表头之后的部分。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
#递归
if list1 is None:
return list2
elif list2 is None:
return list1
elif list1.val > list2.val:
list2.next = self.mergeTwoLists(list1,list2.next)
return list2
else:
list1.next = self.mergeTwoLists(list1.next,list2)
return list1
#非递归
dummp = ListNode(-1)
p = dummp
p1 = list1
p2 = list2
while p1!=None and p2!=None:
if p1.val > p2.val:
p.next = p2
p2 = p2.next
else:
p.next = p1
p1 = p1.next
p = p.next
if p1 is None:
p.next = p2
if p2 is None:
p.next = p1
return dummp.next
2、合并k
个有序链表
这里可以使用二叉堆的概念。(大根堆、小根堆)其主要操作就两个,sink
(下沉)和swim
(上浮),用以维护二叉堆的性质。其主要应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。二叉堆其实就是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。
把 arr[1] 作为整棵树的根的话,每个节点的父节点和左右孩子的索引都可以通过简单的运算得到,这就是二叉堆设计的一个巧妙之处。
二叉堆还分为最大堆和最小堆。最大堆的性质是:每个节点都大于等于它的两个子节点。类似的,最小堆的性质是:每个节点都小于等于它的子节点。
优先级队列这种数据结构有一个很有用的功能,你插入或者删除元素的时候,元素会自动排序,这底层的原理就是二叉堆的操作。
数据结构的功能无非增删查该,优先级队列有两个主要 API,分别是insert
插入一个元素和delMax
删除最大元素(如果底层用最小堆,那么就是delMin
)。
为什么要有上浮 swim 和下沉 sink 的操作呢?为了维护堆结构。
我们要讲的是最大堆,每个节点都比它的两个子节点大,但是在插入元素和删除元素时,难免破坏堆的性质,这就需要通过这两个操作来恢复堆的性质了。
对于最大堆,会破坏堆性质的有有两种情况:
-
如果某个节点 A 比它的子节点(中的一个)小,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉。
-
如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮。
当然,错位的节点 A 可能要上浮(或下沉)很多次,才能到达正确的位置,恢复堆的性质。所以代码中肯定有一个while
循环。
上代码(小根堆):
class Heapq:
pq = [-1]#存放二叉堆元素的数组,一般情况下都不要下标为0的元素,所以提前把这个下标占了
N = 0 #数组中的个数
def parent(self,k):#输入下标k之后得到,得到k的父亲节点
return int(k/2)
def right(self,k):#输入下标k之后,得到k的右孩子
return int(k*2+1)
def left(self,k):#输入下标k之后,得到k的做孩子
return int(k*2)
def exch(self,a,b):#交换
t = a
a = b
b = t
return a , b
def less(self,a,b):#比较
if a < b:
return True
else:
return False
def swim(self,k,pq):
#k上浮的条件:不能是根,而且要比它的父亲小
while k>1 and self.less(pq[k],pq[self.parent(k)]):
#交换值
t = pq[k]
pq[k] = pq[self.parent(k)]
pq[self.parent(k)] = t
#让下标移动到下一个比较的位置
a,b = self.exch(k,self.parent(k))
k = a
def sink(self,k):
while self.left(k) <= self.N: #k如果有左孩子,就有下沉的可能
smaller = self.left(k) #假设k的左孩子小
if self.right(k)<=self.N and #k存在右孩子 self.less(self.pq[self.right(k)],self.pq[self.left(k)]): #而且右孩子比左孩子小
smaller = self.right(k) #将较小值换成右孩子
if self.less(self.pq[smaller],self.pq[k]): #较小的孩子比k小
#交换值
t = self.pq[smaller]
self.pq[smaller] = self.pq[k]
self.pq[k] = t
#让下标移动到下一个比较位置
a ,b =self.exch(k,smaller)
k = a
else:
break #比较小的孩子也比k大,就跳出循环
def insert(self,data):#插入到堆底,然后swim
self.N += 1
self.pq.append(data)
self.swim(self.N,self.pq)
def delmin(self):#把堆顶和堆底互换,然后删除掉最后的节点,再sink堆顶
t = self.pq[1]
self.pq[1] = self.pq[self.N]
self.pq[self.N] = t
self.pq.pop()
self.N -= 1
self.sink(1)
return t
因此上述题目的解题过程是:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
#将所有链表插到小根堆中,然后执行delmin,将返回的值插入到新的链表中
dummp = ListNode(-1)
p = dummp
h = Heapq()
h.N = 0
h.pq = [0]
for i in range(len(lists)):
while lists[i]:
h.insert(lists[i].val)
lists[i] = lists[i].next
for i in range(h.N):
t = h.delmin()
p.next = ListNode(t)
p = p.next
return dummp.next
3、寻找单链表的倒数第k
个节点
解题思路:
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
p1 = head
p2 = head
for i in range(n):
p1 = p1.next #先让p1走n步
if p1: #走完n步之后,要判断一下,此时的p1是否已经走到了链表的尽头
while p1.next: #当p1的指向链表的尾端时,p2的next就是我们要删除的元素
p1 = p1.next
p2 = p2.next
p3 = p2.next
p2.next = p3.next #删除p2.next
else: #如果走了n步之后, p1已经到了链表的尽头,说明要删除的元素是表头
head = head.next
return head
4、寻找单链表的中点
class Solution:
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
return slow
5、判断单链表是否包含环并找出环起点
如果要计算环的起点:
即相遇表示为有环,相遇之后,将其中一个重新指回head,再次相遇即为起点。
class Solution:
def hasCycle(self, head: Optional[ListNode]) -> bool:
fast = head
slow = head
while fast and fast.next: #进入循环后,如果没有环,就会在链表末退出
fast = fast.next.next
slow = slow.next
if fast == slow: #如果有环,则两个指针会相遇,返回true
return True
return False
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
fast = head
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow: #如果有环
slow = head #让其中一个指针重新指向头结点
while fast != slow: #当两个指针未相遇时,按照步长一致走
fast = fast.next
slow = slow.next
return slow #相遇后返回指针
return None
6、判断两个单链表是否相交并找出交点
让两个指针分别遍历a-b,b-a,如果有相交节点,则两个指针会相遇。如果不想交,两个指针最后会走到null
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
p1 = headA
p2 = headB
while p1 != p2: #在保证没有环的情况下,如果没有相交,最后都指向了null,有相交,则相等
if p1 is None:
p1 = headB
else:
p1 = p1.next
if p2 is None:
p2 = headA
else:
p2 = p2.next
return p1