# 47. Permutations II

LeetCode Problem Link (opens new window)

Given a collection of numbers, nums, that might contain duplicates, return all possible unique permutations in any order.

Example 1:

  • Input: nums = [1,1,2]
  • Output: [ [1,1,2], [1,2,1], [2,1,1] ]

Example 2:

  • Input: nums = [1,2,3]
  • Output: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

Constraints:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

# Strategy

This problem differs from 0046.Permutations (opens new window) in that the sequence may contain duplicate numbers and requires returning all unique permutations.

This problem involves deduplication.

In problems like 0040.Combination Sum II (opens new window) and 0090.Subsets II (opens new window), we have discussed how to handle deduplication in detail for both combination and subset problems.

Permutations are essentially handled similarly.

It is crucial to sort the elements for deduplication so that we can easily check adjacent elements to determine if they have been used repetitively.

Using the sorted example of [1,1,2] as a demonstration, the deduplication process can be visualized as:

47.PermutationsII1

In the graph, for the same level in the tree, if the previous value (i.e., nums[i-1]) has been used, it should be skipped to avoid duplicates.

Typically, combinations and permutation problems involve collecting results at the leaf nodes of a tree structure, whereas subset problems involve collecting results at all nodes.

In 0046.Permutations (opens new window), we have detailed the approach to solve permutation problems, and in 0040.Combination Sum II (opens new window) and 0090.Subsets II (opens new window), the deduplication methods have been explained. Let’s jump to the code:

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // If a valid permutation is found
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        for (int i = 0; i < nums.size(); i++) {
            // If the element is the same as the previous one and has been used in the same tree level, skip it
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            if (used[i] == false) {
                used[i] = true;
                path.push_back(nums[i]);
                backtracking(nums, used);
                path.pop_back();
                used[i] = false;
            }
        }
    }
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(), nums.end()); // Sort the array
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return result;
    }
};

// Time Complexity: In the worst case where all elements are unique, the complexity is O(n! * n). There are n! permutations. For each answer, we need O(n) to copy it to the result array.
// Space Complexity: O(n), as the depth of the recursion tree is determined by the number of elements.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • Time Complexity: O(n! * n)
  • Space Complexity: O(n)

# Expansion

The key deduplication code is:

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
    continue;
}
1
2
3

It is also correct to replace it with used[i - 1] == true. The deduplication code would be:

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
    continue;
}
1
2
3

Why is this possible? As mentioned before, deduplication across the same tree level uses used[i - 1] == false, while deduplication across the same tree branch uses used[i - 1] == true.

For permutation problems, both tree level deduplication and tree branch deduplication are feasible, although tree level deduplication is more efficient!

Does this sound abstract? Let me illustrate with an example using the input: [1,1,1].

Tree level deduplication (used[i - 1] == false) results in the following tree structure:

47.PermutationsII2

Tree branch deduplication (used[i - 1] == true) results in the following tree structure:

47.PermutationsII3

It’s clear that deduplication at the tree level is more efficient, as it avoids redundant searches, unlike tree branch deduplication.

# Conclusion

This problem applies previous deduplication techniques. Interestingly, deduplication conditions such as:

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
    continue;
}
1
2
3

and:

if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
    continue;
}
1
2
3

both work, but understanding why can be puzzling for many. By illustrating the tree structures for input [1,1,1], we can clearly identify why two approaches work and which is more efficient!

You might ask, if both used[i - 1] == false and used[i - 1] == true work, why add this condition at all?

Why not write it simply:

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}
1
2
3

This doesn’t work. The used[i - 1] condition ensures either continuous true or false states, which matters, instead of switching randomly. Hence, this condition is needed.

Does it make sense now?

# Code in Other Languages

