【每日一题】按字典序排在最后的子串

1163. 按字典序排在最后的子串

关键词:双指针

题目来源:1163. 按字典序排在最后的子串 - 力扣(Leetcode)

题目描述

 T双指针

给你一个字符串 s ,找出它的所有子串并按字典序排列,返回排在最后的那个子串。

输入:s = "abab"
输出:"bab"
解释:我们可以找出 7 个子串 ["a", "ab", "aba", "abab", "b", "ba", "bab"]。按字典序排在最后的子串是 "bab"。
输入:s = "leetcode"
输出:"tcode"
数据范围
1 <= s.length <= 4e5
s 仅含有小写英文字符。

问题分析

看到字典序和字符串,首先想到的就是字典树,但是,由于数据范围较大,使用字典树并不合适。

不难发现,字典序最大的子串必然是字符串s的某个后缀。这很容易证明,设s长度为n,下标从0开始,若s[i..j]是字典序最大的子串,则必有s[i..n]≥s[i..j],得证。

于是,可以使用双指针,枚举每一个后缀。

设i为目前已知的字典序最大的后缀的首字符的下标,j为当前待比较的后缀的首字符的下标,sf[i]表示以s[i]为首字符的后缀,f(i)表示sf[i]的字典序,。

若s[i]<s[j],则f(i)<f(j),可令i=j,j=j+1。

若s[i]==s[j],则sf[i]和sf[j]必然存在相同的前缀,设其在第k处不同,也即s[i+k]!=s[j+k]

先考虑s[i+k]<s[j+k],此时f(i)<f(j)

  • 若s[i..i+k]与s[j..j+k]不相交(端点相同不算相交),即i+k≤j,则i=j,j=j+1

  • 若s[i..i+k]与s[j..j+k]相交,即i+k>j,则如下图所示

    已知f(i)<f(j),设m=j-i,自然有j=i+m,考虑下一个待考虑后缀(的首字符的位置)

    对于j+1,s[j+1]s[i+m+1],也即sf[j+1]sf[i+m+1],而f(i+m+1)<f(j+m+1),故,在考虑j+m+1的情况下,没必要考虑j+1

    对于j+2,同理,在考虑j+m+2的情况下,没必要考虑j+2

    ...

    对于j+k-m,同理,在考虑j+m+(k-m)的情况下,没必要考虑j+(k-m)

    综上所述,第一个有必要考虑的是j+(k-m)+1,又m=j-i,所以j+(k-m)+1=i+k+1。

    故下一个需要考虑的是i+k+1,故令t=i,i=j,j=t+k+1。

再考虑s[i+k]>s[j+k],此时f(i)>f(j)

  • 若s[i..i+k]与s[j..j+k]不相交(端点相同不算相交),即i+k≤j,则i=j,j=j+1

  • 若s[i..i+k]与s[j..j+k]相交,即i+k>j,则如下图所示

    已知f(i)>f(j),设m=j-i,自然有j=i+m,考虑下一个待考虑后缀(的首字符的位置)

    对于j+1,f(i+1)>f(j+1),而i+1不用考虑(已经考虑过或者不用考虑),所以j+1不用考虑

    对于j+2,f(i+2)>f(j+2),而i+2不用考虑,所以j+2不用考虑

    ...

    对于j+k-m,f(i+k-m)>f(j+k-m),而i+k-m不用考虑,所以j+k-m不用考虑

    ...

    对于j+k,f(i+k)>f(j+k),而i+k不用考虑,所以j+k不用考虑

    综上所述,第一个有必要考虑的位置为j+k+1,故令j=j+k+1

整理一下指针移动的情况

  • 当s[i+k]<s[j+k]时,t=i,i=j,j=max( j+1, t+k+1 )

    i+k≤j即i+k+1≤j+1时,j取j+1,i+k>j即i+k+1>j+1时,j取i+k+1,合起来就是j=max( j+1, t+k+1 ),使用临时变量t是因为原先的i已被更新。

  • 当s[i+k]≥s[j+k]时,j=j+k+1

    当sf[i]与sf[j]没有相同前缀时,k=0,j=j+1,不影响结果。

时间复杂度:O(n)

空间复杂度:O(1)

代码实现

string lastSubstring(string s) {
    int i = 0, j = 1, n = s.size(), k, t;
    while (k = 0, j < n) {
        // 找到第一个不同的位置
        while (j + k < n && s[i + k] == s[j + k])k++;
        // 分情况讨论
        if (j + k < n && s[i + k] < s[j + k]) {
            t = i, i = j;
            j = max(j + 1, t + k + 1);
        } else j = j + k + 1;
    }
    // 返回以以s[i]为首字符的后缀
    return s.substr(i);
}
posted @ 2023-04-24 21:27  字节幺零二四  阅读(32)  评论(0)    收藏  举报