Given a string s
, return the last substring of s
in lexicographical order.
Example 1:
Input: s = "abab" Output: "bab" Explanation: The substrings are ["a", "ab", "aba", "abab", "b", "ba", "bab"]. The lexicographically maximum substring is "bab".
Example 2:
Input: s = "leetcode" Output: "tcode"
Constraints:
1 <= s.length <= 4 * 105
s
contains only lowercase English letters.
题目:给定一个字符串,输出最大子字符串。
思路:可以分析得到,最大子字符串一定是此字符串的一个后缀,难点在找字符串的起始点。用两个指针,一个start记录当前最大的起始点,一个i往前遍历找下一个起始点。时间复杂度是难点。分三种情况:
1)如果s[i] < s[start], 很简单,直接往前走就好了i++
2) 如果s[i] > s[start], 也很简单,直接更新start = i;
3) 难点在如果s[i] == s[start]怎么确定两个子字符串哪个大。可以直接比较子字符串大小,但时间复杂度太大了。只能一个一个往后遍历,用两个指针fast和slow将相等的地方都跳过去。这里也分三种情况:
- fast先到字符串结尾了,则start, slow指针遍历的子字符串肯定是最大,直接返回
- s[fast] < s[slow], 则start位置不动,原子字符串最大,遍历过的地方不需再遍历,更新i值
- s[fast] > s[slow], 则start也需要更新到i, 遍历过的地方不需要再遍历,更新i值。
这里有些不好理解,为什么遍历过的地方不需再遍历了。在跳过相等位置的时候有几种情况:
1)相等的字符串很短 ,比i和start距离短,例如zxyabcdzxybcda,zxya...zxyb..., 则其实z后的x和y已经被i遍历过了,也与z比较过了,因此可以肯定的是比z小,指针可以安全的跳到第二个z处和b处。如果是前一字符串大,则start不变,指针i直接跳到fast之后即可。后一字符串大,指针start更新,i需要往后挪一位。
2)相等的字符串很长,比i和start的距离长,例如zabcdzabcdzac, 中间相等的地方可以证明是相同的字符串(具体说是start与i之间的字符串)不停循环组成。如果是前一字符串大,则不需要更新start,将指针i更新到fast之后(因为fast之前肯定不会有比当前字符大的)。如果是后一字符串大,就比较复杂了,说明fast肯定跳到新一轮循环中,而且新一轮循环的字符串不是全部,中间有一个字符变大了,我们没办法确定大的字符有多大,因此指针需要移到这个循环的开始位置重新比较。可以肯定当前slow指针肯定在循环中,为使代码兼容且简洁,指针i移到slow之后。
代码中s[fast] > s[slow]的i指针用 "i=max(i, slow),i++" 来更新,i = i+1即为上述第一种情况时,i = slow+1即为第二种情况。
代码:
class Solution {
public:
string lastSubstring(string s) {
int start = 0, i = 1;
while(i < s.length()){
if(s[i] > s[start]) start = i;
else if(s[i] == s[start]){
int slow = start, fast = i;
while(fast < s.length() && s[slow] == s[fast]){
slow++;
fast++;
}
if(fast == s.length()) return s.substr(start);
else if(s[fast] > s[slow]) {
start = i;
i = max(i, slow);
} else i = fast;
}
i++;
}
return s.substr(start);
}
};
time: O(N), space:O(1)