跳到主要内容

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

查看所有标签

· 阅读需 3 分钟

1、题干

给定一个二叉树的根节点 root ,树中每个节点都存放有一个 09 之间的数字。

每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123

计算从根节点到叶节点生成的 所有数字之和

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

 

示例 1:

输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25

示例 2:

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026

 

提示:

  • 树中节点的数目在范围 [1, 1000]
  • 0 <= Node.val <= 9
  • 树的深度不超过 10

 

注意:本题与主站 129 题相同: https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/

执行结果

1.png

解题思路

总体思路:DFS递归遍历所有节点,递归函数增加一个形参path,用于记录根节点到当前节点这条路径上所有数字拼接成的字符串,最后把所有path转成数字并累加即可。

官解直接使用10进制计算,时长和内存都会低一些。这个解法思路算简单,时间复杂度和空间复杂度也跟官解一样,但执行时长和内存消耗略高,问题主要在以下两点:

  • 时间主要消耗在:paths.push()、字符串累加、最后多了一步paths转换累加
  • 内存主要消耗在:存储paths、每个dfs调用栈的字符串形参path、末尾的paths.reduce的回调函数的调用栈空间

代码

var sumNumbers = function (root) {
const paths = [];
function dfs(node, path) {
if (!node) return;
if (!node.left && !node.right) paths.push(path + node.val);
dfs(node.left, path + node.val);
dfs(node.right, path + node.val);
}
dfs(root, '');
return paths.reduce((acc, cur) => +cur + acc, 0);
};

· 阅读需 3 分钟

1、题干

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

 

示例1:

输入: root = [1,3,2,5,3,null,9]
输出: [1,3,9]
解释:
1
/ \
3 2
/ \ \
5 3 9

示例2:

输入: root = [1,2,3]
输出: [1,3]
解释:
1
/ \
2 3

示例3:

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

示例4:

输入: root = [1,null,2]
输出: [1,2]
解释:
  1
  \
  2

示例5:

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

 

提示:

  • 二叉树的节点个数的范围是 [0,104]
  • -231 <= Node.val <= 231 - 1

 

注意:本题与主站 515 题相同: https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/

解法1:DFS递归实现

DFS递归遍历树的每个节点,递归函数的形参中添加1个层级参数,便于计算每层节点最大值。

执行结果

1.png

代码

