10.15 复习

This commit is contained in:
zhangsan 2025-10-15 20:25:53 +08:00
parent 3d5587b297
commit 7417736f7a
18 changed files with 393 additions and 10 deletions

View File

@ -0,0 +1,135 @@
package All;
/**
* 题目 443. 压缩字符串
* 描述给你一个字符数组 chars 请使用下述算法压缩
*
* 从一个空字符串 s 开始对于 chars 中的每组 连续重复字符
*
* 如果这一组长度为 1 则将字符追加到 s
* 否则需要向 s 追加字符后跟这一组的长度
* 压缩后得到的字符串 s 不应该直接返回 需要转储到字符数组 chars 需要注意的是如果组长度为 10 10 以上则在 chars 数组中会被拆分为多个字符
*
* 请在 修改完输入数组后 返回该数组的新长度
*
* 你必须设计并实现一个只使用常量额外空间的算法来解决此问题
*
* 注意数组中超出返回长度的字符无关紧要应予忽略
示例 1
输入chars = ["a","a","b","b","c","c","c"]
输出返回 6 输入数组的前 6 个字符应该是["a","2","b","2","c","3"]
解释"aa" "a2" 替代"bb" "b2" 替代"ccc" "c3" 替代
注意数组中超出返回长度的字符无关紧要应予忽略
* 链接https://leetcode.cn/problems/string-compression/
*/
public class Compress {
/*
自己写的又臭又长
*/
public int compress2(char[] chars) {
// Arrays.sort(chars);
int cnt=1;
char cur=chars[0];
int read=1,write=0;
while (read<chars.length){
if(chars[read]==cur)
cnt++;
else {
chars[write++]=cur;
cur=chars[read];
if(cnt==1){
read++;
continue;
}
StringBuilder sb=new StringBuilder();
while (cnt!=0){
int now=cnt%10;
cnt/=10;
sb.insert(0,now);
}
for (int i = 0; i < sb.length(); i++) {
chars[write++]=sb.charAt(i);
}
cnt=1;
}
read++;
}
chars[write++]=cur;
if(cnt!=1){
StringBuilder sb=new StringBuilder();
while (cnt!=0){
int now=cnt%10;
cnt/=10;
sb.insert(0,now);
}
for (int i = 0; i < sb.length(); i++) {
chars[write++]=sb.charAt(i);
}
}
return write;
}
public int compress(char[] chars) {
int write = 0; // 写指针
int read = 0; // 读指针
while (read < chars.length) {
char cur = chars[read];
int count = 0;
// 统计连续相同字符
while (read < chars.length && chars[read] == cur) {
read++;
count++;
}
// 写入当前字符
chars[write++] = cur;
// 如果数量大于 1写入数字多位数拆开
if (count > 1) {
// 直接转字符串再逐位写入
for (char c : String.valueOf(count).toCharArray()) {
chars[write++] = c;
}
}
}
return write;
}
private String compress1(char[] chars) {
if (chars.length == 0) return "";
StringBuilder sb = new StringBuilder();
char current = chars[0];
int cnt = 1;
for (int i = 1; i < chars.length; i++) {
if (chars[i] == current) {
cnt++;
} else {
sb.append(current);
if (cnt > 1) sb.append(cnt);
current = chars[i];
cnt = 1;
}
}
// 处理最后一组字符
sb.append(current);
if (cnt > 1) sb.append(cnt);
return sb.toString();
}
public static void main(String[] args) {
Compress solution = new Compress();
char[] chars = {'a','b','c'};
int res=solution.compress(chars);
System.out.println(res);
System.out.println(chars);
}
}

View File

@ -0,0 +1,33 @@
package All;
/**
* 题目 1823. 找出游戏的获胜者
* 描述共有 n 名小伙伴一起做游戏小伙伴们围成一圈 顺时针顺序 1 n 编号确切地说从第 i 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置其中 1 <= i < n 从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置
* 游戏遵循如下规则
* 从第 1 名小伙伴所在位置 开始
* 沿着顺时针方向数 k 名小伙伴计数时需要 包含 起始时的那位小伙伴逐个绕圈进行计数一些小伙伴可能会被数过不止一次
* 你数到的最后一名小伙伴需要离开圈子并视作输掉游戏
* 如果圈子中仍然有不止一名小伙伴从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始回到步骤 2 继续执行
* 否则圈子中最后一名小伙伴赢得游戏
* 给你参与游戏的小伙伴总数 n 和一个整数 k 返回游戏的获胜者
示例 1
输入n = 5, k = 2
输出3
解释游戏运行步骤如下
1) 从小伙伴 1 开始
2) 顺时针数 2 名小伙伴也就是小伙伴 1 2
3) 小伙伴 2 离开圈子下一次从小伙伴 3 开始
4) 顺时针数 2 名小伙伴也就是小伙伴 3 4
5) 小伙伴 4 离开圈子下一次从小伙伴 5 开始
6) 顺时针数 2 名小伙伴也就是小伙伴 5 1
7) 小伙伴 1 离开圈子下一次从小伙伴 3 开始
8) 顺时针数 2 名小伙伴也就是小伙伴 3 5
9) 小伙伴 5 离开圈子只剩下小伙伴 3 所以小伙伴 3 是游戏的获胜者
* 链接https://leetcode.cn/problems/find-the-winner-of-the-circular-game/
*/
public class FindTheWinner {
public int findTheWinner(int n, int k) {
return 0;
}
}

