面试题 04.01. 节点间通路
节点间通路。给定有向图,设计一个算法,找出两个节点之间是否存在一条路径。
示例1:
输入:n = 3, graph = [[0, 1], [0, 2], [1, 2], [1, 2]], start = 0, target = 2
输出:true
示例2:
输入:n = 5, graph = [[0, 1], [0, 2], [0, 4], [0, 4], [0, 1], [1, 3], [1, 4], [1, 3], [2, 3], [3, 4]], start = 0, target = 4
输出 true
提示:
节点数量n在[0, 1e5]范围内。
节点编号大于等于 0 小于 n。
图中可能存在自环和平行边。
public boolean findWhetherExistsPath(int n, int[][] graph, int start, int target) {
Node[] nodes = new Node[n];
for (int i = 0; i < graph.length; i++) {
if (nodes[graph[i][0]] == null) {
nodes[graph[i][0]] = new Node();
}
if (nodes[graph[i][1]] == null) {
nodes[graph[i][1]] = new Node();
}
if (nodes[graph[i][0]].children == null) {
nodes[graph[i][0]].children = new HashSet<Integer>();
}
nodes[graph[i][0]].children.add(graph[i][1]);
}
if (nodes[start] == null || nodes[target] == null) {
return false;
}
boolean[] ans = new boolean[n];
doTrue(nodes, start, ans);
return ans[target];
}
private void doTrue(Node[] nodes, int start, boolean[] ans) {
if (ans[start]) {
return;
}
ans[start] = true;
if (nodes[start].children == null) {
return;
}
for (Integer child : nodes[start].children) {
doTrue(nodes, child, ans);
}
}
class Node {
Set<Integer> children;
}
面试题 04.02. 最小高度树
给定一个有序整数数组,元素各不相同且按升序排列,编写一个算法,创建一棵高度最小的二叉搜索树。
示例:
给定有序数组: [-10,-3,0,5,9],
一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树:
0
/ \
-3 9
/ /
-10 5
public TreeNode sortedArrayToBST(int[] nums) {
return getTreeNode(nums, 0, nums.length - 1);
}
private TreeNode getTreeNode(int[] nums, int start, int end) {
if (start > end) {
return null;
}
int middle = (start + end) / 2;
TreeNode treeNode = new TreeNode(nums[middle]);
treeNode.left = getTreeNode(nums, start, middle - 1);
treeNode.right = getTreeNode(nums, middle + 1, end);
return treeNode;
}
面试题 04.03. 特定深度节点链表
给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。
示例:
输入:[1,2,3,4,5,null,7,8]
1
/ \
2 3
/ \ \
4 5 7
/
8
输出:[[1],[2,3],[4,5,7],[8]]
public ListNode[] listOfDepth(TreeNode tree) {
if (tree == null) {
return new ListNode[0];
}
int deep = getDeep(tree);
ListNode[] ans = new ListNode[deep];
ListNode[] list = new ListNode[deep];
addAns(tree, ans, list, 0);
return ans;
}
private void addAns(TreeNode tree, ListNode[] ans, ListNode[] list, int k) {
if (tree == null) {
return;
}
ListNode node = new ListNode(tree.val);
if (list[k] == null) {
ans[k] = node;
list[k] = node;
} else {
list[k].next = node;
list[k] = node;
}
addAns(tree.left, ans, list, k + 1);
addAns(tree.right, ans, list, k + 1);
}
private int getDeep(TreeNode tree) {
if (tree == null) {
return 0;
}
return Math.max(getDeep(tree.left), getDeep(tree.right))+1;
}
面试题 04.04. 检查平衡性
实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个节点,其两棵子树的高度差不超过 1。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true 。
示例 2:
给定二叉树 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false 。
boolean balanced = true;
public boolean isBalanced(TreeNode root) {
int k = getDeep(root);
return balanced;
}
private int getDeep(TreeNode node) {
if (node == null) {
return 0;
}
int left = getDeep(node.left);
int right = getDeep(node.right);
if (left - right > 1 || right - left > 1) {
balanced = false;
}
return Math.max(left, right) + 1;
}
面试题 04.05. 合法二叉搜索树
实现一个函数,检查一棵二叉树是否为二叉搜索树。
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/ \
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
public boolean isValidBST(TreeNode root) {
if (root == null) {
return true;
}
int[] ans = getValid(root);
return ans[2] == 1;
}
private int[] getValid(TreeNode node) {
if (node.left == null && node.right == null) {
return new int[]{node.val, node.val, 1};
}
if (node.left == null) {
int[] ans = getValid(node.right);
if (ans[2] == 0) {
return new int[]{0, 0, 0};
}
return new int[]{node.val, ans[1], node.val < ans[0] ? 1 : 0};
}
if (node.right == null) {
int[] ans = getValid(node.left);
if (ans[2] == 0) {
return new int[]{0, 0, 0};
}
return new int[]{ans[0], node.val, node.val > ans[1] ? 1 : 0};
}
int[] left = getValid(node.left);
int[] right = getValid(node.right);
if (left[2] == 0 || right[2] == 0) {
return new int[]{0, 0, 0};
}
return new int[]{left[0], right[1], (node.val > left[1]) && (node.val < right[0]) ? 1 : 0};
}
面试题 04.06. 后继者
设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。
如果指定节点没有对应的“下一个”节点,则返回null。
示例 1:
输入: root = [2,1,3], p = 1
2
/ \
1 3
输出: 2
示例 2:
输入: root = [5,3,6,2,4,null,null,1], p = 6
5
/ \
3 6
/ \
2 4
/
1
输出: null
//标记前者
TreeNode node = null;
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
if (root == null) {
return null;
}
if (root.left != null) {
TreeNode node = inorderSuccessor(root.left, p);
if (node != null) {
//找到答案直接返回
return node;
}
}
if (node == p) {
//前者和p一样,直接返回本答案
return root;
} else {
//把root标记着前者
node = root;
}
if (root.right == null) {
return null;
}
return inorderSuccessor(root.right, p);
}
面试题 04.08. 首个共同祖先
设计并实现一个算法,找出二叉树中某两个节点的第一个共同祖先。不得将其他的节点存储在另外的数据结构中。注意:这不一定是二叉搜索树。
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输入: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
说明:
所有节点的值都是唯一的。
p、q 为不同节点且均存在于给定的二叉树中。
TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
//查询左节点
TreeNode left = lowestCommonAncestor(root.left, p, q);
if (ans != null) {
//如果有答案,直接返回
return ans;
}
if (left != null) {
//左节点有1个结果
if (root == p || root == q) {
//如果root也是,那成功了
ans = root;
return root;
}
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (right != null) {
//右节点也有结果了
//那root就是公共节点
ans = root;
return root;
} else {
return left;
}
}
//下面判断左节点查询无果
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (ans != null) {
//找到答案,直接返回
return ans;
}
if (right != null) {
if (root == p || root == q) {
//右结果是结果之一,就判断root了
ans = root;
return root;
} else {
return right;
}
}
if (root == p || root == q) {
return root;
}
return null;
}
面试题 04.09. 二叉搜索树序列
从左向右遍历一个数组,通过不断将其中的元素插入树中可以逐步地生成一棵二叉搜索树。给定一个由不同节点组成的二叉树,输出所有可能生成此树的数组。
示例:
给定如下二叉树
2
/ \
1 3
返回:
[
[2,1,3],
[2,3,1]
]
public List<List<Integer>> BSTSequences(TreeNode root) {
if (root == null) {
List<List<Integer>> ans = new ArrayList<>();
ans.add(new ArrayList<>());
return ans;
}
//查询倒序的
List<List<Integer>> left = getAns(root);
List<List<Integer>> ans = new ArrayList<>();
//倒置
for (List<Integer> list : left) {
List<Integer> res = new ArrayList<>(list.size());
for (int i = list.size() - 1; i >= 0; i--) {
res.add(list.get(i));
}
ans.add(res);
}
return ans;
}
private List<List<Integer>> getAns(TreeNode node) {
if (node.left == null && node.right == null) {
//如果没有子节点,那就是自己
List<List<Integer>> ans = new ArrayList<>();
List<Integer> list = new ArrayList<>();
list.add(node.val);
ans.add(list);
return ans;
}
if (node.right == null) {
//右节点为空时,所有的之前的可能性,最后加上本val即可
List<List<Integer>> anss = BSTSequences(node.left);
for (List<Integer> list : anss) {
list.add(node.val);
}
return anss;
}
if (node.left == null) {
//左节点为空时,所有的之前的可能性,最后加上本val即可
List<List<Integer>> anss = BSTSequences(node.right);
for (List<Integer> list : anss) {
list.add(node.val);
}
return anss;
}
List<List<Integer>> left = BSTSequences(node.left);
List<List<Integer>> right = BSTSequences(node.right);
List<List<Integer>> ans = new ArrayList<>();
for (List<Integer> lis1 : left) {
for (List<Integer> lis2 : right) {
//左节点的可能性与右节点的可能性,进行混合
addHH(ans, new ArrayList<>(), lis1, lis2, 0, 0, node.val);
}
}
return ans;
}
//混合规矩很简单,原来在前面的还在前面,最后加上本val即可
private void addHH(List<List<Integer>> ans, List<Integer> res, List<Integer> lis1, List<Integer> lis2,
int sta1, int sta2, int val) {
if (sta1 == lis1.size() && sta2 == lis2.size()) {
res.add(val);
ans.add(res);
//遍历结束
return;
}
if (sta1 == lis1.size()) {
res.add(lis2.get(sta2));
addHH(ans, res, lis1, lis2, sta1, sta2 + 1, val);
return;
}
if (sta2 == lis2.size()) {
res.add(lis1.get(sta1));
addHH(ans, res, lis1, lis2, sta1 + 1, sta2, val);
return;
}
List<Integer> res2 = new ArrayList<>(res);
//两个可能性分别考虑
res.add(lis1.get(sta1));
addHH(ans, res, lis1, lis2, sta1 + 1, sta2, val);
res2.add(lis2.get(sta2));
addHH(ans, res2, lis1, lis2, sta1, sta2 + 1, val);
}
面试题 04.10. 检查子树
检查子树。你有两棵非常大的二叉树:T1,有几万个节点;T2,有几万个节点。设计一个算法,判断 T2 是否为 T1 的子树。
如果 T1 有这么一个节点 n,其子树与 T2 一模一样,则 T2 为 T1 的子树,也就是说,从节点 n 处把树砍断,得到的树与 T2 完全相同。
示例1:
输入:t1 = [1, 2, 3], t2 = [2]
输出:true
示例2:
输入:t1 = [1, null, 2, 4], t2 = [3, 2]
输出:false
提示:
树的节点数目范围为[0, 20000]。
答:每个节点都去判断,如果所有节点数值都一样,判断会很多次。我的想法是,找到同一深度的,再去判断,能减少判断次数。
boolean ans = false;
public boolean checkSubTree(TreeNode t1, TreeNode t2) {
if (t2 == null) {
return true;
}
if (t1 == null) {
return false;
}
//获取t2的深度
int deep = getDeep(t2);
getDeepByCheck(t1, deep, t2);
return ans;
}
private int getDeepByCheck(TreeNode node, int deep, TreeNode t2) {
if (node == null) {
return 0;
}
//判断子节点
int thisDeep = Math.max(getDeepByCheck(node.left, deep, t2), getDeepByCheck(node.right, deep, t2)) + 1;
if (thisDeep == deep) {
if (ans) {
//如果已经成功,就不再判断
return thisDeep;
}
if (check(node, t2)) {
ans = true;
}
}
return thisDeep;
}
//判断是否一样
private boolean check(TreeNode node1, TreeNode node2) {
if (node1 == null && node2 == null) {
return true;
}
if (node1 == null || node2 == null) {
return false;
}
if (node1.val != node2.val) {
return false;
}
if (!check(node1.left, node2.left)) {
return false;
}
if (!check(node1.right, node2.right)) {
return false;
}
return true;
}
private int getDeep(TreeNode node) {
if (node == null) {
return 0;
}
return Math.max(getDeep(node.left), getDeep(node.right)) + 1;
}
面试题 04.12. 求和路径
给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。
示例:
给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
3
解释:和为 22 的路径有:[5,4,11,2], [5,8,4,5], [4,11,7]
提示:
节点总数 <= 10000
原答案。
int ans = 0;
public int pathSum(TreeNode root, int sum) {
check(root, sum);
return ans;
}
private List<Integer> check(TreeNode node, int sum) {
if (node == null) {
return new ArrayList<>();
}
List<Integer> set1 = check(node.left, sum);
List<Integer> set2 = check(node.right, sum);
List<Integer> res = new ArrayList<>();
for (Integer v : set1) {
res.add(v + node.val);
if (v + node.val == sum) {
ans++;
}
}
for (Integer v : set2) {
res.add(v + node.val);
if (v + node.val == sum) {
ans++;
}
}
res.add(node.val);
if (sum == node.val) {
ans++;
}
return res;
}
最快的答案,理解之后。
public int pathSum(TreeNode root, int sum) {
if (root == null) {
return 0;
}
Map<Integer, Integer> map = new HashMap<>();
//默认有一个sum为0的
map.put(0, 1);
return doPathSum(root, map, sum, 0);
}
//all代表从根节点一直加到本节点一路的值的总和
//map保留这一路的值
//如一个树一直left的结果是:1,2,3,4,5,6
//那all分别是1,3,6,10,15
//如果sum是3,那总共就有2个可能性,3-0,6-3;
//这么一考虑,就可以理解成,每个树从上到下的链表,我们用map来保存前序和,差值就是中间一部分的和的值
private int doPathSum(TreeNode node, Map<Integer, Integer> map, int sum, int all) {
if (node == null) {
return 0;
}
all += node.val;
int res = 0;
res += map.getOrDefault(all - sum, 0);
//递归下的都是要用到的
map.put(all, map.getOrDefault(all, 0) + 1);
res += doPathSum(node.left, map, sum, all);
res += doPathSum(node.right, map, sum, all);
//不干扰后面的,进行减除
map.put(all, map.get(all) - 1);
return res;
}