Last Substring in Lexicographical Order — Problem & Solution
Last Substring in Lexicographical Order solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A hard problem.
Last Substring in Lexicographical Order is a hard coding problem on strings and two pointers, asked in Amazon, Google, MathWorks and Two Sigma interviews.
- Difficulty: Hard
- Topics: Strings, Two Pointers
- Asked at: Amazon, Google, MathWorks, Two Sigma, Salesforce
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Consider every non-empty substring of s and sort them in lexicographic (dictionary) order. Return the substring that comes last.
Comparison is the usual one: the first differing character decides, and a string that is a proper prefix of another comes before it.
Example 1
Input: s = "kairokai"
Output: rokai
Explanation: `r` is the largest letter and occurs once, so the answer is the suffix starting there.
Example 2
Input: s = "zazb"
Output: zb
Explanation: Both `zazb` and `zb` start with `z`; at the second character `b` beats `a`.
Example 3
Input: s = "bbbb"
Output: bbbb
Constraints
1 <= s.length <= 4 * 10^5s consists of lowercase English letters
Hints for Last Substring in Lexicographical Order
Hint 1
Extending a substring to the right never makes it smaller, so the answer is always a suffix of
s.Hint 2
Comparing all suffixes directly is quadratic. Keep two candidate starts
i < jand the lengthkof their common prefix.Hint 3
If
s[i+k] < s[j+k], no start ini..i+kcan win — jumpitomax(i + k + 1, j). Ifs[i+k] > s[j+k], skipjpastj + k. Each step discards at leastk + 1starts.
How to solve Last Substring in Lexicographical Order
The answer is the lexicographically largest suffix, and two competing starts can be compared character by character while discarding whole blocks of losing starts at once — the same trick as the minimum-rotation algorithm.
Approach
- Set
i = 0(best start so far),j = 1(challenger),k = 0(matched length). - While
j + k < n: ifs[i+k] == s[j+k], incrementk. - If
s[i+k] < s[j+k]: every start ini..i+kloses to the matching start inj..j+k, so seti = max(i + k + 1, j),j = i + 1,k = 0. - If
s[i+k] > s[j+k]: by the same argument every start inj..j+kloses, so setj = j + k + 1,k = 0. - Return the suffix starting at
i.
Why it works
When the suffixes at i and j agree on k characters and then s[i+k] < s[j+k], any start i + p (with p <= k) is beaten by j + p: they share k - p characters and then differ the same way. So all of i..i+k can be dropped; j itself survives as the new best unless it was among the dropped ones. The symmetric case drops j..j+k. Each step removes at least k + 1 candidates, and k only grows while characters match, so the total work is linear.
Complexity
- Time —
O(n) - Space —
O(1) besides the returned suffix
Pitfalls
- Sorting or comparing all suffixes is
O(n^2)(or worse) — too slow at4 * 10^5. - After a loss of
i, the newimust be at leastj; jumping toi + k + 1alone can skip past a valid candidate or move beforej. - On an all-equal string like
bbbbthe whole string wins (longer beats its own prefix).
Reference solution
Python
def lastSubstring(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]:
i = max(i + k + 1, j)
j = i + 1
k = 0
else:
j = j + k + 1
k = 0
return s[i:]Java
public static String lastSubstring(String s) {
int n = s.length(), i = 0, j = 1, k = 0;
while (j + k < n) {
char a = s.charAt(i + k), b = s.charAt(j + k);
if (a == b) {
k++;
} else if (a < b) {
i = Math.max(i + k + 1, j);
j = i + 1;
k = 0;
} else {
j = j + k + 1;
k = 0;
}
}
return s.substring(i);
}C++
string lastSubstring(string s) {
int n = s.size(), 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]) {
i = max(i + k + 1, j);
j = i + 1;
k = 0;
} else {
j = j + k + 1;
k = 0;
}
}
return s.substr(i);
}JavaScript
var lastSubstring = function(s) {
var n = s.length, i = 0, j = 1, k = 0;
while (j + k < n) {
var a = s.charCodeAt(i + k), b = s.charCodeAt(j + k);
if (a === b) {
k++;
} else if (a < b) {
i = Math.max(i + k + 1, j);
j = i + 1;
k = 0;
} else {
j = j + k + 1;
k = 0;
}
}
return s.substring(i);
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 515 strings problems · the whole catalogue
Learn the technique: Strings · Two Pointers Technique