跳到主要内容

31 篇博文 含有标签「二叉树」

查看所有标签

· 阅读需 2 分钟

1、题干

给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。

提醒一下, 二叉搜索树 满足下列约束条件:

  • 节点的左子树仅包含键 小于 节点键的节点。
  • 节点的右子树仅包含键 大于 节点键的节点。
  • 左右子树也必须是二叉搜索树。

 

示例 1:

输入:[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]

示例 2:

输入:root = [0,null,1]
输出:[1,null,1]

 

提示:

  • 树中的节点数在 [1, 100] 范围内。
  • 0 <= Node.val <= 100
  • 树中的所有值均 不重复 。

 

注意:该题目与 538: https://leetcode-cn.com/problems/convert-bst-to-greater-tree/  相同

2、思路

根据题意使用DFS模拟,需要注意:

  • 对左子节点求和,需要将父节点求和结果、右子树节点之和、自身数值3者累加
  • 对右子节点求和,需要将右边祖先节点求和结果、右子树节点之和、自身数值3者累加

3、代码

function bstToGst(root: TreeNode | null): TreeNode | null {
return dfs(root), root;
}

function dfs(root?: TreeNode, pSum = 0): number {
if (!root) return 0;

const val = root.val;
const rSum = dfs(root.right, pSum);
root.val = val + pSum + rSum;
const lSum = dfs(root.left, root.val);

return val + rSum + lSum;
}

· 阅读需 2 分钟

1、题干

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

 

示例 1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

 

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

2、思路

后序遍历数组最后一个元素是根节点,中序遍历数组中根节点前、后两边分别是左子树与右子树,根据这两个性质递归即可,时间复杂度最优 O(nlogn)O(n\log{n}),最差 O(n2)O(n^2)

3、代码

function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
if (!inorder.length) return null;

const rv = postorder.at(-1);
const root = new TreeNode(rv);

const c = inorder.indexOf(rv);

const inLeft = inorder.slice(0, c);
const inRight = inorder.slice(c + 1);

const postLeft = postorder.slice(0, c);
const postRight = postorder.slice(c, postorder.length - 1);

root.left = buildTree(inLeft, postLeft);
root.right = buildTree(inRight, postRight);

return root;
}

· 阅读需 2 分钟

1、题干

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

 

示例 1:

输入:p = [1,2,3], q = [1,2,3]
输出:true

示例 2:

输入:p = [1,2], q = [1,null,2]
输出:false

示例 3:

输入:p = [1,2,1], q = [1,1,2]
输出:false

 

提示:

  • 两棵树上的节点数目都在范围 [0, 100]
  • -104 <= Node.val <= 104

2、思路

DFS 判断两棵树当前节点值是否相等,左右子树是否相等

3、代码

function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean {
if (p && q) return p.val === q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);

return !p && !q;
};

· 阅读需 2 分钟

1、题干

给定一个二叉树:

struct Node {
int val;
Node *left;
Node *right;
Node *next;
}

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL

初始状态下,所有 next 指针都被设置为 NULL

 

示例 1:

