All Permutations of a String with duplicate characters
Problem
Given a string s that may contain duplicate characters, return all unique permutations of s (order does not matter).
Examples
Example 1
Input: s = "abc"
Output: ["abc","acb","bac","bca","cab","cba"]
Explanation: 3! = 6 unique permutations.
Example 2
Input: s = "aba"
Output: ["aab","aba","baa"]
Explanation: Duplicates reduce the number of unique permutations.
Example 3
Input: s = "ABCA"
Output: ["AABC","AACB","ABAC","ABCA","ACBA","ACAB","BAAC","BACA","BCAA","CABA","CAAB","CBAA"]
Solution
We present two common approaches: (1) sort-and-skip duplicates while backtracking, and (2) backtracking using a frequency (count) map. Both generate exactly the set of unique permutations; the second avoids duplicate branches explicitly and is often cleaner.
Method 1 — Sort-and-skip backtracking
Intuition
Sort the characters so equal characters are adjacent. During backtracking, when deciding which character to place at the current position, skip a character if it is the same as a previous character that has not been used at this recursion level — that ensures we don't generate the same permutation multiple times.
Approach
- Sort
sinto a char arrayarr. - Maintain a boolean
used[]of the same length to mark which positions are already placed. - Recursively build the current permutation
cur:- If
i == n, addcurtoans. - Otherwise, iterate
jfrom0ton-1:- If
used[j]is true, skip. - If
j > 0andarr[j] == arr[j-1]and!used[j-1], skip (this removes duplicate branches). - Mark
used[j] = true, appendarr[j], recurse, then undo.
- If
- If
This generates each unique permutation exactly once.
Code
C++
class Solution {
public:
vector<string> permuteUnique(string s) {
int n = s.size();
vector<string> ans;
if (n == 0) return ans;
sort(s.begin(), s.end());
string cur; cur.reserve(n);
vector<bool> used(n, false);
dfs(s, used, cur, ans);
return ans;
}
private:
void dfs(const string &s, vector<bool> &used, string &cur, vector<string> &ans) {
int n = s.size();
if ((int)cur.size() == n) {
ans.push_back(cur);
return;
}
for (int i = 0; i < n; ++i) {
if (used[i]) continue;
if (i > 0 && s[i] == s[i-1] && !used[i-1]) continue;
used[i] = true;
cur.push_back(s[i]);
dfs(s, used, cur, ans);
cur.pop_back();
used[i] = false;
}
}
};
Go
package main
import "sort"
type Solution struct{}
func (Solution) Permute(s string) []string {
n := len(s)
if n == 0 { return []string{} }
arr := []byte(s)
sort.Slice(arr, func(i, j int) bool { return arr[i] < arr[j] })
used := make([]bool, n)
var ans []string
var cur []byte
var dfs func()
dfs = func() {
if len(cur) == n {
ans = append(ans, string(append([]byte(nil), cur...)))
return
}
for i := 0; i < n; i++ {
if used[i] { continue }
if i > 0 && arr[i] == arr[i-1] && !used[i-1] { continue }
used[i] = true
cur = append(cur, arr[i])
dfs()
cur = cur[:len(cur)-1]
used[i] = false
}
}
dfs()
return ans
}
Java
import java.util.*;
class Solution {
public List<String> permute(String s) {
List<String> ans = new ArrayList<>();
if (s == null || s.length() == 0) return ans;
char[] arr = s.toCharArray();
Arrays.sort(arr);
boolean[] used = new boolean[arr.length];
StringBuilder cur = new StringBuilder();
dfs(arr, used, cur, ans);
return ans;
}
private void dfs(char[] arr, boolean[] used, StringBuilder cur, List<String> ans) {
if (cur.length() == arr.length) {
ans.add(cur.toString());
return;
}
for (int i = 0; i < arr.length; i++) {
if (used[i]) continue;
if (i > 0 && arr[i] == arr[i-1] && !used[i-1]) continue;
used[i] = true;
cur.append(arr[i]);
dfs(arr, used, cur, ans);
cur.deleteCharAt(cur.length() - 1);
used[i] = false;
}
}
}
Kotlin
class Solution {
fun permute(s: String): List<String> {
val arr = s.toCharArray().apply { sort() }
val ans = mutableListOf<String>()
val used = BooleanArray(arr.size)
val cur = StringBuilder()
fun dfs() {
if (cur.length == arr.size) { ans.add(cur.toString()); return }
for (i in arr.indices) {
if (used[i]) continue
if (i > 0 && arr[i] == arr[i-1] && !used[i-1]) continue
used[i] = true
cur.append(arr[i])
dfs()
cur.deleteCharAt(cur.length - 1)
used[i] = false
}
}
dfs()
return ans
}
}
Python
class Solution:
def permute(self, s: str) -> list[str]:
n = len(s)
if n == 0:
return []
arr = sorted(s)
used = [False] * n
ans: list[str] = []
def dfs(cur: list[str]) -> None:
if len(cur) == n:
ans.append(''.join(cur))
return
for i in range(n):
if used[i]:
continue
if i > 0 and arr[i] == arr[i-1] and not used[i-1]:
continue
used[i] = True
cur.append(arr[i])
dfs(cur)
cur.pop()
used[i] = False
dfs([])
return ans
Rust
pub struct Solution;
impl Solution {
pub fn permute(s: String) -> Vec<String> {
let mut arr: Vec<char> = s.chars().collect();
arr.sort();
let n = arr.len();
let mut used = vec![false; n];
let mut ans = Vec::new();
let mut cur = Vec::with_capacity(n);
fn dfs(arr: &Vec<char>, used: &mut Vec<bool>, cur: &mut Vec<char>, ans: &mut Vec<String>) {
if cur.len() == arr.len() {
ans.push(cur.iter().collect());
return;
}
for i in 0..arr.len() {
if used[i] { continue }
if i > 0 && arr[i] == arr[i-1] && !used[i-1] { continue }
used[i] = true;
cur.push(arr[i]);
dfs(arr, used, cur, ans);
cur.pop();
used[i] = false;
}
}
dfs(&arr, &mut used, &mut cur, &mut ans);
ans
}
}
TypeScript
export class Solution {
permute(s: string): string[] {
const arr = s.split('').sort();
const n = arr.length;
const used = new Array<boolean>(n).fill(false);
const ans: string[] = [];
const cur: string[] = [];
function dfs(): void {
if (cur.length === n) { ans.push(cur.join('')); return; }
for (let i = 0; i < n; i++) {
if (used[i]) continue;
if (i > 0 && arr[i] === arr[i-1] && !used[i-1]) continue;
used[i] = true;
cur.push(arr[i]);
dfs();
cur.pop();
used[i] = false;
}
}
dfs();
return ans;
}
}
Complexity
- ⏰ Time complexity:
O(n * P)wherePis the number of unique permutations (in worst caseP = n!for distinct characters). Each permutation is built inO(n)time. - 🧺 Space complexity:
O(n + P*n)if including output;O(n)auxiliary recursion/used arrays.
Method 2 — Backtracking with frequency map (count-selection)
Intuition
Instead of permuting positions, track counts of each unique character and at each step choose a character with remaining count > 0. This avoids duplicate branches entirely and is efficient when many duplicates exist.
Approach
- Build a map
countfrom character → remaining count. - Recursively append any character
cwithcount[c] > 0, decrementcount[c], recurse, then restorecount[c]. - When the current length reaches
n, append the current permutation toans.
This method generates each unique permutation exactly once without explicit duplicate checks.
Code
C++
class Solution2 {
public:
vector<string> permuteUnique(string s) {
int n = s.size();
unordered_map<char,int> cnt;
for (char c : s) ++cnt[c];
vector<string> ans;
string cur; cur.reserve(n);
dfs(cnt, n, cur, ans);
return ans;
}
private:
void dfs(unordered_map<char,int> &cnt, int n, string &cur, vector<string> &ans) {
if ((int)cur.size() == n) { ans.push_back(cur); return; }
for (auto &p : cnt) {
char c = p.first;
if (p.second == 0) continue;
--p.second;
cur.push_back(c);
dfs(cnt, n, cur, ans);
cur.pop_back();
++p.second;
}
}
};
Python
class Solution2:
def permuteUnique(self, s: str) -> list[str]:
n = len(s)
from collections import Counter
cnt = Counter(s)
ans: list[str] = []
cur: list[str] = []
def dfs() -> None:
if len(cur) == n:
ans.append(''.join(cur))
return
for c in list(cnt.keys()):
if cnt[c] == 0:
continue
cnt[c] -= 1
cur.append(c)
dfs()
cur.pop()
cnt[c] += 1
dfs()
return ans
Complexity
- ⏰ Time complexity:
O(n * P)wherePis number of unique permutations; this method avoids duplicate recursion branches, so it's often faster in practice when duplicates exist. - 🧺 Space complexity:
O(n + P*n)including output;O(k + n)auxiliary wherekis number of distinct characters for thecountmap.