- 魔法师2026-09-1813093
力扣 LeetCode

1520. 最多的不重叠子字符串 - 力扣(LeetCode)
1520. 最多的不重叠子字符串 - 给你一个只包含小写字母的字符串 s ,你需要找到 s 中最多数目的非空子字符串,满足如下条件:
1. 这些字符串之间互不重叠,也就是说对于任意两个子字符串 s[i..j] 和 s[x..y] ,要么 j < x 要么 i > y 。
2. 如果一个子字符串包含字符 char ,那么 s 中所有 char...思路
朴素思路
- 先从左到右、从右到左分别遍历一遍,记录每种字母出现的第一个、最后一个和中间夹杂的其他字母。
- 遍历26个字母,如果字母的范围内夹杂了其他字母,那么更新范围,直到不再变动位置。
- 按范围从小到大排序并遍历,取与之前选中字符串不重叠的字符串加入到结果中。
PS
朴素、低效且丑陋
代码```
class Solution {
public List<String> maxNumOfSubstrings(String s) {
int[] chars = new int[s.length()];
for (int i = 0; i < chars.length; i++) {
chars[i] = s.charAt(i) - 'a';
}
int[] first = new int[26];
int[] last = new int[26];
HashSet<Integer>[] sets = new HashSet[26];
Arrays.fill(first, -1);
Arrays.fill(last, -1);
for (int i = 0; i < 26; i++) {
sets[i] = new HashSet<>();
}
for (int i = chars.length - 1; i>= 0; i--) {
if (last[chars[i]] == -1) {
last[chars[i]] = i;
}
}
for (int i = 0; i < chars.length; i++) {
if (first[chars[i]] == -1) {
first[chars[i]] = i;
}
for (int j = 0; j < 26; j++) {
if (first[j] != -1 && last[j] > i) {
sets[j].add(chars[i]);
}
}
}
boolean changed = false;
do {
changed = false;
for (int i = 0; i < 26; i++) {
for (Integer j : sets[i]) {
if (first[i] > first[j]) {
first[i] = first[j];
changed = true;
}
if (last[i] < last[j]) {
last[i] = last[j];
changed = true;
}
}
}
} while (changed);
List<int[]> rec = new ArrayList<>();
for (int i = 0; i < 26; i++) {
if (first[i] != -1) {
rec.add(new int[]{last[i] - first[i] + 1, first[i], last[i]});
}
}
rec.sort((a, b) -> a[0] - b[0]);
boolean[] vis = new boolean[rec.size()];
for (int i = 0; i < rec.size(); i++) {
int[] r = rec.get(i);
boolean v = true;
for (int j = 0; j < i; j++) {
if (!vis[j]) {
continue;
}
int[] t = rec.get(j);
if (r[1] <= t[1] && r[2] >= t[1]) {
v = false;
break;
}
}
if (v) {
vis[i] = true;
}
}
List<String> result = new ArrayList<>();
for (int i = 0; i < vis.length; i++) {
if (vis[i]) {
int[] r = rec.get(i);
result.add(s.substring(r[1], r[2] + 1));
}
}
return result;
}
}主楼 - SomeBottle2026-09-1811179
有点丑陋了。思路还是比较明确的,找每个字符首次 (
l) 和最后出现 (r) 的位置,其之间出现的字符 \alpha 可能全出现在(l, r)、也可能最早出现在l之前、亦可能最晚出现在r之后。\alpha 出现在
r之后,根据题目要求需要扩展r;但 \alpha 出现在l之前的话,不用管当前区间,因为处理到该字符的时候就会考虑到当前这个区间。扩展出区间后,按贪心思路,选择最早结束的、没有发生交叠的区间即可。```
class Solution {
public:
vector<string> maxNumOfSubstrings(string s) {
// 也就是说子字符串中包含的字符都应该只出现在这个子字符串中
// 一个字符如果后面还会出现,其两次出现之间可能有更短的子字符串,也可能牵涉有字符在后面还会出现
vector<vector<int>> ap(26,vector<int>(2,-1)); // 存每个字符首次和最后出现的位置
for(int i=0;i<s.size();i++){
if(ap[s[i]-'a'][0]==-1){
ap[s[i]-'a'][0]=i;
}
ap[s[i]-'a'][1]=i;
}
// 检查每个字符对应的子字符串范围
vector<pair<int,int>> intvs; // 存合法的子字符串范围区间 <r, l>,先存 r 是为了方便排序
for(int i=0;i<26;i++){
if(ap[i][0]==-1){
// 不存在该字母
continue;
}
int l=ap[i][0],r=ap[i][1];
bool skip=false;
for(int j=l;j<=r;j++){
// 出现在之间的字符,如果其最早出现在 l 之前,说明当前区间和之前的交叠了,出现在之前的字符涉及的子字符串包含了当前区间,可以跳过
if(ap[s[j]-'a'][0]<l){
skip=true;
break;
}
// 出现在之间的字符,可能在当前字符最后出现之后还有出现
// 为了让子字符串包含的字符只出现在子字符串中
// 扩展右边界
r=max(r,ap[s[j]-'a'][1]);
}
if(skip){
continue;
}
intvs.emplace_back(r,l);
}
// 按右端排序
sort(intvs.begin(),intvs.end());
// 扫描选择出不重叠的子字符串
int lastR=-1;
vector<string> res;
for(auto& p:intvs){
if(p.second<lastR){
// 当前区间开始点小于上个区间结束点,有重叠
continue;
}
lastR=p.first;
res.emplace_back(s.substr(p.second,p.first-p.second+1));
}
return res;
}
}; - rainnguy2026-09-1835856
好久没做了,以后每天争取来做一题
- CPython2026-09-1814995
看见hard就不想做,无法战胜.
13. 罗马数字转整数 - 力扣(LeetCode)```
f = {
'I': 1,
'V': 5,
'X': 10,
'L': 50,
'C': 100,
'D': 500,
'M': 1000,
}class Solution:
def romanToInt(self, s: str) -> int:
ans = 0
for x, y in pairwise(s):
x, y = f[x], f[y]
ans += x if x >= y else -x
return ans + f[s[-1]]