Medium
题目描述
给你一个字符串 s。
请你考虑执行以下操作直到 s 变为空字符串:
- 对于从 ‘a’ 到 ‘z’ 的每个字母字符,删除
s中该字符的第一次出现(如果存在)。
例如,设初始 s = "aabcbbca"。我们执行以下操作:
- 删除下划线字符
s = "aabcbbca"。结果字符串是s = "abbca"。 - 删除下划线字符
s = "abbca"。结果字符串是s = "ba"。 - 删除下划线字符
s = "ba"。结果字符串是s = ""。
返回在执行最后一次操作之前的字符串 s 的值。在上面的例子中,答案是 "ba"。
示例 1:
输入: s = "aabcbbca"
输出: "ba"
解释: 如题目描述。
示例 2:
输入: s = "abcd"
输出: "abcd"
解释: 我们执行以下操作:
- 删除下划线字符 s = "abcd"。结果字符串是 s = ""。
最后一次操作之前的字符串是 "abcd"。
提示:
1 <= s.length <= 5 * 10^5s仅包含小写英文字母。
提示:
- 在最后一次操作之前,只有原始字符串中出现次数最多的字符会保留。
- 只保留每个出现次数最多的字符的最后一次出现。
解题思路
解题思路:
这道题的关键观察是:在每轮操作中,我们按字母顺序删除每个字符的第一次出现。最终剩下的字符都是那些在原字符串中出现次数最多的字符,并且是它们的最后一次出现。
具体分析:
- 首先统计每个字符的出现次数,找到最大出现次数
- 在最后的非空字符串中,只会包含出现次数等于最大次数的字符
- 对于这些字符,只有它们的最后一次出现会被保留
- 我们从右到左遍历原字符串,记录每个最频繁字符的最后出现位置
- 按照这些位置的相对顺序构建最终结果
算法步骤:
- 统计所有字符的频次,找到最大频次
- 从右往左遍历字符串,对于频次等于最大频次的字符,记录其位置(只记录第一次遇到的,即原字符串中的最后一次出现)
- 按照记录的位置顺序输出字符
这个方法的时间复杂度是O(n),空间复杂度是O(1)(因为只有26个小写字母)。
代码实现
class Solution {
public:
string lastNonEmptyString(string s) {
vector<int> count(26, 0);
// 统计每个字符的出现次数
for (char c : s) {
count[c - 'a']++;
}
// 找到最大出现次数
int maxCount = *max_element(count.begin(), count.end());
string result;
vector<bool> used(26, false);
// 从右往左遍历,找到每个最频繁字符的最后出现位置
for (int i = s.length() - 1; i >= 0; i--) {
int idx = s[i] - 'a';
if (count[idx] == maxCount && !used[idx]) {
result = s[i] + result;
used[idx] = true;
}
}
return result;
}
};
class Solution:
def lastNonEmptyString(self, s: str) -> str:
from collections import Counter
# 统计每个字符的出现次数
count = Counter(s)
max_count = max(count.values())
result = []
used = set()
# 从右往左遍历,找到每个最频繁字符的最后出现位置
for i in range(len(s) - 1, -1, -1):
char = s[i]
if count[char] == max_count and char not in used:
result.append(char)
used.add(char)
# 反转结果,因为我们是从右往左添加的
return ''.join(reversed(result))
public class Solution {
public string LastNonEmptyString(string s) {
int[] count = new int[26];
// 统计每个字符的出现次数
foreach (char c in s) {
count[c - 'a']++;
}
// 找到最大出现次数
int maxCount = count.Max();
StringBuilder result = new StringBuilder();
bool[] used = new bool[26];
// 从右往左遍历,找到每个最频繁字符的最后出现位置
for (int i = s.Length - 1; i >= 0; i--) {
int idx = s[i] - 'a';
if (count[idx] == maxCount && !used[idx]) {
result.Insert(0, s[i]);
used[idx] = true;
}
}
return result.ToString();
}
}
var lastNonEmptyString = function(s) {
const count = new Array(26).fill(0);
// 统计每个字符的出现次数
for (const char of s) {
count[char.charCodeAt(0) - 97]++;
}
// 找到最大出现次数
const maxCount = Math.max(...count);
let result = '';
const used = new Set();
// 从右往左遍历,找到每个最频繁字符的最后出现位置
for (let i = s.length - 1; i >= 0; i--) {
const char = s[i];
const idx = char.charCodeAt(0) - 97;
if (count[idx]
复杂度分析
| 复杂度 | 大小 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(1) |
其中 n 是字符串 s 的长度。空间复杂度为 O(1) 是因为我们只使用了固定大小的数组来存储 26 个字母的信息。