Given a string s
, you are asked to find the last substring in lexicographical order among all possible non-empty substrings of s
.
You must return this substring as a string.
s
.s
are lowercase English letters.
Example:
Input: s = "abab"
Output: "bab"
At first glance, the brute-force approach would be to generate all possible substrings of s
and compare them to find the largest in lexicographical order. However, this is highly inefficient, especially for long strings, since the number of substrings is quadratic in the length of s
.
To optimize, we should recognize that the "last" substring in lexicographical order must start at one of the positions where the largest character occurs. For example, if 'z'
is the largest character, the answer must start at a position where 'z'
appears.
We can further optimize by comparing suffixes starting at each candidate position, but we want to avoid comparing the same substrings repeatedly. The problem is similar to finding the maximal suffix or using the "two pointers" technique to compare possible starting positions efficiently.
i
and j
, to represent candidate starting positions. Always keep i
as the current best and j
as the next candidate.
i
and j
.k
, compare s[i + k]
and s[j + k]
:
k
.s[i + k]
> s[j + k]
, j
cannot be the answer, so move j
forward.s[i + k]
< s[j + k]
, i
cannot be the answer, so move i
forward (to j
), and move j
to i + 1
.j
reaches the end.i
will be the answer.
This approach is efficient because it avoids redundant substring comparisons and only compares as much as necessary to decide which starting index is better.
Example Input: s = "abab"
'b'
, which appears at indices 1 and 3.'b'
vs 'b'
: equal, move to next character.'a'
vs ''
(end of string): since the right substring is exhausted, "bab" is larger.
"bab"
.The process efficiently eliminates other candidates by direct comparison, never generating all substrings.
s
.
This efficiency comes from never revisiting the same substring comparison and always moving at least one pointer forward.
The key insight is that the last lexicographical substring must start at one of the positions with the largest character. By using a two-pointer technique, we efficiently compare candidate substrings without generating all possibilities. This leads to an elegant, linear-time solution that leverages properties of lexicographical order and suffix comparison.
class Solution:
def lastSubstring(self, s: str) -> str:
n = len(s)
i, j, k = 0, 1, 0
while j + k < n:
if s[i + k] == s[j + k]:
k += 1
elif s[i + k] > s[j + k]:
j = j + k + 1
k = 0
else:
i = max(i + k + 1, j)
j = i + 1
k = 0
return s[i:]
class Solution {
public:
string lastSubstring(string s) {
int n = s.size();
int i = 0, j = 1, k = 0;
while (j + k < n) {
if (s[i + k] == s[j + k]) {
k++;
} else if (s[i + k] > s[j + k]) {
j = j + k + 1;
k = 0;
} else {
i = max(i + k + 1, j);
j = i + 1;
k = 0;
}
}
return s.substr(i);
}
};
class Solution {
public String lastSubstring(String s) {
int n = s.length();
int i = 0, j = 1, k = 0;
while (j + k < n) {
if (s.charAt(i + k) == s.charAt(j + k)) {
k++;
} else if (s.charAt(i + k) > s.charAt(j + k)) {
j = j + k + 1;
k = 0;
} else {
i = Math.max(i + k + 1, j);
j = i + 1;
k = 0;
}
}
return s.substring(i);
}
}
var lastSubstring = function(s) {
let n = s.length;
let i = 0, j = 1, k = 0;
while (j + k < n) {
if (s[i + k] === s[j + k]) {
k++;
} else if (s[i + k] > s[j + k]) {
j = j + k + 1;
k = 0;
} else {
i = Math.max(i + k + 1, j);
j = i + 1;
k = 0;
}
}
return s.substring(i);
};