1105. 填充书架
难度:中等
题目:
给你一个字符串 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 来指向后缀子字符串 si。我们使用指针 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]
si[k] > sj[k]
当 sj为 si的前缀子字符串,即 j+k=n时,与情况 si[k]>sj[k]类似。
方法二:
我们注意到,如果一个子串从位置 i 开始,那么字典序最大的子串一定是 s[i,..n−1],即从位置 i 开始的最长后缀。因此,我们只需要找出字典序最大的后缀子串即可。
我们使用双指针 i 和 j,其中指针 i 指向当前字典序最大的子串的起始位置,指针 j 指向当前考虑的子串的起始位置。另外,用一个变量 k 记录当前比较到的位置。初始时 i=0, j=1, k=0。
每一次,我们比较 s[i+k] 和 s[j+k]:
如果 s[i+k]=s[j+k],说明 s[i,..i+k] 和 s[j,..j+k] 相同,我们将 k 加 1,继续比较 s[i+k] 和 s[j+k];
如果 s[i+k]<s[j+k],说明 s[j,..j+k] 的字典序更大。此时,我们更新 i=i+k+1,并将 k 重置为 0。如果此时 i≥j,那么我们将指针 j 更新为 i+1,即 j=i+1。这里我们跳过了以 s[i,..,i+k] 为起始位置的所有后缀子串,因为它们的字典序一定小于对应的 s[j,..,j+k] 为起始位置的后缀子串。
同理,如果 s[i+k]>s[j+k],说明 s[i,..,i+k] 的字典序更大。此时,我们更新 j=j+k+1,并将 k 重置为 0。这里我们跳过了以 s[j,..,j+k] 为起始位置的所有后缀子串,因为它们的字典序一定小于对应的 s[i,..,i+k] 为起始位置的后缀子串。
最后,我们返回以 i 为起始位置的后缀子串即可,即 s[i,..,n−1]。
代码
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);
}
};
每天记录一下做题思路。