
👨💻程序员三明治 :个人主页
🔥 个人专栏 : 《设计模式精解》 《重学数据结构》
🤞先做到 再看见!
目录
- 概述
- 思路
-
- 定义二叉树类
- 将二叉树的完全二叉树形式定义为数组
- 根据数组构造二叉树
-
- 1)整体思路(两趟构建)
- [2)第一段循环:创建节点列表 + 确定根节点](#2)第一段循环:创建节点列表 + 确定根节点)
- 3)第二段循环:按"堆下标规则"连接左右孩子
- 4)返回结果
- 5)一个小例子帮助理解
- 核心完整代码
- [Leetcode113. 路径总和②](#Leetcode113. 路径总和②)
概述
二叉树可以有两种存储方式,一种是 链式存储,另一种是顺序存储。
链式存储,就是大家熟悉的二叉树,用指针指向左右孩子。
顺序存储,就是用一个数组来存二叉树,其方式如图所示:

如果父节点的数组下标是i,那么它的左孩子下标就是i * 2 + 1,右孩子下标就是 i * 2 + 2。
思路
定义二叉树类
java
class TreeNode {
int val;
TreeNode left, right;
public TreeNode() {}
public TreeNode(int val) {
this.val = val;
}
}
将二叉树的完全二叉树形式定义为数组
以下图为例:

我们可以定义为数组:
java
int[] nums = new int[]{5, 4, 8, 11, -1, 13, 4, 7, 2, -1, -1, -1, -1, 5, 1};
根据数组构造二叉树
buildBinaryTree 的作用是:把一个用数组按层序(堆式下标)表示的二叉树构建成真正的链式二叉树 。其中 -1 用来表示"这个位置没有节点(null)"。
1)整体思路(两趟构建)
这段代码分两步做:
- 先把数组每个位置都变成一个
**TreeNode**或**null**,放进**nodeList**(下标一一对应) - 再按下标规则,把每个非空节点的左/右孩子指针连起来
这样做的好处是:第二步连指针时,孩子节点对象已经准备好了,直接取 nodeList 里的引用即可。
2)第一段循环:创建节点列表 + 确定根节点
java
if (nums.length == 0) return null;
List<TreeNode> nodeList = new ArrayList<>();
TreeNode root = null;
for (int i = 0; i < nums.length; i++) {
TreeNode node = null;
if (nums[i] != -1) {
node = new TreeNode(nums[i]);
}
nodeList.add(node);
if (i == 0) {
root = node;
}
}
逐行解释:
nums.length == 0:空数组,当然没有树,直接返回null。nodeList:用来存放所有节点(或 null),并且保证**nodeList[i]**对应**nums[i]**的位置。TreeNode node = null;:默认这个位置没有节点。if (nums[i] != -1):如果不是-1,就创建真实节点new TreeNode(nums[i]);否则保持null。nodeList.add(node):把这个位置的节点(或 null)放进列表。if (i == 0) root = node;:数组的第 0 位代表根节点,所以根就是nodeList[0]。
注意:如果 nums[0] == -1,那么根就是 null,最终返回空树。
3)第二段循环:按"堆下标规则"连接左右孩子
java
for (int i = 0; i < nums.length; i++) {
TreeNode node = nodeList.get(i);
if (node != null) {
if (i * 2 + 1 < nums.length) {
node.left = nodeList.get(i * 2 + 1);
}
if (i * 2 + 2 < nums.length) {
node.right = nodeList.get(i * 2 + 2);
}
}
}
这里使用的是数组表示完全二叉树的经典下标关系:
- 对于下标
i的节点:- 左孩子下标:
2*i + 1 - 右孩子下标:
2*i + 2
- 左孩子下标:
代码关键点:
TreeNode node = nodeList.get(i);- 取出当前位置的节点引用。
if (node != null):- 只有当前节点真实存在,才有必要去给它连孩子;如果本身是空位置(null),就跳过。
if (i * 2 + 1 < nums.length)/if (i * 2 + 2 < nums.length):- 防止孩子下标越界。
node.left = nodeList.get(2*i + 1):- 直接把列表中对应位置的节点引用挂到
left上。 - 如果那个位置是
null,那就等价于"左孩子为空",完全符合预期。
- 直接把列表中对应位置的节点引用挂到
4)返回结果
java
return root;
root 就是整棵树的入口节点(可能为 null)。
5)一个小例子帮助理解
假设:
nums = [1, 2, 3, -1, 4]
nodeList 会变成:
- 0: 1
- 1: 2
- 2: 3
- 3: null
- 4: 4
连接关系:
- i=0(1):left=2(i=1),right=3(i=2)
- i=1(2):left=null(i=3),right=4(i=4)
最终树结构:
plain
1
/ \
2 3
\
4
核心完整代码
java
private static TreeNode buildBinaryTree(int[] nums) {
// 1.把输入数值数组,先转化为二叉树节点列表
if (nums.length == 0) return null;
List<TreeNode> nodeList = new ArrayList<>();
TreeNode root = null;
for (int i = 0; i < nums.length; i++) {
TreeNode node = null;
if (nums[i] != -1) {
node = new TreeNode(nums[i]);
}
nodeList.add(node);
if (i == 0) {
root = node;
}
}
// 2.遍历一遍,根据规则给左右孩子赋值就可以了
for (int i = 0; i < nums.length; i++) {
TreeNode node = nodeList.get(i);
if (node != null) {
if (i * 2 + 1 < nums.length) {
node.left = nodeList.get(i * 2 + 1);
}
if (i * 2 + 2 < nums.length) {
node.right = nodeList.get(i * 2 + 2);
}
}
}
return root;
}
下面我们来看一道力扣题,完美应用了二叉树的构造
Leetcode113. 路径总和②
这里使用ACM代码形式去写
题目描述
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:

