1066-subsets-power-set — Subsets / Power Set
Tutorial
The Problem
The power set of a set S is the set of all subsets, including the empty set and S itself. A set with n elements has 2^n subsets. Generating all subsets is needed for exhaustive search, testing all combinations of feature flags, computing all possible teams from a player list, and in model checking for state space exploration.
Two elegant algorithms: backtracking (include/exclude each element) and bitmasking (each bit of an integer represents whether an element is included).
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
// 1066: All Subsets (Power Set) — Backtracking vs Bitmasking
// Approach 1: Backtracking
fn subsets_backtrack(nums: &[i32]) -> Vec<Vec<i32>> {
let mut results = Vec::new();
let mut current = Vec::new();
fn build(start: usize, nums: &[i32], current: &mut Vec<i32>, results: &mut Vec<Vec<i32>>) {
results.push(current.clone());
for i in start..nums.len() {
current.push(nums[i]);
build(i + 1, nums, current, results);
current.pop();
}
}
build(0, nums, &mut current, &mut results);
results
}
// Approach 2: Bitmasking
fn subsets_bitmask(nums: &[i32]) -> Vec<Vec<i32>> {
let n = nums.len();
let total = 1 << n;
(0..total)
.map(|mask| {
(0..n)
.filter(|&i| mask & (1 << i) != 0)
.map(|i| nums[i])
.collect()
})
.collect()
}
// Approach 3: Iterative doubling (fold)
fn subsets_fold(nums: &[i32]) -> Vec<Vec<i32>> {
nums.iter().fold(vec![vec![]], |acc, &x| {
let mut result = acc.clone();
for mut subset in acc {
subset.push(x);
result.push(subset);
}
result
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backtrack() {
let s = subsets_backtrack(&[1, 2, 3]);
assert_eq!(s.len(), 8);
assert!(s.contains(&vec![]));
assert!(s.contains(&vec![1, 2, 3]));
}
#[test]
fn test_bitmask() {
let s = subsets_bitmask(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_fold() {
let s = subsets_fold(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_empty() {
assert_eq!(subsets_backtrack(&[]).len(), 1);
}
}Key Differences
subsets function is 3 lines using @ List.map; Rust's backtracking is more verbose but explicit.mask & (1 << i) != 0 identically — bitmasking is language-independent.Vec<Vec<i32>> explicitly; OCaml's recursive version builds lists naturally.Seq version follows the same pattern.OCaml Approach
let subsets lst =
let n = List.length lst in
let arr = Array.of_list lst in
List.init (1 lsl n) (fun mask ->
List.init n (fun i ->
if mask land (1 lsl i) <> 0 then [arr.(i)] else []
) |> List.concat
)
Or with backtracking:
let rec subsets = function
| [] -> [[]]
| x :: rest ->
let without = subsets rest in
without @ List.map (fun s -> x :: s) without
The recursive OCaml version is clean and idiomatic — divide into subsets with and without the head element.
Full Source
#![allow(clippy::all)]
// 1066: All Subsets (Power Set) — Backtracking vs Bitmasking
// Approach 1: Backtracking
fn subsets_backtrack(nums: &[i32]) -> Vec<Vec<i32>> {
let mut results = Vec::new();
let mut current = Vec::new();
fn build(start: usize, nums: &[i32], current: &mut Vec<i32>, results: &mut Vec<Vec<i32>>) {
results.push(current.clone());
for i in start..nums.len() {
current.push(nums[i]);
build(i + 1, nums, current, results);
current.pop();
}
}
build(0, nums, &mut current, &mut results);
results
}
// Approach 2: Bitmasking
fn subsets_bitmask(nums: &[i32]) -> Vec<Vec<i32>> {
let n = nums.len();
let total = 1 << n;
(0..total)
.map(|mask| {
(0..n)
.filter(|&i| mask & (1 << i) != 0)
.map(|i| nums[i])
.collect()
})
.collect()
}
// Approach 3: Iterative doubling (fold)
fn subsets_fold(nums: &[i32]) -> Vec<Vec<i32>> {
nums.iter().fold(vec![vec![]], |acc, &x| {
let mut result = acc.clone();
for mut subset in acc {
subset.push(x);
result.push(subset);
}
result
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backtrack() {
let s = subsets_backtrack(&[1, 2, 3]);
assert_eq!(s.len(), 8);
assert!(s.contains(&vec![]));
assert!(s.contains(&vec![1, 2, 3]));
}
#[test]
fn test_bitmask() {
let s = subsets_bitmask(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_fold() {
let s = subsets_fold(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_empty() {
assert_eq!(subsets_backtrack(&[]).len(), 1);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backtrack() {
let s = subsets_backtrack(&[1, 2, 3]);
assert_eq!(s.len(), 8);
assert!(s.contains(&vec![]));
assert!(s.contains(&vec![1, 2, 3]));
}
#[test]
fn test_bitmask() {
let s = subsets_bitmask(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_fold() {
let s = subsets_fold(&[1, 2, 3]);
assert_eq!(s.len(), 8);
}
#[test]
fn test_empty() {
assert_eq!(subsets_backtrack(&[]).len(), 1);
}
}
Deep Comparison
All Subsets (Power Set) — Comparison
Core Insight
Three approaches: backtracking (include/skip decisions), bitmasking (enumerate 0..2^n), and iterative doubling (fold over elements, cloning all subsets with new element added). Each has different elegance-performance tradeoffs.
OCaml Approach
ref list and prepend/tailList.init total + lsl/land bit opsArray.fold_left with list append @ — elegant but allocatesList.rev needed for ordering in backtrack/bitmaskRust Approach
push/pop + clone().map + .filter + .collect() — very idiomaticiter().fold(vec![vec![]], ...) with clone and push1 << n for power of 2Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Bitmask idiom | mask land (1 lsl i) | mask & (1 << i) |
| Iterator chain | List.init + loop | (0..total).map(...).collect() |
| Fold doubling | Array.fold_left + @ | .fold(vec![vec![]], ...) + clone |
| Subset count | 1 lsl n | 1 << n |
| Cloning | List.rev (structural sharing) | .clone() (deep copy) |
Exercises
subsets_of_size(nums: &[i32], k: usize) -> Vec<Vec<i32>> that generates only subsets of exactly size k.subsets_unique(nums: &mut [i32]) -> Vec<Vec<i32>> that handles duplicate elements by sorting and skipping duplicates at each level.fn subsets_iter(nums: &[i32]) -> impl Iterator<Item=Vec<i32>> using bitmask iteration.