# Java

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
        Arrays.sort(nums);
        backTrack(nums, used);
        return result;
    }

    private void backTrack(int[] nums, boolean[] used) {
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            if (used[i] == false) {
                used[i] = true;
                path.add(nums[i]);
                backTrack(nums, used);
                path.remove(path.size() - 1);
                used[i] = false;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Python

class Solution:
    def permuteUnique(self, nums):
        nums.sort()  # Sort the list
        result = []
        self.backtracking(nums, [], [False] * len(nums), result)
        return result

    def backtracking(self, nums, path, used, result):
        if len(path) == len(nums):
            result.append(path[:])
            return
        for i in range(len(nums)):
            if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:
                continue
            used[i] = True
            path.append(nums[i])
            self.backtracking(nums, path, used, result)
            path.pop()
            used[i] = False
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Go

var (
    res [][]int
    path  []int
    st    []bool   // Abbreviation for state
)
func permuteUnique(nums []int) [][]int {
    res, path = make([][]int, 0), make([]int, 0, len(nums))
    st = make([]bool, len(nums))
    sort.Ints(nums)
    dfs(nums, 0)
    return res
}

func dfs(nums []int, cur int) {
    if cur == len(nums) {
        tmp := make([]int, len(path))
        copy(tmp, path)
        res = append(res, tmp)
    }
    for i := 0; i < len(nums); i++ {
        if i != 0 && nums[i] == nums[i-1] && !st[i-1] {  // Deduplication, using st to differentiate levels
            continue
        }
        if !st[i] {
            path = append(path, nums[i])
            st[i] = true
            dfs(nums, cur + 1)
            st[i] = false
            path = path[:len(path)-1]
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# JavaScript

var permuteUnique = function (nums) {
    nums.sort((a, b) => a - b);
    let result = [];
    let path = [];

    function backtracing(used) {
        if (path.length === nums.length) {
            result.push([...path]);
            return;
        }
        for (let i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) {
                continue;
            }
            if (!used[i]) {
                used[i] = true;
                path.push(nums[i]);
                backtracing(used);
                path.pop();
                used[i] = false;
            }
        }
    }
    backtracing([]);
    return result;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# TypeScript

function permuteUnique(nums: number[]): number[][] {
    nums.sort((a, b) => a - b);
    const resArr: number[][] = [];
    const usedArr: boolean[] = new Array(nums.length).fill(false);
    backTracking(nums, []);
    return resArr;
    function backTracking(nums: number[], route: number[]): void {
        if (route.length === nums.length) {
            resArr.push([...route]);
            return;
        }
        for (let i = 0, length = nums.length; i < length; i++) {
            if (i > 0 && nums[i] === nums[i - 1] && usedArr[i - 1] === false) continue;
            if (usedArr[i] === false) {
                route.push(nums[i]);
                usedArr[i] = true;
                backTracking(nums, route);
                usedArr[i] = false;
                route.pop();
            }
        }
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# Swift

func permuteUnique(_ nums: [Int]) -> [[Int]] {
    let nums = nums.sorted() // Sorts the array
    var result = [[Int]]()
    var path = [Int]()
    var used = [Bool](repeating: false, count: nums.count)
    func backtracking() {
        if path.count == nums.count {
            result.append(path)
            return
        }

        for i in 0 ..< nums.count {
            if i > 0, nums[i] == nums[i - 1], !used[i - 1] { continue }
            if used[i] { continue }
            used[i] = true
            path.append(nums[i])
            backtracking()
            // Backtracking
            path.removeLast()
            used[i] = false
        }
    }
    backtracking()
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Rust

impl Solution {
    fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, nums: &Vec<i32>, used: &mut Vec<bool>) {
        let len = nums.len();
        if path.len() == len {
            result.push(path.clone());
            return;
        }
        for i in 0..len {
            if i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false { continue; }
            if used[i] == false {
                used[i] = true;
                path.push(nums[i]);
                Self::backtracking(result, path, nums, used);
                path.pop();
                used[i] = false;
            }
        }
    }

    pub fn permute_unique(nums: Vec<i32>) -> Vec<Vec<i32>> {
        let mut result: Vec<Vec<i32>> = Vec::new();
        let mut path: Vec<i32> = Vec::new();
        let mut used = vec![false; nums.len()];
        let mut nums= nums;
        nums.sort();
        Self::backtracking(&mut result, &mut path, &nums, &mut used);
        result
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# C

// Temporary array
int *path;
// Return array
int **ans;
int *used;
int pathTop, ansTop;

// Copy path to ans
void copyPath() {
    int *tempPath = (int*)malloc(sizeof(int) * pathTop);
    int i;
    for(i = 0; i < pathTop; ++i) {
        tempPath[i] = path[i];
    }
    ans[ansTop++] = tempPath;
}

void backTracking(int* used, int *nums, int numsSize) {
    if(pathTop == numsSize)
        copyPath();
    int i;
    for(i = 0; i < numsSize; i++) {
        if(used[i] || (i != 0 && nums[i] == nums[i-1] && used[i-1] == 0))
            continue;

        used[i] = 1;
        path[pathTop++] = nums[i];
        backTracking(used, nums, numsSize);
        used[i] = 0;
        --pathTop;
    }
}

int cmp(void* elem1, void* elem2) {
    return *((int*)elem1) - *((int*)elem2);
}

int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    qsort(nums, numsSize, sizeof(int), cmp);
    pathTop = ansTop = 0;
    path = (int*)malloc(sizeof(int) * numsSize);
    ans = (int**)malloc(sizeof(int*) * 1000);
    used = (int*)malloc(sizeof(int) * numsSize);
    int i;
    for(i = 0; i < numsSize; i++) {
        used[i] = 0;
    }

    backTracking(used, nums, numsSize);

    *returnSize = ansTop;
    *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);
    int z;
    for(z = 0; z < ansTop; z++) {
        (*returnColumnSizes)[z] = numsSize;
    }
    return ans;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# Scala

object Solution {
  import scala.collection.mutable
  def permuteUnique(nums: Array[Int]): List[List[Int]] = {
    var result = mutable.ListBuffer[List[Int]]()
    var path = mutable.ListBuffer[Int]()
    var num = nums.sorted // Firstly, sort the data

    def backtracking(used: Array[Boolean]): Unit = {
      if (path.size == num.size) {
        result.append(path.toList)
        return
      }
      for (i <- num.indices if used(i) == false) {
        if (i == 0 || (i > 0 && num(i) != num(i - 1)) || used(i-1) == false) {
          used(i) = true
          path.append(num(i))
          backtracking(used)
          path.remove(path.size - 1)
          used(i) = false
        }
      }
    }

    backtracking(new Array[Boolean](nums.length))
    result.toList
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# C#

public class Solution
{
    public List<IList<int>> res = new List<IList<int>>();
    public List<int> path = new List<int>();
    public IList<IList<int>> PermuteUnique(int[] nums)
    {
        Array.Sort(nums);
        BackTracking(nums, new bool[nums.Length]);
        return res;
    }
    public void BackTracking(int[] nums, bool[] used)
    {
        if (nums.Length == path.Count)
        {
            res.Add(new List<int>(path));
            return;
        }
        for (int i = 0; i < nums.Length; i++)
        {
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            if (used[i]) continue;
            path.Add(nums[i]);
            used[i] = true;
            BackTracking(nums, used);
            path.RemoveAt(path.Count - 1);
            used[i] = false;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Last updated:: 9/4/2025, 3:19:38 PM
Copyright © 2025 keetcoder