代码随想录算法训练营第X天 | 栈和队列系列与二叉树系列--读博文复习小记

栈和队列相互实现

用两个栈实现队列的题中,最关键的是意识到:由于栈的先入后出特性,将栈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

二叉树系列5中的题也都挺经典的,都要多刷

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值