输入:root = [1,2,3,4,5,null,7]
输出:[1,#,2,3,#,4,5,7,#]
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化输出按层序遍历顺序(由 next 指针连接),'#' 表示每层的末尾。

示例 2:

输入:root = []
输出:[]

 

提示:

  • 树中的节点数在范围 [0, 6000]
  • -100 <= Node.val <= 100

进阶:

  • 你只能使用常量级额外空间。
  • 使用递归解题也符合要求,本题中递归程序的隐式栈空间不计入额外空间复杂度。

    2、思路

    BFS模拟,每一层的节点用一个队列存储,队列中节点的 next 指向下一个节点即可。

    这题没有 Rust 编码环境。。

    3、代码

    function connect(root: Node | null): Node | null {
    let queue: Node[] = root ? [root] : [];

    while (queue.length) {
    const nextQueue = [];
    for (let i = 0; i < queue.length; i++) {
    queue[i].next = queue[i + 1];
    if (queue[i].left) nextQueue.push(queue[i].left);
    if (queue[i].right) nextQueue.push(queue[i].right);
    }
    queue = nextQueue;
    }

    return root;
    };

    · 阅读需 2 分钟

    1、题干

    给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。

    (如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)

     

    示例 1:

    输入:root = [8,3,10,1,6,null,14,null,null,4,7,13]
    输出:7
    解释:
    我们有大量的节点与其祖先的差值,其中一些如下:
    |8 - 3| = 5
    |3 - 7| = 4
    |8 - 1| = 7
    |10 - 13| = 3
    在所有可能的差值中,最大值 7 由 |8 - 1| = 7 得出。

    示例 2:

    输入:root = [1,null,2,null,0,3]
    输出:3

     

    提示:

    • 树中的节点数在 2 到 5000 之间。
    • 0 <= Node.val <= 105

    2、思路

    递归求子孙节点的最小值与最大值,分别求最大值最小值与当前节点值的差的绝对值,比较得出最大差值。

    3、代码

    function maxAncestorDiff(root: TreeNode | null): number {
    let ans = -Infinity;

    function dfs(node: TreeNode) {
    if (!node) return [Infinity, -Infinity];
    if (!node.left && !node.right) return [node.val, node.val];

    const r1 = dfs(node.left);
    const r2 = dfs(node.right);

    const min = Math.min(r1[0], r2[0]);
    const max = Math.max(r1[1], r2[1]);

    ans = Math.max(ans, Math.abs(node.val - min), Math.abs(node.val - max));

    return [Math.min(min, node.val), Math.max(max, node.val)];
    }

    return dfs(root), ans;
    };

    4、复杂度

    • 时间复杂度:O(n)O(n)
    • 空间复杂度:O(n)O(n)

    5、执行结果

    image.png

    · 阅读需 3 分钟

    1、题干

    给你一棵 完整二叉树 的根,这棵树有以下特征:

    • 叶子节点 要么值为 0 要么值为 1 ,其中 0 表示 False ,1 表示 True 。
    • 非叶子节点 要么值为 2 要么值为 3 ,其中 2 表示逻辑或 OR3 表示逻辑与 AND 。

    计算 一个节点的值方式如下:

    • 如果节点是个叶子节点,那么节点的  为它本身,即 True 或者 False 。
    • 否则,计算 两个孩子的节点值,然后将该节点的运算符对两个孩子值进行 运算 。

    返回根节点 root 的布尔运算值。

    完整二叉树 是每个节点有 0 个或者 2 个孩子的二叉树。

    叶子节点 是没有孩子的节点。

     

    示例 1:

    输入:root = [2,1,3,null,null,0,1]
    输出:true
    解释:上图展示了计算过程。
    AND 与运算节点的值为 False AND True = False 。
    OR 运算节点的值为 True OR False = True 。
    根节点的值为 True ,所以我们返回 true 。

    示例 2:

    输入:root = [0]
    输出:false
    解释:根节点是叶子节点,且值为 false,所以我们返回 false 。

     

    提示:

    • 树中节点数目在 [1, 1000] 之间。
    • 0 <= Node.val <= 3
    • 每个节点的孩子数为 0 或 2 。
    • 叶子节点的值为 0 或 1 。
    • 非叶子节点的值为 2 或 3

    2、思路

    使用 DFS 模拟

    3、代码

    function evaluateTree(root: TreeNode | null): boolean {
    if (!root || (!root.left && !root.right)) return !!root?.val;

    const l = evaluateTree(root.left);
    const r = evaluateTree(root.right);

    return root.val === 2 ? (l || r) : (l && r);
    };

    4、复杂度

    • 时间复杂度:O(n)O(n)
    • 空间复杂度:O(n)O(n)

    5、执行结果

    image.png

    · 阅读需 4 分钟

    1、题干

    有两位极客玩家参与了一场「二叉树着色」的游戏。游戏中,给出二叉树的根节点 root,树上总共有 n 个节点,且 n 为奇数,其中每个节点上的值从 1 到 n 各不相同。

    最开始时:

    • 「一号」玩家从 [1, n] 中取一个值 x1 <= x <= n);
    • 「二号」玩家也从 [1, n] 中取一个值 y1 <= y <= n)且 y != x

    「一号」玩家给值为 x 的节点染上红色,而「二号」玩家给值为 y 的节点染上蓝色。

    之后两位玩家轮流进行操作,「一号」玩家先手。每一回合,玩家选择一个被他染过色的节点,将所选节点一个 未着色 的邻节点(即左右子节点、或父节点)进行染色(「一号」玩家染红色,「二号」玩家染蓝色)。

    如果(且仅在此种情况下)当前玩家无法找到这样的节点来染色时,其回合就会被跳过。

    若两个玩家都没有可以染色的节点时,游戏结束。着色节点最多的那位玩家获得胜利 ✌️。

    现在,假设你是「二号」玩家,根据所给出的输入,假如存在一个 y 值可以确保你赢得这场游戏,则返回 true ;若无法获胜,就请返回 false

     

    示例 1 :

    输入:root = [1,2,3,4,5,6,7,8,9,10,11], n = 11, x = 3
    输出:true
    解释:第二个玩家可以选择值为 2 的节点。

    示例 2 :

    输入:root = [1,2,3], n = 3, x = 1
    输出:false

     

    提示:

    • 树中节点数目为 n
    • 1 <= x <= n <= 100
    • n 是奇数
    • 1 <= Node.val <= n
    • 树中所有值 互不相同

    还好不是博弈论

    2、思路

    DFS 求出整棵树的节点总数 c 和节点 x 的左右子树的节点总数 clcr,继而可以求得 x 子树之外的节点总数 c - cl - cr - 1,3者中的最大值如果超过节点总数的一半 c/2,则必定存在必胜值 y

    3、代码

    function btreeGameWinningMove(root: TreeNode | null, n: number, x: number): boolean {
    let cl = 0, cr = 0;

    function dfs(node: TreeNode) {
    if (!node) return 0;

    const c1 = dfs(node.left);
    const c2 = dfs(node.right);

    if (node.val === x) cl = c1, cr = c2;

    return c1 + c2 + 1;
    }

    const c = dfs(root);

    return Math.max(c - cl - cr - 1, cl, cr) > c / 2;
    };

    4、复杂度

    • 时间复杂度:O(n)O(n)
    • 空间复杂度:O(1)O(1)

    5、执行结果

    image.png

    · 阅读需 4 分钟

    1、题干

    设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

    请实现 KthLargest 类:

    • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
    • int add(int val)val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

     

    示例:

    输入:
    ["KthLargest", "add", "add", "add", "add", "add"]
    [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
    输出:
    [null, 4, 5, 5, 8, 8]

    解释:
    KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
    kthLargest.add(3); // return 4
    kthLargest.add(5); // return 5
    kthLargest.add(10); // return 5
    kthLargest.add(9); // return 8
    kthLargest.add(4); // return 8

     

    提示:

    • 1 <= k <= 104
    • 0 <= nums.length <= 104
    • -104 <= nums[i] <= 104
    • -104 <= val <= 104
    • 最多调用 add 方法 104
    • 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

     

    注意:本题与主站 703 题相同: https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/

    2、解法1-优先队列

    使用优先队列解题,初始化时把所有元素加入到最小优先队列pq中,添加元素时直接把元素加入pq,若pq元素个数小于k则直接返回-1,若pq元素个数超过k则逐一移除最后返回队头元素。


    3、代码

    var KthLargest = function (k, nums) {
    this.k = k;
    this.pq = new MinPriorityQueue({ compare: (a, b) => a - b });
    for (const n of nums) this.pq.enqueue(n);
    };

    KthLargest.prototype.add = function (val) {
    this.pq.enqueue(val);
    if (this.pq.size() < this.k) return -1;
    while (this.pq.size() > this.k) this.pq.dequeue();
    return this.pq.front();
    };

    4、复杂度

    • 时间复杂度:O(nlogn)O(nlogn)
    • 空间复杂度:O(n)O(n)

    5、执行结果

    执行用时: 132 ms 内存消耗: 46.6 MB


    6、解法2-计数排序

    对数据流使用计数排序,首次确定第 k 大元素 kIdx 需要遍历所有元素;此后添加数据需要再确定第 k 大元素时,如果添加的数据比 kIdx 小则直接返回 kIdx ;如果添加的数据比 kIdx 小则从下标 kIdx 开始在容器 list 找到更大且元素值不为0的下标返回即可。


    这个题目中使用该算法坑略多,只作参考,不建议使用。


    7、复杂度

    • 时间复杂度:O(n)O(n)
    • 空间复杂度:O(n)O(n)

    8、代码

    var KthLargest = function (k, nums) {
    this.k = k;
    this.size = nums.length;
    this.kIdx = -1;
    this.list = new Array(2e4 + 1).fill(0);
    for (const n of nums) this.list[n + 1e4]++;
    };

    KthLargest.prototype.add = function (val) {
    (val += 1e4), this.list[val]++, this.size++;
    if (this.size < this.k) return -1;

    if (this.kIdx < 0) {
    for (let i = this.list.length - 1, k = this.k; i >= 0; i--) {
    if (k < this.list[i]) this.list[i] = k;
    if ((k -= this.list[i]) <= 0) {
    this.kIdx = i;
    break;
    }
    }
    return this.kIdx - 1e4;
    }

    if (this.size > this.k && val >= this.kIdx) {
    this.list[this.kIdx]--;
    for (let i = this.kIdx; i < this.list.length; i++) {
    if (this.list[i]) {
    this.kIdx = i;
    break;
    }
    }
    }

    return this.kIdx - 1e4;
    };

    9、执行结果

    1.png

    · 阅读需 4 分钟

    1、题干

    设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

    请实现 KthLargest 类:

    • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
    • int add(int val)val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

     

    示例:

    输入:
    ["KthLargest", "add", "add", "add", "add", "add"]
    [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]]
    输出:
    [null, 4, 5, 5, 8, 8]

    解释:
    KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
    kthLargest.add(3); // return 4
    kthLargest.add(5); // return 5
    kthLargest.add(10); // return 5
    kthLargest.add(9); // return 8
    kthLargest.add(4); // return 8

     

    提示:

    • 1 <= k <= 104
    • 0 <= nums.length <= 104
    • -104 <= nums[i] <= 104
    • -104 <= val <= 104
    • 最多调用 add 方法 104
    • 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

     

    注意:本题与主站 703 题相同: https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/

    2、解法1-优先队列

    使用优先队列解题,初始化时把所有元素加入到最小优先队列pq中,添加元素时直接把元素加入pq,若pq元素个数小于k则直接返回-1,若pq元素个数超过k则逐一移除最后返回队头元素。


    3、代码

    var KthLargest = function (k, nums) {
    this.k = k;
    this.pq = new MinPriorityQueue({ compare: (a, b) => a - b });
    for (const n of nums) this.pq.enqueue(n);
    };

    KthLargest.prototype.add = function (val) {
    this.pq.enqueue(val);
    if (this.pq.size() < this.k) return -1;
    while (this.pq.size() > this.k) this.pq.dequeue();
    return this.pq.front();
    };

    4、复杂度

    • 时间复杂度:O(nlogn)O(nlogn)
    • 空间复杂度:O(n)O(n)

    5、执行结果

    执行用时: 132 ms 内存消耗: 46.6 MB


    6、解法2-计数排序

    对数据流使用计数排序,首次确定第 k 大元素 kIdx 需要遍历所有元素;此后添加数据需要再确定第 k 大元素时,如果添加的数据比 kIdx 小则直接返回 kIdx ;如果添加的数据比 kIdx 小则从下标 kIdx 开始在容器 list 找到更大且元素值不为0的下标返回即可。


    这个题目中使用该算法坑略多,只作参考,不建议使用。


    7、复杂度

    • 时间复杂度:O(n)O(n)
    • 空间复杂度:O(n)O(n)

    8、代码

    var KthLargest = function (k, nums) {
    this.k = k;
    this.size = nums.length;
    this.kIdx = -1;
    this.list = new Array(2e4 + 1).fill(0);
    for (const n of nums) this.list[n + 1e4]++;
    };

    KthLargest.prototype.add = function (val) {
    (val += 1e4), this.list[val]++, this.size++;
    if (this.size < this.k) return -1;

    if (this.kIdx < 0) {
    for (let i = this.list.length - 1, k = this.k; i >= 0; i--) {
    if (k < this.list[i]) this.list[i] = k;
    if ((k -= this.list[i]) <= 0) {
    this.kIdx = i;
    break;
    }
    }
    return this.kIdx - 1e4;
    }

    if (this.size > this.k && val >= this.kIdx) {
    this.list[this.kIdx]--;
    for (let i = this.kIdx; i < this.list.length; i++) {
    if (this.list[i]) {
    this.kIdx = i;
    break;
    }
    }
    }

    return this.kIdx - 1e4;
    };

    9、执行结果

    1.png

    · 阅读需 3 分钟

    1、题干

    给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。

     

    示例 1:

    输入: root = [8,6,10,5,7,9,11], k = 12
    输出: true
    解释: 节点 5 和节点 7 之和等于 12

    示例 2:

    输入: root = [8,6,10,5,7,9,11], k = 22
    输出: false
    解释: 不存在两个节点值之和为 22 的节点

     

    提示:

    • 二叉树的节点个数的范围是  [1, 104].
    • -104 <= Node.val <= 104
    • root 为二叉搜索树
    • -105 <= k <= 105

     

    注意:本题与主站 653 题相同: https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/

    2、解法1

    DFS遍历+哈希查找:DFS遍历所有节点node,在哈希表set中查找是否存在k - node.val,若存在则返回true否则继续遍历直至结束则返回false

    3、代码

    var findTarget = function (root, k) {
    const set = new Set();
    function dfs(node) {
    if (!node) return false;
    if (set.has(k - node.val)) return true;
    set.add(node.val);
    return dfs(node.left) || dfs(node.right);
    }
    return dfs(root);
    };

    4、复杂度

    时间复杂度:O(n)O(n)

    空间复杂度:O(n)O(n)

    5、执行结果

    1.png


    6、解法2

    DFS遍历+DFS二分查找:DFS遍历所有节点node,利用BST特性进行DFS二分查找是否存在k - node.val,若存在则返回true否则继续遍历直至结束则返回false

    这里二分搜索的时间复杂度受到二叉树平衡性的影响,最坏情况下二叉树可能退化成链表,导致搜索时间复杂度为O(n)O(n),总体时间复杂度为O(n2)O(n^2)

    7、代码

    var findTarget = function (root, k) {
    function find(node, num) {
    if (!node) return false;
    if (num === node.val) return true;
    return num > node.val ? find(node.right, num) : find(node.left, num);
    }

    function findSum(node, num) {
    if (!node) return false;
    return (num !== 2 * node.val && find(root, num - node.val)) || findSum(node.right, num) || findSum(node.left, num);
    }

    return findSum(root, k);
    };

    8、复杂度

    时间复杂度:平衡二叉搜索树的情况下为O(nlogn)O(nlogn) ,非平衡二叉搜索树的情况下超过O(nlogn)O(nlogn),最差为O(n2)O(n^2)

    空间复杂度:O(1)O(1)

    9、执行结果

    1.png