View File

@ -0,0 +1,140 @@
package All;
/**
* 题目描述
*
* 给定一个仅包含大小写英文字母的字符串 s
* 你可以删除最多 k 个字符以使得删除后压缩字符串的长度最小
*
* 压缩规则如下类似于 LeetCode 443 字符串压缩
* - 将连续出现的相同字符压缩为字符 + 出现次数
* - 如果某个字符只出现一次则不写次数
* - 例如
* "aabcccccaaa" 压缩后为 "a2bc5a3"长度为 7
*
* 你可以选择删除字符串中的任意 k 个字符也可以不删除
* 目标是使得压缩后的字符串长度最短
*
* 要求返回
* 删除最多 k 个字符后压缩字符串的最短可能长度
*
* 示例
* 输入s = "aabcccccaaa", k = 0
* 输出7
* 解释
* 压缩后为 "a2bc5a3"长度为 7
*/
public class GetLengthOfOptimalCompression {
private int minLen = Integer.MAX_VALUE;
/*
用回溯法DFS枚举所有可能的删除方式
对每个保留下来的字符串调用 compress1() 求压缩长度
最后取最小值
会超时时间复杂度非常大
*/
public int getLengthOfOptimalCompression(String s, int k) {
if (s == null || s.length() == 0) return 0;
char[] chars = s.toCharArray();
dfs(chars, k, 0, new StringBuilder());
return minLen;
}
// 回溯枚举删除情况
private void dfs(char[] s, int k, int index, StringBuilder current) {
// 如果删除次数超过 k直接剪枝
if (k < 0) return;
// 到达字符串末尾
if (index == s.length) {
String compressed = compress1(current.toString().toCharArray());
minLen = Math.min(minLen, compressed.length());
return;
}
// 选择1保留当前字符
current.append(s[index]);
dfs(s, k, index + 1, current);
current.setLength(current.length() - 1);
// 选择2删除当前字符
dfs(s, k - 1, index + 1, current);
}
// 你的压缩逻辑原样复用
private String compress1(char[] chars) {
if (chars.length == 0) return "";
StringBuilder sb = new StringBuilder();
char current = chars[0];
int cnt = 1;
for (int i = 1; i < chars.length; i++) {
if (chars[i] == current) {
cnt++;
} else {
sb.append(current);
if (cnt > 1) sb.append(cnt);
current = chars[i];
cnt = 1;
}
}
sb.append(current);
if (cnt > 1) sb.append(cnt);
return sb.toString();
}
/*
标准解法是使用 动态规划DP + 记忆化 DFS时间复杂度 O(n²·k)
dp[i][k] = 从下标 i 开始最多删除 k 个字符后最短压缩长度
从第 i 位开始枚举字符
统计连续相同字符出现次数 count
删除掉部分不同字符消耗 k
更新最小压缩长度
dp[i][k] = min(
dp[j+1][k - (段长 - count)] + 压缩该段的长度
)
*/
public int getLengthOfOptimalCompression2(String s, int k) {
int n = s.length();
Integer[][] memo = new Integer[n][k + 1];
return dfs(s.toCharArray(), 0, k, memo);
}
private int dfs(char[] s, int i, int k, Integer[][] memo) {
if (k < 0) return Integer.MAX_VALUE / 2; // 删除过多
if (i >= s.length || s.length - i <= k) return 0; // 全删完
if (memo[i][k] != null) return memo[i][k];
int res = Integer.MAX_VALUE;
int[] cnt = new int[26];
int most = 0;
// i 开始逐步扩展区间
for (int j = i; j < s.length; j++) {
int idx = s[j] - 'a';
cnt[idx]++;
most = Math.max(most, cnt[idx]);
int del = (j - i + 1) - most; // 删除不匹配字符
if (del > k) continue;
res = Math.min(
res,
1 + numLength(most) + dfs(s, j + 1, k - del, memo)
);
}
return memo[i][k] = res;
}
private int numLength(int x) {
if (x <= 1) return 0;
if (x < 10) return 1;
if (x < 100) return 2;
return 3;
}
}

