Skip to content
DSA in JavaScript
Recursion & Backtracking

Combinations

Algorithm · Core

Level: CoreBacktracking

Step through it: Backtracking pattern

[ ][1][ ][1,2][1][2][ ]

holding [ ]

Step 1 of 7

Goal: list every subset of [1, 2]. At each number we choose: take it or skip it. All the choices form a tree of decisions. Start with nothing chosen.

When the order doesn't matter, it is a Combination.

When the order does matter it is a Permutation.

"My fruit salad is a combination of apples, grapes and bananas" We don't care what order the fruits are in, they could also be "bananas, grapes and apples" or "grapes, apples and bananas", its the same fruit salad.

Combinations without repetitions

This is how lotteries work. The numbers are drawn one at a time, and if we have the lucky numbers (no matter what order) we win!

No Repetition: such as lottery numbers (2,14,15,27,30,33)

Number of combinations

where n is the number of things to choose from, and we choose r of them, no repetition, order doesn't matter.

It is often called "n choose r" (such as "16 choose 3"). And is also known as the Binomial Coefficient.

Combinations with repetitions

Repetition is Allowed: such as coins in your pocket (5,5,5,10,10)

Or let us say there are five flavours of ice cream: banana, chocolate, lemon, strawberry and vanilla.

We can have three scoops. How many variations will there be?

Let's use letters for the flavours: {b, c, l, s, v}. Example selections include:

  • {c, c, c} (3 scoops of chocolate)
  • {b, l, v} (one each of banana, lemon and vanilla)
  • {b, v, v} (one of banana, two of vanilla)

Number of combinations

Where n is the number of things to choose from, and we choose r of them. Repetition allowed, order doesn't matter.

Cheatsheet

Permutations and Combinations Overview

Combinations overview

Combinations with repetitionCombinations without repetition

Made with okso.app

Code

combineWithRepetitions.js
/**
 * @param {*[]} comboOptions
 * @param {number} comboLength
 * @return {*[]}
 */
export default function combineWithRepetitions(comboOptions, comboLength) {
  if (comboLength === 0) {
    return [[]];
  }

  if (comboLength < 0) {
    return [];
  }

  // If the length of the combination is 1 then each element of the original array
  // is a combination itself.
  if (comboLength === 1) {
    return comboOptions.map((comboOption) => [comboOption]);
  }

  // Init combinations array.
  const combos = [];

  // Remember characters one by one and concatenate them to combinations of smaller lengths.
  // We don't extract elements here because the repetitions are allowed.
  comboOptions.forEach((currentOption, optionIndex) => {
    // Generate combinations of smaller size.
    const smallerCombos = combineWithRepetitions(
      comboOptions.slice(optionIndex),
      comboLength - 1,
    );

    // Concatenate currentOption with all combinations of smaller size.
    smallerCombos.forEach((smallerCombo) => {
      combos.push([currentOption].concat(smallerCombo));
    });
  });

  return combos;
}
combineWithoutRepetitions.js
/**
 * @param {*[]} comboOptions
 * @param {number} comboLength
 * @return {*[]}
 */
export default function combineWithoutRepetitions(comboOptions, comboLength) {
  if (comboLength === 0) {
    return [[]];
  }

  if (comboLength < 0) {
    return [];
  }

  // If the length of the combination is 1 then each element of the original array
  // is a combination itself.
  if (comboLength === 1) {
    return comboOptions.map((comboOption) => [comboOption]);
  }

  // Init combinations array.
  const combos = [];

  // Extract characters one by one and concatenate them to combinations of smaller lengths.
  // We need to extract them because we don't want to have repetitions after concatenation.
  comboOptions.forEach((currentOption, optionIndex) => {
    // Generate combinations of smaller size.
    const smallerCombos = combineWithoutRepetitions(
      comboOptions.slice(optionIndex + 1),
      comboLength - 1,
    );

    // Concatenate currentOption with all combinations of smaller size.
    smallerCombos.forEach((smallerCombo) => {
      combos.push([currentOption].concat(smallerCombo));
    });
  });

  return combos;
}

References

Practice

Spotted a mistake or something unclear? Report it on GitHub.

On this page