Browse curriculum

Permutations II

Generate all unique permutations of numbers that may repeat: sort, then skip a value when its equal left neighbour is not yet in the path.

Problem Understanding

Permutations II: given numbers nums that may contain duplicates, return every unique permutation, in any order.

Example: nums = [1, 1, 2] returns [[1,1,2], [1,2,1], [2,1,1]]. Plain permutations would list each of these twice.

The Intuition: Use Equal Values in Order

Start from Permutations: choose an unused number, recurse, un-choose. With duplicates, two equal values can swap places and produce the same list twice.

The fix is a single rule after sorting:

  • Skip nums[i] if nums[i] == nums[i − 1] and nums[i − 1] is not in the path.

That makes equal values always enter the path left to right, so there is only one way to place each group of twins. The same sort-and-skip idea removes duplicates in Subsets II and Combination Sum II.

Interactive Walkthrough

The sorted numbers are on top, with used ones tinted; a twin skipped by the rule turns red. The path is below, and the unique permutations collect beside the figure. Try Two pairs: 24 orderings collapse to 6.

Dry Run Table

Input: nums = [1, 1, 2] (indexes 0, 1, 2)

Path Index tried Action
[] 0 (1) choose
[1] 1 (1) choose: twin 0 is used
[1, 1] 2 (2) choose, record [1,1,2]
[1] 2 (2) choose, then index 1: record [1,2,1]
[] 1 (1) skip: twin 0 is unused
[] 2 (2) choose, then 0, 1: record [2,1,1]

Answer: 3 unique permutations.

The Solution Template

The Permutations template plus a sort and one skip check.

Permutations II Code
1var permuteUnique = function(nums) {
2 nums.sort((a, b) => a - b);
3 const res = [], path = [], used = new Array(nums.length).fill(false);
4 const backtrack = () => {
5 if (path.length === nums.length) {
6 res.push([...path]);
7 return;
8 }
9 for (let i = 0; i < nums.length; i++) {
10 if (used[i]) continue;
11 if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) continue;
12 used[i] = true;
13 path.push(nums[i]);
14 backtrack();
15 path.pop();
16 used[i] = false;
17 }
18 };
19 backtrack();
20 return res;
21};

Edge Cases & Common Mistakes

  • Forgetting to sort: the skip rule only works when equal values are adjacent.
  • used[i − 1] instead of !used[i − 1]: also produces unique results but explores far more dead branches; the !used form prunes at the top.
  • All values equal: exactly one permutation.
  • No duplicates: the rule never fires, and the result is the same as Permutations.
  • Deduplicating with a set afterwards: correct but wasteful; skip during the search instead.

Summary

Sort the numbers, then run the choose / recurse / un-choose template with one extra rule: skip nums[i] when it equals nums[i − 1] and nums[i − 1] is unused. Equal values are then always placed in order, so every unique permutation is built exactly once.

More practice: DSA interview questions with answers · patterns cheat sheet

The approach, step by step

  1. Sort the numbers

    Equal values end up next to each other.

  2. Choose with the skip rule

    For each unused index, skip it if nums[i] == nums[i − 1] and nums[i − 1] is unused; otherwise mark it, append it, recurse, then pop and unmark.

  3. Record full paths

    When the path holds every number, copy it into the result.

Frequently asked questions

How do you avoid duplicate permutations?

Sort the array so equal values are adjacent. When choosing the next number, skip nums[i] if it equals nums[i − 1] and nums[i − 1] is not currently used. That forces equal values to be used left to right, so two equal values never swap places and every distinct ordering is built once.


Why check that the previous twin is unused?

If nums[i − 1] is unused at this level, choosing nums[i] here starts the same subtree that choosing nums[i − 1] already explored, because the two values are equal. If nums[i − 1] is already in the path, nums[i] is the next copy in line and is allowed.


Could you just put the permutations in a set?

Yes, but the search still builds every duplicate first, which can be n! work for many repeats. Skipping at the branch avoids generating them at all.

Applying for software engineering roles? Free, no sign-up:

AI resume checker — match your resume to the job description