字符串-串的最大表示-后缀数组-1163. 按字典序排在最后的子串

2020-03-13 13:48:05

问题描述:

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

示例 1:

输入:"abab"
输出:"bab"
解释:我们可以找出 7 个子串 ["a", "ab", "aba", "abab", "b", "ba", "bab"]。按字典序排在最后的子串是 "bab"。

示例 2:

输入:"leetcode"
输出:"tcode"

提示:

1 <= s.length <= 4 * 10^5
s 仅含有小写英文字符。

问题求解:

首先看数据规模,暴力求解是肯定不行的。时间复杂度基本在O(n) / O(nlogn)级别可以通过。

本题有个点非常重要就是最大的子串一定是其后缀,试想如果最大的子串不是后缀,那么往后追加字符必然比它本身大,矛盾。

但是仅仅观察到这一步暴力求解依然只能作出O(n ^ 2)的解,如何更进一步呢?

想到后缀自然就想到后缀数组,如果使用倍增法,可以在O(nlogn)得到解,如果使用DC3/SA-IS可以在O(n)得到解,可行。

另外,本题还可以转化为字符串的最大表示,本质上是一样的。

简单的来说,字符串的最大表示和最小表示类似,就是遇到小的直接跳过即可,采用这种算法可以在O(n)的时间复杂度得到解。

方法一:字符串的最大表示

时间复杂度:O(n)

  public String lastSubstring(String s) {
        int n = s.length();
        int i = 0; 
        int j = 1;
        int k = 0;
        while (i < n && j < n && k < n) {
            int diff = s.charAt((i + k) % n) - s.charAt((j + k) % n);
            if (diff == 0) k++;
            else {
                if (diff > 0) j = j + k + 1;
                else i = i + k + 1;
                k = 0;
                if (i == j) j++;
            }
        }
        return s.substring(Math.min(i, j), n);
    }

 

方法二:后缀数组-倍增

时间复杂度:O(nlogn)

    public String lastSubstring(String s) {
        int n = s.length();
        Integer[] sa = new Integer[n + 1];
        int[] rank = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            sa[i] = i;
            rank[i] = i == n ? -1 : s.charAt(i);
        }
        for (int k = 1; k < n; k *= 2) {
            int offset = k;
            Arrays.sort(sa, new Comparator<Integer>(){
                public int compare(Integer o1, Integer o2) {
                    if (rank[o1] != rank[o2]) return Integer.compare(rank[o1], rank[o2]);
                    else {
                        int r1 = o1 + offset <= n ? rank[o1 + offset] : -1;
                        int r2 = o2 + offset <= n ? rank[o2 + offset] : -1;
                        return Integer.compare(r1, r2);
                    }
                }
            });
            int[] new_rank = new int[n + 1];
            new_rank[sa[0]] = 0;
            for (int i = 1; i <= n; i++) {
                new_rank[sa[i]] = new_rank[sa[i - 1]];
                if (!isEqual(rank, sa[i], sa[i - 1], k)) new_rank[sa[i]]++;
            }
            for (int i = 0; i <= n; i++) {
                rank[i] = new_rank[i];
            }
        }
        return s.substring(sa[n]);
    }
    
    boolean isEqual(int[] rank, int i, int j, int k) {
        int n = rank.length;
        if (rank[i] != rank[j]) return false;
        int r1 = i + k <= n ? rank[i + k] : -1;
        int r2 = j + k <= n ? rank[j + k] : -1;
        return r1 == r2;
    }

  

posted @ 2020-03-13 13:58  hyserendipity  阅读(574)  评论(0编辑  收藏  举报