LeetCode之树、链表

目录

1、遍历

2、路径问题

257、二叉树的所有路径

剑指 Offer 55 - I. 二叉树的深度

112、路径总和

113、路径总和II

437、路径总和III

124、二叉树中的最大路径和(字节)

3、二叉搜索树

98. 验证二叉搜索树

108. 将有序数组转换为二叉搜索树

538. 把二叉搜索树转换为累加树

96. 不同的二叉搜索树

95. 不同的二叉搜索树 II

剑指 Offer 36. 二叉搜索树与双向链表

700. 二叉搜索树中的搜索

面试题 04.06. 后继者

99. 恢复二叉搜索树

4、两棵树比较问题

872. 叶子相似的树

572. 另一个树的子树

其他

剑指 Offer 37. 序列化二叉树

剑指 Offer 55 - I. 二叉树的深度

剑指 Offer 55 - II. 平衡二叉树

226. 翻转二叉树

543. 二叉树的直径

563. 二叉树的坡度

114. 二叉树展开为链表

617、合并二叉树

208. 实现 Trie (前缀树)

105. 从前序与中序遍历序列构造二叉树

222. 完全二叉树的节点个数

662. 二叉树最大宽度

链表

206. 反转链表

92. 反转链表 II

25. K 个一组翻转链表

61. 旋转链表

143. 重排链表

141. 环形链表

142. 环形链表 II

变体:求环的长度

382. 链表随机节点


1、遍历

树的前序、中序、后序遍历(栈 stack)

树的层序遍历(队列 queue)

DFS的递归写法

def preorder(root):
    def order(root):
        if not root:
            return []
        res.append(root.val)  # 中
        order(root.left)     # 左
        order(root.right)    # 右

    res = []
    order(root)
    return res

迭代写法:

即通过栈stack来显式的模拟出来

  前序遍历
def preorder(root):
    if not root:
        return []
    res = []
    stack = [root]
    while stack:
        node = stack.pop()
        if node:
            res.append(node.val)  # 中
            if node.right:
                stack.append(node.right)  # 右,先进后出
            if node.left:
                stack.append(node.left)   # 左
    return res
 中序遍历
def inorder(root):
    if not root:
        return []
    res = []
    stack = []
    while stack or root:
        if root:  # 不断往左子树走,每走一次就将当前节点保存到栈中。
            stack.append(root)
            root = root.left
        else:
            tmp = stack.pop() # 当前节点为空,说明左边走到头了,从栈中弹出并保存
            res.append(tmp.val)
            root = tmp.right
    return res
 后序遍历(左、右、根  转换为 根、右、左)
def postorder(root):
    if not root:
        return []
    res = []
    stack = [root]
    while stack:
        node = stack.pop()
        res.append(node.val)
        if node.left:
            stack.append(node.left)
        if node.right:
            stack.append(node.right)

    return res[::-1]

树的层次遍历:

  返回的是[1, 2, 3]这种形式
def levelorder(root):
    if not root:
        return None
    queue = collections.deque()
    queue.append(root)
    res = []
    while queue:
        node = queue.popleft()
        res.append(node.val)
        if node.left:
            queue.append(node.left)
        if node.right:
            queue.append(node.right)
    return res
   返回 [[1], [2,3], [null, null, 4, 5]]
import collections
def levelorder(root):
    if not root:
        return []
    queue = collections.deque()
    queue.append(root)
    res = []
    while queue:
        tmp = []
        for i in range(len(queue)):
            node = queue.popleft()
            tmp.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        res.append(tmp)
    return res

2、路径问题

257、二叉树的所有路径

题意:返回从根节点到叶子节点的所有路径

输入:

   1
 /   \
2     3
 \
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3

题解:dfs的思想

class Solution:
    def binaryTreePaths(self, root: TreeNode) -> List[str]:
        if not root:
            return []
        def dfs(node, path):
            if not node:
                return
            path += str(node.val)
            if not node.left and not node.right: # 当是叶子节点时,就将整个路径加入到结果中。
                res.append(path)
            else:                   # 不是叶子节点,就继续递归子节点
                path += '->'
                dfs(node.left, path)
                dfs(node.right, path)
        res = []
        dfs(root, '')
        return res

