每日一题-1163. 按字典序排在最后的子串

206 阅读2分钟

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]

image.png

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);
    }
};

image.png

每天记录一下做题思路。