给你一个只包含小写字母的字符串 s ,你需要找到 s 中最多数目的非空子字符串,满足如下条件:
这些字符串之间互不重叠,也就是说对于任意两个子字符串 s[i..j] 和 s[k..l] ,要么 j < k 要么 i > l 。
如果一个子字符串包含字符 char ,那么 s 中所有 char 字符都应该在这个子字符串中。
请你找到满足上述条件的最多子字符串数目。如果有多个解法有相同的子字符串数目,请返回这些子字符串总长度最小的一个解。可以证明最小总长度解是唯一的。
请注意,你可以以 任意 顺序返回最优解的子字符串。
示例 1:
输入:s = “adefaddaccc”
输出:[“e”,”f”,”ccc”]
解释:下面为所有满足第二个条件的子字符串:
[
“adefaddaccc”
“adefadda”,
“ef”,
“e”,
“f”,
“ccc”,
]
如果我们选择第一个字符串,那么我们无法再选择其他任何字符串,所以答案为 1 。如果我们选择 “adefadda” ,剩下子字符串中我们只可以选择 “ccc” ,它是唯一不重叠的子字符串,所以答案为 2 。同时我们可以发现,选择 “ef” 不是最优的,因为它可以被拆分成 2 个子字符串。所以最优解是选择 [“e”,”f”,”ccc”] ,答案为 3 。不存在别的相同数目子字符串解。
示例 2:
输入:s = “abbaccd”
输出:[“d”,”bb”,”cc”]
解释:注意到解 [“d”,”abba”,”cc”] 答案也为 3 ,但它不是最优解,因为它的总长度更长。
提示:
1 <= s.length <= 10^5
s 只包含小写英文字母。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-number-of-non-overlapping-substrings
思路:(抄自官方题解)
所以解决问题的第一步是需要预处理出字符集中每个字符对应的最短字符串,由于字符集很小,我们可以暴力处理这一部分的答案。我们先遍历字符串,确定字符 i 第一次出现的位置 l和最后一次出现的位置 r,由于 [l,r]中间可能存在其他字符,因此为了满足题目的第二点要求,我们需要遍历 [l,r] 中的所有字符,利用它们的左右端点来更新 l和r,保证「如果一个子字符串包含字符 c,那么 s 中所有 c 字符都应该在这个子字符串中」。
预处理完以后,我们将每个字符串的起始位置看作一个个线段[li ,ri],问题就转化成了有一个 [0,n−1] 的一维数轴,其中n=s.length,我们需要用尽可能多的线段去覆盖这个数轴,且线段间互不相交,线段之和最小。
官方题解中后续使用了排序的方式保证贪心的有效性。
基于前面的预处理思路,我的思路是:
预处理结束后,对于每个s[i]开头的字串,找到长度最短的子串,将其答案更新,局部最优-> 全局最优->ok
复杂度分析:
时间复杂度:O(nΣ + n), Σ是字符集长度,n是s长度,相较于官方题解的O(nΣ + ΣlogΣ) (实际应为O(nΣ +2n+ ΣlogΣ))*,不需要排序。
空间复杂度:O(Σ)
本方法:
官方题解:
与本题思路类似的有:
763. 划分字母区间
var maxNumOfSubstrings = function (s) {
let letter = new Array(26).fill(undefined).map(() => {
return new Array(2).fill(-1);
});
for (let i = 0; i < s.length; i++) {
let char_index = s.charCodeAt(i) - 97;
if (letter[char_index][0] === -1) {
letter[char_index] = [i, i];
} else {
letter[char_index][1] = i;
}
}
const check = (i) => {
let char_i = s.codePointAt(i) - 97;
let right = letter[char_i][1];
for (let j = i; j < right; j++) {
let char_j = s.charCodeAt(j) - 97;
if (letter[char_j][0] < i) {
return -1;
}
right = Math.max(right, letter[char_j][1]);
}
return right;
};
let ans = [];
let right = -1;
for (let i = 0; i < s.length; i++) {
let char_i = s.charCodeAt(i) - 97;
if (i === letter[char_i][0]) {
let newRight = check(i);
if (newRight !== -1) {
//满足此条件,说明以s[i]开头至少有一个子串满足条件
if (i > right) {
ans.push("");
}
right = newRight;
//保证满足条件的以s[i]开头的子串长度最小 -> 局部最小 -> 全局最小
ans[ans.length - 1] = s.substr(i, right - i + 1);
}
}
}
return ans;
};