Every distinct order when songs repeat

Backtracking Mid-level Python 3.13+ Reviewed By Best Latest Apps

Predict the output

What does this print?

Python 3.13
from itertools import permutations
all_orders = list(permutations([1, 1, 2]))
print(len(all_orders), len(set(all_orders)))

Options

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A 6 3

Right: itertools treats the two 1s as different positions, so half the orderings repeat.

The skip rule produces the three distinct orderings directly, without building the repeated ones first.

Why not the others?
  • B 3 3. permutations does not remove repeats.
  • C 6 6. Swapping the two 1s gives the same tuple, which the set merges.

30-second answer

Sort the ids, then build orderings as usual with used flags, but skip a song when it equals the previous id and that previous copy is not in use. This makes equal copies always appear in the same relative order, so each distinct ordering is built once. O(n · n!) worst case, much less with many repeats. Apart from the output, it uses O(n) space.

Practise saying it · 60 s. No recording, just a timer

Explain distinct orderings with repeats.

Say your answer out loud, then check which of these points you covered:

  • Sort first
  • Skip equal unused previous
  • Each distinct order once

Deep dive

The skip rule is the general 'no duplicate branches' trick for sorted input.

Building all orderings and putting them in a set works, but wastes work and memory.

A count map (value → copies left) is an equally clean alternative.

The mirror rule, skipping when the previous copy is in use, also builds each ordering once; it just prunes later.

Python solution Ran on Python 3.13.5 · mypy --strict
Python 3.13
def distinct_orders(songs: list[int]) -> list[list[int]]:
    items = sorted(songs)
    used = [False] * len(items)
    path: list[int] = []
    out: list[list[int]] = []

    def place() -> None:
        if len(path) == len(items):
            out.append(path[:])
            return
        for i, s in enumerate(items):
            if used[i]:
                continue
            if i and s == items[i - 1] and not used[i - 1]:
                continue  # equal copies are used left to right
            used[i] = True
            path.append(s)
            place()
            path.pop()
            used[i] = False

    place()
    return out

Common mistakes

  • Checking only s == items[i - 1] and dropping not used[i - 1]: the second copy can then never be placed, so [1, 1, 2] gives no orderings at all.
  • Forgetting to sort first, so equal values are not adjacent: [1, 2, 1] then yields each of its 3 orderings twice.

Senior take & follow-ups

State the skip rule in words: 'equal copies are used left to right'.

Follow-up questions

Think of your answer first, then open the hint.

  1. How many distinct orders does [1, 1, 2] have?

    Show hint

    3: 3! / 2!.

  2. How does the count-map version work?

    Show hint

    At each slot, try each distinct value with copies left, decrement, recurse, increment.

What each operation costs: Depth First Search

From the author

DSA Master - Data & Algorithms

Free on iPhone and iPad: watch 50+ data structures and algorithms step through animations, run the code in the playground, and try the daily challenge.

Suggest a fix if something here is wrong or out of date.