var largestValues = function (root) {
const res = [];
function dfs(node, level) {
if (!node) return;
if (res[level] === undefined || node.val > res[level]) res[level] = node.val;
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
dfs(root, 0);
return res;
};

解法2:BFS非递归实现

BFS非递归遍历树的每个节点,需要借助队列实现。与常规BFS非递归实现不同,此处队列是一个矩阵,队列中的元素是每层节点的集合。

执行结果

2.png

代码

var largestValues = function (root) {
const queue = root ? [[root]] : [], res = [];
for (const curNodes of queue) {
let max = -Infinity, nodes = [];
for (const node of curNodes) {
if (node.val > max) max = node.val;
if (node.left) nodes.push(node.left);
if (node.right) nodes.push(node.right);
}
if (nodes.length) queue.push(nodes);
res.push(max);
}
return res;
};

由于非递归实现需要额外借助数组实现,且存在大量Array.push()等耗时操作,所以时间和空间上会消耗得更多

· 阅读需 2 分钟

1、题干

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

 

示例 1:

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

示例 2:

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

 

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1 

 

注意:本题与主站 513 题相同: https://leetcode-cn.com/problems/find-bottom-left-tree-value/

执行结果

2.png

解题思路

总体思路:使用DFS递归遍历树的所有节点,当第一次遍历到层级大于所有已遍历节点的节点时,该节点就是当前层级的最左子节点;随着遍历层级递增,最终能找到最底层的最左子节点。

  • 使用DFS递归遍历树的所有节点
  • 记录当前节点的层级level与已遍历节点的最大层级maxLevel
  • 每当level超过maxLevel时,将当前节点赋值给res,另外更新maxLevel
  • 遍历完成后,res就是要找的节点,返回该节点的值即可

代码

var findBottomLeftValue = function (root) {
let res, maxLevel = 0;
function dfs(node, level) {
if (!node) return;
if (level > maxLevel) res = node, maxLevel = level;
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
dfs(root, 1);
return res && res.val;
};

· 阅读需 2 分钟

1、题干

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

 

示例 1:

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

示例 2:

输入: [1,null,3]
输出: [1,3]

示例 3:

输入: []
输出: []

 

提示:

  • 二叉树的节点个数的范围是 [0,100]
  • -100 <= Node.val <= 100 

 

注意:本题与主站 199 题相同:https://leetcode-cn.com/problems/binary-tree-right-side-view/

执行结果

0.png

解题思路

总体思路:使用DFS递归遍历树的所有节点,创建结果数组res,将当前节点的层级当做res的下标、val当做res的值,以此更新res,右边节点的值会不断覆盖左边节点,得到的数组就是从右侧所能看到的节点值。

代码

var rightSideView = function (root) {
const res = [];
function dfs(node, level) {
if (!node) return;
res[level] = node.val;
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
dfs(root, 0);
return res;
};

· 阅读需 2 分钟

1、题干

给定一个二叉树 根节点 root ,树的每个节点的值要么是 0,要么是 1。请剪除该二叉树中所有节点的值为 0 的子树。

节点 node 的子树为 node 本身,以及所有 node 的后代。

 

示例 1:

输入: [1,null,0,0,1]
输出: [1,null,0,null,1]
解释:
只有红色节点满足条件“所有不包含 1 的子树”。
右图为返回的答案。


示例 2:

输入: [1,0,1,0,0,0,1]
输出: [1,null,1,null,1]
解释:


示例 3:

输入: [1,1,0,1,1,0,1,0]
输出: [1,1,0,1,1,null,1]
解释:


 

提示:

  • 二叉树的节点个数的范围是 [1,200]
  • 二叉树节点的值只会是 01

 

注意:本题与主站 814 题相同:https://leetcode-cn.com/problems/binary-tree-pruning/

执行结果

1.png

解题思路

总体思路:使用DFS递归遍历树的节点,递归过程中把值为0的叶子节点逐个剪除,就能得到剪枝后的二叉树。

代码

var pruneTree = function (root) {
function dfs(node) {
if (!node) return;
if (dfs(node.left)) node.left = null;
if (dfs(node.right)) node.right = null;
if (node.val === 0 && !node.left && !node.right) return true;
}
return dfs(root) ? null : root;
};

· 阅读需 3 分钟

1、题干

给定一个二叉树的根节点 root ,树中每个节点都存放有一个 09 之间的数字。

每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123

计算从根节点到叶节点生成的 所有数字之和

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

 

示例 1:

输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25

示例 2:

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026

 

提示:

  • 树中节点的数目在范围 [1, 1000]
  • 0 <= Node.val <= 9
  • 树的深度不超过 10

 

注意:本题与主站 129 题相同: https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/

执行结果

1.png

解题思路

总体思路:DFS递归遍历所有节点,递归函数增加一个形参path,用于记录根节点到当前节点这条路径上所有数字拼接成的字符串,最后把所有path转成数字并累加即可。

官解直接使用10进制计算,时长和内存都会低一些。这个解法思路算简单,时间复杂度和空间复杂度也跟官解一样,但执行时长和内存消耗略高,问题主要在以下两点:

  • 时间主要消耗在:paths.push()、字符串累加、最后多了一步paths转换累加
  • 内存主要消耗在:存储paths、每个dfs调用栈的字符串形参path、末尾的paths.reduce的回调函数的调用栈空间

代码

var sumNumbers = function (root) {
const paths = [];
function dfs(node, path) {
if (!node) return;
if (!node.left && !node.right) paths.push(path + node.val);
dfs(node.left, path + node.val);
dfs(node.right, path + node.val);
}
dfs(root, '');
return paths.reduce((acc, cur) => +cur + acc, 0);
};

· 阅读需 3 分钟

1、题干

给你一个大小为 m x n 的矩阵 board 表示甲板,其中,每个单元格可以是一艘战舰 'X' 或者是一个空位 '.' ,返回在甲板 board 上放置的 战舰 的数量。

战舰 只能水平或者垂直放置在 board 上。换句话说,战舰只能按 1 x k1 行,k 列)或 k x 1k 行,1 列)的形状建造,其中 k 可以是任意大小。两艘战舰之间至少有一个水平或垂直的空位分隔 (即没有相邻的战舰)。

 

示例 1:

输入:board = [["X",".",".","X"],[".",".",".","X"],[".",".",".","X"]]
输出:2

示例 2:

输入:board = [["."]]
输出:0

 

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 200
  • board[i][j]'.''X'

 

进阶:你可以实现一次扫描算法,并只使用 O(1) 额外空间,并且不修改 board 的值来解决这个问题吗?

执行结果

1.png

解法1-寻找船头

由于战舰只能水平或者垂直放置在board上,那就意味着船头位置的左边和上边必然是空位。因此只要双循环遍历整个board矩阵,遇到船头则战舰数量加一。

同理可以寻找船尾,船尾的右边和下边必然是空位。

代码

一行强迫症写法

var countBattleships = function (board) {
return board.reduce((acc, cur, i) => acc + cur.filter((c, j) => c === 'X' && board[i][j - 1] !== 'X' && (!i || board[i - 1][j] !== 'X')).length, 0);
};

常规写法

var countBattleships = function (board) {
let res = 0;

for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board[0].length; j++) {
if (board[i][j] == 'X' && board[i][j - 1] !== 'X' && (!i || board[i - 1][j] !== 'X')) res++;
}
}

