从上到下打印二叉树Ⅱ

剑指 Offer 32 - II. 从上到下打印二叉树 II - 力扣(Leetcode)

题目要求

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

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

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

1
2
3
4
5
[
[3],
[9,20],
[15,7]
]

提示:

节点总数 <= 1000

思路分析

  • 对于一层层打印二叉树,一般都使用队列 (先进先出) 来实现;

  • 先把二叉树头节点放入队列,然后进入循环:

    • 获取队列大小为k;

    • 循环取出k个节点,这k个节点为其中的一层;

    • 并判断左右子树是否为空,若不为空,将左右子树放入队列;

    • 然后继续把队列里的元素拿出来并用集合存放,直到队列为空;

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
// 判断特殊情况
if(root == null){
return new ArrayList<>();
}
// 创建一个队列
Queue<TreeNode> queue = new LinkedList<>();
// 创建一个集合
List<List<Integer>> res = new ArrayList<>();
// 将头节点放入队列
queue.add(root);
// 进入循环
while(!queue.isEmpty()){
// k 来存放队列大小
int k = queue.size();
// 定义一个临时集合
List<Integer> temp = new ArrayList<>();
// 循环取出k个节点
for(int i=0;i<k;i++){
TreeNode t = queue.poll();
// 将t放入临时集合
temp.add(t.val);
// 将左右子树放入队列
if(t.left != null) queue.add(t.left);
if(t.right != null) queue.add(t.right);
}
// 将临时集合放入res中
res.add(temp);
}
// 返回res
return res;
}
}

从上到下打印二叉树Ⅲ

剑指 Offer 32 - III. 从上到下打印二叉树 III - 力扣(Leetcode)

题目要求

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

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

1
2
3
4
5
  3
/ \
9 20
/ \
15 7

返回其层次遍历结果:

1
2
3
4
5
[
[3],
[20,9],
[15,7]
]

提示

节点总数 <= 1000

思路分析

  1. 根据题意分析,奇数行从左向右打印,偶数行从右向左打印;
  2. 基本思路可以参考上一题;
  3. 我们只需要定义一个变量sum,初始为1,while循环最后sum++,然后在执行时进行判断,如果时奇数就不变,如果是偶数就从集合的前面插入元素(默认集合都是在后面插入元素)。
  4. 因为使用addFirst方法,临时集合需要改用为链表LinkedList;

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
// 判断特殊情况
if(root == null){
return new ArrayList<>();
}
// 创建一个队列
Queue<TreeNode> queue = new LinkedList<>();
// 创建一个集合
List<List<Integer>> res = new ArrayList<>();
// 定义变量判断是奇数行还是偶数行
int sum = 1;
// 将头节点放入队列
queue.add(root);
// 进入循环
while(!queue.isEmpty()){
// k 来存放队列大小
int k = queue.size();
// 定义一个链表
LinkedList<Integer> temp = new LinkedList<>();
// 循环取出k个节点
for(int i=0;i<k;i++){
TreeNode t = queue.poll();
// 将t放入临时集合
// 判断奇偶性,如果是奇数行不改变,否则从前面插入元素
if(sum % 2 == 1){
temp.add(t.val);
}else{
temp.addFirst(t.val);
}
// 将左右子树放入队列
if(t.left != null) queue.add(t.left);
if(t.right != null) queue.add(t.right);
}
// 将临时集合放入res中
res.add(temp);
// 控制行的奇偶性
sum++;
}
// 返回res
return res;
}
}

二叉搜索树的后序遍历序列

剑指 Offer 33. 二叉搜索树的后序遍历序列 - 力扣(Leetcode)

题目要求

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

1
2
3
4
5
    5
/ \
2 6
/ \
1 3

例子:

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

输入: [1,3,2,6,5]
输出: true

提示:

数组长度 <= 1000

思路分析

递归

后序遍历,根节点都是在末尾,通过二叉搜索树的特性(左子树所有节点小于根节点,右子树所有节点大于根节点),可以分割左右子树,然后左右子树的头节点也是在末尾,可以根据特性将子树再进行分割。

单调栈(参考Leetcode题解-数据结构和算法

image-202212191223207.png

他的后续遍历结果是:

[3,6,5,9,8,11,13,12,10]

从前往后不好看,我们来从后往前看:

[10,12,13,11,8,9,5,6,3]

发现两个规律:

  • 挨着的两个数如果arr[i]<arr[i+1],那么arr[i+1]一定是arr[i]的右子节点

  • 如果arr[i]>arr[i+1],那么arr[i+1]一定是arr[0]……arr[i]中某个节点的左子节点,并且这个值是大于arr[i+1]中最小的

遍历数组的所有元素,如果栈为空,就把当前元素压栈。如果栈不为空,并且当前元素大于栈顶元素,说明是升序的,那么就说明当前元素是栈顶元素的右子节点,就把当前元素压栈,如果一直升序,就一直压栈。当前元素小于栈顶元素,说明是倒序的,说明当前元素是某个节点的左子节点,我们目的是要找到这个左子节点的父节点,就让栈顶元素出栈,直到栈为空或者栈顶元素小于当前值为止,其中最后一个出栈的就是当前元素的父节点。

代码实现

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public boolean verifyPostorder(int[] postorder) {
// 特殊情况
if(postorder == null){
return true;
}
// 递归
return f(postorder,0,postorder.length-1);
}
// 递归方法
boolean f(int[] postorder,int i,int j){
// 树为空或只有一个几点
if(i>=j){
return true;
}
// 获取根节点root
int root = postorder[j];
// p用来存放分割后的右子树部分,及第一个大于根节点的元素
int p = i;
// 获取第一个大于或等于root的元素
while(postorder[p] < root) p++;
// 判断p~j-1 这个范围是否存在小于root的元素,若存在不符合后序遍历结果
for(int k=p;k<j;k++){
if(postorder[k] < root){
return false;
}
}
// 递归判断左右子树
return f(postorder,i,p-1) && f(postorder,p,j-1);
}
}