剑指 Offer 55 - I. 二叉树的深度

题意:最长路径的长度为树的深度。

给定二叉树 [3,9,20,null,null,15,7]

    3
   / \
  9  20
    /  \
   15   7

返回它的最大深度 3 。

题解:

递归的方法:

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right))+1

或者用dfs的方法

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        def dfs(node, depth):
            if not node:
                return
            depth += 1
            if max_depth[0]<depth:
                max_depth[0] = depth
            dfs(node.left, depth)
            dfs(node.right, depth)

        max_depth = [0]
        dfs(root, 0)
        return max_depth[0]

112、路径总和

题意:

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum。

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true

题解:

dfs(node, tmp)    # tmp为逐步累加和

class Solution:
    def hasPathSum(self, root: TreeNode, targetSum: int) -> bool:
        if not root:
            return False
        self.flag = False
        def dfs(node, tmp):
            if not node:
                return 
            tmp += node.val   # 逐步累加
            if not node.left and not node.right:
                if tmp==targetSum:
                    self.flag = True
                    return         # 直接返回
            dfs(node.left, tmp)
            dfs(node.right, tmp)
        dfs(root, 0)
        return self.flag

113、路径总和II

题意:

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。


输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

题解:

返回所有路径

class Solution:
    def pathSum(self, root, targetSum):
        if not root:
            return []
        res = []
        path = []
        def dfs(node, tmp):
            if not node:
                return
            tmp += node.val
            path.append(node.val)
            if not node.left and not node.right and tmp==targetSum:
                res.append(path[:])   # 复制出整个序列,否则是传引用
            dfs(node.left, tmp)
            dfs(node.right, tmp)
            path.pop()    # 恢复path
        dfs(root, 0)
        return res

437、路径总和III

题意:

路径不再需要从根节点出发,也不再需要在叶子节点结束,只需要路径方向是从父节点到子节点。 返回路径和为target的路径数目

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

     10
     /   \
    5   -3
   /  \     \
  3   2   11
 /  \    \
3  -2   1

返回 3。和等于 8 的路径有:

f1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

题解:

方法一:

类似于 求解  和为k的子数组的个数

使用 “前缀和”+字典的方法

class Solution:
    def pathSum(self, root: TreeNode, targetSum: int) -> int:
        if not root:
            return 0
        self.d = collections.defaultdict(int)
        self.d[0] = 1
        def dfs(node, tmp):
            if not node:
                return 0
            tmp += node.val
            count = self.d[tmp-targetSum]   # 当前路径和-target 是否出现在字典中过,记录其次数
            self.d[tmp] += 1
            
            left = dfs(node.left, tmp)
            right = dfs(node.right, tmp)
            self.d[tmp] -= 1
            return left + right + count
        return dfs(root, 0)

时间复杂度:O(N)

空间复杂度:O(N)

方法二:双重递归

class Solution:
    def pathSum(self, root: TreeNode, targetSum: int) -> int:
        if not root:
            return 0
        self.d = collections.defaultdict(int)
        def dfs(node, tmp):  # 计算当前节点下的满足条件的路径数
            count = 0
            if not node:
                return 0
            tmp += node.val
            if tmp==targetSum:
                count += 1
            count += dfs(node.left, tmp)
            count += dfs(node.right, tmp)
            return count
        return dfs(root, 0) + self.pathSum(root.left, targetSum)+ self.pathSum(root.right, targetSum)

时间复杂度:O(N^2)

空间复杂度:O(N)

124、二叉树中的最大路径和(字节)

题意:

路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。

给你一个二叉树的根节点 root ,返回其 最大路径和 。

题解:

定义maxgain(node)函数,来计算以node节点为根节点的子树中,寻找以node节点为起点的一条路径,使得该路径上的节点值之和最大。

递归地调用maxgain()函数即可得到每个节点的最大贡献值(  node.val + max(left, right)   )。

class Solution:
    def maxPathSum(self, root: TreeNode) -> int:
        self.res = float('-inf')     # 由于节点的值有负数,所以最开始初始化要为负无穷

        def maxgain(node):
            if not node:
                return 0
            left = max(maxgain(node.left),0)     # 进行递归
            right = max(maxgain(node.right), 0)
            s = node.val + left + right    # 路径之和
            self.res = max(self.res, s)
            return node.val + max(left, right)
        maxgain(root)
        return self.res

