跳到主要内容

41 篇博文 含有标签「深度优先搜索」

查看所有标签

· 阅读需 4 分钟

1、题干

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于已构建的神奇字典中。

实现 MagicDictionary 类:

  • MagicDictionary() 初始化对象
  • void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
  • bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false

 

示例:

输入
inputs = ["MagicDictionary", "buildDict", "search", "search", "search", "search"]
inputs = [[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
输出
[null, null, false, true, false, false]

解释
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False

 

提示:

  • 1 <= dictionary.length <= 100
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 仅由小写英文字母组成
  • dictionary 中的所有字符串 互不相同
  • 1 <= searchWord.length <= 100
  • searchWord 仅由小写英文字母组成
  • buildDict 仅在 search 之前调用一次
  • 最多调用 100search

 

注意:本题与主站 676 题相同: https://leetcode-cn.com/problems/implement-magic-dictionary/

2、解法1-字符串比较

数组存储字典,查找时直接进行字符串比较,如果不匹配的字符数量为1返回true,否则继续查找直至结束返回false

3、代码

var MagicDictionary = function () {
this.dict = [];
};

MagicDictionary.prototype.buildDict = function (dictionary) {
this.dict = dictionary;
};

MagicDictionary.prototype.search = function (searchWord) {
for (let i = 0; i < this.dict.length; i++) {
if (searchWord.length !== this.dict[i].length) continue;

let diff = 0;
for (let j = 0; j < searchWord.length; j++) {
if (diff > 1) break;
if (searchWord[j] !== this.dict[i][j]) diff++;
}

if (diff === 1) return true;
}

return false;
};

4、执行结果

image.png

5、解法2-哈希表

哈希表存储字典,查找时对源字符串的每一位做替换,若替换后哈希表存在该字符串则返回true,否则继续查找直至结束返回false

6、代码

var MagicDictionary = function () {
this.dict = new Set();
};

MagicDictionary.prototype.buildDict = function (dictionary) {
for (const w of dictionary) this.dict.add(w);
};

MagicDictionary.prototype.search = function (searchWord) {
for (let i = 0; i < searchWord.length; i++) {
const pre = searchWord.slice(0, i), post = searchWord.slice(i + 1);
for (let j = 97; j < 123; j++) {
const c = String.fromCharCode(j);
if (c === searchWord[i]) continue;
if (this.dict.has(pre + c + post)) return true;
}
}

return false;
};

7、执行结果

  • 执行用时: 860 ms
  • 内存消耗: 48.3 MB

· 阅读需 5 分钟

1、题干

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 aibi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。

 

示例 1:

输入: edges = [[1,2],[1,3],[2,3]]
输出: [2,3]

示例 2:

输入: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
输出: [1,4]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ai < bi <= edges.length
  • ai != bi
  • edges 中无重复元素
  • 给定的图是连通的 

 

注意:本题与主站 684 题相同: https://leetcode-cn.com/problems/redundant-connection/

并查集还没学会,只能先用树和图的思想解题

image.png


2、解题思路

在树的概念中,叶子结点是指没有子结点的结点,叶子节点只跟父节点存在关联,即只有1条边。如果某个节点下只有叶子节点,把该节点下的所有叶子节点都移除掉之后它自己就变成了叶子节点。如果循环往复地移除树的所有叶子节点,最终这棵树的节点数会变为0

如果按题目意思在树中添加一条边,这棵树会变成一个有环图,而在移除所有叶子节点的操作中,环上的所有节点都不会被移除,本题就是利用这个结论解题。


总体思路 :循环往复地移除树上所有的叶子节点,直到不存在叶子节点,最后遍历找到环中最后出现的边。


大体步骤

  • 先用矩阵建立节点关联关系,记为 matrix
    • matrix 是一维数组,下标表示节点值
    • matrix 中的元素是哈希集合,集合中包含与当前节点关联的所有节点
  • 遍历 matrix 并把当前节点加入队列 queue 中,进行叶子节点的判断与移除
    • 若该节点是叶子节点,则双向移除该节点与父节点的关联关系,同时把父节点加入队列 queue 中进行下一轮处理
    • 若该节点不是叶子节点则跳过
  • 最后遍历二维数组 edges 并在 matrix 中查找出处于环上且序号最大的边

3、代码

var findRedundantConnection = function (edges) {
const n = edges.length, matrix = new Array(n + 1).fill(0);

for (const [i, j] of edges) {
!matrix[i] ? (matrix[i] = new Set([j])) : matrix[i].add(j);
!matrix[j] ? (matrix[j] = new Set([i])) : matrix[j].add(i);
}

for (let k = 1; k <= n; k++) {
const queue = [k];

for (const i of queue) {
if (matrix[i].size !== 1) continue;

const j = matrix[i].keys().next().value;
matrix[i].delete(j), matrix[j].delete(i);
queue.push(j);
}
}

return edges.reduce((a, [i, j]) => (matrix[i].size > 1 && matrix[j].size > 1 ? [i, j] : a), null);
};

4、复杂度

  • 时间复杂度:O(nlogn)O(nlogn),复杂度比较高的代码在双层循环剪叶子那一段,外层循环 nn 次,内层循环最多为树的高度 lognlogn
  • 空间复杂度:O(n)O(n)

5、执行结果

  • 执行用时: 68 ms
  • 内存消耗: 42.1 MB

image.png

· 阅读需 5 分钟

1、题干

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 aibi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。

 

示例 1:

输入: edges = [[1,2],[1,3],[2,3]]
输出: [2,3]

示例 2:

输入: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
输出: [1,4]

 

提示:

  • n == edges.length
  • 3 <= n <= 1000
  • edges[i].length == 2
  • 1 <= ai < bi <= edges.length
  • ai != bi
  • edges 中无重复元素
  • 给定的图是连通的 

 

注意:本题与主站 684 题相同: https://leetcode-cn.com/problems/redundant-connection/

并查集还没学会,只能先用树和图的思想解题

image.png


2、解题思路

在树的概念中,叶子结点是指没有子结点的结点,叶子节点只跟父节点存在关联,即只有1条边。如果某个节点下只有叶子节点,把该节点下的所有叶子节点都移除掉之后它自己就变成了叶子节点。如果循环往复地移除树的所有叶子节点,最终这棵树的节点数会变为0

如果按题目意思在树中添加一条边,这棵树会变成一个有环图,而在移除所有叶子节点的操作中,环上的所有节点都不会被移除,本题就是利用这个结论解题。


总体思路 :循环往复地移除树上所有的叶子节点,直到不存在叶子节点,最后遍历找到环中最后出现的边。


大体步骤

  • 先用矩阵建立节点关联关系,记为 matrix
    • matrix 是一维数组,下标表示节点值
    • matrix 中的元素是哈希集合,集合中包含与当前节点关联的所有节点
  • 遍历 matrix 并把当前节点加入队列 queue 中,进行叶子节点的判断与移除
    • 若该节点是叶子节点,则双向移除该节点与父节点的关联关系,同时把父节点加入队列 queue 中进行下一轮处理
    • 若该节点不是叶子节点则跳过
  • 最后遍历二维数组 edges 并在 matrix 中查找出处于环上且序号最大的边

3、代码

var findRedundantConnection = function (edges) {
const n = edges.length, matrix = new Array(n + 1).fill(0);

for (const [i, j] of edges) {
!matrix[i] ? (matrix[i] = new Set([j])) : matrix[i].add(j);
!matrix[j] ? (matrix[j] = new Set([i])) : matrix[j].add(i);
}

for (let k = 1; k <= n; k++) {
const queue = [k];

for (const i of queue) {
if (matrix[i].size !== 1) continue;

const j = matrix[i].keys().next().value;
matrix[i].delete(j), matrix[j].delete(i);
queue.push(j);
}
}

return edges.reduce((a, [i, j]) => (matrix[i].size > 1 && matrix[j].size > 1 ? [i, j] : a), null);
};

4、复杂度

  • 时间复杂度:O(nlogn)O(nlogn),复杂度比较高的代码在双层循环剪叶子那一段,外层循环 nn 次,内层循环最多为树的高度 lognlogn
  • 空间复杂度:O(n)O(n)

5、执行结果

  • 执行用时: 68 ms
  • 内存消耗: 42.1 MB

image.png

· 阅读需 7 分钟

1、题干

在一个 106 x 106 的网格中,每个网格上方格的坐标为 (x, y)

现在从源方格 source = [sx, sy] 开始出发,意图赶往目标方格 target = [tx, ty] 。数组 blocked 是封锁的方格列表,其中每个 blocked[i] = [xi, yi] 表示坐标为 (xi, yi) 的方格是禁止通行的。

每次移动,都可以走到网格中在四个方向上相邻的方格,只要该方格 在给出的封锁列表 blocked 上。同时,不允许走出网格。

只有在可以通过一系列的移动从源方格 source 到达目标方格 target 时才返回 true。否则,返回 false

 

示例 1:

输入:blocked = [[0,1],[1,0]], source = [0,0], target = [0,2]
输出:false
解释:
从源方格无法到达目标方格,因为我们无法在网格中移动。
无法向北或者向东移动是因为方格禁止通行。
无法向南或者向西移动是因为不能走出网格。

示例 2:

输入:blocked = [], source = [0,0], target = [999999,999999]
输出:true
解释:
因为没有方格被封锁,所以一定可以到达目标方格。

 

提示:

  • 0 <= blocked.length <= 200
  • blocked[i].length == 2
  • 0 <= xi, yi < 106
  • source.length == target.length == 2
  • 0 <= sx, sy, tx, ty < 106
  • source != target
  • 题目数据保证 sourcetarget 不在封锁列表内

2、解题思路

迷宫的封锁列表blocked会形成围栏,假设从起点和终点分别进行深度遍历,遍历过程中设定步数最大值steps为围栏最大面积,若2条路径行进的步数都超过最大步数或者遍历过程中起点和终点都能访问到则表示起止点连通,则表示起止点连通,反之不连通。


注意点

  • 最开始认为围栏的最大面积是(n2)2(\dfrac{n}{2}) ^ 2,是一块正方形区域。实际官解给出的是n(n1)2\dfrac{n(n-1)}{2},是一块等腰直角三角形区域,其中nblocked数组长度。具体实现时没看官解,限制的是n22\dfrac{n^2}{2},实际上围栏最大面积略偏高不影响结果,因为在不被围的情况下可以走的步数肯定远超这个值。这个最大面积的推导和精确计算可以说有点复杂了,答题时被用例28教育了一番。
  • 遍历过程中如果起止两个点都能访问到则表示起止点连通(用例[[0,3],[1,0],[1,1],[1,2],[1,3]] [0,0] [0,2]
  • 走过的坐标使用哈希表记录,以免重复遍历导致死循环
  • JS若使用递归实现在执行用例28时会出现栈溢出(本地溢出,力扣判题程序未溢出,后面补充了递归DFS代码)

3、代码-非递归

var isEscapePossible = function (blocked, source, target) {
const hash = (x, y) => 1123 * x + y;
const maxSteps = blocked.length ** 2 / 2, dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
const blockSet = blocked.reduce((a, c) => a.add(hash(c[0], c[1])), new Set());

function move(s, t, steps, visited) {
const stack = [s];
while (stack.length) {
const [x, y] = stack.pop();
if (x < 0 || x >= 1e6 || y < 0 || y >= 1e6 || blockSet.has(hash(x, y)) || visited.has(hash(x, y))) continue;
if (--steps <= 0 || (x === t[0] && y === t[1])) return true;
visited.add(hash(x, y));
for (const d of dirs) stack.push([x + d[0], y + d[1]]);
}
return false;
}

return move(source, target, maxSteps, new Set()) && move(target, source, maxSteps, new Set());
};

4、执行结果

执行用时: 156 ms 内存消耗: 58.8 MB


5、代码-递归

var isEscapePossible = function (blocked, source, target) {
const hash = (x, y) => 1123 * x + y;
const maxSteps = blocked.length ** 2 / 2, dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
const blockSet = blocked.reduce((a, c) => a.add(hash(c[0], c[1])), new Set());

function dfs(x, y, steps, visited, t) {
if (x < 0 || x >= 1e6 || y < 0 || y >= 1e6 || blockSet.has(hash(x, y)) || visited.has(hash(x, y))) return false;
if (--steps <= 0 || (x === t[0] && y === t[1])) return true;
visited.add(hash(x, y));
return dirs.reduce((a, c) => a || dfs(x + c[0], y + c[1], steps, visited, t), false);
}

return dfs(...source, maxSteps, new Set(), target) && dfs(...target, maxSteps, new Set(), source);
};

6、执行结果

执行用时: 120 ms 内存消耗: 47.8 MB

1.png


7、优化点

  • 问题:最开始哈希表保存的坐标是字符串拼接而来,但是字符串消耗空间更多、操作耗时更多,所以执行结果的时长和内存偏高
  • 优化:不用字符串做key,使用质数乘法把坐标转成唯一数字,使用该唯一数字做key
  • 结果:优化后两种代码执行结果用时减半(240ms 到 120ms),内存消耗也大幅减少(57.3 MB 到 47.8 MB),递归DFS代码执行结果破双百

字符串做key比较容易理解和实现且不会冲突,但消耗时间空间较多;而数字做key的好处在于消耗时间空间较少,但麻烦点在于实现时要注意避免key冲突的问题。

· 阅读需 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

· 阅读需 3 分钟

1、题干

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

 

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

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

 

示例 1:

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

示例 3:

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

示例 4:

输入:root = [3,2,4,1]
输出:[7,9,4,10]

 

提示:

  • 树中的节点数介于 0 和 104 之间。
  • 每个节点的值介于 -104 和 104 之间。
  • 树中的所有值 互不相同
  • 给定的树为二叉搜索树。

 

注意:

2、解题思路

按中序相反的顺序(右子节点、父节点、左子节点)深度遍历所有节点,所有节点将会从大到小依次被访问,遍历过程中利用前缀和思想叠加节点之和,将当前节点值修改为前缀和加自身值presum + node.val即可。

如果不好理解,可以使用中序遍历所有节点并将节点依次存入数组,这样数组中的节点是按照值的大小升序排列,然后再倒序遍历数组并累加节点值、修改节点值即可。


3、代码

var convertBST = function (root) {
let presum = 0;
function dfs(node) {
if (!node) return;
dfs(node.right);
node.val = (presum += node.val);
dfs(node.left, node.val);
}
return dfs(root), root;
};

4、复杂度

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

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

5、执行结果

1.png

· 阅读需 3 分钟

1、题干

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

 

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

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

 

示例 1:

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

示例 3:

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

示例 4:

输入:root = [3,2,4,1]
输出:[7,9,4,10]

 

提示:

  • 树中的节点数介于 0 和 104 之间。
  • 每个节点的值介于 -104 和 104 之间。
  • 树中的所有值 互不相同
  • 给定的树为二叉搜索树。

 

注意:

2、解题思路

按中序相反的顺序(右子节点、父节点、左子节点)深度遍历所有节点,所有节点将会从大到小依次被访问,遍历过程中利用前缀和思想叠加节点之和,将当前节点值修改为前缀和加自身值presum + node.val即可。

如果不好理解,可以使用中序遍历所有节点并将节点依次存入数组,这样数组中的节点是按照值的大小升序排列,然后再倒序遍历数组并累加节点值、修改节点值即可。


3、代码

var convertBST = function (root) {
let presum = 0;
function dfs(node) {
if (!node) return;
dfs(node.right);
node.val = (presum += node.val);
dfs(node.left, node.val);
}
return dfs(root), root;
};

4、复杂度

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

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

5、执行结果

1.png

· 阅读需 2 分钟

1、题干

给定一棵二叉搜索树和其中的一个节点 p ,找到该节点在树中的中序后继。如果节点没有中序后继,请返回 null

节点 p 的后继是值比 p.val 大的节点中键值最小的节点,即按中序遍历的顺序节点 p 的下一个节点。

 

示例 1:

输入:root = [2,1,3], p = 1
输出:2
解释:这里 1 的中序后继是 2。请注意 p 和返回值都应是 TreeNode 类型。

示例 2:

输入:root = [5,3,6,2,4,null,null,1], p = 6
输出:null
解释:因为给出的节点没有中序后继,所以答案就返回 null 了。

 

提示:

  • 树中节点的数目在范围 [1, 104] 内。
  • -105 <= Node.val <= 105
  • 树中各节点的值均保证唯一。

 

注意:本题与主站 285 题相同: https://leetcode-cn.com/problems/inorder-successor-in-bst/

2、解题思路

中序遍历记录前一节点pre,若前一节点与p引用相同,则当前节点node就是p的中序后继,记为res即最终返回结果。

3、代码

var inorderSuccessor = function (root, p) {
let pre, res = null;
function dfs(node) {
if (!node) return;
dfs(node.left);
if (pre === p) res = node;
pre = node;
dfs(node.right);
}
return dfs(root), res;
};

4、执行结果

1.png

· 阅读需 2 分钟

1、题干

给定一棵二叉搜索树和其中的一个节点 p ,找到该节点在树中的中序后继。如果节点没有中序后继,请返回 null

节点 p 的后继是值比 p.val 大的节点中键值最小的节点,即按中序遍历的顺序节点 p 的下一个节点。

 

示例 1:

输入:root = [2,1,3], p = 1
输出:2
解释:这里 1 的中序后继是 2。请注意 p 和返回值都应是 TreeNode 类型。

示例 2:

输入:root = [5,3,6,2,4,null,null,1], p = 6
输出:null
解释:因为给出的节点没有中序后继,所以答案就返回 null 了。

 

提示:

  • 树中节点的数目在范围 [1, 104] 内。
  • -105 <= Node.val <= 105
  • 树中各节点的值均保证唯一。

 

注意:本题与主站 285 题相同: https://leetcode-cn.com/problems/inorder-successor-in-bst/

2、解题思路

中序遍历记录前一节点pre,若前一节点与p引用相同,则当前节点node就是p的中序后继,记为res即最终返回结果。

3、代码

var inorderSuccessor = function (root, p) {
let pre, res = null;
function dfs(node) {
if (!node) return;
dfs(node.left);
if (pre === p) res = node;
pre = node;
dfs(node.right);
}
return dfs(root), res;
};

4、执行结果

1.png

· 阅读需 3 分钟

1、题干

给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

 

示例 1:

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

示例 2:

输入:root = [5,1,7]
输出:[1,null,5,null,7]

 

提示:

  • 树中节点数的取值范围是 [1, 100]
  • 0 <= Node.val <= 1000

 

注意:本题与主站 897 题相同: https://leetcode-cn.com/problems/increasing-order-search-tree/

2、解法1-DFS+改变指针

深度遍历所有节点,在递归左子节点后构造新的二叉树,newRoot用于记录新树的根节点,若newRoot为空则赋值为当前节点,lastNode用于记录新树的最后一个节点,若lastNode有值则将其右子节点置为当前节点,然后将lastNode置为当前节点,当前节点的左子节点置为空,最后返回newRoot

3、代码

var increasingBST = function (root) {
let newRoot = null, lastNode = null;
function dfs(node) {
if (!node) return;
dfs(node.left);
if (!newRoot) newRoot = node;
if (lastNode) lastNode.right = node;
lastNode = node;
node.left = null;
dfs(node.right);
}
dfs(root);

return newRoot;
};

4、执行结果

1.png

5、解法2-DFS+数组

深度遍历将节点按中序存入数组中,结束后对节点数组进行遍历,将当前节点的左子节点置空右子节点置为下一个数组元素,最后返回数组首个元素。

6、代码

var increasingBST = function (root) {
const arr = [];
function dfs(node) {
if (!node) return;
dfs(node.left);
arr.push(node);
dfs(node.right);
}
dfs(root);

for (let i = 0; i < arr.length; i++) {
arr[i].left = null;
arr[i].right = arr[i + 1] || null;
}

return arr[0];
};

7、执行结果

执行用时: 68 ms 内存消耗: 39.2 MB