面试题53-Ⅱ.0~n-1中缺失的数字
-
根据题意,数组可以按照以下规则划分为两部分。
- 左子数组: nums[i] = i;
- 右子数组: nums[i] ≠ i;
-
缺失的数字等于 “右子数组的首位元素” 对应的索引;因此考虑使用二分法查找 “右子数组的首位元素” 。
class Solution {
public int missingNumber(int[] nums) {
if(nums.length < 1) return -1;
int i = 0, j = nums.length - 1;
while(i <= j) {
int mid = i + (j - i) / 2;
//若相等,则右子数组的首位元素一定在闭区间[m+1,j]中,因此执行 i = mid + 1
if(nums[mid] == mid) i = mid + 1;
//若不等,则左子数组的末位元素一定在闭区间[i,m-1]中,因此执行 j = mid - 1
else j = mid - 1;
}
//跳出循环时,i指向右子树组首位元素,j指向左子数组末位元素
return i;
}
}
- 时间复杂度 O(logN): 二分法为对数级别复杂度。
- 空间复杂度 O(1): 几个变量使用常数大小的额外空间。
————————————————————————————————————————
面试题54、二叉搜索树的第k大节点
解题思路:
本文基于此性质:二叉搜索树的中序遍历为递增序列
-
根据以上性质,易得二叉搜索树的中序遍历倒序为递减序列
-
因此,求“二叉搜索树第 k 大的节点” 可转化为求 “此树的中序遍历倒序的第 k 个节点”。
其中需要注意的是: -
1.提前返回:若 k = 0,代表已找到目标节点,无序继续遍历,因此直接返回;
-
2.记录结果:若 k = 0,代表当前节点为第 k 大的节点,因此记录 res = root.val;
class Solution {
int res, k;
public int kthLargest(TreeNode root, int k) {
this.k = k;
dfs(root);
return res;
}
void dfs(TreeNode root) {
if(root == null) return;
dfs(root.right);
if(k == 0) return;
if(--k == 0) res = root.val;
dfs(root.left);
}
}
- 时间复杂度 O(N) : 当树退化为链表时(全部为右子节点),无论 k 的值大小,递归深度都为 N ,占用 O(N) 时间。
- 空间复杂度 O(N) : 当树退化为链表时(全部为右子节点),系统使用 O(N) 大小的栈空间。
————————————————————————————————————————
面试题55-Ⅰ.二叉树的深度
DFS
此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度等于左子树的深度与右子树的深度中的最大值 +1 。
算法解析:
-
1.终止条件: 当 root 为空,说明已越过叶节点,因此返回 深度 0 。
-
2.递推工作: 本质上是对树做后序遍历。
- 计算节点 root 的 左子树的深度 ,即调用 maxDepth(root.left);
- 计算节点 root 的 右子树的深度 ,即调用 maxDepth(root.right);
-
3.返回值: 返回此树的深度 ,即 max(maxDepth(root.left), maxDepth(root.right)) + 1。
class Solution {
//计算root为根节点的树的深度
public int maxDepth(TreeNode root) {
if(root == null) return 0;
//左子树深度与右子树深度中的最大值 + 1
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
- 时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
- 空间复杂度 O(N) : 最差情况下(当树退化为链表时),递归深度可达到 N 。
BFS
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
int dep = 0;
queue.add(root);
while(!queue.isEmpty()) {
int len = queue.size();
for(int i = 0; i < len; i++) {
TreeNode node = queue.poll();
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
//对当前层操作完,深度应该+1。操作一层,深度加一层
dep++;
}
return dep;
}
}
- 时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
- 空间复杂度 O(N) : 最差情况下(当树平衡时),队列 queue 同时存储 N/2 个节点。
————————————————————————————————————————
面试题55-Ⅱ.平衡二叉树
后序遍历-从底至顶
思路是对二叉树做后序遍历,从底至顶返回子树深度,若判定某子树不是平衡树则 “剪枝” ,直接向上返回。
class Solution {
public boolean isBalanced(TreeNode root) {
return dfs(root) != -1;
}
//计算root树的深度,若返回-1,则说明该树不是平衡二叉树,否则返回该树的深度
public int dfs(TreeNode root) {
if(root == null) return 0;
int left = dfs(root.left); //计算左子树的深度
if(left == -1) return -1; //若为-1,说明左子树不是平衡二叉树,直接返回-1
int right = dfs(root.right); //计算右子树的深度
if(right == -1) return -1; //若为-1,说明右子树不是平衡二叉树,直接返回-1
//如果左右子树的深度差 < 2,则说明以该节点是平衡二叉树的头节点,则返回该节点的深度,否则返回-1
return Math.abs(left - rigth) < 2 ? Math.max(left, right) + 1 : -1;
}
}
- 时间复杂度 O(N): N 为树的节点数;最差情况下,需要递归遍历树的所有节点。
- 空间复杂度 O(N): 最差情况下(树退化为链表时),系统递归需要使用 O(N) 的栈空间。
先序遍历-从顶至底
一个根节点为Node的子树要是一颗平衡树必须满足以下三个条件:
-
Node的左子树是平衡树
-
Node的右子树是平衡树
-
Node的左子树和右子树的深度差值小于等于1
abs(depth(root.left) - depth(root.right)) <= 1 :判断 当前子树 是否是平衡树;
isBalanced(root.left) : 先序遍历递归,判断 当前子树的左子树 是否是平衡树;
isBalanced(root.right) : 先序遍历递归,判断 当前子树的右子树 是否是平衡树;
class Solution {
//判断当前树是否为平衡树
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(depth(root.left) - depth(root.right)) <= 1
&& isBalanced(root.left) && isBalanced(root.right);
}
//计算root为根节点的树的深度
public int depth(TreeNode root) {
if(root == 0) return 0;
//该树的深度 = 它左子树的深度和右子树的深度中较大的那个 + 1
return Math.max(depth(root.left), depth(root.right)) + 1;
}
}