Lexicographically Largest Subsequence asks you to pick characters from a string (keeping their order) so the result is as large as possible in dictionary order. The whole problem collapses to a single greedy scan once you flip your perspective and read the string backwards.
Problem. Given a string s, return its lexicographically largest subsequence. A subsequence
keeps the original left-to-right order but may drop any characters. "Largest" means: compare strings the
way a dictionary would, character by character.
Example: s = "abdca" → answer "dca" (pick the d, then c, then the final a).
The slow way first
You could generate every subsequence and take the maximum — but there are 2^n of them, so that is hopeless for anything but tiny strings.
The question to ask: what makes one subsequence beat another? In dictionary order, the first character dominates everything after it. So the answer must start with the single largest character in the string. After that character, the answer must start with the largest character that appears to its right, and so on. Each chosen character must be the biggest of everything remaining after the previous pick.
The idea: keep characters that never drop
That "biggest from here onward" rule is awkward going left to right, but it becomes trivial right to left. Scan from the end. Keep a running best = the largest character seen so far (which, going backwards, is the largest character to the right). A character belongs in the answer exactly when it is greater than or equal to best — because nothing to its right is bigger than it.
Because we walked backwards, the kept characters come out in reverse order, so the final step is just to reverse the list.
Walk through it
Step through the animation. The pointer i moves right to left. For "abdca": a is kept (best = a), c beats it (best = c), d beats that (best = d), then b and a are both smaller than d and get dropped. We kept [a, c, d]; reversing gives "dca".
Pseudocode
kept = empty list
best = "" (smaller than any character)
for ch in s read from right to left:
if ch >= best:
best = ch
append ch to kept
reverse kept
return kept joined into a stringThe Python solution
def largest_subsequence(s):
kept = []
best = ""
for ch in reversed(s):
if ch >= best:
best = ch
kept.append(ch)
kept.reverse()
return "".join(kept)bestis the largest character seen so far in the backward scan — equivalently, the largest character to the right of where we are.reversed(s)walks the string from the last character to the first.- The test
ch >= bestis the whole greedy decision: keepchonly if nothing to its right is bigger. - We use
>=(not>) so that ties are kept — equal characters later in the string still belong in the largest subsequence. - Because we built
keptbackwards,kept.reverse()puts it back into original order before we join it.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (all subsequences) | O(2^n) (slow) | exponential blowup |
| Greedy backward scan | O(n) (moderate) | one pass + a reverse |
O(n) (moderate)We touch each character once and keep at most n of them, so the work is linear and the extra space is the output list.
When this pattern shows up
When a greedy choice depends on the maximum (or minimum) of everything still ahead, try scanning in the opposite direction so that quantity becomes a simple running value you already have. "Largest subsequence," "next greater element," and many monotonic-stack problems share this right-to-left trick.
Use >=, not >. With strict > you would drop characters that tie the running max, losing valid
picks — for "zz" you would return "z" instead of the correct "zz".
Practice
For s = 'abdca', after the backward scan reaches d, which earlier characters (b and a) get kept?
1. Why do we scan the string from right to left?
2. Why keep a character only when ch >= best?
3. Why use >= instead of >?
4. Why is there a reverse step at the end?