说明:代码没毛病。建议手敲。直接复制可能有对齐之类的问题。
链表
- 单向链表
- 双向链表
- 结点定义
class ListNode:
def __init__(self,x):
self.val = x
self.next = None
栈:
- 后进先出
队列:
- 先进先出
这三种数据结构是手撕代码的最高频出现的基础数据结构了
链表相关
题目1:从尾到头打印一个单链表(返回一个list)
def Reverselist(ListNode):
if not ListNode:
return []
lis = []
while ListNode:
lis.append(ListNode)
ListNode = ListNode.next
return lis[::-1]
题目2:翻转一个单链表,输出反转后的头结点
非递归解法
def ReverseList(pHead):
if not pHead or not pHead.next:
return pHead
# 三个指针:
# last :前一个结点
# pHead:当前节点
# next_one:下一个结点
last = None
while pHead:
# 遍历链表直到pHead == None,那么last最终移到反转后的头结点
next_one = pHead.next # 记录下一个结点位置
pHead.next = last # 指针反向
last = pHead # 指针后移
pHead = next_one # 指针后移
return last #返回头结点
递归解法
def ReverseList(head):
# 递归终止条件
if not head or not head.next:
return head
# 缩小范围递归剩余的链表
newHead = ReverseList(head.next)
# 处理没有调整的第一个和第二个结点
tail = head.next # 找到当前尾结点:旧的头结点head此时仍然指向尾结点
tail.next = head # 尾结点指向旧头结点
head.next = None # 旧头结点指向None
return newHead # 返回新的头结点
题目3:判断一个链表是不是回文结构
思路1:空间
O
(
n
)
O(n)
O(n)
链表入栈。
逐个弹出与原链表比较
def huiwenList(head):
if not head or not head.next:
return True
stack = []
pHead = head
while pHead:
stack.append(pHead)
pHead = pHead.next
while stack:
pHead = stack.pop()
if pHead.val != head.val:
return False
else:
head = head.next
return True
思路2:空间
O
(
n
/
2
)
O(n/2)
O(n/2)
链表的后一半入栈(偶数个则后一半。奇数个则跳过中点)
如何找到中点呢?
一个慢指针每次走一步,一个快指针每次走两步。
因为快指针每次走两步,所以判断是否走到最后的条件有两种:
fast.next == None or fast.next.next == None
分别对应奇数个结点和偶数个结点
快指针不能继续前进时,慢指针指向表长/2的位置
def huiwenList(phead):
if not phead or phead.next:
return True
slow, fast = phead,phead
while fast.next or fast.next.next:
slow = slow.next
fast = fast.next.next
stack =[]
slow = slow.next
while slow:
stack.append(slow)
slow = slow.next
while stack:
node = stack.pop()
if node.val != phead.val:
return False
phead = phead.next
return True
思路3
翻转链表的后半段,与前半段进行比较。空间
O
(
1
)
O(1)
O(1),时间
O
(
n
)
O(n)
O(n)
def Reverselist(head):
if not head or not head.next:
return headt
newHead = Reverselist(head.next)
tail = head.next
tail.next = head
head.next =None
return newHead
def huiwenList(phead):
if not head or not head.next:
return True
slow, fast = phead,phead
while fast.next or fast.next.next:
slow = slow.next
fast = fast.next.next
stack =[]
slow = slow.next
revHead = Reverselist(slow)
while reveHead: # 如果是奇数个结点,a+1+a个,反转部分是a个,所以revHead先于phead 变成空结点
if phead.val != revHead.val:
return False
revHead = revHead.next
phead = phead.next
return True
题目4:如何判断一个链表有环。找到环的入口结点
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路1
可以接著一个辅助list,如果list有结点值,则说明有环,没有当前节点值则加入list
如果list没有环,指针轮空时停止。
如果list有环,list中出现当前元素时停止
def EntryNodeOfLoop(pHead):
if not pHead or not pHead.next:
return None
lis = []
while pHead:
if pHead in lis:
return pHead
else:
lis.append(pHead)
pHead = pHead.next
思路2
链表有环:
设置一个快指针,一个慢指针,如果链表有环,则一定会相遇。
入口结点位置确定:
可以证明。两节点相遇后。一个结点回到起点。同速度前进。下次相遇点为入口节点
边界检查:
空结点,单结点,双节点不成环
def EntryNodeOfLoop(pHead):
if not pHead or not pHead.next or not pHead.next.next:
return False
slow = pHead.next
fast = pHead.next.next
while slow!=fast:
if not fast.next or fast.next.next:
return None
fast = fast.next.next
slow = slow.next
slow = pHead
while slow!=fast:
slow = slow.next
fast = fast.next
return fast
题目4.1:判断两个链表有没有公共结点并返回公结点
思路1
O
(
n
2
)
O(n^2)
O(n2)
遍历第一个链表,检查遍历结点在另一个链表里有没有
这个思路,,代码很短,但是复杂度很高
def FindFirstCommonNode(pHead1, pHead2):
if not pHead1 or not pHead2:
return None
lis = []
while pHead1:
lis.append(pHead1)
pHead1 = pHead1.next
while pHead2:
if pHead2 in lis:
return pHead2
pHead2 = pHead2.next
return None
思路2
两个链表相交之后就不会分离
(每个结点只有一个指针,相交之后就呈Y形,而不是X形)
所以两个链表如有不同,出现在Y上的两分叉长度不同
可以先找到一个链表比另一个链表长多少(k)
指针在长链表上先走k步
然后同步遍历到值相同or遍历完毕。
def FindFirstCommonNode(pHead1, pHead2):
if not pHead1 or not pHead2:
return None
p1,p2 = pHead1,pHead2 # 需要两次遍历,不能丢失链表头结点
# 先走完短链表
while p1 and p2:
p1 = p1.next
p2 = p2.next
# 然后对长链表计算剩余节点个数
count = 0
flag = 1 # 默认p1是长链表
# 如果p1 是长链表,继续遍历
if p1:
while p1:
count+=1
p1 = p1.next
# 如果p2是长链表,修改flag值继续遍历
elif p2:
flag = 2
while p2:
count+=1
p2 = p2.next
# 第二次遍历,先在长链表上走count步
if flag ==1:
while count:
pHead1= pHead1.next
count -=1
else:
while count:
pHead2 = pHead2.next
count -=1
# 然后继续同步遍历
while pHead1 and pHead2:
if pHead1.val == pHead2.val:
return pHead1
else:
pHead1 = pHead1.next
pHead2 = pHead2.next
# 如果没有找到公共节点
return None
题目5:找到链表的倒数第k个结点
这个比较简单。设置两个指针,第一个先走k步。
此时两个指针相差k个结点
之后两个指针同步前进。
当前指针走到none时,后指针就是倒数第k个
但是注意:
- 检查链表合法性
- 检查k的合法性
def lastK(pHead,k):
# 检查链表
if not pHead:
return None
p1 = pHead
p2 = pHead
for i in range(k):
# 检查 k
if p1:
p1 = p1.next
else:
return None
while p1:
p1 = p1.next
p2 = p2.next
return p2
题目6:删除链表中的重复结点
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路
题目的重点:
- 排序链表,说明重复节点连续
- 不保留重复结点:则不止要记住重复开始的结点,还要记住重复前的一个结点。重复结束的结点。
- 头结点也可能是重复结点,所以需要先加一个前面的结点。
代码逻辑:
- 检查边界
- curNode初始化为头结点,创建一个preNode并指向当前结点curNode
- 使用curNode遍历链表:
如果curNode不是最后一个结点且curNode的值与下一个结点值相同:
比较下一个结点nextNode和curNode的值:
+ 值相同则继续比较直到找到不同值nextNode 或者nextNode已空:
preNode指向该nextNode,curNode移到该NextNode
+ 值不同则:preNode和curNode后移
class Solution:
def deleteDuplication(self, pHead):
if not pHead or not pHead.next:
return pHead
preHead = ListNode(-1)
preHead.next = pHead
preNode = preHead
curNode = pHead
while curNode: # 遍历链表
# 如果当前结点有下一个结点,且值相同
if curNode.next and curNode.next.val == curNode.val:
# 记录下一个结点的位置,
nextNode = curNode.next
# 从下一个结点开始找到值不同的结点
while nextNode and nextNode.val == curNode.val:
nextNode = nextNode.next
# 找到值不同的结点后,跳过中间的重复节点curNode到nextNode
# preNode.next指到这个不同结点,curNode定位到这个结点
preNode.next = nextNode
curNode = nextNode
# 没有下一个结点(curNode.next == None)或者值不相同,两指针顺序后移继续遍历
else:
preNode = curNode
curNode = curNode.next
return preHead.next
题目7:合并两个排序链表
这个题很简单。和归并排序的归并方法是一样的。
加一个头结点mergeHead。
加一个遍历指针p。
但别忘了每次添加节点后,遍历指针p要后移一下
class Solution:
def Merge(self, pHead1, pHead2):
mergeHead = ListNode(90)
p = mergeHead
while pHead1 and pHead2:
if pHead1.val <= pHead2.val:
p.next =pHead1
pHead1 = pHead1.next
else:
p.next = pHead2
pHead2 = pHead2.next
p = p.next #别忘了这一句啊!!!
if pHead1:
p.next = pHead1
if pHead2:
p.next = pHead2
return mergeHead.next
题目8:复制一个复杂链表
题目:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
结点定义如下:
class RandomListNode:
def __init__(self, x):
self.label = x
self.next = None
self.random = None
思路
一个是复制,一个是拆分。
class RandomListNode:
def __init__(self,x):
self.val = x
self.next = None
self.random = None
class Solution:
def Clone(self,pHead):
# 合法性检查
if not pHead:
return None
# 复制链表主链
p = pHead
while p:
# 复制当前结点
copy = RandomListNode(p.val)
org_next = p.next
p.next = copy
copy.next = org_next
# 访问下一个结点
p = org_next
# 处理random指针:不做复制,主链random指针指向的结点,random链指向相应的复制的结点。
p = pHead
# 重新遍历链表
while p:
if p.random:
# p.next是复制的结点,给复制的结点的random指针赋值
# p.random是主链random指向的结点,p.random.next是相应的复制的结点
p.next.random = p.random.next
p = p.next.next
# 把复制的链表剥离:
pFirst = pHead
pSecond = pHead.next
while (pFirst.next):
temp = pFirst.next
pFirst.next = temp.next
pFirst = temp
return pSecond
'''这样写会错,p.next.next可能会空!!!
p = pHead.next
newHead = p
while p:
nextNode = p.next.next
p.next = nextNode
p = nextNode
return newHead
'''
栈和队列相关
题目1:两个栈实现一个队列
栈:先入后出
队列:先入先出
所以:
栈和队列元素进入的方式都是一样的。
出队不同:
只需要将一个栈的出栈顺序反过来–>
将元素弹出并压入另一个–>
那么另一个栈的栈顶元素就是队首元素了
所以,需要出队时:
如果另一个栈不是空栈,直接弹出栈顶元素。
如果是空栈。就先将第一个栈里的元素出栈,压入第二个栈。
然后再弹出栈顶元素。
如果第一个栈也是空的。则队列已空。
pop()逻辑:
两个栈,一个入队栈,一个中转栈
- 检查中转栈空不空:
- 不空:中转站出栈一个元素
- 空: 检查入队栈空不空
- 空: 返回None
- 不空:入队栈全弹出到中转栈,然后中转栈出栈一个元素
class Solution:
def __init__(self):
self.stack1 = []
self.stack2 = []
def push(self,node):
self.stack1.append(node)
def pop(self):
if not self.stack2:
if not self.stack1:
return None
else:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
题目2:两个队列实现一个栈
题目3:实现包含min函数的栈
class Solution:
def __init__(self):
# 一个堆栈
self.stack =[]
# 一个负责保存最小值的栈
self.min_stack =[]
def push(self, node):
# 基本压栈
self.stack.append(node)
# 如果最小值栈为空或者压栈元素小于等于最小值栈顶:压栈元素入栈
if not self.min_stack or self.min_stack[-1]>=node:
self.min_stack.append(node)
def pop(self):
# 如果栈空
if not self.stack:
return None
else:
# 否则正常出栈
node = self.stack.pop()
# 如果最小值栈顶正好是出栈元素,最小值栈顶也出栈
if node == self.min_stack[-1]:
self.min_stack.pop()
# 返回出栈元素
return node
def top(self):
# 如果栈空
if not self.stack:
return None
else:
# 否则返回栈顶
return self.stack[-1]
题目4:给一个栈的压入序列1,判断序列2是不是弹出序列
def IsPopOrder(pushV, popV):
if not pushV and not popV:
return True
if len(pushV)!=len(popV):
return False
stack =[]
for item in pushV:
stack.append(item)
# 栈顶元素等于出栈序列第一个元素,就栈出栈,序列出一个
while len(stack) and stack[-1] == popV[0]:
stack.pop()
popV.pop(0)
if len(stack):
return False
else:
return True