View File

@ -15,6 +15,7 @@ package binary_search;
*/
//不会
//二刷不会
//三刷不会
public class FindMedianSortedArrays {
@ -63,7 +64,7 @@ public class FindMedianSortedArrays {
// 每次比较 nums1 nums2 各自第 k/2 个候选如果不足 k/2则取末尾
int half = k / 2;
int newIndex1 = Math.min(index1 + half, len1) - 1;
int newIndex1 = Math.min(index1 + half, len1) - 1; //例如 index=0要找第 3 个元素 索引 = 0 + 3 - 1 = 2
int newIndex2 = Math.min(index2 + half, len2) - 1;
int pivot1 = nums1[newIndex1];
int pivot2 = nums2[newIndex2];
@ -80,4 +81,30 @@ public class FindMedianSortedArrays {
}
}
}
/*
如果不考虑时间复杂度
*/
public int findKthMerge(int[] nums1, int[] nums2, int k) {
int i = 0, j = 0, count = 0;
int val = 0;
while (i < nums1.length || j < nums2.length) {
int a = (i < nums1.length) ? nums1[i] : Integer.MAX_VALUE;
int b = (j < nums2.length) ? nums2[j] : Integer.MAX_VALUE;
if (a <= b) {
val = a;
i++;
} else {
val = b;
j++;
}
count++;
if (count == k) return val;
}
return -1; // 理论上不会走到这里
}
}

View File

