每日算法-250601
每日算法 - 250601
记录今天完成的算法题目。
1. 1749. 任意子数组和的绝对值的最大值
题目描述
思路
前缀和
解题过程
子数组的和 sum(nums[i..j])
可以通过前缀和 prefixSum[j] - prefixSum[i-1]
来计算(规定 prefixSum[-1] = 0
)。
我们要求的是 abs(prefixSum[j] - prefixSum[i-1])
的最大值。
这等价于找到所有前缀和(包括 0
)中的最大值 maxP
和最小值 minP
,那么结果就是 maxP - minP
。
具体做法:
-
在原数组
nums
上计算前缀和,即nums[k]
更新为nums[0] + ... + nums[k]
。 -
在计算前缀和的过程中,我们记录到目前为止遇到的最大前缀和
current_max_prefix
和最小前缀和current_min_prefix
。current_max_prefix
初始化为nums[0]
(第一个前缀和)。current_min_prefix
初始化为nums[0]
(第一个前缀和)。
-
遍历数组(从第二个元素开始)更新前缀和,并相应更新
current_max_prefix
和current_min_prefix
。 -
最终的答案考虑以下三种情况的绝对值:
- 最大前缀和本身:
abs(current_max_prefix)
。这对应子数组从索引0
开始到某个位置结束的情况,其和为current_max_prefix - 0
。 - 最小前缀和本身:
abs(current_min_prefix)
。这对应子数组从索引0
开始到某个位置结束的情况,其和为current_min_prefix - 0
。 - 最大前缀和与最小前缀和的差:
abs(current_max_prefix - current_min_prefix)
。这对应子数组从使得前缀和为current_min_prefix
的位置之后开始,到使得前缀和为current_max_prefix
的位置结束(或反之)。
这三种情况的覆盖可以用
max(abs(current_max_prefix), abs(current_min_prefix), abs(current_max_prefix - current_min_prefix))
来概括。 - 最大前缀和本身:
复杂度
- 时间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组
nums
的长度。我们遍历数组一次计算前缀和并更新最大最小值。 - 空间复杂度: O ( 1 ) O(1) O(1),我们是在原数组上进行修改,或者如果不能修改原数组,则需要 O ( N ) O(N) O(N) 空间存前缀和。题目中代码是原地修改。
Code
class Solution {public int maxAbsoluteSum(int[] nums) {int max = nums[0], min = nums[0];for (int i = 1; i < nums.length; i++) {nums[i] += nums[i - 1];max = Math.max(max, nums[i]);min = Math.min(min, nums[i]);}return Math.max(Math.abs(max - min), Math.max(Math.abs(min), Math.abs(max)));}
}
2. 3361. 两个字符串的切换距离
题目描述
思路
前缀和
解题过程
题目要求计算将字符串 ss
中的每个字符 s[i]
变换到 tt
中对应字符 t[i]
的最小代价之和。
对于任意一对字符 start_char
和 end_char
,变换有两种方式:
- 一直向后(字典序增大)变换,例如 ‘a’ -> ‘b’ -> ‘c’ …,如果超过 ‘z’ 则回到 ‘a’。
- 一直向前(字典序减小)变换,例如 ‘c’ -> ‘b’ -> ‘a’ …,如果小于 ‘a’ 则回到 ‘z’。
我们可以预处理出两种代价的前缀和数组:
-
nextCostL[k]
:从字符 ‘a’ 一直向后变换到字符'a'+k
,再到'a'+(k+1)%26
的累积代价。更准确地说,nextCost[j]
是从字符j
变换到(j+1)%26
的代价。那么nextCostL[k]
存储sum(nextCost[0]...nextCost[k])
。 -
previousCostL[k]
:类似地,previousCost[j]
是从字符j
变换到(j-1+26)%26
的代价。previousCostL[k]
存储sum(previousCost[0]...previousCost[k])
。
遍历字符串 ss
和 tt
,对于每一对字符 s[i]
和 t[i]
:
- 获取它们的数字表示
start = s[i]-'a'
和end = t[i]-'a'
。 - 如果
start == end
,代价为 0。 - 否则,利用预处理的前缀和数组计算向后变换的代价
nextSum
和向前变换的代价previousSum
。getVal(prefixArr, index)
是一个辅助函数,用于安全地获取前缀和prefixArr[index]
,如果index < 0
则返回0
。
- 取
Math.min(nextSum, previousSum)
累加到总结果ret
。
复杂度
- 时间复杂度: O ( N + C ) O(N + C) O(N+C)
- 空间复杂度: O ( 1 ) O(1) O(1)
Code
class Solution {public long shiftDistance(String ss, String tt, int[] nextCost, int[] previousCost) {char[] s = ss.toCharArray(), t = tt.toCharArray();long ret = 0;long[] nextCostL = new long[26], previousCostL = new long[26];nextCostL[0] = nextCost[0];previousCostL[0] = previousCost[0];for (int i = 1; i < 26; i++) {nextCostL[i] = nextCostL[i - 1] + nextCost[i];previousCostL[i] = previousCostL[i - 1] + previousCost[i];}for (int i = 0; i < s.length; i++) {int start = s[i] - 'a', end = t[i] - 'a';if (start == end) {continue;}long nextSum = 0, previousSum = 0;if (start < end) {nextSum = getVal(nextCostL, end - 1) - getVal(nextCostL, start - 1);previousSum = getVal(previousCostL, start) + (getVal(previousCostL, 25) - getVal(previousCostL, end));} else {nextSum = (getVal(nextCostL, 25) - getVal(nextCostL, start - 1)) +getVal(nextCostL, end - 1);previousSum = getVal(previousCostL, start) - getVal(previousCostL, end);}ret += Math.min(nextSum, previousSum);}return ret;}private long getVal(long[] prefixArr, int index) {if (index < 0) {return 0;}return prefixArr[index];}
}