9.21 复习

This commit is contained in:
zhangsan 2025-09-21 20:29:19 +08:00
parent 585a2321f3
commit 5fd8c62f8a
24 changed files with 298 additions and 213 deletions

View File

@ -14,7 +14,7 @@ import java.util.Set;
输入nums = [4,6,7,7] 输入nums = [4,6,7,7]
输出[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]] 输出[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
* 链接https://leetcode.cn/problems/palindrome-partitioning/ * 链接https://leetcode.cn/problems/non-decreasing-subsequences/
*/ */
//不会做 //不会做
public class FindSubsequences { public class FindSubsequences {

View File

@ -1,4 +1,5 @@
package binary_search; package binary_search;
/** /**
* 题目 33. 搜索旋转排序数组 (search) * 题目 33. 搜索旋转排序数组 (search)
* 描述整数数组 nums 按升序排列数组中的值 互不相同 * 描述整数数组 nums 按升序排列数组中的值 互不相同

View File

@ -9,27 +9,24 @@ package bitwise;
* 链接https://leetcode.cn/problems/add-binary/ * 链接https://leetcode.cn/problems/add-binary/
*/ */
public class AddBinary { public class AddBinary {
public String addBinary(String a, String b) { public String addBinary(String a, String b) {
int len1=a.length(),len2=b.length(); int i = a.length() - 1;
int done=0; int j = b.length() - 1;
int carry=0; int carry = 0;
int cura,curb; StringBuilder sb = new StringBuilder();
StringBuilder sb=new StringBuilder();
while (done<Math.max(len1,len2)){ while (i >= 0 || j >= 0 || carry > 0) {
if(done<len1) int x = (i >= 0) ? a.charAt(i--) - '0' : 0;
cura=a.charAt(len1-1-done)-'0'; int y = (j >= 0) ? b.charAt(j--) - '0' : 0;
else cura=0; int sum = x + y + carry;
if(done<len2) sb.append(sum % 2);
curb=b.charAt(len2-1-done)-'0'; carry = sum / 2;
else curb=0;
int cur=(cura+curb+carry)%2;
carry=(cura+curb+carry)/2;
sb.insert(0,cur);
done++;
} }
if(carry==1)sb.insert(0,'1'); return sb.reverse().toString();
return sb.toString();
} }
} }

View File

@ -2,9 +2,9 @@ package digit;
/** /**
* 题目 66. 加一 (plusOne) * 题目 66. 加一 (plusOne)
* 描述给定一个由 整数 组成的 非空 数组所表示的非负整数在该数的基础上加一 * 描述给定一个由 整数 组成的 非空 数组所表示的非负整数在该数的基础上加一
*
* 最高位数字存放在数组的首位 数组中每个元素只存储单个数字 * 最高位数字存放在数组的首位 数组中每个元素只存储单个数字
*
* 你可以假设除了整数 0 之外这个整数不会以零开头 * 你可以假设除了整数 0 之外这个整数不会以零开头
* *
示例 1 示例 1
@ -15,6 +15,12 @@ package digit;
* 链接https://leetcode.cn/problems/plus-one/ * 链接https://leetcode.cn/problems/plus-one/
*/ */
public class PlusOne { public class PlusOne {
/*
循环从数组的最后一位最低位开始
每次先对当前位 +1
再取 % 10这样如果是 10 就会变成 0表示需要向前一位进位
如果进位之后该位不是 0说明没有继续进位的需求可以直接返回结果
*/
public int[] plusOne(int[] digits) { public int[] plusOne(int[] digits) {
for (int i = digits.length - 1; i >= 0; i--) { for (int i = digits.length - 1; i >= 0; i--) {
digits[i]++; digits[i]++;

View File

@ -6,6 +6,7 @@ import java.util.Arrays;
* 题目 518. 零钱兑换 II (change) * 题目 518. 零钱兑换 II (change)
* 描述给你一个整数数组 coins 表示不同面额的硬币另给一个整数 amount 表示总金额 * 描述给你一个整数数组 coins 表示不同面额的硬币另给一个整数 amount 表示总金额
* 请你计算并返回可以凑成总金额的硬币组合数如果任何硬币组合都无法凑出总金额返回 0 * 请你计算并返回可以凑成总金额的硬币组合数如果任何硬币组合都无法凑出总金额返回 0
*
* 假设每一种面额的硬币有无限个 * 假设每一种面额的硬币有无限个
* 题目数据保证结果符合 32 位带符号整数 * 题目数据保证结果符合 32 位带符号整数
@ -22,6 +23,23 @@ import java.util.Arrays;
*/ */
//二刷会做 //二刷会做
public class Change { public class Change {
public int change2(int amount, int[] coins) {
int[]dp=new int[amount+1];
// Arrays.fill(dp,amount+1);
dp[0]=1;
for (int i = 0; i < coins.length; i++) {
for (int j = 1; j <= amount; j++) {
if(j>=coins[i])
dp[j]=dp[j]+dp[j-coins[i]];
}
}
return dp[amount];
}
public int change1(int amount, int[] coins) { public int change1(int amount, int[] coins) {
int[] dp=new int[amount+1]; int[] dp=new int[amount+1];
dp[0]=1; dp[0]=1;

View File

@ -18,16 +18,31 @@ import java.util.PriorityQueue;
//没做出来 //没做出来
//二刷会做 //二刷会做
public class LengthOfLIS { public class LengthOfLIS {
/**
* 动态规划解法
* 定义dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度
* 转移方程dp[i] = max(dp[j] + 1)其中 j < i nums[j] < nums[i]
* 答案所有 dp[i] 的最大值
*/
public int lengthOfLIS1(int[] nums) { public int lengthOfLIS1(int[] nums) {
int length=nums.length; int length = nums.length;
int[] dp=new int[length]; // dp[i] nums[i] 结尾的最长递增子序列的长度
int MMax=1; int[] dp = new int[length];
Arrays.fill(dp,1); // 记录全局最大值至少为 1单个元素
int MMax = 1;
// 初始化每个元素单独成序列时长度为 1
Arrays.fill(dp, 1);
// 枚举每个元素作为结尾
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
// 向前找比 nums[i] 小的元素
for (int j = 0; j < i; j++) { for (int j = 0; j < i; j++) {
if(nums[i]>nums[j]) { if (nums[i] > nums[j]) {
// 如果 nums[i] 可以接在 nums[j] 后面
dp[i] = Math.max(dp[i], dp[j] + 1); dp[i] = Math.max(dp[i], dp[j] + 1);
MMax=Math.max(MMax,dp[i]); // 更新全局最大值
MMax = Math.max(MMax, dp[i]);
} }
} }
} }

View File

@ -30,27 +30,34 @@ public class LongestCommonSubsequence {
} }
/**
* 动态规划解法
* 定义dp[i][j] 表示 text1 i 个字符 text2 j 个字符 的最长公共子序列长度
* 转移方程
* 如果 text1[i-1] == text2[j-1] dp[i][j] = dp[i-1][j-1] + 1
* 否则dp[i][j] = max(dp[i-1][j], dp[i][j-1])
* 初始化dp[0][*] = 0, dp[*][0] = 0空串和任何串的 LCS 都是 0
* 答案dp[n][m]
*/
public int longestCommonSubsequence(String text1, String text2) { public int longestCommonSubsequence(String text1, String text2) {
int n=text1.length(),m=text2.length(); int n = text1.length(), m = text2.length();
int[][]dp=new int[n+1][m+1]; // dp[i][j] 表示 text1 i 个字符 text2 j 个字符 LCS 长度
int[][] dp = new int[n + 1][m + 1];
// 遍历所有子串的组合
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) { for (int j = 1; j <= m; j++) {
if(text1.charAt(i-1)==text2.charAt(j-1)) if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j]=dp[i-1][j-1]+1; // 当前字符相同可以在 LCS 的基础上 +1
else dp[i][j] = dp[i - 1][j - 1] + 1;
dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]); } else {
// 当前字符不同只能跳过 text1[i-1] text2[j-1]
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
} }
} }
}
// 最终答案text1 的前 n 个字符和 text2 的前 m 个字符的 LCS
return dp[n][m]; return dp[n][m];
} }
} }

View File

@ -1,7 +1,4 @@
package dynamic_programming; package dynamic_programming;
import java.util.Arrays;
/** /**
* 题目 5. 最长回文子串 (longestPalindrome) * 题目 5. 最长回文子串 (longestPalindrome)
* 描述给你一个字符串 s找到 s 中最长的 回文 子串 * 描述给你一个字符串 s找到 s 中最长的 回文 子串
@ -16,7 +13,13 @@ import java.util.Arrays;
*/ */
//二刷不会 //二刷不会
public class LongestPalindrome { public class LongestPalindrome {
/*
遍历顺序为什么是 i 从大到小j 从小到大
dp[i][j] 依赖 dp[i+1][j-1]
要保证在计算 dp[i][j] dp[i+1][j-1] 已经算出来
所以外层循环必须让 i 从大到小保证 i+1 的状态先计算过
内层循环 j 自然从 i 往右递增覆盖所有子串
*/
public String longestPalindrome(String s) { public String longestPalindrome(String s) {
int n = s.length(); int n = s.length();
if (n < 2) return s; if (n < 2) return s;

View File

@ -1,66 +1,53 @@
package dynamic_programming; package dynamic_programming;
/** /**
* 题目 188. 买卖股票的最佳时机 IV (maxProfit) * 题目188. 买卖股票的最佳时机 IV (maxProfit)
* 描述给你一个整数数组 prices 和一个整数 k 其中 prices[i] 是某支给定的股票在第 i 天的价格 * 描述给你一个整数数组 prices 和一个整数 k 其中 prices[i] 是某支给定的股票在第 i 天的价格
* 设计一个算法来计算你所能获取的最大利润你最多可以完成 k 笔交易也就是说你最多可以买 k k * 设计一个算法来计算你所能获取的最大利润你最多可以完成 k 笔交易
* 注意你不能同时参与多笔交易你必须在再次购买前出售掉之前的股票 * 注意你不能同时参与多笔交易你必须在再次购买前出售掉之前的股票
*
* 示例 1 * 示例 1
输入k = 2, prices = [2,4,1] * 输入k = 2, prices = [2,4,1]
输出2 * 输出2
解释在第 1 (股票价格 = 2) 的时候买入在第 2 (股票价格 = 4) 的时候卖出这笔交易所能获得利润 = 4-2 = 2 * 解释在第 1 (股票价格 = 2) 的时候买入在第 2 (股票价格 = 4) 的时候卖出这笔交易所能获得利润 = 4-2 = 2
*
* 链接https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/ * 链接https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
*/ */
//不会 //不会
//二刷不会 //二刷不会
public class MaxProfit4 { public class MaxProfit4 {
/**
* 状态定义
* dp[i][j] 表示第 i 天结束时所处状态 j 下的最大利润
* 状态编号
* 0: 没有操作初始状态
* 1: 第一次买入
* 2: 第一次卖出
* 3: 第二次买入
* 4: 第二次卖出
* ...
* 2*k-1: k 次买入
* 2*k : k 次卖出
*/
public int maxProfit(int k, int[] prices) { public int maxProfit(int k, int[] prices) {
// 如果没有价格数据无法交易利润为 0 int len = prices.length;
if (prices.length == 0) return 0; if (len == 0) return 0;
int n = prices.length; int[][] dp = new int[len][2 * k + 1];
// dp[i][j] i 天结束时处于状态 j时能获得的最大利润 // 初始化所有买入状态第一天买入就是 -prices[0]
// 状态 j 0 2*k
// j = 0 从未操作过可以看作初始状态利润为 0
// j = 2*t-1 t 次买入后持有股票的状态
// j = 2*t t 次卖出后空仓的状态
int[][] dp = new int[n][2 * k + 1];
// 初始化 0 天买入的那些状态
// 对于每一笔交易 t1 t k在第 0 买入手里就是 prices[0]
for (int t = 1; t <= k; t++) { for (int t = 1; t <= k; t++) {
dp[0][2*t - 1] = -prices[0]; dp[0][2 * t - 1] = -prices[0];
// dp[0][2*t] 默认是 0还没卖出自然利润为 0
} }
// 从第 1 天开始逐日更新 for (int i = 1; i < len; i++) {
for (int i = 1; i < n; i++) {
// 枚举交易次数
for (int t = 1; t <= k; t++) { for (int t = 1; t <= k; t++) {
int buyState = 2*t - 1; // t 次买入 int buy = 2 * t - 1; // t 次买入
int sellState = 2*t; // t 次卖出 int sell = 2 * t; // t 次卖出
// 转移方程 1买入状态 dp[i][buy] = Math.max(dp[i - 1][buy], dp[i - 1][buy - 1] - prices[i]); // 买入
// 要么继承昨天已经买入的状态 dp[i-1][buyState] dp[i][sell] = Math.max(dp[i - 1][sell], dp[i - 1][buy] + prices[i]); // 卖出
// 要么今天刚用上一次卖出的资金买入 dp[i-1][buyState-1] 扣除 prices[i]
dp[i][buyState] = Math.max(
dp[i-1][buyState],
dp[i-1][buyState - 1] - prices[i]
);
// 转移方程 2卖出状态
// 要么继承昨天已经卖出的状态 dp[i-1][sellState]
// 要么今天把第 t 次买入的股票卖出 dp[i-1][buyState] 上加上 prices[i]
dp[i][sellState] = Math.max(
dp[i-1][sellState],
dp[i-1][buyState] + prices[i]
);
} }
} }
// 答案 n-1 完成第 k 次卖出后的最大利润 return dp[len - 1][2 * k];
return dp[n-1][2*k];
} }
} }

View File

@ -19,42 +19,17 @@ public class MaxProfit5 {
int n = prices.length; int n = prices.length;
if (n == 0) return 0; if (n == 0) return 0;
// dp[i][0]: 持有股票 int[][] dp = new int[n][3];
// dp[i][1]: 不持有且不在冷冻期可买入/休息 dp[0][0] = -prices[0]; // 持有
// dp[i][2]: 今天卖出刚卖出 dp[0][1] = 0; // 空仓
// dp[i][3]: 今天冷冻期昨天卖出 dp[0][2] = 0; // 冷冻期
int[][] dp = new int[n][4];
// 0 天的初始状态
dp[0][0] = -prices[0]; // 买入
dp[0][1] = 0; // 空仓休息
dp[0][2] = 0; // 不可能刚卖出
dp[0][3] = 0; // 不可能处于冷冻期
// 从第 1 天开始状态转移
for (int i = 1; i < n; i++) { for (int i = 1; i < n; i++) {
int p = prices[i]; dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] - prices[i]); // 持有
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][2]); // 空仓
// 1. 持有股票要么延续持有要么从空仓/冷冻期买入 dp[i][2] = dp[i-1][0] + prices[i]; // 冷冻期
dp[i][0] = Math.max(
dp[i-1][0],
Math.max(dp[i-1][1] - p, dp[i-1][3] - p)
);
// 2. 不持有且不在冷冻期要么昨儿就休息要么昨儿冷冻期结束
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
// 3. 今天卖出一定是从持有状态卖出
dp[i][2] = dp[i-1][0] + p;
// 4. 今天冷冻期只能由昨天卖出进入
dp[i][3] = dp[i-1][2];
} }
// 最后一天不持有股票的三种状态取最大 return Math.max(dp[n-1][1], dp[n-1][2]);
return Math.max(
Math.max(dp[n-1][1], dp[n-1][2]),
dp[n-1][3]
);
} }
} }

View File

@ -25,6 +25,7 @@ package graph;
* 链接https://leetcode.cn/problems/clone-graph/ * 链接https://leetcode.cn/problems/clone-graph/
*/ */
//不会做 //不会做
//二刷不会
import graph.Node; import graph.Node;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -18,24 +18,21 @@ package graph;
* 链接https://leetcode.cn/problems/flood-fill/ * 链接https://leetcode.cn/problems/flood-fill/
*/ */
public class FloodFill { public class FloodFill {
void dfs(int[][] image,int[][] visited,int origin,int i,int j,int color){ void dfs(int[][] image, int i, int j, int origin, int color) {
int r=image.length; int r = image.length, c = image[0].length;
int c=image[0].length; if (i < 0 || i >= r || j < 0 || j >= c || image[i][j] != origin) return;
if(i<0||i>=r||j<0||j>=c||visited[i][j]==1||image[i][j]!=origin) image[i][j] = color;
return; dfs(image, i - 1, j, origin, color);
image[i][j]=color; dfs(image, i + 1, j, origin, color);
visited[i][j]=1; dfs(image, i, j - 1, origin, color);
dfs(image,visited,origin,i-1,j,color); dfs(image, i, j + 1, origin, color);
dfs(image,visited,origin,i+1,j,color);
dfs(image,visited,origin,i,j-1,color);
dfs(image,visited,origin,i,j+1,color);
} }
public int[][] floodFill(int[][] image, int sr, int sc, int color) { public int[][] floodFill(int[][] image, int sr, int sc, int color) {
int r=image.length; int origin = image[sr][sc];
int c=image[0].length; if (origin == color) return image; // 避免死循环
int origin=image[sr][sc]; dfs(image, sr, sc, origin, color);
int[][] visited=new int[r][c];
dfs(image,visited,origin,sr,sc,color);
return image; return image;
} }
} }

View File

@ -22,16 +22,16 @@ import java.util.*;
public class MinMutation { public class MinMutation {
/** /**
* 准备工作 * 准备工作
*
* 将基因库 bank 中的有效基因串存入一个 Set<String> valid用于 O(1) 检查 * 将基因库 bank 中的有效基因串存入一个 Set<String> valid用于 O(1) 检查
* 如果 endGene 不在 valid 说明无法达到目标直接返回 1 * 如果 endGene 不在 valid 说明无法达到目标直接返回 1
* BFS 初始化 * BFS 初始化
*
* 使用队列 Queue<String> 存放当前层当前步数所有可达的基因串 * 使用队列 Queue<String> 存放当前层当前步数所有可达的基因串
* 另用一个 Set<String> visited 记录已经访问过的基因串避免重复遍历 * 另用一个 Set<String> visited 记录已经访问过的基因串避免重复遍历
* 将起点 startGene 入队标记已访问将步数 steps 0 * 将起点 startGene 入队标记已访问将步数 steps 0
* 层次遍历 * 层次遍历
*
* 每次遍历一层时取出当前队列大小 sz表示这一层共有 sz 个节点要处理 * 每次遍历一层时取出当前队列大小 sz表示这一层共有 sz 个节点要处理
* 对每个节点基因串进行 * 对每个节点基因串进行
* 若它等于 endGene则返回当前 steps * 若它等于 endGene则返回当前 steps

View File

@ -8,11 +8,11 @@ class Node {
public List<Node> neighbors; public List<Node> neighbors;
public Node() { public Node() {
val = 0; val = 0;
neighbors = new ArrayList<Node>(); neighbors = new ArrayList<>();
} }
public Node(int _val) { public Node(int _val) {
val = _val; val = _val;
neighbors = new ArrayList<Node>(); neighbors = new ArrayList<>();
} }
public Node(int _val, ArrayList<Node> _neighbors) { public Node(int _val, ArrayList<Node> _neighbors) {
val = _val; val = _val;

View File

@ -20,35 +20,61 @@ package greedy;
* 链接https://leetcode.cn/problems/gas-station/ * 链接https://leetcode.cn/problems/gas-station/
*/ */
public class CanCompleteCircuit { public class CanCompleteCircuit {
/*
先计算出每个加油站的盈余 diff[i] = gas[i] - cost[i]
然后遍历每个可能的起点
如果 diff[i] >= 0就从这里尝试出发模拟一圈
如果中途 cur < 0说明这个起点不可行退出
如果能跑完一圈就返回这个起点
时间复杂度最坏情况下是 O(n^2)因为可能每个起点都尝试一遍
*/
public int canCompleteCircuit1(int[] gas, int[] cost) { public int canCompleteCircuit1(int[] gas, int[] cost) {
int sumgas=0,sumcost=0; int sumGas = 0, sumCost = 0;
int[] diff=new int[gas.length]; int[] diff = new int[gas.length];
// 1. 计算每个加油站的盈余 diff[i] = gas[i] - cost[i]
for (int i = 0; i < gas.length; i++) { for (int i = 0; i < gas.length; i++) {
sumgas+=gas[i]; sumGas += gas[i];
sumcost+=cost[i]; sumCost += cost[i];
diff[i]=gas[i]-cost[i]; diff[i] = gas[i] - cost[i];
} }
if(sumcost>sumgas)
return -1; // 2. 如果总消耗大于总油量不可能绕一圈
if (sumCost > sumGas) return -1;
// 3. 枚举每个加油站作为起点
for (int i = 0; i < diff.length; i++) { for (int i = 0; i < diff.length; i++) {
if(diff[i]>=0){ // 只有当前站盈余不为负才有可能作为起点
int j=(i+1)%diff.length; if (diff[i] >= 0) {
int cur=diff[i]; int j = (i + 1) % diff.length; // 下一个站
boolean flag=true; int cur = diff[i]; // 当前油箱剩余
while(j!=i){ boolean canTravel = true;
cur+=diff[j];
if(cur<0) { // 4. 尝试从 i 出发跑一圈
flag=false; while (j != i) {
cur += diff[j];
if (cur < 0) { // 中途油不够失败
canTravel = false;
break; break;
} }
j=(j+1)%diff.length; j = (j + 1) % diff.length;
} }
if(flag)
return i; // 5. 如果成功跑完一圈返回起点
if (canTravel) return i;
} }
} }
return -1; return -1;
} }
/*
贪心法,如果从 A 出发到不了 B那么 A A B 之间的点都不可能是起点
用一个变量 tank 记录从当前候选起点开始跑的盈余油量
如果 tank < 0说明到不了当前点直接跳过这一段把起点移到 i+1
最终如果全局总盈余 total >= 0返回最后更新的起点
时间复杂度O(n)只需一遍扫描
*/
public int canCompleteCircuit(int[] gas, int[] cost) { public int canCompleteCircuit(int[] gas, int[] cost) {
int total = 0; // 总盈余记录所有加油站油量差值的总和 int total = 0; // 总盈余记录所有加油站油量差值的总和
int tank = 0; // 当前油箱剩余当前起点下的油量累计 int tank = 0; // 当前油箱剩余当前起点下的油量累计

View File

@ -16,7 +16,14 @@ package greedy;
* 链接https://leetcode.cn/problems/candy/ * 链接https://leetcode.cn/problems/candy/
*/ */
//没想到想复杂了 //没想到想复杂了
//二刷不会
public class Candy { public class Candy {
/*
每个孩子至少 1 颗糖果
如果 ratings[i] > ratings[i-1]那么 candies[i] > candies[i-1]
如果 ratings[i] > ratings[i+1]那么 candies[i] > candies[i+1]
也就是说每个孩子的糖果数只和相邻孩子比较相关不需要全局比较
*/
public int candy(int[] ratings) { public int candy(int[] ratings) {
int n = ratings.length; int n = ratings.length;
int[] candies = new int[n]; int[] candies = new int[n];

View File

@ -17,6 +17,8 @@ import java.util.Arrays;
* 链接https://leetcode.cn/problems/assign-cookies/ * 链接https://leetcode.cn/problems/assign-cookies/
*/ */
//二刷会做
public class FindContentChildren { public class FindContentChildren {
public int findContentChildren(int[] g, int[] s) { public int findContentChildren(int[] g, int[] s) {
int i=0,j=0,cnt=0; int i=0,j=0,cnt=0;

View File

@ -19,6 +19,7 @@ package greedy;
//不会 https://programmercarl.com/0045.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8FII.html#%E6%80%9D%E8%B7%AF //不会 https://programmercarl.com/0045.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8FII.html#%E6%80%9D%E8%B7%AF
//二刷不会 //二刷不会
//三刷不会
public class Jump { public class Jump {
//要从覆盖范围出发不管怎么跳覆盖范围内一定是可以跳到的以最小的步数增加覆盖范围覆盖范围一旦覆盖了终点得到的就是最少步数 //要从覆盖范围出发不管怎么跳覆盖范围内一定是可以跳到的以最小的步数增加覆盖范围覆盖范围一旦覆盖了终点得到的就是最少步数
public int jump(int[] nums) { public int jump(int[] nums) {

View File

@ -42,9 +42,9 @@ public class MergeKLists {
if (l == r) return lists[l]; if (l == r) return lists[l];
if (l > r) return null; if (l > r) return null;
int mid = (l + r) >> 1; int mid = (l + r) >> 1;
return mergeTwoLists(
mergeRange(lists, l, mid), ListNode left = mergeRange(lists, l, mid);
mergeRange(lists, mid + 1, r) ListNode right = mergeRange(lists, mid + 1, r);
); return mergeTwoLists(left, right);
} }
} }

View File

@ -13,6 +13,7 @@ import java.util.*;
* 链接https://leetcode.cn/problems/partition-list/ * 链接https://leetcode.cn/problems/partition-list/
*/ */
//二刷不会
public class Partition { public class Partition {
public ListNode partition(ListNode head, int x) { public ListNode partition(ListNode head, int x) {
// 虚拟头 // 虚拟头

View File

@ -16,6 +16,14 @@ import java.util.Deque;
* 链接https://leetcode.cn/problems/basic-calculator-ii/ * 链接https://leetcode.cn/problems/basic-calculator-ii/
*/ */
//不会 学下思路 //不会 学下思路
/**
* 题目227. 基本计算器 II
* 思路
* - 用栈保存待相加的数
* - 遇到 + - 直接压栈正数或负数
* - 遇到 * / 弹出栈顶数与当前数计算后再压栈
* - 最后把栈里的数加起来就是答案
*/
public class Calculate2 { public class Calculate2 {
/** /**
1. 操作符优先级 1. 操作符优先级
@ -29,49 +37,66 @@ public class Calculate2 {
*/ */
//s = " 3+5 / 2 " //s = " 3+5 / 2 "
public int calculate(String s) { public int calculate(String s) {
Deque<Integer> stack = new ArrayDeque<Integer>(); Deque<Integer> stack = new ArrayDeque<>();
char preSign = '+'; char preSign = '+'; // 上一个操作符初始设为 '+'
int num = 0; int num = 0; // 当前数字
int n = s.length(); int n = s.length();
for (int i = 0; i < n; ++i) { for (int i = 0; i < n; ++i) {
if (Character.isDigit(s.charAt(i))) { char c = s.charAt(i);
num = num * 10 + s.charAt(i) - '0';
// 1. 遇到数字构造当前数可能是多位
if (Character.isDigit(c)) {
num = num * 10 + (c - '0');
} }
if (!Character.isDigit(s.charAt(i)) && s.charAt(i) != ' ' || i == n - 1) {
// 2. 遇到操作符 或者 到达末尾说明一个完整的数字结束了
if ((!Character.isDigit(c) && c != ' ') || i == n - 1) {
switch (preSign) { switch (preSign) {
case '+': case '+': // 上一个符号是 +直接把 num 入栈
stack.push(num); stack.push(num);
break; break;
case '-': case '-': // 上一个符号是 - -num 入栈
stack.push(-num); stack.push(-num);
break; break;
case '*': case '*': // 上一个符号是 *弹出一个数与 num 相乘再入栈
stack.push(stack.pop() * num); stack.push(stack.pop() * num);
break; break;
default: case '/': // 上一个符号是 /弹出一个数与 num 相除再入栈
stack.push(stack.pop() / num); stack.push(stack.pop() / num);
break;
} }
preSign = s.charAt(i); // 更新当前操作符
preSign = c;
// 重置 num准备下一轮
num = 0; num = 0;
} }
} }
// 3. 把栈中所有数相加得到结果
int ans = 0; int ans = 0;
while (!stack.isEmpty()) { while (!stack.isEmpty()) {
ans += stack.pop(); ans += stack.pop();
} }
return ans; return ans;
} }
public static void main(String[] args) { public static void main(String[] args) {
Calculate2 calculator = new Calculate2(); Calculate2 calculator = new Calculate2();
// 测试用例 1 // 测试用例 1
String expression1 = "3/2"; String expression1 = "3/2";
System.out.println("输入: " + expression1); System.out.println("输入: " + expression1);
System.out.println("输出: " + calculator.calculate(expression1)); // 期望输出 5 System.out.println("输出: " + calculator.calculate(expression1)); // 期望输出 1
// 测试用例 2 // 测试用例 2
String expression2 = "3+2*2"; String expression2 = "3+2*2";
System.out.println("输入: " + expression2); System.out.println("输入: " + expression2);
System.out.println("输出: " + calculator.calculate(expression2)); // 期望输出 7 System.out.println("输出: " + calculator.calculate(expression2)); // 期望输出 7
// 测试用例 3
String expression3 = " 3+5 / 2 ";
System.out.println("输入: " + expression3);
System.out.println("输出: " + calculator.calculate(expression3)); // 期望输出 5
} }
} }

View File

@ -2,9 +2,6 @@ package stack;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList;
import java.util.Stack;
/** /**
* 题目 394. 字符串解码 (decodeString) * 题目 394. 字符串解码 (decodeString)
* 描述给定一个经过编码的字符串返回它解码后的字符串 * 描述给定一个经过编码的字符串返回它解码后的字符串
@ -16,7 +13,7 @@ import java.util.Stack;
输入s = "3[a]2[bc]" 输入s = "3[a]2[bc]"
输出"aaabcbc" 输出"aaabcbc"
* 链接https://leetcode.cn/problems/search-in-rotated-sorted-array/ * 链接https://leetcode.cn/problems/decode-string/
*/ */
//不会 有难度 //不会 有难度
//二刷不会 //二刷不会
@ -25,8 +22,8 @@ public class DecodeString {
// s = "3[a]2[bc]" "3[a2[c]]" // s = "3[a]2[bc]" "3[a2[c]]"
public String decodeString(String s) { public String decodeString(String s) {
// 定义两个栈 // 定义两个栈
Deque<Integer> countStack = new LinkedList<>(); Deque<Integer> countStack = new ArrayDeque<>();
Deque<StringBuilder> stringStack = new LinkedList<>(); Deque<StringBuilder> stringStack = new ArrayDeque<>();
StringBuilder current = new StringBuilder(); StringBuilder current = new StringBuilder();
int k = 0; // 用于保存数字 int k = 0; // 用于保存数字

View File

@ -109,35 +109,53 @@ public class Trap {
* leftMax rightMax 分别记录从左右遇到的最高柱子 * leftMax rightMax 分别记录从左右遇到的最高柱子
* 每次比较 height[left] height[right] * 每次比较 height[left] height[right]
* 谁矮就处理谁因为能接水的高度取决于当前方向上历史最高柱子 * 谁矮就处理谁因为能接水的高度取决于当前方向上历史最高柱子
*
* 如果 height[left] < height[right]
* 那么当前位置 left 能否接水只取决于 左边最高柱子因为右边一定更高不会成为瓶颈
* 如果 height[left] >= height[right]
* 同理此时能否接水只取决于 右边最高柱子
*
* 用双指针减少了空间整体时间仍然是 O(n) * 用双指针减少了空间整体时间仍然是 O(n)
* @param height * @param height
* @return * @return
*/ */
// 双指针法竖着收集雨水
public int trap3(int[] height) { public int trap3(int[] height) {
int left = 0, right = height.length - 1; int left = 0, right = height.length - 1; // 左右指针
int leftMax = 0, rightMax = 0; int leftMax = 0, rightMax = 0; // 左右边的最高柱子
int sum = 0; int sum = 0; // 结果总接水量
// 两指针从两边往中间走
while (left < right) { while (left < right) {
// 情况 1左柱子 < 右柱子
// 说明此时能否接水取决于左边的最高柱子
if (height[left] < height[right]) { if (height[left] < height[right]) {
if (height[left] > leftMax) { if (height[left] >= leftMax) {
// 如果当前柱子比左边最高还高更新 leftMax
leftMax = height[left]; leftMax = height[left];
} else { } else {
// 否则左边最高柱子挡住了水能接的水 = leftMax - 当前高度
sum += leftMax - height[left]; sum += leftMax - height[left];
} }
left++; left++; // 左指针右移
} else { }
// 情况 2右柱子 <= 左柱子
// 说明能否接水取决于右边的最高柱子
else {
if (height[right] >= rightMax) { if (height[right] >= rightMax) {
// 如果当前柱子比右边最高还高更新 rightMax
rightMax = height[right]; rightMax = height[right];
} else { } else {
// 否则右边最高柱子挡住了水能接的水 = rightMax - 当前高度
sum += rightMax - height[right]; sum += rightMax - height[right];
} }
right--; right--; // 右指针左移
} }
} }
return sum; return sum;
} }
//单调栈 横着收集雨水 //单调栈 横着收集雨水
/** /**
* 使用一个单调递减栈存的是索引遇到比栈顶高的柱子就说明可以接水了 * 使用一个单调递减栈存的是索引遇到比栈顶高的柱子就说明可以接水了

View File

@ -45,33 +45,34 @@ public class CheckSubarraySum {
return false; return false;
} }
//前缀和 这题需要判断子数组和是否为 k 的倍数因此我们关心的是前缀和对 k 取余后的值是否相等 //前缀和 这题需要判断子数组和是否为 k 的倍数因此我们关心的是前缀和对 k 取余后的值是否相等
/**
* 题目523. 连续的子数组和
* 思路简述
* 1. 用前缀和取余数如果两个前缀和的余数相同说明它们之间的子数组和可以被 k 整除
* 2. HashMap 记录余数第一次出现的位置
* 3. 如果当前下标 i 与之前相同余数的下标差 >= 2说明存在符合条件的子数组
* 4. 特殊初始化余数 0 在下标 -1方便处理前缀和本身就是 k 倍数的情况
*/
public boolean checkSubarraySum(int[] nums, int k) { public boolean checkSubarraySum(int[] nums, int k) {
// 特殊情况k == 0 的处理寻找连续和为 0 的子数组 // key: 前缀和 mod k 的余数, value: 最早出现的下标
if (k == 0) { HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length - 1; i++) { map.put(0, -1); // 初始化余数 0 出现在 -1 位置
if (nums[i] == 0 && nums[i+1] == 0) {
return true;
}
}
return false;
}
// 哈希表存储前缀和 mod k 的余数及其第一次出现的下标
HashMap<Integer, Integer> modMap = new HashMap<>();
modMap.put(0, -1); // 余数为0时将下标设为-1方便处理从数组开头开始的情况
int prefixSum = 0;
int sum = 0;
for (int i = 0; i < nums.length; i++) { for (int i = 0; i < nums.length; i++) {
prefixSum += nums[i]; sum += nums[i];
int mod = prefixSum % k; int remainder = sum % k;
// 如果这个余数之前出现过则当前下标和之前下标之差至少大于1 if (remainder < 0) remainder += k; // 保证余数非负
if (modMap.containsKey(mod)) {
if (i - modMap.get(mod) > 1) { if (map.containsKey(remainder)) {
int prevIndex = map.get(remainder);
// 子数组长度至少为 2
if (i - prevIndex >= 2) {
return true; return true;
} }
} else { } else {
// 记录第一次出现该余数的下标 // 只记录第一次出现的余数位置
modMap.put(mod, i); map.put(remainder, i);
} }
} }
return false; return false;