return res;
};

解法2-DFS

双循环遍历整个board矩阵,遇到战舰则战舰数量加一,然后深度遍历战舰所占位置,把遍历过的战舰位置修改成空位避免重复遍历。

代码

var countBattleships = function (board) {
let res = 0;
function dfs(i, j) {
if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] !== 'X') return;

board[i][j] = '.';
dfs(i + 1, j);
dfs(i - 1, j);
dfs(i, j + 1);
dfs(i, j - 1);
}

for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board[0].length; j++) {
if (board[i][j] === 'X') dfs(i, j), res++;
}
}

return res;
};

· 阅读需 3 分钟

1、题干

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。

 

示例 1:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:



扁平化后的链表如下图:


示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

1---2---NULL
|
3---NULL

示例 3:

输入:head = []
输出:[]

 

如何表示测试用例中的多级链表?

示例 1 为例:

 1---2---3---4---5---6--NULL
|
7---8---9---10--NULL
|
11--12--NULL

序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

 

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 10^5

 

注意:本题与主站 430 题相同: https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list/

执行结果

1.png

解题思路

  • 使用栈存储后续需要遍历的节点,先压入node.next再压入node.child
  • 当栈内元素不为空时,弹出栈顶元素进行处理
  • 当前节点属于以下2种情况需特殊处理(代码中的link()函数):
    • 1、child有值:将当前节点与子节点转成兄弟节点,并将当前节点的child置位空
    • 2、nextchild为空值:将当前节点与栈顶节点转成兄弟节点,并将当前节点的child置位空

代码

var flatten = function (head) {
const link = (cur, next) => (cur.next = next, cur.child = null, next.prev = cur);
let node, stack = [head];

while (node = stack.pop()) {
if (node.next) stack.push(node.next);
if (node.child) stack.push(node.child), link(node, node.child);
if (!node.next && !node.child && stack.length) link(node, stack[stack.length - 1]);
}

return head;
};

· 阅读需 3 分钟

1、题干

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。

 

示例 1:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:

输入的多级列表如下图所示:



扁平化后的链表如下图:


示例 2:

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:

输入的多级列表如下图所示:

1---2---NULL
|
3---NULL

示例 3:

输入:head = []
输出:[]

 

如何表示测试用例中的多级链表?

示例 1 为例:

 1---2---3---4---5---6--NULL
|
7---8---9---10--NULL
|
11--12--NULL

序列化其中的每一级之后:

[1,2,3,4,5,6,null]
[7,8,9,10,null]
[11,12,null]

为了将每一级都序列化到一起,我们需要每一级中添加值为 null 的元素,以表示没有节点连接到上一级的上级节点。

[1,2,3,4,5,6,null]
[null,null,7,8,9,10,null]
[null,11,12,null]

合并所有序列化结果,并去除末尾的 null 。

[1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]

 

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 10^5

 

注意:本题与主站 430 题相同: https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list/

执行结果

1.png

解题思路

  • 使用栈存储后续需要遍历的节点,先压入node.next再压入node.child
  • 当栈内元素不为空时,弹出栈顶元素进行处理
  • 当前节点属于以下2种情况需特殊处理(代码中的link()函数):
    • 1、child有值:将当前节点与子节点转成兄弟节点,并将当前节点的child置位空
    • 2、nextchild为空值:将当前节点与栈顶节点转成兄弟节点,并将当前节点的child置位空

代码

var flatten = function (head) {
const link = (cur, next) => (cur.next = next, cur.child = null, next.prev = cur);
let node, stack = [head];

while (node = stack.pop()) {
if (node.next) stack.push(node.next);
if (node.child) stack.push(node.child), link(node, node.child);
if (!node.next && !node.child && stack.length) link(node, stack[stack.length - 1]);
}

return head;
};

· 阅读需 2 分钟

1、题干

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

 

示例 1:

输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 300
  • grid[i][j] 的值为 '0''1'

解题思路

  • 遍历矩阵grid,以grid[i][j]为起点往上下左右4个方向进行深度遍历
  • 深度遍历过程中需要注意2点
    • 遍历过的节点修改为0,即grid[i][j] = '0',以免重复遍历
    • grid[i][j]必须在矩阵中且grid[i][j] === '1'才累加岛屿数量

代码

var numIslands = function (grid) {
const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
function dfs(i, j) {
if (i < 0 || j < 0 || i >= grid.length || j >= grid[0].length || grid[i][j] === '0') return false;
grid[i][j] = '0';
for (const [di, dj] of dirs) dfs(i + di, j + dj);
return true;
}

let count = 0;
for (let i = 0; i < grid.length; i++) {
for (let j = 0; j < grid[i].length; j++) {
if (dfs(i, j)) count++;
}
}

return count;
};