111.二叉树的最小深度
该题目和之前的最大深度有点类似,但是该题目因为是求的是从根节点出发到最近的一个叶子结点,所以当我们出现结点只有一边子树的时候,我们需要走单边,而不是碰到了子树就返回,那么算出来的结果就是有问题的(举例是树退化成链表的样式)
//树的最小深度(从根结点出发,到达最近的叶子结点的结点个数)
public int minDepth(TreeNode root) {
if(root == null) {
return 0;
}
//说明找到了叶子结点
if(root.left == null && root.right == null) {
return 1;
}
//说明左子树为空,只能往右子树进行递归
if(root.left == null) {
return minDepth(root.right) + 1;
}
//说明右子树为空,只能往左子树进行递归
if(root.right == null) {
return minDepth(root.left) + 1;
}
//当左右子树均不为空,则两边同时递归
return Math.min(minDepth(root.left),minDepth(root.right)) + 1;
}
112.路径总和
该题目需要注意的特点就是start = 根结点,end = 叶子结点。
这个条件必须满足,所以如果到终点时候不是叶子结点,那么我们可以直接返回false。
类似结点左子树为空,我们访问左子树,那么可以直接返回false,然后去查看右子树的路径。
如果查询路径呢,这里采用的是DFS的方法,一直遍历到叶子结点才进行sum 与 target 的比较,相同则true。
//路径总和(路径终点必须为叶子结点)
public boolean hasPathSum(TreeNode root, int sum) {
return dfs(root,0,sum);
}
public boolean dfs(TreeNode root, int sum, int target) {
//说明此路不通,直接返回false
if(root == null) {
return false;
}
//首先加上自身
sum += root.val;
//说明碰到叶子结点,加上自身后可直接返回
if(root.left == null && root.right == null) {
return sum == target;
}
return dfs(root.left,sum,target) || dfs(root.right,sum,target);
}
113.路径总和2
和112差不多,不过这里需要记录一下路径怎么走的,所以需要保存和回溯。
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>> lists = new ArrayList<>();
dfs(lists,new ArrayList<>(),root,0,sum);
return lists;
}
public void dfs(List<List<Integer>> lists, List<Integer> list, TreeNode root, int sum, int target) {
//说明此路不通,直接返回false
if(root == null) {
return;
}
//首先加上自身
sum += root.val;
list.add(root.val);
//说明碰到叶子结点,加上自身后可直接返回
if(root.left == null && root.right == null) {
if(sum == target) {
lists.add(new ArrayList<>(list));
list.remove(list.size() - 1);
return;
}
}
dfs(lists, list, root.left, sum, target);
dfs(lists, list, root.right, sum, target);
list.remove(list.size() - 1);
}
114.二叉树展开成链表
该题目我的思路是:
1.将root的左右子树分别完成链表化(即所有节点都在右子树上)
2.在将左子树尾结点与右子树头结点进行连接。
3.将root右节点连接左子树头结点,并且将root左节点断开。
//将树退化成链表(左子树尾部连接右子树头部)
public void flatten(TreeNode root) {
if(root == null) {
return;
}
//将左右子树分别成链表(此时root还没有将左右子树进行合并)
flatten(root.left);
flatten(root.right);
if(root.left != null) { //左子树为空,那么所有元素都在右子树不用处理
TreeNode tail = root.left;
//找寻左子树的尾结点
while(tail.right != null) {
tail = tail.right;
}
tail.right = root.right;
root.right = root.left;
root.left = null;
}
}
116.填充每个节点的下一个右侧结点指针
第一种方式就是使用层次遍历的方式,需要使用额外空间。
第二种使用递归的方式(该题目说明递归不算空间复杂度),我们通过父亲们结点来完成孩子next指针的指向问题。
特殊情况就是父亲的right指针应该指向父亲的兄弟节点的left,这个情况需要考虑
//给定的是完美二叉树(兄弟之间有一个向右的next指针)
public Node connect(Node root) {
//为它们的子孙进行横向指针
dfs(root);
return root;
}
public void dfs(Node root) {
if(root == null) {
return;
}
//因为是完全二叉树,如果左边没有,那么右边一定没有
if(root.left == null) {
return;
}
//左子树指向右子树
root.left.next = root.right;
if(root.right != null) {
//通过兄弟指针进行访问
if(root.next != null) { //说明还有兄弟指针
root.right.next = root.next.left;
}
}
dfs(root.left);
dfs(root.right);
}
117.填充每个节点的下一个右侧结点
该题我是用的层次遍历
//给定的是非完全二叉树(兄弟之间有一个向右的next指针)
public Node connect(Node root) {
//为它们的子孙进行横向指针
Deque<Node> deque = new LinkedList<>();
if(root == null) {
return null;
}
deque.addLast(root);
//选择层次遍历
while(deque.size() > 0) {
int size = deque.size();
//出队队首元素
Node prev = deque.pollFirst();
if(prev.left != null) {
deque.addLast(prev.left);
}
if(prev.right != null) {
deque.addLast(prev.right);
}
Node now = null;
for(int i=1; i<size; i++) {
now = deque.pollFirst();
prev.next = now;
prev = now;
if(now.left != null) {
deque.addLast(now.left);
}
if(now.right != null) {
deque.addLast(now.right);
}
}
}
return root;
}
118.杨辉三角
就很简单的动态规划,因为杨辉三角左对齐以后可以发现,当前值是上一行 + 左上角的和
//杨慧三角
public List<List<Integer>> generate(int numRows) {
if(numRows == 0) {
return new ArrayList<>();
}
int[][] dp = new int[numRows][numRows];
//初始化第一列
for(int i=0; i<dp.length; i++) {
dp[i][0] = 1;
}
for(int i=1; i<dp.length; i++) {
for(int j=1; j<=i; j++) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}
}
List<List<Integer>> lists = new ArrayList<>();
for(int i=0; i<dp.length; i++) {
List<Integer> list = new ArrayList<>();
for(int j=0; j<=i; j++) {
list.add(dp[i][j]);
}
lists.add(list);
}
return lists;
}
119.杨辉三角2
和118一样,只不过这里是指定一行,一样也需要一个dp数组对前面行的信息进行保存
//杨慧三角返回第几行(从 0 开始计数)
public List<Integer> getRow(int rowIndex) {
int[][] dp = new int[rowIndex + 1][rowIndex + 1];
//初始化第一列
for(int i=0; i<dp.length; i++) {
dp[i][0] = 1;
}
for(int i=1; i<dp.length; i++) {
for(int j=1; j<=i; j++) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}
}
List<Integer> list = new ArrayList<>();
for(int i=0; i<rowIndex+1; i++) {
list.add(dp[rowIndex][i]);
}
return list;
}
120.三角形最小路径和
这种路径问题,肯定是使用的动态规划的方法,题目中的要求走法是只能往下一层同一下标或者右下角的位置进行移动。
那么我们动态规划的思想就是反着来,即只能往正上方或者左上角位置移动。
简单的方法就是使用二维数组O(N^2),但是题目的最优化空间复杂度是O(N),我们这里可以发现其实他这个走法只是跟上一层有关系,所以我们可以将二维数组优化成一位数组(这个数组保存的是上一行的最短U路径即可)
在这个时候,我们访问对应行也有讲究。
如果我们正序访问的话,那么我们会发现会出现覆盖现象(类比插入排序),所以这个时候我们必须倒序访问,这样才不会影响前面的访问问题(保证访问的值都是上一层的旧值)
注意:需要考虑边界问题,比如第一列只能向上走,最后一列只能向左上角走。
//三角形最短路径(只能向下,或者想右下移动)
//使用动态规划解决问题,则考虑向上和左上移动,看看那个路径小
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle.size() == 0) {
return 0;
}
int size = triangle.size();
int ans = Integer.MAX_VALUE;
//用于保存上一层的信息
int[] dp = new int[size];
for(int i=0; i<size; i++) {
//倒序访问,防止覆盖上一层信息
for(int j=i; j>=0; j--) {
if(j == i) { //最后一列,只能由上一层的最右边移动过来
if(j > 1) {
dp[j] = dp[j - 1] + triangle.get(i).get(j);
}else { // 说明是第一行(第一行第一列情况),直接赋初值即可
dp[j] = triangle.get(i).get(j);
}
continue;
}
if(j == 0) { //第一列,只能有正上方移动过来
// j = i = 0 前面处理过了,这里不需要考虑(即非第一行,肯定有上一层的存在)
dp[j] = dp[j] + triangle.get(i).get(j);
continue;
}
dp[j] = Math.min(dp[j],dp[j - 1]) + triangle.get(i).get(j);
}
}
for(int i : dp) {
ans = Math.min(ans,i);
}
return ans;
}