9.28
This commit is contained in:
parent
8d4440e0dc
commit
425feff3c7
87
src/main/java/All/DiffWaysToCompute.java
Normal file
87
src/main/java/All/DiffWaysToCompute.java
Normal file
@ -0,0 +1,87 @@
|
||||
package All;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目: 241. 为运算表达式设计优先级
|
||||
* 描述:给你一个由数字和运算符组成的字符串 expression ,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以 按任意顺序 返回答案。
|
||||
* 生成的测试用例满足其对应输出值符合 32 位整数范围,不同结果的数量不超过 104 。
|
||||
|
||||
示例 1:
|
||||
输入:expression = "2-1-1"
|
||||
输出:[0,2]
|
||||
解释:
|
||||
((2-1)-1) = 0
|
||||
(2-(1-1)) = 2
|
||||
|
||||
* 链接:https://leetcode.cn/problems/different-ways-to-add-parentheses/
|
||||
*/
|
||||
//不会
|
||||
public class DiffWaysToCompute {
|
||||
/*
|
||||
核心思想
|
||||
|
||||
表达式由数字和运算符组成。
|
||||
我们要尝试给不同的运算符添加括号(改变计算顺序),得到不同结果。
|
||||
遇到运算符就把它当“分隔点”:
|
||||
左边子串 → 所有可能结果
|
||||
右边子串 → 所有可能结果
|
||||
然后把左右结果组合在一起。
|
||||
递归过程
|
||||
|
||||
如果当前子串全是数字 → 直接转成整数,作为结果返回。
|
||||
否则,枚举子串中的运算符 + - *:
|
||||
分别递归处理左右部分。
|
||||
把左右子结果两两组合,用当前运算符计算,放进结果集。
|
||||
结束条件
|
||||
|
||||
子串没有运算符,说明是纯数字,直接返回该数字。
|
||||
*/
|
||||
char[] cs; // 存储输入字符串的字符数组,便于处理
|
||||
|
||||
public List<Integer> diffWaysToCompute(String s) {
|
||||
cs = s.toCharArray();
|
||||
// 从整个表达式 [0, n-1] 开始递归
|
||||
return dfs(0, cs.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归函数:计算表达式 s[l..r] 的所有可能结果
|
||||
*/
|
||||
List<Integer> dfs(int l, int r) {
|
||||
List<Integer> ans = new ArrayList<>();
|
||||
|
||||
// 枚举区间内的每一个字符
|
||||
for (int i = l; i <= r; i++) {
|
||||
// 如果是数字,跳过;我们只在运算符处切分
|
||||
if (cs[i] >= '0' && cs[i] <= '9') continue;
|
||||
|
||||
// 遇到运算符 cs[i],把表达式分为左右两部分
|
||||
List<Integer> left = dfs(l, i - 1);
|
||||
List<Integer> right = dfs(i + 1, r);
|
||||
|
||||
// 枚举左右子表达式的结果,两两组合
|
||||
for (int a : left) {
|
||||
for (int b : right) {
|
||||
int cur;
|
||||
if (cs[i] == '+') cur = a + b;
|
||||
else if (cs[i] == '-') cur = a - b;
|
||||
else cur = a * b; // cs[i] == '*'
|
||||
ans.add(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 ans 为空,说明整个区间没有运算符,是一个纯数字
|
||||
if (ans.isEmpty()) {
|
||||
int cur = 0;
|
||||
for (int i = l; i <= r; i++) {
|
||||
cur = cur * 10 + (cs[i] - '0'); // 将字符转成数字
|
||||
}
|
||||
ans.add(cur);
|
||||
}
|
||||
|
||||
return ans;
|
||||
}
|
||||
}
|
||||
100
src/main/java/All/LastRemaining.java
Normal file
100
src/main/java/All/LastRemaining.java
Normal file
@ -0,0 +1,100 @@
|
||||
package All;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目: 390. 消除游戏
|
||||
* 描述:列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:
|
||||
* 从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。
|
||||
* 重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。
|
||||
* 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
|
||||
* 给你整数 n ,返回 arr 最后剩下的数字。
|
||||
|
||||
示例 1:
|
||||
输入:n = 9
|
||||
输出:6
|
||||
解释:
|
||||
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
arr = [2, 4, 6, 8]
|
||||
arr = [2, 6]
|
||||
arr = [6]
|
||||
|
||||
* 链接:https://leetcode.cn/problems/elimination-game/
|
||||
*/
|
||||
//不会
|
||||
public class LastRemaining {
|
||||
/*
|
||||
我们关心的其实不是整个数组,而是:
|
||||
|
||||
当前这一轮的 首项 a1 是多少;
|
||||
当前剩下多少个数 cnt;
|
||||
当前每两个数之间的间隔 step。
|
||||
每一轮删除操作之后:
|
||||
|
||||
元素个数减半 cnt = cnt / 2。
|
||||
间隔翻倍 step = step * 2。
|
||||
首项 a1 根据「正向 / 反向」和剩余个数的奇偶性决定是否要往后挪。
|
||||
*/
|
||||
public int lastRemaining(int n) {
|
||||
int a1 = 1; // 当前序列的首项(第一轮就是1)
|
||||
int k = 0; // 当前轮数,从0开始(偶数轮:从左往右;奇数轮:从右往左)
|
||||
int cnt = n; // 当前序列里剩余的元素个数
|
||||
int step = 1; // 相邻两个数的间隔,第一轮是1,之后每轮翻倍
|
||||
|
||||
while (cnt > 1) {
|
||||
if (k % 2 == 0) { // 偶数轮:从左往右删除
|
||||
// 左到右时,首项必然被删掉,所以新的首项要往后移一步
|
||||
a1 = a1 + step;
|
||||
} else { // 奇数轮:从右往左删除
|
||||
// 如果剩余的个数是奇数,那么首项会被删掉,需要往后移一步
|
||||
// 如果是偶数,那么首项保留,不变
|
||||
a1 = (cnt % 2 == 0) ? a1 : a1 + step;
|
||||
}
|
||||
k++; // 进入下一轮
|
||||
cnt = cnt >> 1; // 等价于 cnt = cnt / 2,每一轮都会删掉一半(右移一位)
|
||||
step = step << 1; // 等价于 step = step * 2,间隔翻倍
|
||||
}
|
||||
return a1;
|
||||
}
|
||||
|
||||
//容易想,但时间复杂度高
|
||||
public int lastRemaining2(int n) {
|
||||
// 初始化数组 [1, 2, ..., n]
|
||||
List<Integer> arr = new ArrayList<>();
|
||||
for (int i = 1; i <= n; i++) {
|
||||
arr.add(i);
|
||||
}
|
||||
|
||||
boolean leftToRight = true; // true 表示从左到右,false 表示从右到左
|
||||
|
||||
// 模拟过程,直到只剩下一个数
|
||||
while (arr.size() > 1) {
|
||||
//只收集未被删除的元素
|
||||
List<Integer> next = new ArrayList<>();
|
||||
|
||||
if (leftToRight) { // 从左到右
|
||||
for (int i = 1; i < arr.size(); i += 2) {
|
||||
next.add(arr.get(i));
|
||||
}
|
||||
} else { // 从右到左
|
||||
for (int i = arr.size() - 2; i >= 0; i -= 2) {
|
||||
next.add(arr.get(i));
|
||||
}
|
||||
// 注意反向删除后,结果是反序的,要反转一下
|
||||
Collections.reverse(next);
|
||||
}
|
||||
|
||||
arr = next; // 更新为新的一轮
|
||||
leftToRight = !leftToRight; // 改变方向
|
||||
}
|
||||
|
||||
return arr.get(0); // 最后剩下的唯一元素
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LastRemaining solution = new LastRemaining();
|
||||
System.out.println(solution.lastRemaining(9)); // 输出 6
|
||||
}
|
||||
}
|
||||
65
src/main/java/All/LongestSubstring.java
Normal file
65
src/main/java/All/LongestSubstring.java
Normal file
@ -0,0 +1,65 @@
|
||||
package All;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* 题目: 395. 至少有 K 个重复字符的最长子串
|
||||
* 描述:给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。
|
||||
* 如果不存在这样的子字符串,则返回 0。
|
||||
|
||||
示例 1:
|
||||
输入:s = "ababbc", k = 2
|
||||
输出:5
|
||||
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。
|
||||
|
||||
* 链接:https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/
|
||||
*/
|
||||
//不会
|
||||
public class LongestSubstring {
|
||||
/*
|
||||
整体思路
|
||||
|
||||
我们要找的子串必须满足:每个字符的出现次数 ≥ k。
|
||||
如果整个字符串中有某些字符出现次数 < k,那么这些字符一定不可能出现在结果子串中。
|
||||
所以我们可以把这些字符当作分隔符,把字符串分割成若干子串,递归处理。
|
||||
递归过程
|
||||
|
||||
如果当前子串长度小于 k,直接返回 0(不可能满足条件)。
|
||||
统计每个字符出现次数。
|
||||
找到那些出现次数 < k 的字符,把它们作为“分隔符”,切开子串,递归求解每一部分。
|
||||
如果所有字符的出现次数都 ≥ k,那么整个子串就是合法解,直接返回长度。
|
||||
结束条件
|
||||
|
||||
子串长度 < k → 返回 0。
|
||||
子串完全合法 → 返回子串长度。
|
||||
否则递归分治,返回最大值。
|
||||
*/
|
||||
public int longestSubstring(String s, int k) {
|
||||
// 如果字符串长度小于 k,不可能有满足条件的子串
|
||||
if (s.length() < k) return 0;
|
||||
|
||||
// 统计每个字符的出现次数
|
||||
HashMap<Character, Integer> counter = new HashMap<>();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
counter.put(s.charAt(i), counter.getOrDefault(s.charAt(i), 0) + 1);
|
||||
}
|
||||
|
||||
// 遍历所有字符,找到出现次数 < k 的字符
|
||||
for (char c : counter.keySet()) {
|
||||
if (counter.get(c) < k) {
|
||||
// 这个字符不能出现在任何合法子串中
|
||||
// 因此我们用它作为分隔符,切分字符串
|
||||
int res = 0;
|
||||
for (String t : s.split(String.valueOf(c))) {
|
||||
// 递归处理切分后的子串
|
||||
res = Math.max(res, longestSubstring(t, k));
|
||||
}
|
||||
return res; // 返回最长的那个子串结果
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有字符出现次数都 >= k
|
||||
// 那么整个字符串就是一个合法的解
|
||||
return s.length();
|
||||
}
|
||||
}
|
||||
66
src/main/java/All/MaxCoins.java
Normal file
66
src/main/java/All/MaxCoins.java
Normal file
@ -0,0 +1,66 @@
|
||||
package All;
|
||||
/**
|
||||
* 题目: 312. 戳气球
|
||||
* 描述:有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
|
||||
* 现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
|
||||
* 求所能获得硬币的最大数量。
|
||||
|
||||
示例 1:
|
||||
输入:nums = [3,1,5,8]
|
||||
输出:167
|
||||
解释:
|
||||
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
|
||||
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
|
||||
|
||||
* 链接:https://leetcode.cn/problems/burst-balloons/
|
||||
*/
|
||||
//不会
|
||||
public class MaxCoins {
|
||||
/*
|
||||
以 nums = [3,1,5,8],加虚拟气球 [1,3,1,5,8,1]。
|
||||
|
||||
我们看区间 (0, 5):
|
||||
|
||||
如果最后戳 i=3 (值=5),此时左右邻居是 val[0]=1 和 val[5]=1。
|
||||
收益 = 1*5*1 + (0,3)区间最大值 + (3,5)区间最大值。
|
||||
如果最后戳 i=2 (值=1),邻居是 val[0]=1 和 val[5]=1,
|
||||
收益 = 1*1*1 + (0,2)区间最大值 + (2,5)区间最大值。
|
||||
注意:我们根本不用管它前面戳没戳过谁,因为只要保证它是最后一个,它的邻居就固定成了边界。
|
||||
*/
|
||||
int[][] memo;
|
||||
int[] nums;
|
||||
|
||||
public int maxCoins(int[] arr) {
|
||||
int n = arr.length;
|
||||
// 构造新数组,在两端加 1
|
||||
nums = new int[n + 2];
|
||||
nums[0] = nums[n + 1] = 1;
|
||||
for (int i = 0; i < n; i++) nums[i + 1] = arr[i];
|
||||
|
||||
memo = new int[n + 2][n + 2];
|
||||
return dfs(0, n + 1);
|
||||
}
|
||||
|
||||
// 戳破 (l, r) 开区间内的所有气球
|
||||
//dfs(l, r) 表示 开区间 (l, r) 内的最大值
|
||||
int dfs(int l, int r) {
|
||||
if (l + 1 >= r) return 0; // 区间内没有气球了
|
||||
if (memo[l][r] > 0) return memo[l][r];
|
||||
|
||||
int res = 0;
|
||||
// 枚举最后一个戳破的气球 i 如果 i 是区间 (l, r) 里最后一个被戳的气球,那么它的邻居 一定是 l 和 r(因为 (l, r) 之间其它气球都已经戳光了)。
|
||||
for (int i = l + 1; i < r; i++) {
|
||||
int coins = nums[l] * nums[i] * nums[r]; // 戳破 i 的得分
|
||||
coins += dfs(l, i) + dfs(i, r); // 左右子区间结果
|
||||
res = Math.max(res, coins);
|
||||
}
|
||||
|
||||
memo[l][r] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
MaxCoins solver = new MaxCoins();
|
||||
System.out.println(solver.maxCoins(new int[]{3,1,5,8})); // 输出 167
|
||||
}
|
||||
}
|
||||
@ -33,9 +33,12 @@ public class BuildTree {
|
||||
return helper(0,cnt-1,0,cnt-1,preorder,inorder);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private Map<Integer, Integer> indexMap;
|
||||
|
||||
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left) {
|
||||
public TreeNode myBuildTree(int[] preorder, int preorder_left, int preorder_right, int inorder_left) {
|
||||
if (preorder_left > preorder_right) {
|
||||
return null;
|
||||
}
|
||||
@ -49,20 +52,20 @@ public class BuildTree {
|
||||
int size_left_subtree = inorder_root - inorder_left;
|
||||
// 递归地构造左子树,并连接到根节点
|
||||
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
|
||||
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left);
|
||||
root.left = myBuildTree(preorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left);
|
||||
// 递归地构造右子树,并连接到根节点
|
||||
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
|
||||
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1);
|
||||
root.right = myBuildTree(preorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1);
|
||||
return root;
|
||||
}
|
||||
|
||||
public TreeNode buildTree(int[] preorder, int[] inorder) {
|
||||
int n = preorder.length;
|
||||
// 构造哈希映射,帮助我们快速定位根节点
|
||||
indexMap = new HashMap<Integer, Integer>();
|
||||
indexMap = new HashMap<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
indexMap.put(inorder[i], i);
|
||||
}
|
||||
return myBuildTree(preorder, inorder, 0, n - 1, 0);
|
||||
return myBuildTree(preorder, 0, n - 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user