9.21 复习
This commit is contained in:
parent
585a2321f3
commit
5fd8c62f8a
@ -14,7 +14,7 @@ import java.util.Set;
|
||||
输入: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]]
|
||||
|
||||
* 链接:https://leetcode.cn/problems/palindrome-partitioning/
|
||||
* 链接:https://leetcode.cn/problems/non-decreasing-subsequences/
|
||||
*/
|
||||
//不会做
|
||||
public class FindSubsequences {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
package binary_search;
|
||||
|
||||
/**
|
||||
* 题目: 33. 搜索旋转排序数组 (search)
|
||||
* 描述:整数数组 nums 按升序排列,数组中的值 互不相同 。
|
||||
|
||||
@ -9,27 +9,24 @@ package bitwise;
|
||||
|
||||
* 链接:https://leetcode.cn/problems/add-binary/
|
||||
*/
|
||||
|
||||
|
||||
public class AddBinary {
|
||||
public String addBinary(String a, String b) {
|
||||
int len1=a.length(),len2=b.length();
|
||||
int done=0;
|
||||
int carry=0;
|
||||
int cura,curb;
|
||||
StringBuilder sb=new StringBuilder();
|
||||
while (done<Math.max(len1,len2)){
|
||||
if(done<len1)
|
||||
cura=a.charAt(len1-1-done)-'0';
|
||||
else cura=0;
|
||||
if(done<len2)
|
||||
curb=b.charAt(len2-1-done)-'0';
|
||||
else curb=0;
|
||||
int cur=(cura+curb+carry)%2;
|
||||
carry=(cura+curb+carry)/2;
|
||||
sb.insert(0,cur);
|
||||
done++;
|
||||
int i = a.length() - 1;
|
||||
int j = b.length() - 1;
|
||||
int carry = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
while (i >= 0 || j >= 0 || carry > 0) {
|
||||
int x = (i >= 0) ? a.charAt(i--) - '0' : 0;
|
||||
int y = (j >= 0) ? b.charAt(j--) - '0' : 0;
|
||||
int sum = x + y + carry;
|
||||
sb.append(sum % 2);
|
||||
carry = sum / 2;
|
||||
}
|
||||
|
||||
if(carry==1)sb.insert(0,'1');
|
||||
return sb.toString();
|
||||
return sb.reverse().toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ package digit;
|
||||
/**
|
||||
* 题目: 66. 加一 (plusOne)
|
||||
* 描述:给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
|
||||
*
|
||||
|
||||
* 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
|
||||
*
|
||||
|
||||
* 你可以假设除了整数 0 之外,这个整数不会以零开头。
|
||||
*
|
||||
示例 1:
|
||||
@ -15,6 +15,12 @@ package digit;
|
||||
* 链接:https://leetcode.cn/problems/plus-one/
|
||||
*/
|
||||
public class PlusOne {
|
||||
/*
|
||||
循环从数组的最后一位(最低位)开始。
|
||||
每次先对当前位 +1。
|
||||
再取 % 10,这样如果是 10 就会变成 0,表示需要向前一位进位。
|
||||
如果进位之后该位不是 0,说明没有继续进位的需求,可以直接返回结果。
|
||||
*/
|
||||
public int[] plusOne(int[] digits) {
|
||||
for (int i = digits.length - 1; i >= 0; i--) {
|
||||
digits[i]++;
|
||||
|
||||
@ -6,6 +6,7 @@ import java.util.Arrays;
|
||||
* 题目: 518. 零钱兑换 II (change)
|
||||
* 描述:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
|
||||
* 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。
|
||||
*
|
||||
* 假设每一种面额的硬币有无限个。
|
||||
* 题目数据保证结果符合 32 位带符号整数。
|
||||
|
||||
@ -22,6 +23,23 @@ import java.util.Arrays;
|
||||
*/
|
||||
//二刷会做
|
||||
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) {
|
||||
int[] dp=new int[amount+1];
|
||||
dp[0]=1;
|
||||
|
||||
@ -18,16 +18,31 @@ import java.util.PriorityQueue;
|
||||
//没做出来
|
||||
//二刷会做
|
||||
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) {
|
||||
int length=nums.length;
|
||||
int[] dp=new int[length];
|
||||
int MMax=1;
|
||||
Arrays.fill(dp,1);
|
||||
int length = nums.length;
|
||||
// dp[i]:以 nums[i] 结尾的最长递增子序列的长度
|
||||
int[] dp = new int[length];
|
||||
// 记录全局最大值,至少为 1(单个元素)
|
||||
int MMax = 1;
|
||||
|
||||
// 初始化,每个元素单独成序列时,长度为 1
|
||||
Arrays.fill(dp, 1);
|
||||
|
||||
// 枚举每个元素作为结尾
|
||||
for (int i = 0; i < length; i++) {
|
||||
// 向前找比 nums[i] 小的元素
|
||||
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);
|
||||
MMax=Math.max(MMax,dp[i]);
|
||||
// 更新全局最大值
|
||||
MMax = Math.max(MMax, dp[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
int n=text1.length(),m=text2.length();
|
||||
int[][]dp=new int[n+1][m+1];
|
||||
int n = text1.length(), m = text2.length();
|
||||
// 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 j = 1; j <= m; j++) {
|
||||
if(text1.charAt(i-1)==text2.charAt(j-1))
|
||||
dp[i][j]=dp[i-1][j-1]+1;
|
||||
else
|
||||
dp[i][j]=Math.max(dp[i][j-1],dp[i-1][j]);
|
||||
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
|
||||
// 当前字符相同,可以在 LCS 的基础上 +1
|
||||
dp[i][j] = dp[i - 1][j - 1] + 1;
|
||||
} 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];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
package dynamic_programming;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 题目: 5. 最长回文子串 (longestPalindrome)
|
||||
* 描述:给你一个字符串 s,找到 s 中最长的 回文 子串。
|
||||
@ -16,7 +13,13 @@ import java.util.Arrays;
|
||||
*/
|
||||
//二刷不会
|
||||
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) {
|
||||
int n = s.length();
|
||||
if (n < 2) return s;
|
||||
|
||||
@ -1,66 +1,53 @@
|
||||
package dynamic_programming;
|
||||
/**
|
||||
* 题目: 188. 买卖股票的最佳时机 IV (maxProfit)
|
||||
* 题目:188. 买卖股票的最佳时机 IV (maxProfit)
|
||||
* 描述:给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。
|
||||
* 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。
|
||||
* 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
|
||||
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||
|
||||
*
|
||||
* 示例 1:
|
||||
输入:k = 2, prices = [2,4,1]
|
||||
输出:2
|
||||
解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
|
||||
|
||||
* 输入:k = 2, prices = [2,4,1]
|
||||
* 输出:2
|
||||
* 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
|
||||
*
|
||||
* 链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
|
||||
*/
|
||||
//不会
|
||||
//二刷不会
|
||||
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) {
|
||||
// 如果没有价格数据,无法交易,利润为 0
|
||||
if (prices.length == 0) return 0;
|
||||
int len = prices.length;
|
||||
if (len == 0) return 0;
|
||||
|
||||
int n = prices.length;
|
||||
// dp[i][j]:第 i 天结束时,处于「状态 j」时能获得的最大利润
|
||||
// 状态 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 天买入的那些状态
|
||||
// 对于每一笔交易 t(1 ≤ t ≤ k),在第 0 天「买入」后,手里就是 −prices[0]
|
||||
int[][] dp = new int[len][2 * k + 1];
|
||||
// 初始化所有买入状态:第一天买入就是 -prices[0]
|
||||
for (int t = 1; t <= k; t++) {
|
||||
dp[0][2*t - 1] = -prices[0];
|
||||
// dp[0][2*t] 默认是 0:还没卖出,自然利润为 0
|
||||
dp[0][2 * t - 1] = -prices[0];
|
||||
}
|
||||
|
||||
// 从第 1 天开始,逐日更新
|
||||
for (int i = 1; i < n; i++) {
|
||||
// 枚举交易次数
|
||||
for (int i = 1; i < len; i++) {
|
||||
for (int t = 1; t <= k; t++) {
|
||||
int buyState = 2*t - 1; // 第 t 次买入后
|
||||
int sellState = 2*t; // 第 t 次卖出后
|
||||
int buy = 2 * t - 1; // 第 t 次买入
|
||||
int sell = 2 * t; // 第 t 次卖出
|
||||
|
||||
// 【转移方程 1:买入状态】
|
||||
// 要么:继承昨天已经买入的状态 dp[i-1][buyState]
|
||||
// 要么:今天刚用上一次卖出的资金买入——从 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]
|
||||
);
|
||||
dp[i][buy] = Math.max(dp[i - 1][buy], dp[i - 1][buy - 1] - prices[i]); // 买入
|
||||
dp[i][sell] = Math.max(dp[i - 1][sell], dp[i - 1][buy] + prices[i]); // 卖出
|
||||
}
|
||||
}
|
||||
|
||||
// 答案:第 n-1 天,完成第 k 次卖出后的最大利润
|
||||
return dp[n-1][2*k];
|
||||
return dp[len - 1][2 * k];
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,42 +19,17 @@ public class MaxProfit5 {
|
||||
int n = prices.length;
|
||||
if (n == 0) return 0;
|
||||
|
||||
// dp[i][0]: 持有股票
|
||||
// dp[i][1]: 不持有,且不在冷冻期(可买入/休息)
|
||||
// dp[i][2]: 今天卖出(刚卖出)
|
||||
// dp[i][3]: 今天冷冻期(昨天卖出)
|
||||
int[][] dp = new int[n][4];
|
||||
int[][] dp = new int[n][3];
|
||||
dp[0][0] = -prices[0]; // 持有
|
||||
dp[0][1] = 0; // 空仓
|
||||
dp[0][2] = 0; // 冷冻期
|
||||
|
||||
// 第 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++) {
|
||||
int p = prices[i];
|
||||
|
||||
// 1. 持有股票:要么延续持有,要么从空仓/冷冻期买入
|
||||
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];
|
||||
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]); // 空仓
|
||||
dp[i][2] = dp[i-1][0] + prices[i]; // 冷冻期
|
||||
}
|
||||
|
||||
// 最后一天不持有股票的三种状态取最大
|
||||
return Math.max(
|
||||
Math.max(dp[n-1][1], dp[n-1][2]),
|
||||
dp[n-1][3]
|
||||
);
|
||||
return Math.max(dp[n-1][1], dp[n-1][2]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ package graph;
|
||||
* 链接:https://leetcode.cn/problems/clone-graph/
|
||||
*/
|
||||
//不会做
|
||||
//二刷不会
|
||||
import graph.Node;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -18,24 +18,21 @@ package graph;
|
||||
* 链接:https://leetcode.cn/problems/flood-fill/
|
||||
*/
|
||||
public class FloodFill {
|
||||
void dfs(int[][] image,int[][] visited,int origin,int i,int j,int color){
|
||||
int r=image.length;
|
||||
int c=image[0].length;
|
||||
if(i<0||i>=r||j<0||j>=c||visited[i][j]==1||image[i][j]!=origin)
|
||||
return;
|
||||
image[i][j]=color;
|
||||
visited[i][j]=1;
|
||||
dfs(image,visited,origin,i-1,j,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);
|
||||
void dfs(int[][] image, int i, int j, int origin, int color) {
|
||||
int r = image.length, c = image[0].length;
|
||||
if (i < 0 || i >= r || j < 0 || j >= c || image[i][j] != origin) return;
|
||||
image[i][j] = color;
|
||||
dfs(image, i - 1, j, origin, color);
|
||||
dfs(image, i + 1, j, origin, color);
|
||||
dfs(image, i, j - 1, origin, color);
|
||||
dfs(image, i, j + 1, origin, color);
|
||||
}
|
||||
|
||||
public int[][] floodFill(int[][] image, int sr, int sc, int color) {
|
||||
int r=image.length;
|
||||
int c=image[0].length;
|
||||
int origin=image[sr][sc];
|
||||
int[][] visited=new int[r][c];
|
||||
dfs(image,visited,origin,sr,sc,color);
|
||||
int origin = image[sr][sc];
|
||||
if (origin == color) return image; // 避免死循环
|
||||
dfs(image, sr, sc, origin, color);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -22,16 +22,16 @@ import java.util.*;
|
||||
public class MinMutation {
|
||||
/**
|
||||
* 准备工作
|
||||
*
|
||||
|
||||
* 将基因库 bank 中的有效基因串存入一个 Set<String> valid,用于 O(1) 检查。
|
||||
* 如果 endGene 不在 valid 中,说明无法达到目标,直接返回 –1。
|
||||
* BFS 初始化
|
||||
*
|
||||
|
||||
* 使用队列 Queue<String> 存放当前层(当前步数)所有可达的基因串。
|
||||
* 另用一个 Set<String> visited 记录已经访问过的基因串,避免重复遍历。
|
||||
* 将起点 startGene 入队、标记已访问;将步数 steps 置 0。
|
||||
* 层次遍历
|
||||
*
|
||||
|
||||
* 每次遍历一层时,取出当前队列大小 sz,表示这一层共有 sz 个节点要处理;
|
||||
* 对每个节点(基因串)进行:
|
||||
* 若它等于 endGene,则返回当前 steps。
|
||||
|
||||
@ -8,11 +8,11 @@ class Node {
|
||||
public List<Node> neighbors;
|
||||
public Node() {
|
||||
val = 0;
|
||||
neighbors = new ArrayList<Node>();
|
||||
neighbors = new ArrayList<>();
|
||||
}
|
||||
public Node(int _val) {
|
||||
val = _val;
|
||||
neighbors = new ArrayList<Node>();
|
||||
neighbors = new ArrayList<>();
|
||||
}
|
||||
public Node(int _val, ArrayList<Node> _neighbors) {
|
||||
val = _val;
|
||||
|
||||
@ -20,35 +20,61 @@ package greedy;
|
||||
* 链接:https://leetcode.cn/problems/gas-station/
|
||||
*/
|
||||
public class CanCompleteCircuit {
|
||||
/*
|
||||
先计算出每个加油站的盈余 diff[i] = gas[i] - cost[i]。
|
||||
然后遍历每个可能的起点:
|
||||
如果 diff[i] >= 0,就从这里尝试出发,模拟一圈。
|
||||
如果中途 cur < 0,说明这个起点不可行,退出。
|
||||
如果能跑完一圈,就返回这个起点。
|
||||
时间复杂度:最坏情况下是 O(n^2)(因为可能每个起点都尝试一遍)。
|
||||
*/
|
||||
public int canCompleteCircuit1(int[] gas, int[] cost) {
|
||||
int sumgas=0,sumcost=0;
|
||||
int[] diff=new int[gas.length];
|
||||
int sumGas = 0, sumCost = 0;
|
||||
int[] diff = new int[gas.length];
|
||||
|
||||
// 1. 计算每个加油站的盈余 diff[i] = gas[i] - cost[i]
|
||||
for (int i = 0; i < gas.length; i++) {
|
||||
sumgas+=gas[i];
|
||||
sumcost+=cost[i];
|
||||
diff[i]=gas[i]-cost[i];
|
||||
sumGas += gas[i];
|
||||
sumCost += 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++) {
|
||||
if(diff[i]>=0){
|
||||
int j=(i+1)%diff.length;
|
||||
int cur=diff[i];
|
||||
boolean flag=true;
|
||||
while(j!=i){
|
||||
cur+=diff[j];
|
||||
if(cur<0) {
|
||||
flag=false;
|
||||
// 只有当前站盈余不为负,才有可能作为起点
|
||||
if (diff[i] >= 0) {
|
||||
int j = (i + 1) % diff.length; // 下一个站
|
||||
int cur = diff[i]; // 当前油箱剩余
|
||||
boolean canTravel = true;
|
||||
|
||||
// 4. 尝试从 i 出发,跑一圈
|
||||
while (j != i) {
|
||||
cur += diff[j];
|
||||
if (cur < 0) { // 中途油不够,失败
|
||||
canTravel = false;
|
||||
break;
|
||||
}
|
||||
j=(j+1)%diff.length;
|
||||
j = (j + 1) % diff.length;
|
||||
}
|
||||
if(flag)
|
||||
return i;
|
||||
|
||||
// 5. 如果成功跑完一圈,返回起点
|
||||
if (canTravel) return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
贪心法,如果从 A 出发到不了 B,那么 A 和 A 到 B 之间的点都不可能是起点
|
||||
用一个变量 tank 记录从当前候选起点开始跑的盈余油量。
|
||||
如果 tank < 0,说明到不了当前点,直接跳过这一段,把起点移到 i+1。
|
||||
最终如果全局总盈余 total >= 0,返回最后更新的起点。
|
||||
时间复杂度:O(n),只需一遍扫描。
|
||||
*/
|
||||
public int canCompleteCircuit(int[] gas, int[] cost) {
|
||||
int total = 0; // 总盈余:记录所有加油站油量差值的总和
|
||||
int tank = 0; // 当前油箱剩余:当前起点下的油量累计
|
||||
|
||||
@ -16,7 +16,14 @@ package greedy;
|
||||
* 链接:https://leetcode.cn/problems/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) {
|
||||
int n = ratings.length;
|
||||
int[] candies = new int[n];
|
||||
|
||||
@ -17,6 +17,8 @@ import java.util.Arrays;
|
||||
|
||||
* 链接:https://leetcode.cn/problems/assign-cookies/
|
||||
*/
|
||||
|
||||
//二刷会做
|
||||
public class FindContentChildren {
|
||||
public int findContentChildren(int[] g, int[] s) {
|
||||
int i=0,j=0,cnt=0;
|
||||
|
||||
@ -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
|
||||
//二刷不会
|
||||
//三刷不会
|
||||
public class Jump {
|
||||
//要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最少步数!
|
||||
public int jump(int[] nums) {
|
||||
|
||||
@ -42,9 +42,9 @@ public class MergeKLists {
|
||||
if (l == r) return lists[l];
|
||||
if (l > r) return null;
|
||||
int mid = (l + r) >> 1;
|
||||
return mergeTwoLists(
|
||||
mergeRange(lists, l, mid),
|
||||
mergeRange(lists, mid + 1, r)
|
||||
);
|
||||
|
||||
ListNode left = mergeRange(lists, l, mid);
|
||||
ListNode right = mergeRange(lists, mid + 1, r);
|
||||
return mergeTwoLists(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import java.util.*;
|
||||
|
||||
* 链接:https://leetcode.cn/problems/partition-list/
|
||||
*/
|
||||
//二刷不会
|
||||
public class Partition {
|
||||
public ListNode partition(ListNode head, int x) {
|
||||
// 虚拟头
|
||||
|
||||
@ -16,6 +16,14 @@ import java.util.Deque;
|
||||
* 链接:https://leetcode.cn/problems/basic-calculator-ii/
|
||||
*/
|
||||
//不会 学下思路
|
||||
/**
|
||||
* 题目:227. 基本计算器 II
|
||||
* 思路:
|
||||
* - 用栈保存待相加的数
|
||||
* - 遇到 + 或 - 时,直接压栈(正数或负数)
|
||||
* - 遇到 * 或 / 时,弹出栈顶数,与当前数计算后再压栈
|
||||
* - 最后把栈里的数加起来就是答案
|
||||
*/
|
||||
public class Calculate2 {
|
||||
/**
|
||||
1. 操作符优先级
|
||||
@ -29,49 +37,66 @@ public class Calculate2 {
|
||||
*/
|
||||
//s = " 3+5 / 2 "
|
||||
public int calculate(String s) {
|
||||
Deque<Integer> stack = new ArrayDeque<Integer>();
|
||||
char preSign = '+';
|
||||
int num = 0;
|
||||
Deque<Integer> stack = new ArrayDeque<>();
|
||||
char preSign = '+'; // 上一个操作符,初始设为 '+'
|
||||
int num = 0; // 当前数字
|
||||
int n = s.length();
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (Character.isDigit(s.charAt(i))) {
|
||||
num = num * 10 + s.charAt(i) - '0';
|
||||
char c = s.charAt(i);
|
||||
|
||||
// 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) {
|
||||
case '+':
|
||||
case '+': // 上一个符号是 +,直接把 num 入栈
|
||||
stack.push(num);
|
||||
break;
|
||||
case '-':
|
||||
case '-': // 上一个符号是 -,把 -num 入栈
|
||||
stack.push(-num);
|
||||
break;
|
||||
case '*':
|
||||
case '*': // 上一个符号是 *,弹出一个数与 num 相乘再入栈
|
||||
stack.push(stack.pop() * num);
|
||||
break;
|
||||
default:
|
||||
case '/': // 上一个符号是 /,弹出一个数与 num 相除再入栈
|
||||
stack.push(stack.pop() / num);
|
||||
break;
|
||||
}
|
||||
preSign = s.charAt(i);
|
||||
// 更新当前操作符
|
||||
preSign = c;
|
||||
// 重置 num,准备下一轮
|
||||
num = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 把栈中所有数相加得到结果
|
||||
int ans = 0;
|
||||
while (!stack.isEmpty()) {
|
||||
ans += stack.pop();
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Calculate2 calculator = new Calculate2();
|
||||
|
||||
// 测试用例 1
|
||||
String expression1 = "3/2";
|
||||
System.out.println("输入: " + expression1);
|
||||
System.out.println("输出: " + calculator.calculate(expression1)); // 期望输出 5
|
||||
System.out.println("输出: " + calculator.calculate(expression1)); // 期望输出 1
|
||||
|
||||
// 测试用例 2
|
||||
String expression2 = "3+2*2";
|
||||
System.out.println("输入: " + expression2);
|
||||
System.out.println("输出: " + calculator.calculate(expression2)); // 期望输出 7
|
||||
|
||||
// 测试用例 3
|
||||
String expression3 = " 3+5 / 2 ";
|
||||
System.out.println("输入: " + expression3);
|
||||
System.out.println("输出: " + calculator.calculate(expression3)); // 期望输出 5
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,6 @@ package stack;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* 题目: 394. 字符串解码 (decodeString)
|
||||
* 描述:给定一个经过编码的字符串,返回它解码后的字符串。
|
||||
@ -16,7 +13,7 @@ import java.util.Stack;
|
||||
输入:s = "3[a]2[bc]"
|
||||
输出:"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]]"
|
||||
public String decodeString(String s) {
|
||||
// 定义两个栈
|
||||
Deque<Integer> countStack = new LinkedList<>();
|
||||
Deque<StringBuilder> stringStack = new LinkedList<>();
|
||||
Deque<Integer> countStack = new ArrayDeque<>();
|
||||
Deque<StringBuilder> stringStack = new ArrayDeque<>();
|
||||
|
||||
StringBuilder current = new StringBuilder();
|
||||
int k = 0; // 用于保存数字
|
||||
|
||||
@ -109,35 +109,53 @@ public class Trap {
|
||||
* 用 leftMax 和 rightMax 分别记录从左、右遇到的最高柱子
|
||||
* 每次比较 height[left] 和 height[right]:
|
||||
* 谁矮,就处理谁:因为能接水的高度取决于当前方向上历史最高柱子
|
||||
*
|
||||
* 如果 height[left] < height[right]:
|
||||
* 那么当前位置 left 能否接水只取决于 左边最高柱子(因为右边一定更高,不会成为瓶颈)。
|
||||
* 如果 height[left] >= height[right]:
|
||||
* 同理,此时能否接水只取决于 右边最高柱子。
|
||||
*
|
||||
* 用双指针减少了空间,整体时间仍然是 O(n)
|
||||
* @param height
|
||||
* @return
|
||||
*/
|
||||
// 双指针法:竖着收集雨水
|
||||
public int trap3(int[] height) {
|
||||
int left = 0, right = height.length - 1;
|
||||
int leftMax = 0, rightMax = 0;
|
||||
int sum = 0;
|
||||
int left = 0, right = height.length - 1; // 左右指针
|
||||
int leftMax = 0, rightMax = 0; // 左右边的最高柱子
|
||||
int sum = 0; // 结果:总接水量
|
||||
|
||||
// 两指针从两边往中间走
|
||||
while (left < right) {
|
||||
// 情况 1:左柱子 < 右柱子
|
||||
// 说明此时能否接水,取决于左边的最高柱子
|
||||
if (height[left] < height[right]) {
|
||||
if (height[left] > leftMax) {
|
||||
if (height[left] >= leftMax) {
|
||||
// 如果当前柱子比左边最高还高,更新 leftMax
|
||||
leftMax = height[left];
|
||||
} else {
|
||||
// 否则,左边最高柱子挡住了水,能接的水 = leftMax - 当前高度
|
||||
sum += leftMax - height[left];
|
||||
}
|
||||
left++;
|
||||
} else {
|
||||
left++; // 左指针右移
|
||||
}
|
||||
// 情况 2:右柱子 <= 左柱子
|
||||
// 说明能否接水,取决于右边的最高柱子
|
||||
else {
|
||||
if (height[right] >= rightMax) {
|
||||
// 如果当前柱子比右边最高还高,更新 rightMax
|
||||
rightMax = height[right];
|
||||
} else {
|
||||
// 否则,右边最高柱子挡住了水,能接的水 = rightMax - 当前高度
|
||||
sum += rightMax - height[right];
|
||||
}
|
||||
right--;
|
||||
right--; // 右指针左移
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
//单调栈 横着收集雨水
|
||||
/**
|
||||
* 使用一个单调递减栈(存的是索引),遇到比栈顶高的柱子,就说明可以接水了。
|
||||
|
||||
@ -45,33 +45,34 @@ public class CheckSubarraySum {
|
||||
return false;
|
||||
}
|
||||
//前缀和 这题需要判断子数组和是否为 k 的倍数,因此我们关心的是前缀和对 k 取余后的值是否相等
|
||||
/**
|
||||
* 题目:523. 连续的子数组和
|
||||
* 思路简述:
|
||||
* 1. 用前缀和取余数:如果两个前缀和的余数相同,说明它们之间的子数组和可以被 k 整除。
|
||||
* 2. 用 HashMap 记录余数第一次出现的位置。
|
||||
* 3. 如果当前下标 i 与之前相同余数的下标差 >= 2,说明存在符合条件的子数组。
|
||||
* 4. 特殊初始化:余数 0 在下标 -1,方便处理前缀和本身就是 k 倍数的情况。
|
||||
*/
|
||||
public boolean checkSubarraySum(int[] nums, int k) {
|
||||
// 特殊情况:k == 0 的处理,寻找连续和为 0 的子数组
|
||||
if (k == 0) {
|
||||
for (int i = 0; i < nums.length - 1; i++) {
|
||||
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;
|
||||
// key: 前缀和 mod k 的余数, value: 最早出现的下标
|
||||
HashMap<Integer, Integer> map = new HashMap<>();
|
||||
map.put(0, -1); // 初始化,余数 0 出现在 -1 位置
|
||||
|
||||
int sum = 0;
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
prefixSum += nums[i];
|
||||
int mod = prefixSum % k;
|
||||
// 如果这个余数之前出现过,则当前下标和之前下标之差至少大于1
|
||||
if (modMap.containsKey(mod)) {
|
||||
if (i - modMap.get(mod) > 1) {
|
||||
sum += nums[i];
|
||||
int remainder = sum % k;
|
||||
if (remainder < 0) remainder += k; // 保证余数非负
|
||||
|
||||
if (map.containsKey(remainder)) {
|
||||
int prevIndex = map.get(remainder);
|
||||
// 子数组长度至少为 2
|
||||
if (i - prevIndex >= 2) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 记录第一次出现该余数的下标
|
||||
modMap.put(mod, i);
|
||||
// 只记录第一次出现的余数位置
|
||||
map.put(remainder, i);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user