栈和队列相互实现
用两个栈实现队列的题中,最关键的是意识到:由于栈的先入后出特性,将栈A倒序至栈B后,栈B的pop顺序就是栈A的反向,同时也是A的队列顺序。
但是在队列实现栈的设定中,将队列A反向的结果,依然是队列A,因为队列是先入先出的,先出元素又再次先入。所以此题是用队列B存储队列A pop 出的前 n-1 个元素,此时队列A中剩下的最后一个元素,即为栈对应的元素,然后交换队列A和队列B。
队列B始终保持为空,因为队列A在pop之后,为空,队列B有 n-1 个元素,交换AB后,队列B为空,回归操作前状态。
同理,会了两个队列的操作,就会用单队列的操作,只不过是 dq.append(dq.pop())。在此题中,一个要注意的点是,栈的top操作,是直接利用索引[-1]实现的,不如此做,就只能再次循环一下队列了。
有效的括号
用栈,想到一共三种情况,注意:当栈为空,且字符串未遍历完成,是False,这种情况同样适用于第一次循环,不需要额外处理,因为循环内的逻辑,首先是三个if 判断是否存在左括号,如果存在,栈就不会为空,而如果不存在,说明字符串第一个字符为右括号,自然匹配失败。
删除字符串中的所有相邻重复项
如果栈为空,入栈。如果栈不空,比较栈顶元素和要入栈的元素,相等,栈顶元素出栈。不相等,入栈元素入栈。
逆波兰表达式求值
遇到数字入栈,遇到操作符,栈顶前两个元素出栈,做运算,将运算结果入栈。
滑动窗口最大值
难,第二次复习还是不会写,记得是要用单调队列,但是不知道如何编写逻辑,让队列单调。
注意一点:在队列中,不需要去维护所有的K个值,之前以为,只能从这个队列里获取要弹出的元素,但是这样做,就不可能形成一个单调队列了。在遍历一遍数组的过程中,当前要弹出和压入的元素,都是可以通过索引获得的。
代码学习,并有一点心得,写在注释中。
from collections import deque
class DQ:
def __init__(self):
self.dq = deque()
def mypop(self,value):
if len(self.dq) != 0 and self.dq[0]==value :
self.dq.popleft()
def push(self,value):
while self.dq and value > self.dq[-1]:
# 这里注意啊,代码随想录的代码这里存在歧义
# 这里的pop是deque自带的"popright",不是上面我们自己定义的pop
# 把我们自己的函数改为 mypop, 依然通过!这里按道理来说,就要是"popright"
self.dq.pop()
self.dq.append(value)
def top(self):
return self.dq[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
res = []
dq = DQ()
n = len(nums)
for i in range(k):
dq.push(nums[i])
res.append(dq.top())
for i in range(k,n):
dq.mypop(nums[i-k])
dq.push(nums[i])
res.append(dq.top())
return res
前 K 个高频元素
不会,不会,完全知识盲区。
#时间复杂度:O(nlogk)
#空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
在第一次学的时候,就找了一篇不错的介绍堆和堆排序的博客,此时大概看了一遍,太复杂了,没有理解,日后深入,在此记录。
Python-heapq
前中后序,二叉树的递归遍历
过
前中后序,二叉树的迭代遍历
前序很自然,因为要处理的节点就是迭代节点的顺序。前序注意因为栈为先入后出,所以前序中左右,但在编写时,先入栈右,再入栈左。
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None :
return []
res = []
stack = [root]
while stack :
node = stack.pop()
res.append(node.val)
if node.right :
stack.append(node.right)
if node.left :
stack.append(node.left)
return res
后序做一下变换,后序:左右中。前序:中左右。
前序颠倒左右顺序,中右左,倒叙,左右中,解决!
中序,和前序不一样,要多加一个节点,用于定位。
这次还是没有独立正确写出来。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None :
return []
stack = []
res = []
cur = root
while cur or stack :
if cur :
stack.append(cur)
cur = cur.left
else :
node = stack.pop()
res.append(node.val)
cur = node.right
return res
要点:一路向左,当左孩子为空时,此时pop出一个元素,这个元素就是我们要处理的元素,此时其是否是叶子节点无所谓,甚至说就是要考虑该节点右孩子不为空的情况,因为此时左孩子为空,节点本身已经处理过了,下一步就是让 cur = node.right,进入右孩子。
上述顺序符合:中序遍历左中右的顺序。
二叉树的统一迭代法
利用填入空指针来标记要处理的节点。
思维要打开一些,不要潜意识想着遍历到叶子节点了就停止,想着再向下一步,因为只有指针走到None,一般情况下才是我们设定的判断条件。
二刷时没有写出来,后面要注意。
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if root == None :
return []
res = []
stack = [root]
while stack :
node = stack.pop()
if node == None :
cur = stack.pop()
res.append(cur.val)
else :
if node.right :
stack.append(node.right)
stack.append(node)
stack.append(None)
if node.left :
stack.append(node.left)
return res
层序遍历
递归法,拿下。
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root == None :
return []
res = []
depth = 1
self.digui(root,depth,res)
return res
def digui(self,root,depth,res):
if root == None :
return
if len(res) < depth :
res.append([])
res[depth-1].append(root.val)
self.digui(root.left,depth+1,res)
self.digui(root.right,depth+1,res)
迭代法:使用队列
这次复习时候,没有一次性写出来,中间卡在了如果将层序遍历结果,保存为矩阵的形式(即嵌套列表),本质上是判断当前层有几个节点的问题,然后想起了只要先计算出当前队列的长度,即为本层的节点数了。
一定要先把size保存下来,不然后面操作队列时,长度就会改变了。
from collections import deque
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if root == None :
return []
dq = deque()
level = []
dq.append(root)
while dq :
size = len(dq)
res = []
for i in range(size):
node = dq.popleft()
res.append(node.val)
if node.left:
dq.append(node.left)
if node.right:
dq.append(node.right)
level.append(res)
return level
翻转二叉树
一共五种方法,递归的,迭代的,统一版本迭代的,层次遍历的,在二叉树系列1的博客中写的很详细了,代码上没什么难度。
对称二叉树
过。两道相关的题:相同的树,另一颗树的子树,也均通过。
二叉树的最大深度
层次遍历的方法就不说了,主要学习递归的方法。最大深度就是根节点的最大高度,后序遍历求高度,前序遍历求深度。
后序遍历简单拿下。
前序遍历,这个时候还是不会写,主要原因是,没有意识到,递归函数中要有depth参数,因为是前序遍历,是先对中间节点做处理的,所以要先判断result和depth谁大。
class Solution:
def __init__(self):
self.result = -1
def maxDepth(self, root: Optional[TreeNode]) -> int:
if root == None :
return 0
self.digui(root,1)
return self.result
def digui(self,root,depth):
self.result = max(self.result,depth)
if root.left == None and root.right == None :
return
if root.left :
self.digui(root.left,depth+1)
if root.right :
self.digui(root.right,depth+1)
return
二叉树的最小深度
这题的关键点在于,弄懂最小深度的定义,是从叶子结点到根节点,而不是任意一个节点,比如有一个节点只有右孩子,那么此节点是不能作为起点计算高度的。
所以相应的逻辑判断就是,左空:return 1+右 ; 右空:return 1+左 ; 均不空: return 1+min(左,右)
class Solution:
def minDepth(self, root: Optional[TreeNode]) -> int:
if root == None :
return 0
if root.left == None and root.right == None :
return 1
elif root.left == None :
return 1 + self.minDepth(root.right)
elif root.right == None :
return 1 + self.minDepth(root.left)
else :
return 1 + min(self.minDepth(root.left),self.minDepth(root.right))
完全二叉树的节点个数
如果是一颗普通二叉树的话,直接遍历就好了,但是这道题明显要用到完全二叉树的性质,可惜在此次复习中,不知道如何运用并解题。
完全二叉树一定是由一些满二叉树,组成的。而满二叉树的节点数有公式,并且,判断一棵树是否是满二叉树很简单,左右孩子一直迭代,看深度是否相等就好了。
重点!
class Solution:
def countNodes(self, root: TreeNode) -> int:
if not root:
return 0
left = root.left
right = root.right
leftDepth = 0 #这里初始为0是有目的的,为了下面求指数方便
rightDepth = 0
while left: #求左子树深度
left = left.left
leftDepth += 1
while right: #求右子树深度
right = right.right
rightDepth += 1
if leftDepth == rightDepth:
return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2,所以leftDepth初始为0
return self.countNodes(root.left) + self.countNodes(root.right) + 1
平衡二叉树
就是后序遍历,加一个全局变量做判断就好了,及时退出递归。
本题就用递归来解吧,迭代法有些过于麻烦了,递归就很简单。
二叉树的所有路径
感觉还好。前序遍历,先把当前节点append到暂存路径中,如果是叶子节点,就保存当前的路径。如果不是,就走左右子树,注意回溯,pop掉当前节点。
class Solution:
def traversal(self, cur, path, result):
path.append(cur.val) # 中
if not cur.left and not cur.right: # 到达叶子节点
sPath = '->'.join(map(str, path))
result.append(sPath)
return
if cur.left: # 左
self.traversal(cur.left, path, result)
path.pop() # 回溯
if cur.right: # 右
self.traversal(cur.right, path, result)
path.pop() # 回溯
def binaryTreePaths(self, root):
result = []
path = []
if not root:
return result
self.traversal(root, path, result)
return result
迭代法也可以看看,就是一个前序遍历,主要学习保存结果的处理逻辑。
class Solution:
def binaryTreePaths(self, root: TreeNode) -> List[str]:
# 题目中节点数至少为1
stack, path_st, result = [root], [str(root.val)], []
while stack:
cur = stack.pop()
path = path_st.pop()
# 如果当前节点为叶子节点,添加路径到结果中
if not (cur.left or cur.right):
result.append(path)
if cur.right:
stack.append(cur.right)
path_st.append(path + '->' + str(cur.right.val))
if cur.left:
stack.append(cur.left)
path_st.append(path + '->' + str(cur.left.val))
return result
左叶子之和
写是写出来了,但是感觉逻辑上不清晰,学习一下解答,解答见之前写的博客:二叉树系列3。下面是我自己写的代码。
class Solution:
def __init__(self):
self.res = 0
def sumOfLeftLeaves(self, root: Optional[TreeNode]) -> int:
if root == None :
return 0
self.digui(root)
return self.res
def digui(self,root):
if root == None :
return None
if root.left :
nleft = self.digui(root.left)
if nleft.left == None and nleft.right == None :
self.res += nleft.val
if root.right :
nright = self.digui(root.right)
return root
代码随想录给出的代码解答,我稍微做了一点点修改,将判断前移,并加入了一个else,应该是更好理解一点了。
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
if root.left is None and root.right is None:
return 0
if root.left and not root.left.left and not root.left.right: # 左子树是左叶子的情况
leftValue = root.left.val
else :
leftValue = self.sumOfLeftLeaves(root.left) # 左
rightValue = self.sumOfLeftLeaves(root.right) # 右
sum_val = leftValue + rightValue # 中
return sum_val
这道题还是有学习价值的,解答的思路是,计算左右子树的左叶子之和,并相加,是典型的递归。我的思路是:遍历,找出所有的左叶子。均可学习。
找树左下角的值
层序遍历的思路就不说了,非常直观,代码也可以直接套层序遍历的模板。递归的思路不好想。
本题的注意点是,是最底层最左边,而不是单纯的最左边(一直left),所以要去判断depth。
本题值得学习。
class Solution:
def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.maxdepth = -1
self.res = 0
self.digui(root,0)
return self.res
def digui(self,root,depth):
if root.left == None and root.right == None:
if depth > self.maxdepth :
self.maxdepth = depth
self.res = root.val
return
if root.left :
self.digui(root.left,depth+1)
if root.right :
self.digui(root.right,depth+1)
return
路径总和i
这个没啥写的,return bool变量就好了。
路径总和ii
这个可以写一写。第一遍写出来了,但是又忘记了在Python中,列表append变量后,如果对此变量修改,列表也会相应修改!所以要 res.append(path.copy())
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if root == None :
return []
self.res = []
path = [root.val]
self.digui(root,targetSum-root.val,path)
return self.res
def digui(self,root,target,path):
if root.left == None and root.right == None and target == 0 :
self.res.append(path.copy())
return
if root.left :
path.append(root.left.val)
self.digui(root.left,target-root.left.val,path)
path.pop()
if root.right :
path.append(root.right.val)
self.digui(root.right,target-root.right.val,path)
path.pop()
return
从中序与后序遍历序列构造二叉树
从前序与中序遍历序列构造二叉树
最大二叉树
讲讲思路吧,这三道题的递归代码都不复杂,只要在,前序or后序or数组求max,中找到中间节点,然后切片数组即可。
合并二叉树
本题的关键在于,如何编写递归逻辑来接住返回值!容易想不明白的点就是:
root1.left root1.right return root1
直接在root1上做修改。
class Solution:
def mergeTrees(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> Optional[TreeNode]:
if root1 == None and root2 == None :
return None
elif root1 == None and root2 != None :
return root2
elif root1 != None and root2 == None :
return root1
else :
root1.val += root2.val
root1.left = self.mergeTrees(root1.left,root2.left)
root1.right = self.mergeTrees(root1.right,root2.right)
return root1
二叉搜索树中的搜索
简单,过。
验证二叉搜索树
难,思路上出现了错误,不能简单判断左右的大小。
忘记了最关键的一点,二叉搜索树,中序遍历!在中序遍历下,二叉搜索树是有序的,是单调递增的!
class Solution:
def __init__(self):
self.pre = None
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if root == None :
return True
left = self.isValidBST(root.left)
if self.pre != None :
if self.pre.val >= root.val :
return False
self.pre = root
right = self.isValidBST(root.right)
return left and right
二叉搜索树的最小绝对差
同上一题,验证二叉搜索树,也是搞一个pre节点。
二叉搜索树中的众数
二刷还是没写出来,累了感觉,思维已经混乱了,后面这道题要重视起来。
class Solution:
def findMode(self, root: Optional[TreeNode]) -> List[int]:
self.pre = None
self.maxcount = 1
self.count = 1
self.res = []
if root == None:
return []
self.digui(root)
return self.res
def digui(self,root):
if root == None:
return
self.digui(root.left)
if self.pre == None :
self.res.append(root.val)
else :
if self.pre.val == root.val :
self.count += 1
else :
self.count = 1
if self.count > self.maxcount :
self.maxcount = self.count
self.res.clear()
self.res.append(root.val)
elif self.count == self.maxcount :
self.res.append(root.val)
self.pre = root
self.digui(root.right)
return
二叉树的最近公共祖先
难,理清逻辑是重点。
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
return self.digui(root,p,q)
def digui(self,root,p,q):
if root == None :
return
if root.val == p.val or root.val == q.val :
return root
left = self.digui(root.left,p,q)
right = self.digui(root.right,p,q)
if left != None and right != None :
return root
elif left == None and right != None :
return right
elif right == None and left != None:
return left
else :
return