Last Substring in Lexicographical Order
HardUpdated: Aug 2, 2025
Practice on:
Problem
Given a string s, return the last substring of s in lexicographical order.
Examples
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 * 10^5scontains only lowercase English letters.
Solution
Method 1 – Two Pointers (Booth's Algorithm Variant)
Intuition
To find the last substring in lexicographical order, we want the substring that starts at the position of the largest suffix. We can use a two-pointer approach to efficiently find the starting index of this substring by comparing suffixes directly.
Approach
- Initialize two pointers
i(current best start) andj(candidate start), and an offsetk. - Compare characters at
s[i + k]ands[j + k]:- If they are equal, increment
k. - If
s[i + k] < s[j + k], moveitoi + k + 1(since a better suffix starts atj). - If
s[i + k] > s[j + k], movejtoj + k + 1(since the current best is still better). - If
i == j, incrementj.
- If they are equal, increment
- Continue until either pointer reaches the end.
- The answer is the substring starting at
i.
Code
C++
class Solution {
public:
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);
k = 0;
j = i + 1;
} else {
j = j + k + 1;
k = 0;
}
}
return s.substr(i);
}
};
Go
func lastSubstring(s string) string {
n := len(s)
i, j, k := 0, 1, 0
for 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)
k = 0
j = i + 1
} else {
j = j + k + 1
k = 0
}
}
return s[i:]
}
func max(a, b int) int { if a > b { return a } else { return b } }
Java
class Solution {
public String lastSubstring(String s) {
int n = s.length(), 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)) {
i = Math.max(i + k + 1, j);
k = 0;
j = i + 1;
} else {
j = j + k + 1;
k = 0;
}
}
return s.substring(i);
}
}
Kotlin
class Solution {
fun lastSubstring(s: String): String {
val n = s.length
var i = 0; var j = 1; var k = 0
while (j + k < n) {
if (s[i + k] == s[j + k]) {
k++
} else if (s[i + k] < s[j + k]) {
i = maxOf(i + k + 1, j)
k = 0
j = i + 1
} else {
j = j + k + 1
k = 0
}
}
return s.substring(i)
}
}
Python
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]:
i = max(i + k + 1, j)
k = 0
j = i + 1
else:
j = j + k + 1
k = 0
return s[i:]
Rust
impl Solution {
pub fn last_substring(s: String) -> String {
let s = s.as_bytes();
let n = s.len();
let (mut i, mut j, mut k) = (0, 1, 0);
while j + k < n {
if s[i + k] == s[j + k] {
k += 1;
} else if s[i + k] < s[j + k] {
i = (i + k + 1).max(j);
k = 0;
j = i + 1;
} else {
j = j + k + 1;
k = 0;
}
}
String::from_utf8(s[i..].to_vec()).unwrap()
}
}
TypeScript
class Solution {
lastSubstring(s: string): string {
const 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]) {
i = Math.max(i + k + 1, j);
k = 0;
j = i + 1;
} else {
j = j + k + 1;
k = 0;
}
}
return s.slice(i);
}
}
Complexity
- ⏰ Time complexity:
O(n), since each character is compared at most twice. - 🧺 Space complexity:
O(1), only pointers and counters are used.