这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战
LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最小深度 2.
class Solution {
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
// null节点不参与比较
if (root.left == null && root.right != null) {
return 1 + minDepth(root.right);
}
// null节点不参与比较
if (root.right == null && root.left != null) {
return 1 + minDepth(root.left);
}
return 1 + Math.min(minDepth(root.left), minDepth(root.right));
}
}
112. 路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
示例: 给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ \
7 2 1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
class Solution {
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return sum - root.val == 0;
}
return hasPathSum(root.left, sum - root.val)
|| hasPathSum(root.right, sum - root.val);
}
}
113. 路径总和 II
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
示例: 给定如下二叉树,以及目标和 sum = 22,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[ [5,4,11,2], [5,8,4,5] ]
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null) return new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
if(root.val == sum && root.left == null && root.right == null){
List<Integer> arr = new ArrayList<>();
arr.add(root.val);
ans.add(arr);
return ans;
}
List<List<Integer>> left = pathSum(root.left,sum - root.val);
List<List<Integer>> right = pathSum(root.right,sum - root.val);
for(List<Integer> list : left){
//这里的插入到指定坐标会让后面的自动向后排序
list.add(0,root.val);
ans.add(list);
}
for(List<Integer> list : right){
list.add(0,root.val);
ans.add(list);
}
return ans;
}
}
114. 二叉树展开为链表
给定一个二叉树,原地将它展开为链表。
例如,给定二叉树
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
class Solution {
//先将左子树拉直,再将右子树拉直,置空左子树,拼接右子树
public void flatten(TreeNode root) {
if(root==null)
return ;
flatten(root.left);
flatten(root.right);
TreeNode temp = root.right;
root.right = root.left;
root.left = null;
while(root.right!=null)
root = root.right;
root.right = temp;
}
}
115. 不同的子序列
给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。
一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE" 是 "ABCDE" 的一个子序列,而 "AEC" 不是)
示例 1:
输入: S = "rabbbit", T = "rabbit" 输出: 3 解释:
如下图所示, 有 3 种可以从 S 中得到 "rabbit" 的方案。 (上箭头符号 ^ 表示选取的字母)
rabbbit
^^^^ ^^
rabbbit
^^ ^^^^
rabbbit
^^^ ^^^
示例 2:
输入: S = "babgbag", T = "bag" 输出: 5 解释:
如下图所示, 有 5 种可以从 S 中得到 "bag" 的方案。 (上箭头符号 ^ 表示选取的字母)
babgbag
^^ ^
babgbag
^^ ^
babgbag
^ ^^
babgbag
^ ^^
babgbag
^^^
PS: 还是佩服大佬们的面向测试用例编程 👍
/**
* 动态规划 但是效率并不高 20ms 35.83%
* 大部分都是二维动态规划 有的代码相同但是是5ms 估计是测试用例有变动
* 但是看到还是有节省的算法 所以一步一步往下优化
*
* * b a b g b a g
* * 1 1 1 1 1 1 1 1
* b 0 1 1 2 2 3 3 3
* a 0 0 1 1 1 1 4 4
* g 0 0 0 0 1 1 1 5
* @param s
* @param t
* @return
*/
public int numDistinct(String s, String t) {
int[][] dp = new int[t.length() + 1][s.length() + 1];
//初始化第一行
for(int j = 0; j <= s.length(); j++){
dp[0][j] = 1;
}
for(int i = 1; i <= t.length(); i++){
for(int j = 1; j <= s.length(); j++){
if(t.charAt(i-1) == s.charAt(j-1)){
dp[i][j] = dp[i-1][j-1] + dp[i][j-1];
}else {
dp[i][j] = dp[i][j-1];
}
}
}
return dp[t.length()][s.length()];
}
/**
* 二维换一维 严格按照二维的流程 参见上面矩阵 这个是15ms
* @param s
* @param t
* @return
*/
public int numDistinct2(String s, String t) {
int[] dp = new int[s.length() + 1];
Arrays.fill(dp, 1);
int pre = 1;
//每行算一次
for(int i = 0; i < t.length(); i++){
//0-n算n+1次
for(int j = 0; j <= s.length(); j++){
//先保存dp[j]下次用
int temp = dp[j];
if(j == 0){
dp[j] = 0;
}else {
if(t.charAt(i) == s.charAt(j-1)){
dp[j] = dp[j-1] + pre;
}else {
dp[j] = dp[j-1];
}
}
pre = temp;
}
}
return dp[s.length()];
}
/**
* 列主序 倒序计算 就不用保存临时值pre了
* 可以按上图二维矩阵的顺序模仿一下 这个是11ms
* @param s
* @param t
* @return
*/
public int numDistinct3(String s, String t) {
// dp[0]表示空串
int[] dp = new int[t.length() + 1];
// dp[0]永远是1,因为不管S多长,都只能找到一个空串,与T相等
dp[0] = 1;
for (int i = 0; i < s.length(); i++){
for (int j = t.length() - 1; j >= 0; j--) {
if (t.charAt(j) == s.charAt(i)) {
dp[j + 1] += dp[j];
}
}
}
return dp[t.length()];
}
/**
* 列主序 先构造字典 就不用遍历t了
* 这样就优化成了答案上的2ms的了
* @param s
* @param t
* @return
*/
public int numDistinct4(String s, String t) {
// dp[0]表示空串
int[] dp = new int[t.length() + 1];
// dp[0]永远是1,因为不管S多长,都只能找到一个空串,与T相等
dp[0] = 1;
//t的字典
int[] map = new int[128];
Arrays.fill(map, -1);
//从尾部遍历的时候可以遍历 next类似链表 无重复值时为-1,
//有重复时例如从rabbit的b开始索引在map[b] = 2 next[2] 指向下一个b的索引为3
// for (int j = t.length() - 1; j >= 0; j--) {
// if (t.charAt(j) == s.charAt(i)) {
// dp[j + 1] += dp[j];
// }
// }
//这段代码的寻址就可以从map[s.charAt(i)] 找到索引j 在用next[j] 一直找和 s.charAt(i)相等的字符 其他的就可以跳过了
//所以这个代码的优化 关键要理解 上面的一维倒算
int[] nexts = new int[t.length()];
for(int i = 0 ; i < t.length(); i++){
int c = t.charAt(i);
nexts[i] = map[c];
map[c] = i;
}
for (int i = 0; i < s.length(); i++){
char c = s.charAt(i);
for(int j = map[c]; j >= 0; j = nexts[j]){
dp[j + 1] += dp[j];
}
}
return dp[t.length()];
}
116. 填充每个节点的下一个右侧节点指针
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
示例:
输入:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":null,"right":null,"val":4},"next":null,"right":
{"$id":"4","left":null,"next":null,"right":null,"val":5},"val":2},"next":null,"right":{"$id":"5","left":
{"$id":"6","left":null,"next":null,"right":null,"val":6},"next":null,"right":
{"$id":"7","left":null,"next":null,"right":null,"val":7},"val":3},"val":1}
输出:{"$id":"1","left":{"$id":"2","left":{"$id":"3","left":null,"next":{"$id":"4","left":null,"next":{"$id":"5","left":null,"next":
{"$id":"6","left":null,"next":null,"right":null,"val":7},"right":null,"val":6},"right":null,"val":5},"right":null,"val":4},"next":
{"$id":"7","left":{"$ref":"5"},"next":null,"right":{"$ref":"6"},"val":3},"right":{"$ref":"4"},"val":2},"next":null,"right":
{"$ref":"7"},"val":1}
解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
PS: 这道题用json来做输入输出简直是要猿命了
class Solution {
public Node connect(Node root) {
if(root == null)
return root;
if(root.left != null)
root.left.next = root.right;
if(root.next != null && root.right != null){
root.right.next = root.next.left;
}
connect(root.left);
connect(root.right);
return root;
}
}
117. 填充每个节点的下一个右侧节点指针 II
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。 使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
示例:
输入:root = [1,2,3,4,5,null,7] 输出:[1,#,2,3,#,4,5,7,#] 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。
提示:
树中的节点数小于 6000 -100 <= node.val <= 100
class Solution {
public Node connect_1(Node root) {
if (root == null) {
return null;
}
// 借助队列实现层次遍历
LinkedList<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
Node node = queue.remove();
if (size > 0) {
node.next = queue.peek();
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
}
return root;
}
public Node connect(Node root) {
if (root == null) {
return null;
}
if (root.left != null) {
if (root.right != null) {
// 若右子树不为空,则左子树的 next 即为右子树
root.left.next = root.right;
} else {
// 若右子树为空,则右子树的 next 由根节点的 next 得出
root.left.next = nextNode(root.next);
}
}
if (root.right != null) {
// 右子树的 next 由根节点的 next 得出
root.right.next = nextNode(root.next);
}
// 先确保 root.right 下的节点的已完全连接,因 root.left 下的节点的连接
// 需要 root.left.next 下的节点的信息,若 root.right 下的节点未完全连
// 接(即先对 root.left 递归),则 root.left.next 下的信息链不完整,将
// 返回错误的信息。可能出现的错误情况如下图所示。此时,底层最左边节点将无
// 法获得正确的 next 信息:
// o root
// / \
// root.left o —— o root.right
// / / \
// o —— o o
// / / \
// o o o
connect(root.right);
connect(root.left);
return root;
}
private Node nextNode(Node node) {
while (node != null) {
if (node.left != null) {
return node.left;
}
if (node.right != null) {
return node.right;
}
node = node.next;
}
return null;
}
}
118. 杨辉三角
给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 5 输出:
[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1]
]
class Solution {
public List<List<Integer>> generate(int numRows) {
int[][] arry = new int[numRows][numRows];
List<List<Integer>> list = new ArrayList<List<Integer>>();
List<Integer> list1 = null;
for (int i = 0; i < numRows; i++) {
arry[i][0] = 1;
list1 = new ArrayList<Integer>();
list1.add(arry[i][0]);
for (int j = 1; j <= i; j++) {
//当前这一位来自与当前位置的上一位和当前位置的上一位的前一列的和
arry[i][j] = arry[i-1][j-1] +arry[i-1][j];
list1.add(arry[i][j]);
}
//当前行添加完,就把当前行添加到总结果中,
list.add(list1);
}
return list;
}
}
119. 杨辉三角 II
给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
在杨辉三角中,每个数是它左上方和右上方的数的和。
示例:
输入: 3 输出: [1,3,3,1] 进阶:
你可以优化你的算法到 O(k) 空间复杂度吗?
* 获取杨辉三角的指定行
* 直接使用组合公式C(n,i) = n!/(i!*(n-i)!)
* 则第(i+1)项是第i项的倍数=(n-i)/(i+1);
class Solution {
public List<Integer> getRow(int rowIndex) {
List<Integer> res = new ArrayList<>(rowIndex + 1);
long cur = 1;
for (int i = 0; i <= rowIndex; i++) {
res.add((int) cur);
cur = cur * (rowIndex-i)/(i+1);
}
return res;
}
}
120. 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if (triangle == null || triangle.size() == 0){
return 0;
}
// 只需要记录每一层的最小值即可
int[] dp = new int[triangle.size()+1];
for (int i = triangle.size() - 1; i >= 0; i--) {
List<Integer> curTr = triangle.get(i);
for (int j = 0; j < curTr.size(); j++) {
//这里的dp[j] 使用的时候默认是上一层的,赋值之后变成当前层
dp[j] = Math.min(dp[j],dp[j+1]) + curTr.get(j);
}
}
return dp[0];
}
}