核心在于:计算结果res时,要计算左右子树、递归返回时只能返回较大的一边。

3、二叉搜索树

性质:中序遍历是一个递增的数组

98. 验证二叉搜索树

题意:验证一棵树是否是二叉搜索树

题解:

方法一:

递归

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        if not root:
            return True
        def dfs(node, lower, upper):
            if not node:
                return
            if node.val<=lower or node.val>=upper:
                return False
            if not dfs(node.left, lower, node.val):  #左子树要均小于node.val
                return False
            if not dfs(node.right, node.val, upper): #右子树要均大于node.val
                return False
            return True
        return dfs(root, -float('inf'), float('inf'))

方法二:

       二叉搜索树的中序遍历是一个递增的数组,因此,可以不断比较当前的数字是否比之前的数字小,若小,则直接返回false。

class Solution:
    def isValidBST(self, root: TreeNode) -> bool:
        if not root:
            return True
        stack = []
        before = float('-inf')
        while stack or root:
            if root:
                stack.append(root)
                root = root.left
            else:
                tmp = stack.pop()
                if tmp.val<=before:
                    return False
                before = tmp.val
                root = tmp.right
        return True

108. 将有序数组转换为二叉搜索树

题意:

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

题解:

       二叉搜索树的根节点是数组的中位数,才能使得高度差最小,即避免有单独的节点在一行。

class Solution:
    def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
        if not nums:
            return
        n = len(nums)
        ind = n//2
        root = TreeNode(nums[ind])  # 中位数
        root.left = self.sortedArrayToBST(nums[:ind])
        root.right = self.sortedArrayToBST(nums[ind+1:])
        return root 

538. 把二叉搜索树转换为累加树

题意:

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和

输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8]
输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8]

题解:

sumtree,反向中序遍历(递减),右、根、左

