来源:力扣(LeetCode)
描述:
给你一个字符串 s
,找出它的所有子串并按字典序排列,返回排在最后的那个子串。
示例 1:
输入:s = "abab"
输出:"bab"
解释:我们可以找出 7 个子串 ["a", "ab", "aba", "abab", "b", "ba", "bab"]。按字典序排在最后的子串是 "bab"。
示例 2:
输入:s = "leetcode"
输出:"tcode"
提示:
- 1 <= s.length <= 4 * 105
- s 仅含有小写英文字符。
方法:双指针
记字符串 s 的长度为 n。首先并非所有的子字符串都需要被考虑到,只有后缀子字符串才可能是排在最后的子字符串。
为什么只有后缀子字符串才可能是排在最后的子字符串?
考虑一个非后缀子字符串 s1,那么 s1 向后延伸得到的后缀子字符串 s2 比 s1 大,即 s2 排在 s1 后面。
我们用 si 表示从 s[i] 开始的后缀子字符串,那么可以用指针 i 来指向后缀子字符串 s1 。我们使用指针 i 指向已知的最大后缀子字符串,j 指向待比较的后缀子字符串,初始时有 i = 0,j = 1。一个简单的做法是从小到大枚举 j,如果 si < sj,那么令 i = j,最终的后缀子字符串 si 就是排在最后的子字符串。类似于字符串匹配,在 si 与 sj 的比较过程中,一部分前缀是相等的,我们利用这一点跳过一些不需要进行比较的后缀子字符串。假设 si 与 sj 在第 k 个字符处不相等,即 si[k] ≠ sj[k],那么有两种情况:
- si[k] < sj[k]
- 当 i + k > j 时
- 对于 m ∈ [1, k] 的后缀子字符串 si + m:
- 如果 m ∈ [k − (j − i) + 1, k],显然有 si + m < sj + m,而 j + m > i + k,因此 si + m 是不需要比较的。
- 如果 m ∈ [1, k − (j − i)],那么 si + m < sj + m = si+m+j−i ,又因为 m < m + j − i ≤ k,而 si + m 右边的后缀子字符串不需要进行比较,因此 si + m 是不需要进行比较的。
- 对于 m ∈ [1, k] 的后缀子字符串 si + m:
- 当 i + k > j 时
综上,下一个需要进行比较的后缀子字符串为 si+k+1。
-
当 i + k ≤ j 时
- 下一个需要进行比较的后缀子字符串为 sj + 1。
-
si[k] > sj[k]
- 对于 m ∈ [1, k] 的后缀子字符串 sj + m:
- 如果 m ∈ [1, j − i],显然有 sj + m < si + m < si 。
- 如果 m ∈ [j − i + 1, k],那么 sj + m < si + m = sj+m+i−j < si(因为 1 ≤ m + i − j < m,而 sj + m 左边的后缀子字符串小于 si,因此 sj + m 也小于 si)。
- 对于 m ∈ [1, k] 的后缀子字符串 sj + m:
综上,对于 m ∈ [1, k],都有 sj + m < si,所以下一个需要比较的后缀子字符串为 sj+k+1。
当 sj 为 si 的前缀子字符串,即 j + k = n 时,与情况 si[k] > sj[k] 类似。
代码:
class Solution {
public:
string lastSubstring(string s) {
int i = 0, j = 1, n = s.size();
while (j < n) {
int k = 0;
while (j + k < n && s[i + k] == s[j + k]) {
k++;
}
if (j + k < n && s[i + k] < s[j + k]) {
int t = i;
i = j;
j = max(j + 1, t + k + 1);
} else {
j = j + k + 1;
}
}
return s.substr(i, n - i);
}
};
执行用时:28 ms, 在所有 C++ 提交中击败了95.00%的用户
内存消耗:17.9 MB, 在所有 C++ 提交中击败了96.00%的用户
复杂度分析
时间复杂度:O(n),其中 n 是字符串 s 的长度。每 k 次比较,i 与 j 共同至少向右移动 k,因此总比较次数不超过 2 × n 次。
空间复杂度:O(1)。返回值不计算空间复杂度。
author:LeetCode-Solution