链表
- 链表的数据结构和list一样,可变的,不同的变量名指向这个内存空间后,谁操作都会让其指向发生变化。如果是str或者int则是改变就改变了索引。
- 每一次是cur.next = xxx 仅仅是变了指向,本次指针没变化,除非是cur = cur.next 或者cur= pre这样,才是把cur的指针变化了。
- 链表脑子里一定要有对应的图产生,没有图和运动很难作对。
链表的基本操作
链表排序 lc 148
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head: ListNode) -> ListNode:
if not (head and head.next):
return head
# step1:找中点的范式,先从dummy开始
dummy = ListNode(0)
dummy.next = head
# step2:两个都从dummy开始
slow = fast = dummy
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# slow的下一个就是中点
mid = slow.next
slow.next = None
right = self.sortList(mid)
left = self.sortList(head)
cur = res = ListNode(0)
while right and left:
if right.val<=left.val:
cur.next = right
right = right.next
else:
cur.next = left
left = left.next
cur = cur.next
cur.next = left or right
return res.next
- 使用归并排序。
删除倒数第k个位置的节点 lc 19
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def removeNthFromEnd_(self, head: 'ListNode', n: 'int') -> 'ListNode':
#别翻转了,直接求出来应该的位置
cnt = 0
cur = head
while cur is not None:
cnt+=1
cur = cur.next
cur = head
for _ in range(cnt-n-1):
cur = cur.next
cur.next = cur.next.next
return head
def removeNthFromEnd(self, head: 'ListNode', n: 'int') -> 'ListNode':
dummy = ListNode(0)
dummy.next = head
# step1: 快指针先走n步
slow, fast = dummy, dummy
for _ in range(n):
fast = fast.next
# step2: 快慢指针同时走,直到fast指针到达尾部节点,此时slow到达倒数第N个节点的前一个节点
while fast and fast.next:
slow, fast = slow.next, fast.next
# step3: 删除节点,并重新连接
slow.next = slow.next.next
return dummy.next
- 有dummy就能保证。next.next合法
- 要么翻转,但是翻转操作比较大。
- 直接求总体长度,或者直接用双指针是最好的。
- 但是必须要dummyNode节点,要不倒数最后一个就不好弄了。
回文链表 lc 234
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
dummy = ListNode(0)
dummy.next = head
slow = fast = dummy
while fast and fast.next:
fast = fast.next.next
slow = slow.next
mid = slow.next
slow.next = None
stack = []
while mid:
stack.append(mid.val)
mid = mid.next
while stack:
if head.val!=stack.pop():
return False
head = head.next
return True
# 废弃
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow=fast = head
stack = []
while fast and fast.next:
stack.append(slow.val)
slow = slow.next
fast = fast.next.next
if fast:
slow = slow.next
while slow is not None:
if stack.pop()!=slow.val:
return False
slow = slow.next
return True
- 与上一题目一样,也是由快慢指针能指向两个位置,先画一下图。
- while是比较精彩的地方,出了while才是True,而且对于一个单独值的链表也能很好的处理。
链表合并
leetcode 21 合并2个有序链表
#class ListNode:
# def __init__(self,x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoList(self,l1,l2):
'''
l1: ListNode
l2: ListNode
rtype: ListNode
'''
curr = dummy = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
curr.next = l1
l1 = l1.next
else:
curr.next = l2
l2 = l2.next
curr = curr.next
curr.next = l1 or l2
return dummy.next
- 这个题目的难度在于链表的数据类型的表示,代码上注释掉的那部分,一个类,每次都是一个数val,和指向下一个指针。是牵一发动全身的感觉。
- 每次都是l这个指针被重新赋值的过程。
合并两个有序的list
class Solution:
def merge(self,lst1,lst2):
res = []
while lst1 and lst2:
if lst1[0]<lst2[0]:
res.append(lst1.pop(0))
else:
res.append(lst2.pop(0))
res.extend(lst1 or lst2)
return res
- 和上面的思路其实是一样的,都是往下舍弃,只不过是对list的操作还是对链表的操作,对链表需要用next。
合并两个有序list到第一个list里 lc 88
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
while m>0 and n>0:
#因为排序好了,所以比较要从后往前走,有点类似于冒泡
if nums1[m-1]<nums2[n-1]:
nums1[m-1+n]=nums2[n-1]
n-=1
else:
nums1[m-1+n],nums1[m-1]=nums1[m-1],nums1[m-1+n]
m-=1
if m == 0 and n>0:
nums1[:n] = nums2[:n]
- 把整个思路想清楚,从后往前走,因为后面是大的。一个一个移动位置。
leetcode 23 合并n个有序链表
from queue import PriorityQueue
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# 自己的思路写的无需记住特别的api
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
heap = []
# 必须要有index,要不不让形成heap
for index,ln in enumerate(lists):
if ln:
heap.append((ln.val,index,ln))
heapq.heapify(heap)
dummy=ListNode(0)
cur = dummy
while heap:
val,index,ln_index = heapq.heappop(heap)
cur.next = ln_index
cur = cur.next
if ln_index.next:
ln_index = ln_index.next
heapq.heappush(heap,(ln_index.val,index,ln_index))
return dummy.next
class Solution:
def mergeKLists(self, lists):
k = len(lists)
q = PriorityQueue(maxsize=k)
curr = dummy = ListNode(None)
for list_idx, node in enumerate(lists):
if node:
q.put((node.val, list_idx, node))
while q.qsize() > 0:
poped = q.get()
curr.next, list_idx = poped[2], poped[1]
curr = curr.next
if curr.next: q.put((curr.next.val, list_idx, curr.next))
return dummy.next
#解法二
from queue import PriorityQueue
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[ListNode]
:rtype: ListNode
"""
head = point = ListNode(0)
q = PriorityQueue()
for index ,l in enumerate(lists):
if l:
q.put((l.val,index, l))
while not q.empty():
val, index,node = q.get()
point.next = node
point = point.next
node = node.next
if node:
q.put((node.val,index, node))
return head.next
- 意思是说先把list的链表存放在堆里,然后再把他们从堆里取出来,还原成链表。
- 使用堆的数据结构让整个取出来的数和排序都是O(logn)
- 因为对原始list形成的链表集合进行enumerate的遍历,所以设置最大值与否一样,设置了最大值如果中途没有消费会堵塞,这个需要启动的多线程多进程,要不然卡住不动了。
链表有环 lc 141
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# 废弃这个方法
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return False
# 必须错开一步否则第一开始进入就废了,但是整个思路不自然废弃掉
slow = head
fast = head.next
while slow and fast and fast.next:
if slow==fast:
return True
else:
#不相等就开足马力跑,没有环就会出去,每次只关心当前所以判断三个
slow = slow.next
fast = fast.next.next
return False
# 比较正常的一种个想法
class Solution:
def hasCycle(self, head: ListNode) -> bool:
s = set()
node = head
while node:
if node in s:
return True
else:
s.add(node)
node = node.next
return False
class Solution:
def hasCycle(self, head: ListNode) -> bool:
fast = slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
return True
return False
- 只有找中点的时候特别的需要dummy和mid=slow.next让后面的少
链表有环 lc 142
找到环的第一次接口的位置
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
s = set()
node = head
while node:
if node in s:
return node
else:
s.add(node)
node = node.next
return
# 和上面那个思路非常顺畅的下来
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
fast = slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
fast = head
while fast!=slow:
fast = fast.next
slow = slow.next
return fast
return
翻转链表 lc 206
class Solution:
def reverseList(self,head):
pre = None
cur = head
while cur is not None:
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
return pre
翻转列表2 lc 92
翻转m,n之间的列表内容
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
#新发生操作的动作
dummynode = ListNode(0)
dummynode.next = head
fixm = dummynode
for _ in range(m-1):
fixm = fixm.next
cur = fixm.next
pre = None
#这得反转时反的2->none,2这个节点有两个值指过来,1,3
for _ in range(n-m+1):
tmp = cur.next
cur.next = pre
pre = cur
cur = tmp
#第一个next是值,第二个是指针
#链表必须先变后面的,在变化前面的
fixm.next.next = cur
fixm.next = pre
return dummynode.next
- 脑子里要清晰的有这个链表图的变化过程
删除排序链表中的重复元素 lc 83
输入: 1->1->2
输出: 1->2
# 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:
dummyNode = ListNode(0)
cur = dummyNode.next = head
while cur is not None and cur.next is not None:
if cur.val != cur.next.val:
cur = cur.next
else:
#下面这个就是在挪动链表的位置
cur.next = cur.next.next
return dummyNode.next
- cur和dummyNode是节点。
- 充分理解链表移动方法,每一次移动本节点已经串进来了,相等是本值之后的两个挪进来,还是有一个链表图的感觉,建造链表图非常重要。
- 经常要想到的就是双指针和快慢指针。
找到链表的交叉处 lc 160
class Soultion:
def getIntersectionNode(self,headA,headB):
ahead = headA
bhead = headB
while ahead!=bhead:
if ahead is not None:
ahead = ahead.next
else:
ahead = headB
if bhead is not None:
bhead = bhead.next
else:
bhead = headA
return ahead
- 还是老习惯,链表要想到双指针,快慢指针。
- 把现在的链表形成互为环,相当于对数字进行补齐。最后一定会相等,要不就是找到了,要不就是没有交点,直接是null导致的相等。
链表两两交换 lc 24
三指针,且
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummynode = ListNode(0)
pre = dummynode
pre.next = head
while pre.next is not None and pre.next.next is not None:
cur = pre.next
future = pre.next.next
cur.next = future.next
future.next = cur
pre.next = future
pre = cur
return dummynode.next
- 这个题目和之前的题目不一样,之前的都是通过next去索引,这个是三指针,且变化了指向后位置自然发生了变化不是通过next,然后移动pre让整个流程一致。
- 每一次都是一个dummynode的位置去变化下两个节点,让流程变化一致。
两数相加 II lc 445
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
stack1,stack2 = [],[]
carry = 0
dummyNode = ListNode(0)
while l1 is not None:
stack1.append(l1.val)
l1 = l1.next
while l2 is not None:
stack2.append(l2.val)
l2 = l2.next
while stack1 or stack2 or carry:
if stack1:
val1 = stack1.pop()
else:
val1 = 0
if stack2:
val2 = stack2.pop()
else:
val2 = 0
sumval = val1+val2+carry
val = sumval%10
carry = sumval //10
node = ListNode(val)
node.next = dummyNode.next
dummyNode.next = node
return dummyNode.next
- 整个流程先思考一个最简单的模式,比如说因为要从末尾加起来,但是链表是单向我的,所以必然需要遍历到尾部(有的不需要),再反着加起来,所以就遍历过去好了。先不要思考奇技淫巧的,先想简单点的实现。
- 因为流程是复杂的,所以先思考出一个通解,比如这个val1+val2+carry,每一次都是执行这个操作,这个操作是原子的,先想出原子的操作是什么。
- 大数相加也是这个操作。
- 本题目是头插。
lc 面试题 02.05. 链表求和
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
flag = 0
stack = []
while l1 or l2 or flag:
res = 0
if l1 is not None:
res+=l1.val
l1 = l1.next
if l2 is not None:
res+=l2.val
l2 = l2.next
res += flag
if res>=10:
res = res %10
flag = 1
else:
flag = 0
stack.append(res)
head = node = ListNode(stack.pop(0))
while stack:
node.next = ListNode(stack.pop(0))
node = node.next
return head
分隔链表 lc 725
分割链表,让链表内长度最大值差1,且长的链表在前面。
输入: root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
输入:root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def splitListToParts(self, root, k):
"""
:type root: ListNode
:type k: int
:rtype: List[ListNode]
"""
cur = root
cnt = 0
res = []
while cur:
cnt+=1
cur = cur.next
each_capacity = cnt//k
rem = cnt%k
#写代码流程复杂第一要思考的问题,先遍历谁
for _ in range(k):
#要重新去组织一个list,所以去取内容
each = dummyNode = ListNode(0)
for _ in range(each_capacity):
#这要阻断,要不一拿就是一串
each.next = ListNode(root.val)
each = each.next
root = root.next
#没必要比较大小,因为每一次就多一个,所以减到0即可
if rem:
each.next = ListNode(root.val)
root = root.next
rem-=1
res.append(dummyNode.next)
return res
- 这个题目非常的好,虽然大家都说不难,但是实际开发就是这样的题目,要先想好要遍历谁,再遍历谁。
- 可能后续要优化,但是开始还是要按照可能性写完。
- 我卡壳的地方是对谁先进行遍历,其次是怎么每次重新开启一个链表。
- 这个和其他的不同是这个要断开,其他的不需要断开,因为如果维护两套指针则交替进行,但是维护一个则必须断开,否则就会沿着一个方向走下去。
奇偶链表 lc 328
给定的链表奇数序号排在前面,偶数序号排在后面。
# 这两个是一样的,只不过这是我自己的风格
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head:
return head
dummyodd = oddNode = head
dummyeven = evenNode = head.next
while evenNode and evenNode.next:
oddNode.next = evenNode.next
oddNode = oddNode.next
evenNode.next = oddNode.next
evenNode = evenNode.next
oddNode.next = dummyeven
return dummyodd
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head:
return head
#一定要设置固定的头的位置,如同dummynode
evenHead = head.next
odd, even = head, evenHead
while even and even.next:
odd.next = even.next
odd = odd.next
even.next = odd.next
even = even.next
odd.next = evenHead
return head
- 难度是这个东西思路不对会产生一堆的指针,指针只能控制最多三个,再多就混乱了,能有两个是最好的。减少不必要的变量能让问题变得简单许多。
- ==减少的方法是交替的使用指针进行,不是每次都开一个新的内容。==上面两两交换也是交替的用相同的指针。有种DNA解体的画面感。