单调栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
public boolean verifyPostorder(int[] postorder) {
// 定义一个栈
Stack<Integer> stack = new Stack<>();
int parent = Integer.MAX_VALUE;

//注意for循环是倒叙遍历的
for (int i = postorder.length - 1; i >= 0; i--) {
// 将倒叙遍历结果存放在cur
int cur = postorder[i];
//如果当前节点小于栈顶元素,说明栈顶元素和当前值构成了倒叙,
//即当前节点是前面某个节点的左子节点,我们要找到他的父节点
while (!stack.isEmpty() && stack.peek() > cur){
// 如果栈不为空,且栈顶元素大于cur,就一直出栈并将出站结果赋给parent
parent = stack.pop();
}
//只要遇到了某一个左子节点,才会执行上面的代码,
// 否则parent就是一个非常大的值,也就是说如果一直没有遇到左子节点,
// 那么右子节点可以非常大
if (cur > parent)
return false;
//入栈
stack.add(cur);
}
return true;
}
}

二叉树中和为某一值的路径

剑指 Offer 34. 二叉树中和为某一值的路径 - 力扣(Leetcode)

题目要求

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

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

例子1:

img-202212191234982

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

例子2:

img-202212191234608

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

提示:

  • 树中节点总数在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

思路分析

  • 根据题意,要求找出左子树节点之和以及右子树节点之和都等于target的路径;
  • 使用深度优先搜索来解决该题:
    • 从根节点开始,查找左子树,每经过一个节点(包括根节点),将让target减去该节点,直到最后一个节点,如果target减去最后一个节点不为0,说明该路径不符合,就回溯查找上一个节点的右子树,直到target减去最后一个节点后结果为0,说明该路径符合要求;
    • 然后从根节点开始,查找右子树,先寻找右子树,直到target减去最后一个节点,若不为0,就回溯到上一个节点的左子树进行查找,直到target减去节点结果为0;
    • 使用集合来存放每次经过的节点,如果遇到不符合的节点发生回溯,就将不符合的节点从集合中删除;

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 定义一个集合和一个临时集合
List<List<Integer>> res;
List<Integer> temp;
public List<List<Integer>> pathSum(TreeNode root, int target) {
// 初始化集合
res = new ArrayList<>();
temp = new ArrayList<>();
// 深度优先搜索
dfs(root,target);
return res;
}
// 深度优先搜索
void dfs(TreeNode root,int target){
// 特殊情况
if(root == null){
return;
}
// 将root加入到temp
temp.add(root.val);
// 计算target-root
target = target - root.val;
// 如果没有左右子树且target为0,就将temp加入到集合中
if(root.left == null && root.right == null && target == 0){
res.add(new ArrayList<>(temp));
}
// 深度搜索左右子树
dfs(root.left,target);
dfs(root.right,target);
// 从temp中移除不符合条件的节点
temp.remove(temp.size()-1);
}
}

复杂链表的复制

剑指 Offer 35. 复杂链表的复制 - 力扣(Leetcode)

题目要求

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

例子1:

img-202212191237229

1
2
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

例子2:

img-202212191237546

1
2
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

例子3:

1
2
3
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null

提示:

  • -10000 <= Node.val <= 10000
  • Node.random 为空(null)或指向链表中的节点。
  • 节点数目不超过 1000 。

思路分析

复制+拆分

例如:有A->B->C->D四个节点,再random下为:A->C,C->A,B->D,D->B

  1. 将每个节点复制一份,即:A->A’->B->B’->C->C’->D->D’,并且有一个cur指针指向第一个A;
  2. 让cur.next.random = cur.random.next;(例如:A(cur.next.random) = A’->C’)
  3. 复制过后不能改变原来链表指针的状态;
  4. 拆分:例如:把A->A’->B->B’->C->C’ 拆分为:A->B->C和A’->B’->C’;

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;

public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
// 特殊情况
if(head == null){
return null;
}
// 复制链表节点
Node cur = head;
while(cur != null){
// 保存cur.next
Node next = cur.next;
// 将cur复制为新节点
cur.next = new Node(cur.val);
// 将cur.next.next指向原本cur.next
cur.next.next = next;
cur = next;
}
// 复制随机节点
cur = head;
while(cur != null){
Node curNew = cur.next;
// 判断cur.random == null,并将cur.random.next或null赋给curNew.random
curNew.random = cur.random == null ? null : cur.random.next;
cur = cur.next.next;
}
// 拆分
// 例如:把A->A'->B->B'->C->C' 拆分为:A->B->C和A'->B'->C'
Node headNew = head.next;
cur = head;
Node curNew = head.next;
while(cur != null){
cur.next = cur.next.next; // 例如:A->B
cur = cur.next;
curNew.next = cur == null ? null : cur.next; // 例如:A'->B'或者->null
curNew = curNew.next;
}
return headNew;
}
}