diff --git a/problems/3720-lexicographically-smallest-permutation-greater-than-target/analysis.md b/problems/3720-lexicographically-smallest-permutation-greater-than-target/analysis.md new file mode 100644 index 0000000..804b373 --- /dev/null +++ b/problems/3720-lexicographically-smallest-permutation-greater-than-target/analysis.md @@ -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. diff --git a/problems/3720-lexicographically-smallest-permutation-greater-than-target/problem.md b/problems/3720-lexicographically-smallest-permutation-greater-than-target/problem.md new file mode 100644 index 0000000..63d8d46 --- /dev/null +++ b/problems/3720-lexicographically-smallest-permutation-greater-than-target/problem.md @@ -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. diff --git a/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827.go b/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827.go new file mode 100644 index 0000000..9998a0c --- /dev/null +++ b/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827.go @@ -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 "" +} diff --git a/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827_test.go b/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827_test.go new file mode 100644 index 0000000..1ebcd66 --- /dev/null +++ b/problems/3720-lexicographically-smallest-permutation-greater-than-target/solution_daily_20260827_test.go @@ -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 +}