1064-permutations — Generate All Permutations
Tutorial
The Problem
Generating all permutations of a sequence is essential for brute-force combinatorial search: testing all orderings of tasks for a scheduling problem, generating all possible moves in game AI, and exhaustive testing of commutative operations. There are n! permutations of n elements.
Two classic algorithms: the swap-based approach (Heap's algorithm variant) and the selection approach using a "used" flags array. Both produce all n! permutations but in different orders.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
// 1064: Generate All Permutations via Backtracking
// Approach 1: Swap-based backtracking
fn permutations_swap(nums: &mut Vec<i32>) -> Vec<Vec<i32>> {
let mut results = Vec::new();
fn permute(start: usize, nums: &mut Vec<i32>, results: &mut Vec<Vec<i32>>) {
if start == nums.len() {
results.push(nums.clone());
return;
}
for i in start..nums.len() {
nums.swap(start, i);
permute(start + 1, nums, results);
nums.swap(start, i);
}
}
permute(0, nums, &mut results);
results
}
// Approach 2: Used-flags approach
fn permutations_flags(nums: &[i32]) -> Vec<Vec<i32>> {
let n = nums.len();
let mut results = Vec::new();
let mut used = vec![false; n];
let mut current = Vec::with_capacity(n);
fn build(
nums: &[i32],
used: &mut Vec<bool>,
current: &mut Vec<i32>,
results: &mut Vec<Vec<i32>>,
) {
if current.len() == nums.len() {
results.push(current.clone());
return;
}
for i in 0..nums.len() {
if !used[i] {
used[i] = true;
current.push(nums[i]);
build(nums, used, current, results);
current.pop();
used[i] = false;
}
}
}
build(nums, &mut used, &mut current, &mut results);
results
}
// Approach 3: Iterator-based using Heap's algorithm
fn permutations_heap(nums: &[i32]) -> Vec<Vec<i32>> {
let mut arr = nums.to_vec();
let n = arr.len();
let mut results = vec![arr.clone()];
let mut c = vec![0usize; n];
let mut i = 0;
while i < n {
if c[i] < i {
if i % 2 == 0 {
arr.swap(0, i);
} else {
arr.swap(c[i], i);
}
results.push(arr.clone());
c[i] += 1;
i = 0;
} else {
c[i] = 0;
i += 1;
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_swap() {
let mut nums = vec![1, 2, 3];
let perms = permutations_swap(&mut nums);
assert_eq!(perms.len(), 6);
assert!(perms.contains(&vec![1, 2, 3]));
assert!(perms.contains(&vec![3, 2, 1]));
}
#[test]
fn test_flags() {
let perms = permutations_flags(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_heap() {
let perms = permutations_heap(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_single() {
assert_eq!(permutations_flags(&[42]).len(), 1);
}
}Key Differences
current array.itertools::permutations**: The itertools crate provides iter.permutations(k) for lazy k-permutations in Rust; OCaml's Base.List has no direct equivalent.OCaml Approach
let permutations lst =
let n = List.length lst in
let arr = Array.of_list lst in
let results = ref [] in
let used = Array.make n false in
let current = Array.make n 0 in
let rec build len =
if len = n then results := Array.to_list current :: !results
else
for i = 0 to n - 1 do
if not used.(i) then begin
current.(len) <- arr.(i);
used.(i) <- true;
build (len + 1);
used.(i) <- false
end
done
in
build 0;
!results
Structurally identical. OCaml's mutable arrays and refs replace Rust's explicit &mut parameters.
Full Source
#![allow(clippy::all)]
// 1064: Generate All Permutations via Backtracking
// Approach 1: Swap-based backtracking
fn permutations_swap(nums: &mut Vec<i32>) -> Vec<Vec<i32>> {
let mut results = Vec::new();
fn permute(start: usize, nums: &mut Vec<i32>, results: &mut Vec<Vec<i32>>) {
if start == nums.len() {
results.push(nums.clone());
return;
}
for i in start..nums.len() {
nums.swap(start, i);
permute(start + 1, nums, results);
nums.swap(start, i);
}
}
permute(0, nums, &mut results);
results
}
// Approach 2: Used-flags approach
fn permutations_flags(nums: &[i32]) -> Vec<Vec<i32>> {
let n = nums.len();
let mut results = Vec::new();
let mut used = vec![false; n];
let mut current = Vec::with_capacity(n);
fn build(
nums: &[i32],
used: &mut Vec<bool>,
current: &mut Vec<i32>,
results: &mut Vec<Vec<i32>>,
) {
if current.len() == nums.len() {
results.push(current.clone());
return;
}
for i in 0..nums.len() {
if !used[i] {
used[i] = true;
current.push(nums[i]);
build(nums, used, current, results);
current.pop();
used[i] = false;
}
}
}
build(nums, &mut used, &mut current, &mut results);
results
}
// Approach 3: Iterator-based using Heap's algorithm
fn permutations_heap(nums: &[i32]) -> Vec<Vec<i32>> {
let mut arr = nums.to_vec();
let n = arr.len();
let mut results = vec![arr.clone()];
let mut c = vec![0usize; n];
let mut i = 0;
while i < n {
if c[i] < i {
if i % 2 == 0 {
arr.swap(0, i);
} else {
arr.swap(c[i], i);
}
results.push(arr.clone());
c[i] += 1;
i = 0;
} else {
c[i] = 0;
i += 1;
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_swap() {
let mut nums = vec![1, 2, 3];
let perms = permutations_swap(&mut nums);
assert_eq!(perms.len(), 6);
assert!(perms.contains(&vec![1, 2, 3]));
assert!(perms.contains(&vec![3, 2, 1]));
}
#[test]
fn test_flags() {
let perms = permutations_flags(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_heap() {
let perms = permutations_heap(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_single() {
assert_eq!(permutations_flags(&[42]).len(), 1);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_swap() {
let mut nums = vec![1, 2, 3];
let perms = permutations_swap(&mut nums);
assert_eq!(perms.len(), 6);
assert!(perms.contains(&vec![1, 2, 3]));
assert!(perms.contains(&vec![3, 2, 1]));
}
#[test]
fn test_flags() {
let perms = permutations_flags(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_heap() {
let perms = permutations_heap(&[1, 2, 3]);
assert_eq!(perms.len(), 6);
}
#[test]
fn test_single() {
assert_eq!(permutations_flags(&[42]).len(), 1);
}
}
Deep Comparison
Permutations — Comparison
Core Insight
Three classic approaches: swap-based (in-place), used-flags (separate buffer), and insertion-based (purely functional in OCaml) / Heap's algorithm (iterative in Rust). Each trades off between elegance and efficiency.
OCaml Approach
Array with index swappingList.concat_map — most idiomatic OCamlList.rev to maintain orderRust Approach
Vec::swap() — clean and idiomaticVec<bool> with push/pop on currentclone() needed to snapshot each permutationComparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Swap | let tmp = ...; ... <- ... (manual) | Vec::swap(i, j) (built-in) |
| Functional style | List.concat_map + insertion | Not natural (would need im crate) |
| Heap's algorithm | Not shown | Iterative with c array |
| Snapshot | Array.to_list | .clone() |
| Space | O(n!) results + O(n) stack | O(n!) results + O(n) stack |
Exercises
permutations_lazy(nums: Vec<i32>) -> impl Iterator<Item=Vec<i32>> using Heap's algorithm as a lazy iterator.permutations_unique(nums: &mut [i32]) -> Vec<Vec<i32>> that handles duplicate elements by sorting and skipping repeated swaps.nth_permutation(nums: &[i32], n: usize) -> Vec<i32> that generates only the nth permutation in lexicographic order using factoradic number representation.