目录
113. 路径总和 II
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例:
给定如下二叉树,以及目标和 sum = 22
,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
private List<List<Integer>> ans = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<Integer> temp = new LinkedList<>();
helper(root, sum, temp);
return ans;
}
private void helper(TreeNode root, int sum, List<Integer> temp) {
if (root == null) {
return;
}
temp.add(root.val);
if (root.left == null && root.right == null && sum == root.val) {
ans.add(new LinkedList<>(temp));
}
helper(root.left, sum - root.val, temp);
helper(root.right, sum - root.val, temp);
temp.remove(temp.size() - 1);
}
解析:如果当前节点为空,则直接返回
临时集合list中添加当前节点的值
如果当前节点的值等于sum,且当前节点左右子节点为空,则满足条件,向集合ans中添加临时集合temp
依次递归查找当前节点的左右子树
还原数据,删除此次递归中向temp集合中添加的值
114. 二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
public void flatten(TreeNode root) {
//递归前序遍历 DFS
if (root == null) {
return;
}
List<TreeNode> list = new LinkedList<>();
helper(root, list);
TreeNode preNode;
TreeNode currNode;
for (int i = 1; i < list.size(); i++) {
preNode = list.get(i - 1);
currNode = list.get(i);
preNode.left = null;
preNode.right = currNode;
}
}
private void helper(TreeNode root, List<TreeNode> list) {
if (root == null) {
return;
}
list.add(root);
helper(root.left, list);
helper(root.right, list);
}
解析:通过递归前序遍历,将树的每个节点存储起来
依次将树的修改每个节点的指针关系
public void flatten(TreeNode root) {
//迭代前序遍历 DFS
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
List<TreeNode> list = new LinkedList<>();
TreeNode currNode = root;
while (currNode != null || !stack.isEmpty()) {
while (currNode != null) {
stack.add(currNode);
list.add(currNode);
currNode = currNode.left;
}
currNode = stack.pop();
currNode = currNode.right;
}
for (int i = 1; i < list.size(); i++) {
TreeNode pre = list.get(i - 1);
TreeNode cur = list.get(i);
//将前一个节点pre的左指针left置空
pre.left = null;
//将前一个节点pre的右指针指向当前节点cur
pre.right = cur;
}
}
解析:通过前序遍历迭代,将树的每个节点存储起来
依次将树的修改每个节点的指针关系
public void flatten(TreeNode root) {
//前序遍历和展开链表同时进行
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
TreeNode pre = null;
TreeNode cur = null;
while (!stack.isEmpty()) {
cur = stack.pop();
if (pre != null) {
pre.left = null;
pre.right = cur;
}
if (cur.right != null) {
stack.add(cur.right);
}
if (cur.left != null) {
stack.add(cur.left);
}
//更新pre指针
pre = cur;
}
}
解析:这种方式为前序和展开链表同时进行,可以较快的提高效率
创建pre前驱节点和cur当前节点
栈中弹出的元素做为cur当前节点
当前驱节点pre不为空的时候,就修改前驱节点pre和当前节点cur的关系,如果为空则说明为根节点,便不用修改
添加当前节点的左右节点进入栈中,要先加入右节点,注意入栈的顺序
public void flatten(TreeNode root) {
//寻找前驱节点
TreeNode curr = root;
while (curr != null) {
if (curr.left != null) {
TreeNode next = curr.left;
TreeNode node = next;
while (node.right != null) {
node = node.right;
}
node.right = curr.right;
curr.right = next;
curr.left = null;
}
curr = curr.right;
}
}
// 1
// / \
// 2 5
// / \ \
//3 4 6
解析:依次将节点的右子树嫁接到左节点的最右支上
创建节点curr指向root节点
curr节点不为空情况下,如果curr节点的左子树不为空
创建指针next指向curr节点的左子节点
创建指针node指向next节点,也即curr节点的左子节点
如果node节点有右子节点,则一直沿着右子节点往下走,直到到最后一个右子节点,此节点为找到的前驱节点
将前驱节点node的右指针指向curr节点的右子节点
curr节点的右子节点指向next,也即curr的左子节点
curr节点的左子节点置空
结束后,curr节点指向他的下一个节点,即他的右子节点
116. 填充每个节点的下一个右侧节点指针
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。
初始状态下,所有 next 指针都被设置为 NULL
。
示例:
输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}
输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":{"$ref":"7"},"val":1}
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
提示:
- 你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
public Node connect(Node root) {
//层次遍历 BFS
if (root == null) {
return root;
}
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
Node node = queue.poll();
//除去最后一个节点
if (i < size - 1) {
node.next = queue.peek();
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
return root;
}
解析:使用广度优先遍历BFS可以轻松解决
只需要当前层的每一个节点的next指针指向下一个节点即可
public Node connect(Node root) {
//使用已建立的next指针
if (root == null) {
return root;
}
Node leftNode = root;
while (leftNode.left != null) {
Node node = leftNode;
while (node != null) {
node.left.next = node.right;
if (node.next != null) {
node.right.next = node.next.left;
}
node = node.next;
}
leftNode = leftNode.left;
}
return root;
}
解析:依次获取这棵树的最左边的节点leftNode
创建当前节点node初始化指向这一层最左边的节点leftNode
然后将当前节点node的下一层节点依次相连,node节点不断右移,直到为空,这一层结束
一层遍历结束后,将当前层最左边的节点leftNode移动到下一层最左边的节点
129. 求根到叶子节点数字之和
给定一个二叉树,它的每个结点都存放一个 0-9
的数字,每条从根到叶子节点的路径都代表一个数字。
例如,从根到叶子节点路径 1->2->3
代表数字 123
。
计算从根到叶子节点生成的所有数字之和。
说明: 叶子节点是指没有子节点的节点。
示例 1:
输入: [1,2,3] 1 / \ 2 3 输出: 25 解释: 从根到叶子节点路径1->2
代表数字12
. 从根到叶子节点路径1->3
代表数字13
. 因此,数字总和 = 12 + 13 =25
.
示例 2:
输入: [4,9,0,5,1] 4 / \ 9 0 / \ 5 1 输出: 1026 解释: 从根到叶子节点路径4->9->5
代表数字 495. 从根到叶子节点路径4->9->1
代表数字 491. 从根到叶子节点路径4->0
代表数字 40. 因此,数字总和 = 495 + 491 + 40 =1026
.
public int sumNumbers(TreeNode root) {
int ans = helper(root, 0);
return ans;
}
private int helper(TreeNode root, int temp) {
if (root == null) {
return 0;
}
//当前节点总和
temp = temp * 10 + root.val;
//当前节点为为叶子节点
if (root.left == null && root.right == null) {
return temp;
}
//返回左右子树的和
return helper(root.left, temp) + helper(root.right, temp);
}
解析:每一步计算当前节点的总和,并传入进下一次递归中去
想计算当前节点的总和值
判断当前节点是否为叶子节点
如果不是叶子节点,返回当前节点左右子树的和
递归左右子树
130. 被围绕的区域
给定一个二维的矩阵,包含 'X'
和 'O'
(字母 O)。
找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例:
X X X X X O O X X X O X X O X X
运行你的函数后,矩阵变为:
X X X X X X X X X X X X X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O'
都不会被填充为 'X'
。 任何不在边界上,或不与边界上的 'O'
相连的 'O'
最终都会被填充为 'X'
。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
class Solution {
private int m;
private int n;
public void solve(char[][] board) {
if (board == null || board.length == 0) {
return;
}
this.m = board.length;
this.n = board[0].length;
for (int i = 0; i < m; i++) {
dfs(board, i, 0);
dfs(board, i, n - 1);
}
for (int i = 0; i < n; i++) {
dfs(board, 0, i);
dfs(board, m - 1, i);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'O') {
board[i][j] = 'X';
} else if (board[i][j] == 'A') {
board[i][j] = 'O';
}
}
}
}
private void dfs(char[][] board, int i, int j) {
if (i < 0 || i > m - 1 || j < 0 || j > n - 1 || board[i][j] != 'O') {
return;
}
if (board[i][j] == 'O') {
board[i][j] = 'A';
}
dfs(board, i + 1, j);
dfs(board, i - 1, j);
dfs(board, i, j + 1);
dfs(board, i, j - 1);
}
}
解析:先将边界的O置为A,相当于做了一个标记,标记这些O是不用转化为X的
然后对边界的O或A进行DFS,将与边界O连接的O都转化为A
然后遍历这个二维矩阵
如果为O,则将它修改为X
如果为A,则将他修改为O
为X的时候,不做任何处理