Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 3720. Lexicographically Smallest Permutation Greater Than Target

[LeetCode Link](https://leetcode.com/problems/lexicographically-smallest-permutation-greater-than-target/)

Difficulty: Medium
Topics: Hash Table, String, Greedy, Counting, Enumeration
Acceptance Rate: 39.5%

## Hints

### Hint 1

The words "permutation of `s`" are doing a lot of work here: the *order* of the letters in `s` is irrelevant, only *how many of each letter* you have. So the first move is to throw `s` away and keep a frequency table of 26 counts. From there the question becomes: how do I lay 26 buckets of letters onto `n` slots so the result beats `target`?

Also notice `n <= 300`. That is a strong signal that an `O(n * 26)` or even `O(n^2)` scan is intended — you are meant to *enumerate* something over the positions, not over the permutations (there can be astronomically many of those).

### Hint 2

Think about what it *means* for a string `a` of length `n` to be strictly greater than `target` of the same length. There must be some index `i` where `a[0..i-1]` is character-for-character equal to `target[0..i-1]`, and `a[i] > target[i]`. Everything after `i` is then completely free — it can be anything.

So enumerate that "breaking index" `i` over all `n` positions. For a fixed `i`, ask two questions: can I even spell `target[0..i-1]` out of my letter counts? And do I have a leftover letter strictly greater than `target[i]`? If yes to both, the smallest string with that breaking index is easy to build greedily.

### Hint 3

The insight that makes this click is *which* breaking index to prefer. Compare two candidates, one that breaks at `i` and one that breaks at `j > i`. The second one still agrees with `target` at position `i`, while the first one is strictly *above* `target` at position `i`. Since positions are compared left to right, the candidate that breaks later is always the smaller one.

So there is no need to build all `n` candidates and compare them: **scan `i` from the largest feasible value downward and return the first candidate that works.** And "largest feasible value" is bounded by the longest prefix of `target` you can actually spell from your counts — once a prefix is unspellable, no longer prefix is either. Cap that at `n - 1`, because breaking at index `n` would mean the answer equals `target`, which is not *strictly* greater.

## Approach

Count the letters of `s` into a fixed `[26]int` array. The order of `s` never matters again.

**Step 1 — find the longest spellable prefix of `target`.**
Walk `target` left to right, consuming letters from a copy of the counts. Stop at the first character you cannot afford. Call the number of characters consumed `maxPrefix`. For any breaking index `i`, the answer must reproduce `target[0..i-1]` exactly, so `i <= maxPrefix` is a hard requirement.

**Step 2 — pick the starting breaking index.**
Set `start = min(maxPrefix, n-1)`. The `n-1` cap matters: if `s` is an exact anagram of `target`, then `maxPrefix == n`, but breaking at index `n` would produce `target` itself, which fails the *strictly* greater requirement.

**Step 3 — scan `i` downward and build greedily.**
Maintain `avail`, the multiset left over after consuming `target[0..i-1]`. Initialize it for `i = start`, then as `i` decreases by one, hand the letter `target[i-1]` back to `avail`. That keeps the whole scan `O(n)` on bookkeeping instead of recomputing counts at each step.

At each `i`, look for the smallest letter `c` in `avail` with `c > target[i]`. If one exists, the answer is:

```
target[0..i-1] + c + (all remaining letters sorted ascending)
```

The prefix is forced. Choosing the *smallest* valid `c` minimizes position `i`, which dominates everything after it. And sorting the leftovers ascending is the smallest possible completion of a free suffix. Return immediately — by Hint 3 this is the global answer.

If the loop runs off the end without finding such a `c`, no permutation of `s` beats `target`, so return `""`.

**Worked example: `s = "abc"`, `target = "bba"`.**
Counts are `{a:1, b:1, c:1}`, `n = 3`. Spelling `target`: `'b'` is affordable, then the second `'b'` is not, so `maxPrefix = 1` and `start = min(1, 2) = 1`.
At `i = 1`: the prefix `"b"` is consumed, leaving `{a:1, c:1}`. We need a letter greater than `target[1] = 'b'` — that is `'c'`. Emit `"b" + "c"`, then the leftovers `{a}` sorted give `"a"`. Answer: `"bca"`. ✓

**Worked example: `s = "leet"`, `target = "code"`.**
`target[0] = 'c'` is not in `s` at all, so `maxPrefix = 0` and the only breaking index is `i = 0`. The smallest available letter above `'c'` is `'e'`; the leftovers `{e, l, t}` sorted give `"elt"`. Answer: `"eelt"`. ✓ Note that the answer shares no prefix with `target` here — the algorithm handles that uniformly.

## Complexity Analysis

Time Complexity: O(n * 26 + n) = O(n), treating the alphabet as a constant. Step 1 is a single `O(n)` pass; step 3 visits each of the `n` breaking indices and scans at most 26 letters at each, and the final string is assembled once in `O(n + 26)`.

Space Complexity: O(1) auxiliary beyond the `O(n)` output buffer — just two fixed `[26]int` count arrays.

## Edge Cases

- **`s` is an exact anagram of `target`** (e.g. `s = "abc"`, `target = "abc"`). `maxPrefix` reaches `n`, and without the `min(maxPrefix, n-1)` cap you would "break" at index `n` and return `target` itself, violating *strictly* greater. Here the correct answer is `"acb"`.
- **No answer exists** (e.g. `s = "baba"`, `target = "bbaa"`). Every breaking index fails, and the function must return `""` rather than a best-effort string. This happens exactly when `target` is greater than or equal to the largest permutation of `s`.
- **`target[0]` is not in `s`.** Then `maxPrefix = 0` and only `i = 0` is considered. Fine — but it means you must not assume the answer shares any prefix with `target`.
- **`n = 1`.** `start` collapses to `0`; the answer is the single character of `s` if it exceeds `target[0]`, else `""`.
- **All characters identical** (e.g. `s = "aaa"`). There is only one permutation, so the answer is `s` itself if `s > target`, else `""`. The counting approach handles this without special-casing.
- **Handing the letter back while scanning down.** An easy off-by-one: when moving from `i` to `i-1` you must return `target[i-1]` to `avail`, not `target[i]`. Getting this wrong silently produces a wrong multiset for the suffix.
- **Duplicate letters in `s`.** Because we work with counts rather than a visited/used boolean array, duplicates never cause the same permutation to be considered twice, and `avail[c]--` correctly leaves the other copies available for the suffix.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
number: "3720"
frontend_id: "3720"
title: "Lexicographically Smallest Permutation Greater Than Target"
slug: "lexicographically-smallest-permutation-greater-than-target"
difficulty: "Medium"
topics:
- "Hash Table"
- "String"
- "Greedy"
- "Counting"
- "Enumeration"
acceptance_rate: 3951.8
is_premium: false
created_at: "2026-08-27T10:05:04.956311+00:00"
fetched_at: "2026-08-27T10:05:04.956311+00:00"
link: "https://leetcode.com/problems/lexicographically-smallest-permutation-greater-than-target/"
date: "2026-08-27"
---

# 3720. Lexicographically Smallest Permutation Greater Than Target

You are given two strings `s` and `target`, both having length `n`, consisting of lowercase English letters.

Return the **lexicographically smallest permutation** of `s` that is **strictly** greater than `target`. If no permutation of `s` is lexicographically strictly greater than `target`, return an empty string.

A string `a` is **lexicographically strictly greater** than a string `b` (of the same length) if in the first position where `a` and `b` differ, string `a` has a letter that appears later in the alphabet than the corresponding letter in `b`.



**Example 1:**

**Input:** s = "abc", target = "bba"

**Output:** "bca"

**Explanation:**

* The permutations of `s` (in lexicographical order) are `"abc"`, `"acb"`, `"bac"`, `"bca"`, `"cab"`, and `"cba"`.
* The lexicographically smallest permutation that is strictly greater than `target` is `"bca"`.



**Example 2:**

**Input:** s = "leet", target = "code"

**Output:** "eelt"

**Explanation:**

* The permutations of `s` (in lexicographical order) are `"eelt"`, `"eetl"`, `"elet"`, `"elte"`, `"etel"`, `"etle"`, `"leet"`, `"lete"`, `"ltee"`, `"teel"`, `"tele"`, and `"tlee"`.
* The lexicographically smallest permutation that is strictly greater than `target` is `"eelt"`.



**Example 3:**

**Input:** s = "baba", target = "bbaa"

**Output:** ""

**Explanation:**

* The permutations of `s` (in lexicographical order) are `"aabb"`, `"abab"`, `"abba"`, `"baab"`, `"baba"`, and `"bbaa"`.
* None of them is lexicographically strictly greater than `target`. Therefore, the answer is `""`.





**Constraints:**

* `1 <= s.length == target.length <= 300`
* `s` and `target` consist of only lowercase English letters.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

// 3720. Lexicographically Smallest Permutation Greater Than Target
//
// Only the letter counts of s matter, never its order. Any answer must agree
// with target on some prefix [0, i) and exceed it at index i, after which the
// suffix is free. A candidate that breaks later is always smaller (it still
// matches target where the earlier candidate already went above it), so we scan
// the breaking index downward from the longest prefix of target that our counts
// can actually spell (capped at n-1, since matching all n would only tie) and
// return the first candidate we can build: the smallest available letter above
// target[i], followed by every leftover letter in ascending order.
//
// Time: O(n * 26), Space: O(1) beyond the output.
func smallestPermutation(s string, target string) string {
n := len(s)

var counts [26]int
for i := 0; i < n; i++ {
counts[s[i]-'a']++
}

// Longest prefix of target spellable from s; no breaking index may exceed it.
maxPrefix := 0
remaining := counts
for maxPrefix < n && remaining[target[maxPrefix]-'a'] > 0 {
remaining[target[maxPrefix]-'a']--
maxPrefix++
}

// Cap at n-1: breaking at n would reproduce target, which is not strictly greater.
start := maxPrefix
if start > n-1 {
start = n - 1
}

// avail holds the multiset left after consuming target[:i].
avail := counts
for k := 0; k < start; k++ {
avail[target[k]-'a']--
}

for i := start; i >= 0; i-- {
for c := int(target[i]-'a') + 1; c < 26; c++ {
if avail[c] == 0 {
continue
}
avail[c]--
out := make([]byte, 0, n)
out = append(out, target[:i]...)
out = append(out, byte('a'+c))
for d := 0; d < 26; d++ {
for j := 0; j < avail[d]; j++ {
out = append(out, byte('a'+d))
}
}
return string(out)
}
if i > 0 {
// Moving to i-1 frees the letter that was pinned at position i-1.
avail[target[i-1]-'a']++
}
}

return ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"sort"
"testing"
)

func TestSolution(t *testing.T) {
tests := []struct {
name string
s string
target string
expected string
}{
{"example 1: breaks at index 1", "abc", "bba", "bca"},
{"example 2: target[0] absent from s", "leet", "code", "eelt"},
{"example 3: no permutation is greater", "baba", "bbaa", ""},
{"edge case: single char, s greater", "b", "a", "b"},
{"edge case: single char, equal", "a", "a", ""},
{"edge case: single char, s smaller", "a", "b", ""},
{"edge case: s is an anagram of target", "abc", "abc", "acb"},
{"edge case: all identical letters, no answer", "aaa", "aab", ""},
{"edge case: all identical letters, answer is s", "bbb", "aaa", "bbb"},
{"edge case: full prefix match, breaks at last index", "aab", "aaa", "aab"},
{"edge case: two letters, must break at index 0", "ba", "ab", "ba"},
{"edge case: target far above every permutation", "abc", "zzz", ""},
{"edge case: target far below every permutation", "zzz", "aaa", "zzz"},
{"edge case: duplicates in s with long shared prefix", "aabbc", "aabbb", "aabbc"},
{"edge case: backtracks past an unusable position", "abcd", "abdd", "acbd"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := smallestPermutation(tt.s, tt.target); got != tt.expected {
t.Errorf("smallestPermutation(%q, %q) = %q, want %q", tt.s, tt.target, got, tt.expected)
}
})
}
}

