每日算法-250529
2909. 元素和最小的山形三元组 II
题目
思路
数组, 前后缀分解
解题过程
对于寻找满足特定条件的三元组
(nums[i], nums[j], nums[k])
且i < j < k
的问题,一个常见的思路是枚举中间元素nums[j]
。
- 确定目标:我们要找的是和最小的 “山形三元组”,即
nums[i] < nums[j]
且nums[k] < nums[j]
。- 枚举
nums[j]
:当j
从1
遍历到n-2
(其中n
是数组长度),我们需要快速找到nums[j]
左侧的最小值 (prevMin
) 和右侧的最小值 (suffixMinVal
)。- 左侧最小值:对于
nums[j]
左侧[0, j-1]
区间的最小值,我们可以在遍历nums[j]
的同时,用一个变量prevMin
动态维护nums[0...j-1]
的最小值。- 右侧最小值:对于
nums[j]
右侧[j+1, n-1]
区间的最小值,我们可以预处理一个后缀最小值数组suffixMin
。suffixMin[x]
表示nums[x...n-1]
区间的最小值。这样,对于当前的nums[j]
, 右侧的最小值就是suffixMin[j+1]
。- 判断与更新:当确定了
prevMin
和suffixMin[j+1]
后,如果prevMin < nums[j]
且nums[j] > suffixMin[j+1]
,说明找到了一个山形三元组。我们计算其和prevMin + nums[j] + suffixMin[j+1]
,并用它来更新全局的最小和结果ret
。- 初始化与边界:
ret
初始化为Integer.MAX_VALUE
。如果遍历结束后ret
仍为此初始值,说明不存在山形三元组,返回 -1。
复杂度
- 时间复杂度: O ( N ) O(N) O(N)
- 预处理
suffixMin
数组需要 O ( N ) O(N) O(N)。 - 主循环遍历
nums[j]
需要 O ( N ) O(N) O(N)。 - 总体为 O ( N ) + O ( N ) = O ( N ) O(N) + O(N) = O(N) O(N)+O(N)=O(N)。
- 预处理
- 空间复杂度: O ( N ) O(N) O(N)
- 需要
suffixMin
数组存储后缀最小值,空间为 O ( N ) O(N) O(N)。
- 需要
Code
class Solution {public int minimumSum(int[] nums) {int ret = Integer.MAX_VALUE;int n = nums.length;int[] suffixMin = new int[n];suffixMin[n - 1] = nums[n - 1];for (int i = n - 2; i >= 0; i--) {suffixMin[i] = Math.min(suffixMin[i + 1], nums[i]);}int prevMin = nums[0];for (int j = 1; j < n - 1; j++) {if (prevMin < nums[j] && nums[j] > suffixMin[j + 1]) {ret = Math.min(ret, (prevMin + nums[j] + suffixMin[j + 1]));}prevMin = Math.min(prevMin, nums[j]);}return ret == Integer.MAX_VALUE ? -1 : ret;}
}
1930. 长度为 3 的不同回文子序列
题目
思路
数组, 哈希表/计数
解题过程
一个长度为 3 的回文子序列形如
s[i]...s[j]...s[k]
变成xyx
,其中s[i] == s[k]
且i < j < k
。
我们的目标是统计这种xyx
形式的不同字符串的个数。
- 枚举外层字符:我们可以枚举构成回文的第一个字符
s[i]
和第三个字符s[k]
。- 字符频次预处理 (
globalMap
):首先,统计原字符串s
中每个字符的出现次数,存储在globalMap
。- 外层循环
i
:遍历i
从0
到n-3
(作为第一个字符s[i]
的索引)。
- 剪枝:如果
globalMap[s[i] - 'a'] <= 1
,说明字符s[i]
在整个字符串中只出现了一次或零次,不可能作为回文的两端,直接跳过。- 内层循环
k
:对于每个s[i]
,从字符串末尾n-1
向前遍历k
到i+1
(作为第三个字符s[k]
的索引)。
- 寻找匹配的
s[k]
:当找到一个s[k] == s[i]
时,我们就确定了回文的两个外层字符。- 中层字符统计:在索引
i
和k
之间 (即i+1
到k-1
),我们需要找到所有不同的字符s[j]
。
- 使用一个局部的
map
(大小为26的数组) 来记录在(i, k)
区间内出现的不同字符。- 遍历
j
从i+1
到k-1
。如果s[j]
在这个map
中是第一次出现 (即map[s[j] - 'a'] < 1
),则说明s[i]s[j]s[k]
是一个新的回文子序列,ret
增加 1。- 同时,用
alike
记录在(i, k)
区间内与s[i]
(或s[k]
) 相同的字符数量。- 优化 (
globalMap
更新与break
):
- 一旦为当前的
s[i]
找到了最靠右的匹配s[k]
并统计了中间的字符,我们就认为与这个s[i]
(在当前索引i
处的)相关的、以s[k]
(在当前索引k
处的)为结尾的回文都已经统计完毕。- 为了避免重复计算(特别是当
s[i]
字符在后续的i
中再次出现时),我们从globalMap
中减去已处理的字符:globalMap[s[i] - 'a'] -= (2 + alike)
。这里的2
代表s[i]
和s[k]
,alike
代表中间与s[i]
相同的字符。这是一种尝试性的优化,旨在减少后续迭代中对相同字符模式的重复探索。- 然后
break
内层k
的循环,因为对于当前的s[i]
,我们只关心它与最右边的s[k]
形成的组合。- 返回
ret
。
复杂度
- 时间复杂度: O ( N 3 ) O(N^3) O(N3)
- 外层
i
循环 N N N 次。 - 内层
k
循环最坏情况下 N N N 次。 - 中层
j
循环最坏情况下 N N N 次。 globalMap
的更新和map
操作是 O ( 1 ) O(1) O(1) (因为字母表大小固定为26)。- 虽然
globalMap
的更新和break
提供了一些剪枝,但最坏情况(例如字符串由许多不同字符组成,每个字符恰好出现两次)可能导致接近 O ( N 3 ) O(N^3) O(N3) 的行为。
- 外层
- 空间复杂度: O ( N ) O(N) O(N)
char[] s
占用 O ( N ) O(N) O(N)。globalMap
和局部的map
占用 O ( ∣ Σ ∣ ) O(|\Sigma|) O(∣Σ∣),即 O ( 1 ) O(1) O(1)。- 所以总体空间复杂度是 O ( N ) O(N) O(N)。
Code
class Solution {public int countPalindromicSubsequence(String ss) {char[] s = ss.toCharArray();int ret = 0, n = s.length;int[] globalMap = new int[26];for (char c : s) {globalMap[c - 'a']++;}for (int i = 0; i < n - 2; i++) {if (globalMap[s[i] - 'a'] > 1) {for (int k = n - 1; k > i + 1; k--) {if (s[i] == s[k]) {int[] map = new int[26];int alike = 0;for (int j = i + 1; j < k; j++) {if (map[s[j] - 'a'] < 1) {ret++;}if (s[j] == s[i]) {alike++;}map[s[j] - 'a']++;}globalMap[s[i] - 'a'] -= 2 + alike;break;}}}}return ret;}
}
1433. 检查一个字符串是否可以打破另一个字符串(复习)
题目
这是第二次写这道题了,写的还不错,基本上算是掌握了,就不再多说了,详细题解见每日算法-250429
代码
class Solution {public boolean checkIfCanBreak(String ss1, String ss2) {char[] s1 = ss1.toCharArray();char[] s2 = ss2.toCharArray();Arrays.sort(s1);Arrays.sort(s2);boolean s1BreakS2 = true;for (int i = 0; i < s1.length; i++) {if (s2[i] > s1[i]) {s1BreakS2 = false;break;}}boolean s2BreakS1 = true;for (int i = 0; i < s1.length; i++) {if (s1[i] > s2[i]) {s2BreakS1 = false;break;}}return s1BreakS2 || s2BreakS1;}
}
682. 棒球比赛
题目
这是第二次写这道题了,写的还不错,基本上算是掌握了,就不再多说了,详细题解见每日算法-250330
代码
class Solution {public int calPoints(String[] operations) {Stack<String> stack = new Stack<>();for (String in : operations) {int a = 0, b = 0;switch (in) {case "+" -> {String num = stack.pop();a = Integer.parseInt(num);b = Integer.parseInt(stack.peek());stack.push(num);stack.push(String.valueOf(a + b));}case "D" -> {a = Integer.parseInt(stack.peek());stack.push(String.valueOf(2 * a));}case "C" -> stack.pop();default -> stack.push(in);}}int sum = 0;while (!stack.isEmpty()) {sum += Integer.parseInt(stack.pop());}return sum;}
}