Medium
题目描述
给你一个仅由小写英文字母组成的字符串 s。
你可以执行以下操作任意次数(包括零次):
- 选择字符串中的任意字符
c,将c的每次出现都替换为英文字母表中的下一个字母。
返回将 s 转换为仅由 ‘a’ 字符组成的字符串所需的最少操作次数。
注意:将字母表视为循环的,因此 ‘a’ 在 ‘z’ 之后。
示例 1:
输入:s = "yz"
输出:2
解释:
- 将 'y' 变为 'z' 得到 "zz"。
- 将 'z' 变为 'a' 得到 "aa"。
因此,答案是 2。
示例 2:
输入:s = "a"
输出:0
解释:
字符串 "a" 仅由 'a' 字符组成。因此,答案是 0。
约束条件:
1 <= s.length <= 5 * 10^5s仅由小写英文字母组成。
解题思路
这道题的关键是理解操作的性质:每次操作会将某个字符的所有出现都向前移动一位。
思路分析:
单字符分析:对于任意字符
c,要将其变为 ‘a’,需要的操作次数是(26 - (c - 'a')) % 26。例如 ‘z’ 需要 1 次操作变为 ‘a’,‘y’ 需要 2 次。贪心策略:关键洞察是我们可以巧妙地安排操作顺序。当我们操作需要更多步骤的字符时,可能会将其他字符"带动"到目标位置,从而减少总操作次数。
最优解:通过分析可以发现,最优策略是找出字符串中所有字符到 ‘a’ 的最大距离。这是因为:
- 我们总是可以先操作距离最远的字符
- 在操作过程中,其他字符可能会被"顺带"处理
- 最终的操作次数等于所有字符中到 ‘a’ 距离的最大值
具体实现:遍历字符串中的所有字符,计算每个字符变为 ‘a’ 需要的步数,取最大值即为答案。
代码实现
class Solution {
public:
int minOperations(string s) {
int maxOps = 0;
for (char c : s) {
int ops = (26 - (c - 'a')) % 26;
maxOps = max(maxOps, ops);
}
return maxOps;
}
};
class Solution:
def minOperations(self, s: str) -> int:
max_ops = 0
for c in s:
ops = (26 - (ord(c) - ord('a'))) % 26
max_ops = max(max_ops, ops)
return max_ops
public class Solution {
public int MinOperations(string s) {
int maxOps = 0;
foreach (char c in s) {
int ops = (26 - (c - 'a')) % 26;
maxOps = Math.Max(maxOps, ops);
}
return maxOps;
}
}
var minOperations = function(s) {
let maxOps = 0;
for (let c of s) {
let ops = (26 - (c.charCodeAt(0) - 'a'.charCodeAt(0))) % 26;
maxOps = Math.max(maxOps, ops);
}
return maxOps;
};
复杂度分析
| 复杂度类型 | 值 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 需要遍历字符串中的每个字符一次 |
| 空间复杂度 | O(1) | 只使用了常量级的额外空间 |