plain
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
思路
毫无疑问,树形递归。递归有两大步:
- 确定递归的终止条件、参数
- 确定单层递归的逻辑
本题终止条件是遇到叶子结点,判断targetSum是否为0,所以这里需要在进入本次递归之前就把这个节点的值给减掉,并把这个节点提前加入到path中。
单层递归我们用的是先序遍历,根左右。
递推参数: 当前节点 root ,当前目标值 targetSum 。
终止条件: 若节点 root 为空,则直接返回。
递推工作:
路径更新: 将当前节点值 root.val 加入路径 path 。
目标值更新: targetSum = targetSum - root.val(即目标值 targetSum 从 原始的targetSum 减至 0 )。
路径记录: 当 (1) root 为叶节点 且 (2) 路径和等于目标值 ,则将此路径 path 加入 res 。
先序遍历: 递归左 / 右子节点。
路径恢复: 向上回溯前,需要将当前节点从路径 path 中删除,即执行 path.pop() 。
代码如下:
java
public class PathSum2 {
static List<Integer> path = new ArrayList<>();
static List<List<Integer>> res = new ArrayList<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int[] nums = new int[]{5, 4, 8, 11, -1, 13, 4, 7, 2, -1, -1, -1, -1, 5, 1};
TreeNode root = buildBinaryTree(nums);
System.out.println("请输出总和数值:");
int targetSum = scanner.nextInt();
res = getPathSum2(root, targetSum);
System.out.println("结果:" + res);
scanner.close();
}
private static List<List<Integer>> getPathSum2(TreeNode root, int targetSum) {
if (root == null) return res;
path.add(root.val);
dfs(root, targetSum - root.val);
return res;
}
private static void dfs(TreeNode root, int targetSum) {
// 递归的终止条件
if (root.left == null && root.right == null) {
if (targetSum == 0) {
res.add(new ArrayList<>(path));
}
}
// 单层递归的逻辑,根左右
if (root.left != null) {
path.add(root.left.val);
dfs(root.left, targetSum - root.left.val);
path.remove(path.size() - 1);
}
if (root.right != null) {
path.add(root.right.val);
dfs(root.right, targetSum - root.right.val);
path.remove(path.size() - 1);
}
}
// 构造二叉树
private static TreeNode buildBinaryTree(int[] nums) {
// 1.把输入数值数组,先转化为二叉树节点列表
if (nums.length == 0) return null;
List<TreeNode> nodeList = new ArrayList<>();
TreeNode root = null;
for (int i = 0; i < nums.length; i++) {
TreeNode node = null;
if (nums[i] != -1) {
node = new TreeNode(nums[i]);
}
nodeList.add(node);
if (i == 0) {
root = node;
}
}
// 2.遍历一遍,根据规则给左右孩子赋值就可以了
for (int i = 0; i < nums.length; i++) {
TreeNode node = nodeList.get(i);
if (node != null) {
if (i * 2 + 1 < nums.length) {
node.left = nodeList.get(i * 2 + 1);
}
if (i * 2 + 2 < nums.length) {
node.right = nodeList.get(i * 2 + 2);
}
}
}
return root;
}
}
class TreeNode {
int val;
TreeNode left, right;
public TreeNode() {}
public TreeNode(int val) {
this.val = val;
}
}
【注意事项】
在使用这段代码时,请务必注意以下两点: 空节点的占位:这段代码依赖于数组是 "完整" 的。如果父节点存在但子节点缺失,数组中必须填入 -1占位。如果数组直接跳过了 -1,那么 i * 2 + 1 的数学关系就会失效,导致树构建错误。 数值冲突:代码中硬编码了 -1代表空。如果你的二叉树节点值本身就可能包含 -1,记得把判断条件改成 Integer.MAX_VALUE 或其他不会冲突的特殊值。
好记性不如烂笔头,建议大家把这段代码收藏起来,下次刷 LeetCode 需要自己写测试用例的时候,直接拿来即用!
如果我的内容对你有帮助,请辛苦动动您的手指为我点赞,评论,收藏。感谢大家!!