// TestSolutionAgainstBruteForce cross-checks the greedy against exhaustive
// permutation enumeration over every length-3 string pair on the alphabet
// {a, b, c} (729 pairs), which covers duplicates, ties and impossible cases.
func TestSolutionAgainstBruteForce(t *testing.T) {
alphabet := []byte{'a', 'b', 'c'}
var words []string
for _, x := range alphabet {
for _, y := range alphabet {
for _, z := range alphabet {
words = append(words, string([]byte{x, y, z}))
}
}
}

for _, s := range words {
for _, target := range words {
got := smallestPermutation(s, target)
want := bruteForceSmallestPermutation(s, target)
if got != want {
t.Errorf("smallestPermutation(%q, %q) = %q, want %q", s, target, got, want)
}
}
}
}

// bruteForceSmallestPermutation enumerates every permutation of s and returns
// the smallest one strictly greater than target, or "" if there is none.
func bruteForceSmallestPermutation(s, target string) string {
best := ""
chars := []byte(s)
sort.Slice(chars, func(i, j int) bool { return chars[i] < chars[j] })

var permute func(prefix []byte, used []bool)
permute = func(prefix []byte, used []bool) {
if len(prefix) == len(chars) {
candidate := string(prefix)
if candidate > target && (best == "" || candidate < best) {
best = candidate
}
return
}
for i := range chars {
if used[i] {
continue
}
used[i] = true
permute(append(prefix, chars[i]), used)
used[i] = false
}
}

permute(make([]byte, 0, len(chars)), make([]bool, len(chars)))
return best
}