class Solution:
    def convertBST(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        def dfs(node):
            if not node:
                return 
            dfs(node.right)
            self.total += node.val    # 加上右侧节点的值和当前节点的值
            node.val = self.total
            dfs(node.left)
        self.total = 0
        dfs(root)
        return root

或者使用迭代的方法,栈是先进后出,所以按照左、根、右的顺序加入即可。

class Solution:
    def convertBST(self, root: TreeNode) -> TreeNode:
        stack = [(1, root)]   # 1表示没访问,0表示已访问
        res = 0
        while stack:
            color, node = stack.pop()
            if not node:
                continue
            if color==1:
                stack.append((1, node.left))
                stack.append((0, node))
                stack.append((1, node.right))
            else:
                res += node.val
                node.val = res
        return root

96. 不同的二叉搜索树

题意:

给定一个整数n,求由n个节点组成的从1到n的互不相同的二叉搜索树的种类数。

输入:n = 3
输出:5

题解:

动态规划的思想

class Solution:
    def numTrees(self, n: int) -> int:
        dp = [1, 1]        
        for i in range(2, n+1):   # 以i为根节点,依次遍历
            res = 0
            for j in range(i):
                res += dp[j]*dp[i-j-1]  # 等于左子树的种类*右子树的种类
            dp.append(res)
        return dp[-1]

95. 不同的二叉搜索树 II

题意:

上题的变体,要求返回所有的二叉搜索树

题解:

class Solution:
    def generateTrees(self, n: int) -> List[TreeNode]:
        def trace_back(start, end):
            if end<start:
                return [None, ]
            res = []
            for i in range(start, end+1):
                left = trace_back(start, i-1)
                right = trace_back(i+1, end)
                # 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
                for l in left:
                    for r in right:
                        curtree = TreeNode(i)
                        curtree.left = l
                        curtree.right = r
                        res.append(curtree)
            return res
        return trace_back(1, n)

剑指 Offer 36. 二叉搜索树与双向链表

题意:

将一个二叉搜索树转换为一个排序的循环双向链表,要求不能创建新的节点,只能调整树中节点指针的指向。

题解:在中序遍历的过程中,构建链表:

        当pre为空时,代表正在访问链表的头结点,记为head;

        当pre不为空时,修改双向结点引用,即pre.right = cur, cur.left = pre。

        保存cur:更新pre=cur,即结点cur是后继结点的pre

class Solution:
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        if not root:
            return 
        self.pre = None  # 初始化

        def dfs(cur):
            if not cur:
                return
            dfs(cur.left)    # 递归左子树
            if self.pre:
                self.pre.right = cur
                cur.left = self.pre
            else:
                self.head = cur   # 记录头结点
            self.pre = cur   # 保存cur到链表中
            dfs(cur.right)   # 递归右子树

        dfs(root)       # 开始中序遍历
        self.head.left = self.pre   # 中序遍历完成,构建循环链表
        self.pre.right = self.head
        return self.head

700. 二叉搜索树中的搜索

题意:

给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL

题解:

class Solution:
    def searchBST(self, root: TreeNode, val: int) -> TreeNode:
        while root and root.val != val:
            if val>root.val:
                root = root.right
            else:
                root = root.left
        return root

面试题 04.06. 后继者

题意:

找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

题解:

方法一:

类似于二分查找的思想,由于二叉搜索树是有序的,若当前节点的值要小于目标节点值,则说明要找的节点在当前节点的右边;否则,说明在左边,只要找到并记录大于目标值的最小节点即可。

class Solution:
    def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode:
        if not root:
            return None
        res = None
        cur = root
        while cur:
            if cur.val <= p.val:
                cur = cur.right
            else:
                res = cur
                cur = cur.left
        return res

方法二:

中序遍历,使用flag来记录是否遇到给定的节点p,当遇到时,反转flag=True。当遍历下一个节点时,因为flag==True,所以可以记录节点,并注意要反转flag为False,就不会再记录之后的节点了。

class Solution:
    def inorderSuccessor(self, root: TreeNode, p: TreeNode) -> TreeNode:
        if not root:
            return None
        self.res = None
        self.flag = False
        def inorder(node):
            if not node:
                return
            inorder(node.left)
            if self.flag:
                self.res = node
                self.flag = False
                return
            if node==p:
                self.flag = True
            inorder(node.right)
        inorder(root)
        return self.res

99. 恢复二叉搜索树

题意:给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。   要求只使用常数空间。

题解:

        若不用考虑空间复杂度,可以使用额外数组来保存中序遍历的结果,然后遍历这个数组,交换错误的节点。

因为要求常数的空间复杂度,使用莫里斯遍历,在中序遍历的时候,比较是否一直是递增的,若出现了递减的情况,记录下当前节点值和前一个节点值。

class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        x,y = None, None
        pre = None
        mostright = None
        while root:
            if root.left:
                mostright = root.left
                while mostright.right and mostright.right!=root:
                    mostright = mostright.right
                if mostright.right == None:
                    mostright.right = root
                    root = root.left
                else:
                    if pre and pre.val>root.val:  # 出现了递减
                        y = root
                        if not x:
                            x = pre
                    pre = root
                    mostright.right = None
                    root = root.right
            else:
                if pre and pre.val>root.val:  # 出现了递减
                    y = root
                    if not x:
                        x = pre
                pre = root
                root = root.right
        if x and y:
            x.val, y.val = y.val, x.val

4、两棵树比较问题

872. 叶子相似的树

题意:

判断两棵树的叶子的值组成的叶值序列是否相等。

输入:root1 = [3,5,1,6,2,9,8,null,null,7,4], root2 = [3,5,1,6,7,4,2,null,null,null,null,null,null,9,8]
输出:true

题解:

dfs(node, res)

class Solution:
    def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool:
        if not root1 and not root2:
            return True
        def dfs(node, res):
            if not node:
                return 
            if not node.left and not node.right:
                res.append(node.val)
            else:
                dfs(node.left, res)
                dfs(node.right, res)
            return res
        res1, res2 = [], []
        dfs(root1, res1)
        dfs(root2, res2)
        return res1==res2

572. 另一个树的子树

题意:

给定两个非空二叉树s和t,判断t是否是s的子树

题解:

递归的思想

class Solution:
    def isSubtree(self, root: TreeNode, subRoot: TreeNode) -> bool:
        if not root:
            return False
        if self.issame(root, subRoot):
            return True
        return self.isSubtree(root.left, subRoot) or self.isSubtree(root.right, subRoot)
    
    def issame(self, nodea, nodeb):   # 判断两棵树是否相同
        if not nodea and not nodeb:
            return True
        if nodea is None or nodeb is None:
            return False
        return nodea.val==nodeb.val and self.issame(nodea.left, nodeb.left) and self.issame(nodea.right, nodeb.right)

其他

剑指 Offer 37. 序列化二叉树

题意:

实现序列化二叉树和反序列化二叉树的功能

题解:

class Codec:
    def serialize(self, root):
        """Encodes a tree to a single string.
        :type root: TreeNode
        :rtype: str
        """
        if not root:
            return '[]'
        q = collections.deque()
        q.append(root)
        res = []
        while q:
            node = q.popleft()
            if node:
                res.append(str(node.val))
                q.append(node.left)
                q.append(node.right)
            else:
                res.append('null')
        return '['+','.join(res)+']'

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        :type data: str
        :rtype: TreeNode
        """
        if data=='[]':
            return
        vals = data[1:-1].split(',')
        i = 1
        root = TreeNode(int(vals[0]))
        q = collections.deque()
        q.append(root)
        while q:
            node = q.popleft()
            if vals[i]!='null':
                node.left = TreeNode(int(vals[i]))
                q.append(node.left)
            i += 1
            if vals[i]!='null':
                node.right = TreeNode(int(vals[i]))
                q.append(node.right)
            i += 1
        return root

剑指 Offer 55 - I. 二叉树的深度

题意:

最长路径的长度为树的深度。

题解:

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        def dfs(node, depth):
            if not node:
                return
            depth += 1
            if max_depth[0]<depth:
                max_depth[0] = depth
            dfs(node.left, depth)
            dfs(node.right, depth)

        max_depth = [0]
        dfs(root, 0)
        return max_depth[0]

或者

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right))+1

剑指 Offer 55 - II. 平衡二叉树

题意:

任意一个节点,其两棵子树的高度差不超过1。

题解:

方法一:

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root:
            return True
        def height(node):
            if not node:
                return 0
            return max(height(node.left), height(node.right))+1
        return abs(height(root.left)-height(root.right))<=1 and self.isBalanced(root.left) and self.isBalanced(root.right)
# abs(height(root.left)-height(root.right))<=1是判断当前子树是否是平衡的(左右子树的高度差是否小于1);
#而self.isBalanced(root.left) and self.isBalanced(root.right)是分别判断当前子树的左子树和右子树是否是平衡的。

时间复杂度为O(n^2)

方法二:自底向上的递归

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root:
            return True
        def dfs(node):
            if not node:
                return 0
            left = dfs(node.left)                          # 左的值
            if left==-1:    # 左子树不平衡时
                return -1      # 整个树就不平衡
            right = dfs(node.right)                           # 右的值
            if right==-1:      # 右子树不平衡时
                return -1      # 整个树不平衡
            return max(dfs(node.left), dfs(node.right))+1 if abs(left-right)<=1 else -1    # 根的值
        return dfs(root)!=-1

时间复杂度为O(n)

226. 翻转二叉树

题意:

输入:

     4
   /   \
  2     7
 / \     /  \
1   3 6   9
输出:

     4
    /   \
  7      2
 / \      /  \
9   6  3   1

题解:等同于剑指 Offer 27. 二叉树的镜像

方法一:

层序遍历的思想

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        queue = [root]
        while queue:
            tmp = queue.pop(0)

            tmp.left,tmp.right = tmp.right, tmp.left
            if tmp.left:   #不为空就加入队列
                queue.append(tmp.left)
            if tmp.right:
                queue.append(tmp.right)
        return root

方法二:

dfs递归

class Solution:
    def invertTree(self, root: TreeNode) -> TreeNode:
        if not root:
            return None
        root.left, root.right = root.right, root.left
        self.invertTree(root.left)   # 对子树进行递归交换
        self.invertTree(root.right)
        return root

543. 二叉树的直径

题意:

一棵二叉树的直径长度是任意两个结点路径长度中的最大值。两结点之间的路径长度是以它们之间边的数目表示。

题解:

class Solution:
    def diameterOfBinaryTree(self, root: TreeNode) -> int:
        self.res = 0
        def dfs(node):
            if not node:
                return 0
            l = dfs(node.left)      #左子树的深度
            r = dfs(node.right)     #右子树的深度
            self.res = max(l+r, self.res)   #求当前路径长度
            return max(l, r)+1

        dfs(root)
        return self.res

563. 二叉树的坡度

题意:

一个树的节点的坡度 定义为,该节点左子树的节点之和和右子树节点之和的 差的绝对值 。如果没有左子树的话,左子树的节点之和为 0;没有右子树的话也是一样。空结点的坡度是 0。

整个树 的坡度就是其所有节点的坡度之和。

题解:

class Solution:
    def findTilt(self, root: TreeNode) -> int:
        if not root:
            return 0
        self.res = 0
        def dfs(node):
            if not node:
                return 0
            left = dfs(node.left)
            right = dfs(node.right)
            self.res += abs(left-right)
            return left + right + node.val  # 每个节点往上返回其自身值和左右所有子节点的和。
        dfs(root)
        return self.res

114. 二叉树展开为链表

题意:

将一个二叉树原地展开为一个链表。展开后的单链表应该同样使用TreeNode,其中right子指针指向链表下一个节点,而左子指针始终为null,展开后的单链表应该和二叉树的先序遍历顺序相同。

题解:

要求是原地操作,先找到左子树的右子树的最深处,然后将root的右子树接到左子树的右子树的最深处,再将root的整个左子树接到右子树上,将root的左子树清空,然后继续下一个节点。

class Solution:
    def flatten(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        while root:
            if not root:
                return []
            if root.left:
                sub_left = root.left
                while sub_left.right:
                    sub_left = sub_left.right
                sub_left.right = root.right
                root.right = root.left
                root.left = None
            root = root.right  # 继续下一个节点

617、合并二叉树

题意:给定两个二叉树,将它们合并为一个新的二叉树。

输入: 
    Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
         3
        / \
       4   5
      / \   \ 
     5   4   7

class Solution:
    def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
        if not root1 or not root2:
            return root1 or root2
        merged = TreeNode(root1.val + root2.val)
        merged.left = self.mergeTrees(root1.left, root2.left)
        merged.right = self.mergeTrees(root1.right, root2.right)
        return merged

208. 实现 Trie (前缀树)

题意:

字典树。因此,看名称“字典” 就知道要实现该树,需要构造字典,然后开始构建节点,节点递归下一个节点。因此我们在初始化时构造了一个空字典。

具体的解题思路如下:
① insert 功能:

从头开始遍历给定的 word 字符,然后判断该字符是否存在当前的字典键里(初始时在自建的字典键里寻找);
如果不存在,那么建立一个键值对,键为当前字符,值为空字典;如果存在,不做任何操作;
最后,跳到该字符的键所对应的值(字典)里,然后遍历下一个字符;
因为有一些单词可能存在包含关系,因此我们要在最后一个字符对应的字典里加一个结束标志 “#”,证明这是一个单词的结束点。
②我们可以按照类似于实现 insert 功能的思想来实现 search 功能:

从头开始遍历给定的 word 字符,然后判断该字符是否存在于当前的字典键里(初始时在自建的字典键里寻找);
如果不存在,直接返回 False;如果存在,继续遍历下一字符;
当所有字符遍历完成时,我们还应该判断最后一个字符对应的字典里是否存在结束标志;
如果存在,说明的确存在该 word,返回 True;如果不存在,说明该 word 只是其他单词的一个子串,字典中并不存在该 word,返回 False。
③ startsWith 功能和 search 功能基本一样,只不过 startsWith 功能只是判断字典树中是否以 prefix 字符串为开始,而不必判断 prefix 是否为一个单词,因此与步骤②相比,它无需判断是否存在结束标志。

题解:

class Trie:
    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.d = {}

    def insert(self, word: str) -> None:
        """
        Inserts a word into the trie.
        """
        tree = self.d
        for i in word:
            if i not in tree:
                tree[i] = {}
            tree = tree[i]
        tree['#'] = '#'

    def search(self, word: str) -> bool:
        """
        Returns if the word is in the trie.
        """
        tree = self.d
        for i in word:
            if i not in tree:
                return False
            tree = tree[i]
        if '#' in tree:
            return True
        return False

    def startsWith(self, prefix: str) -> bool:
        """
        Returns if there is any word in the trie that starts with the given prefix.
        """
        tree = self.d
        for i in prefix:
            if i not in tree:
                return False
            tree = tree[i]
        return True

105. 从前序与中序遍历序列构造二叉树

题解:

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if len(inorder)==0:
            return
        root = TreeNode(preorder[0])  # 为树的节点,不是一个数值
        mid = inorder.index(preorder[0])
        root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
        root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
        return root

222. 完全二叉树的节点个数

题意:给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

        完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h 个节点。

题解:

        遍历树来统计节点的时间复杂度为O(N),N是节点个数。因为题目为完全二叉树,可以利用完全二叉树的特性来计算节点个数。假设完全二叉树的根节点为第0层,总共的层数是h,则节点个数在2**h~2**(h+1)-1之间。

class Solution:
    def countNodes(self, root: TreeNode) -> int:
        def path(root, num): 
            for s in bin(num)[3:]: # 将十进制的num转换为二进制,前两位为0b,第三位为根节点,根节点非空,一定为1
                if s=='0':  # 0即是往左走,1是往右走
                    root = root.left
                else:
                    root = root.right
                if not root:
                    return False
            return True

        if not root:
            return 0
        h = 0
        node = root
        while node.left:
            h += 1
            node = node.left
        left = 2**h
        right = 2**(h+1)-1
        while left<right:   # 二分查找最后一层的最右节点在哪
            mid = (left+right+1)//2 # 向上取整
            if path(root, mid):
                left = mid
            else:
                right = mid - 1
        return left

时间复杂度是O(logN * logN),因为首先需要O(h)的时间去得到最大层数h,然后在使用二分查找确定节点个数时,需要查找的次数是O(h),每次查找需要遍历从根节点开始的一条长度为h的路径,需要O(h)的时间,因此,查找的时间复杂度为O(h^2),因为完全二叉树满足2^h<=n <2^(h+1),因此,有O(h)=O(logn)。

662. 二叉树最大宽度

题意:给定一个二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度。这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。

每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。

题解:

        宽度优先搜索,顺序遍历每个节点,记录节点的position,对于每一个深度,第一个遇到的节点是最左边的节点,最后到达的节点是最右边的节点。

        主要想法是给每个节点一个 position 值,如果我们走向左子树,那么 position -> position * 2,如果我们走向右子树,那么 position -> positon * 2 + 1。当我们在看同一层深度的位置值 L 和 R 的时候,宽度就是 R - L + 1

class Solution:
    def widthOfBinaryTree(self, root: TreeNode) -> int:
        queue = [(root, 0, 0)]
        cur_depth = left = ans = 0
        for node, depth, pos in queue:
            if node:
                queue.append((node.left, depth+1, 2*pos))
                queue.append((node.right, depth+1, 2*pos+1))
                if cur_depth!=depth:  # 深度加1,第一个遇到的节点是最左边的节点
                    cur_depth = depth
                    left = pos
                ans = max(pos - left+1, ans) # 宽度=R-L+1
        return ans

链表

206. 反转链表

题解:

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        pre = None
        cur = head
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

92. 反转链表 II

题意:    部分翻转,即翻转从left到right位置之间的链表节点。

题解:一次遍历,头插法,在需要反转的区间内,每遍历到一个节点,让这个新节点来到反转部分的起始位置

        使用三个指针变量pre, cur, tmp;cur指向待翻转区域的第一个结点;tmp永远指向cur的下一个节点,在循环过程中,cur变化后,tmp会变化;pre永远指向待翻转区域的第一个结点的前一个节点,在循环过程中不变。

class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        node = ListNode(-1)
        node.next = head
        pre = node
        
        for i in range(left-1):
            pre = pre.next
        cur = pre.next
        for j in range(right-left):
            tmp = cur.next
            cur.next = tmp.next
            tmp.next = pre.next
            pre.next = tmp
            
        return node.next

 

25. K 个一组翻转链表

题意:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

        k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。  要求只使用常数额外空间。

题解:先定义一个翻转函数,参数是翻转的头结点,和尾节点。然后再顺序遍历链表,进行翻转。

class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        if not head:
            return None
        head_pre = ListNode(0)
        head_pre.next = head
        pre = head_pre
        while head:
            end = head
            for i in range(k):  # 判断接下来的节点数量是否小于k
                if not end:
                    return head_pre.next
                end = end.next
            newhead = self.reverse(head, end)  # 翻转链表段
            pre.next = newhead      # 将翻转后的链表拼接到原有的位置
            head.next = end

            pre = head    #指向下一个链表的开始
            head = end
        return head_pre.next

    def reverse(self, head, tail):
        pre = None 
        cur = head
        while cur!=tail:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

61. 旋转链表

题意:给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

题解:先判断k是否是链表长度的整数倍,若是,则旋转之后仍然是原链表,直接返回头结点即可。若不是,则使用快慢指针,先让快指针走个k步,然后快慢指针同时向后走。

class Solution:
    def rotateRight(self, head: ListNode, k: int) -> ListNode:
        if not head or not head.next or not k:
            return head
        tmp, count = head, 0
        while tmp:
            tmp = tmp.next
            count += 1
        k = k % count
        if k == 0:   # 如果k>n,则直接返回head
            return head
        fast = slow = head
        for i in range(k):
            fast = fast.next
        while fast.next:
            fast = fast.next
            slow = slow.next
        newhead = slow.next
        slow.next = None
        fast.next = head
        return newhead

143. 重排链表

题意:

给定一个单链表 LL0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→LnL1→Ln-1→L2→Ln-2→…

要求原地操作

题解:

1、先找到链表的中点。  2、翻转右半边的链表   3、合并

class Solution:
    def reorderList(self, head: ListNode) -> None:
        """
        Do not return anything, modify head in-place instead.
        """
        if not head:
            return None
        slow, fast = head, head
        while fast.next and fast.next.next:
            fast = fast.next.next
            slow = slow.next

        def reverse(node):
            if not node:
                return
            cur = node
            pre = None
            while cur:
                tmp = cur.next
                cur.next = pre
                pre = cur
                cur = tmp
            return pre
        
        l1 = head
        l2 = reverse(slow)

        while l1 and l2:
            tmp1 = l1.next
            tmp2 = l2.next
            l1.next = l2    # 顺序不能颠倒
            l1 = tmp1
            l2.next = l1
            l2 = tmp2

141. 环形链表

题意:检查链表中是否有环

题解:

快慢指针

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head:
            return False
        slow, fast = head, head
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if slow==fast:
                return True
        return False

142. 环形链表 II

题意:要求返回入环的链表的第一个节点

题解:

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if not head:
            return None
        flag = False
        slow, fast = head, head#.next
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if slow==fast:
                flag = True
                break
        if flag:
            slow = head  # 让slow在表头,fast此时在相遇点,两者以相同的速度移动,再次相遇的节点就是入环节点。
            while slow!=fast:
                slow = slow.next
                fast = fast.next
            return slow
        return None

变体:求环的长度

题解:就是从第一次相遇,到第二次相遇之间的前进次数。

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        if not head:
            return False
        slow, fast = head, head
        count = 0
        length = 0
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if slow==fast:
                count += 1
            if count==1:
                length += 1
            if count==2:  # 第二次相遇时
                break
        return length

382. 链表随机节点

题意:给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样

题解:

类似于从n个数中等概率地取出k个数,在这里,k = 1。

class Solution:

    def __init__(self, head: ListNode):
        """
        @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node.
        """
        self.head = head

    def getRandom(self) -> int:
        """
        Returns a random node's value.
        """
        count = 0
        cur = self.head
        while cur:
            count += 1
            # 等概率取样,每个样本被取到的概率都是1/count
            # 例如,count为1时,概率为1,即res的初值。count为2时,有1/2的几率选中2,如选中,2即为res,替换之前的res,以此类推。
            if random.randint(1, count)==count:
                res = cur.val
            cur = cur.next
        return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值