@ -17,6 +17,7 @@ package binary_search;
* 链接https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/
*/
//二刷不会
//三刷不会
public class FindMin {
/**
@ -29,7 +30,12 @@ public class FindMin {
* 判断最小值在哪个区间
*
* 左侧有序情况如果 nums[mid] >= nums[left]说明从 left mid 是有序的此时最小值不可能出现在有序部分所以最小值一定在右半部分因此更新 left = mid + 1
* 为什么说'此时最小值不可能出现在有序部分'? 因为 左边最小值此时是nums[left] 但是这里有个前提一开始就判断了nums[left]>nums[right] 故最小值肯定在右半段
*
*
* 旋转点在左侧情况如果 nums[mid] < nums[left]说明中间部分处于旋转状态即最小值可能出现在左半部分或就是 nums[mid]因此将 right 更新为 mid
* 为什么 因为最小值肯定在旋转的区间内4 5 6 7 0 1 2 最小值左边就是最大值
*
* 退出条件
*
* 循环在 left == right 时退出此时 nums[left] 就是整个数组的最小值

View File

@ -13,6 +13,7 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/partition-equal-subset-sum/
*/
//二刷不会
//三刷会做
public class CanPartition {
public boolean canPartition1(int[] nums) {
int total=0,target=0;

View File

@ -1,7 +1,5 @@
package dynamic_programming;
import java.util.Arrays;
/**
* 题目 518. 零钱兑换 II (change)
* 描述给你一个整数数组 coins 表示不同面额的硬币另给一个整数 amount 表示总金额

View File

@ -14,6 +14,7 @@ package dynamic_programming;
*/
//不会
//二刷不会
//三刷不会
public class CountSubstrings {
/**
*布尔类型的dp[i][j]表示区间范围[i,j] 注意是左闭右闭的子串是否是回文子串

View File

@ -14,6 +14,7 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/ones-and-zeroes/
*/
//二刷不会
//三刷会做
public class FindMaxForm {
// 统计字符串中 '0' '1' 的数量
private int[] countZeroAndOne(String str) {

View File

@ -20,6 +20,7 @@ package dynamic_programming;
*/
//二刷不会
//三刷不会,数学转换很重要
//四刷会做
public class FindTargetSumWays {
/**

View File

@ -11,6 +11,7 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/integer-break/
*/
//二刷不会
public class IntegerBreak {
public int integerBreak(int n) {
//dp[i] 为正整数 i 拆分后的结果的最大乘积

View File

@ -14,6 +14,7 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/is-subsequence/
*/
//不会
//二刷会做
public class IsSubsequence {
//动态规划转为求最长公共子序列是否为s的长度
public boolean isSubsequence(String s, String t) {
@ -34,7 +35,6 @@ public class IsSubsequence {
/**
* 我们用两个指针 i 遍历字符串 sj 遍历字符串 t初始均指向各自字符串的开头
*
* 如果 s.charAt(i) == t.charAt(j)说明匹配上了两个指针都往后走i++, j++
* 否则就只能在 t 跳过这个字符j++
* i 走到 s.length() 说明 s 中的所有字符都在 t 中按顺序找到了返回 true否则 j 扫完 t 还没把 s 的所有字符匹配完就返回 false

View File

@ -1,7 +1,7 @@
package dynamic_programming;
/**
* 题目 152. 乘积最大子数组 (MaxProduct)
* 描述给你一个整数数组 nums 请你找出数组中乘积最大的非空连续 子数组该子数组中至少包含一个数字并返回该子数组所对应的乘积
* 描述给你一个整数数组 nums 请你找出数组中乘积最大的非空连续子数组该子数组中至少包含一个数字并返回该子数组所对应的乘积
* 测试用例的答案是一个 32- 整数
* 示例 1
@ -14,6 +14,7 @@ package dynamic_programming;
//不会
//二刷不会
//三刷不会
//四刷会做
public class MaxProduct {
/**
@ -60,9 +61,9 @@ public class MaxProduct {
public static void main(String[] args) {
MaxProduct sol = new MaxProduct();
System.out.println(sol.maxProduct(new int[]{2, 3, -2, 4})); // 输出 6
System.out.println(sol.maxProduct(new int[]{-2, 0, -1})); // 输出 0
System.out.println(sol.maxProduct(new int[]{-2, 3, -4})); // 输出 24
System.out.println(sol.maxProduct(new int[]{-1, -3, -10, 0, 60})); // 输出 60
System.out.println(sol.maxProduct(new int[]{-2, -5, -2, -4, 3})); // 输出 240
// System.out.println(sol.maxProduct(new int[]{-2, 0, -1})); // 输出 0
// System.out.println(sol.maxProduct(new int[]{-2, 3, -4})); // 输出 24
// System.out.println(sol.maxProduct(new int[]{-1, -3, -10, 0, 60})); // 输出 60
// System.out.println(sol.maxProduct(new int[]{-2, -5, -2, -4, 3})); // 输出 240
}
}

View File

@ -14,6 +14,7 @@ package dynamic_programming;
*/
//不会
//二刷不会
//三刷会做
public class MaxProfit4 {
/**
* 状态定义

View File

@ -11,6 +11,7 @@ package dynamic_programming;
*/
//不会
//二刷不会
//三刷会做
public class MaximalSquare {
/**
* dp[i][j] 表示 (i, j) 作为右下角的最大全 1 正方形的边长

View File

@ -11,7 +11,18 @@ package dynamic_programming;
* 链接https://leetcode.cn/problems/delete-operation-for-two-strings/
*/
//二刷会做
public class MinDistance {
/**
* 法一转为LCS
* 假设两个字符串的 最长公共子序列长度 LCS
* 那我们只要
* word1 中那些 不在 LCS 里的字符 删除
* word2 中那些 不在 LCS 里的字符 删除
* 就能让它们都变成 LCS 一样的字符串
* 因此最少删除步数公式minDistance = (len1 - LCS) + (len2 - LCS)
* = len1 + len2 - 2 * LCS
*/
public int minDistance(String word1, String word2) {
int len1=word1.length(),len2=word2.length();
int[][]dp=new int[len1+1][len2+1];
@ -24,4 +35,28 @@ public class MinDistance {
}
return len1+len2-2*dp[len1][len2];
}
/**
* 直接求最小删除步数
* dp[i][j] = word1 的前 i 个字符 word2 的前 j 个字符 变成相同字符串 所需的最少删除步数
*/
public int minDistance2(String word1, String word2) {
int len1 = word1.length(), len2 = word2.length();
int[][] dp = new int[len1 + 1][len2 + 1];
// 初始化任意一个为空必须删完另一个
for (int i = 0; i <= len1; i++) dp[i][0] = i;
for (int j = 0; j <= len2; j++) dp[0][j] = j;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1]; // 字符相等无需删除
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1; // 删除一个
}
}
}
return dp[len1][len2];
}
}

View File

@ -17,6 +17,7 @@ import java.util.Arrays;
*/
//不会
//二刷不会
//三刷不会
public class EraseOverlapIntervals {
/**
* 1.按结束时间排序

View File

@ -41,7 +41,7 @@ public class LRUCache {
/**
* 双向链表节点
*/
class DLinkedNode {
static class DLinkedNode {
int key; // 保存 key便于淘汰节点时同步从 cache 中删除
int value; // 保存 value
DLinkedNode prev; // 前驱