Topics › Recursion and Backtracking › Subsequences Pattern
Power Set
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given a string s, return its power set: every subsequence of s, where a subsequence keeps the original relative order of the characters but may skip any of them. For n characters there are exactly 2^n subsequences (each character is either in or out), including the empty string and s itself.
The GeeksforGeeks version asks for the non-empty subsequences in lexicographic order; the methods below generate all 2^n and a small wrapper produces that exact output. LeetCode’s “Subsets” is the same problem on an integer array.
Constraints: 1 ≤ n ≤ 16 or so; the output has 2^n strings, so the input cannot be large.
Example 1
s = "abc"
answer = ["", "a", "ab", "abc", "ac", "b", "bc", "c"] // 2^3 = 8, shown sorted
Example 2
s = "ba"
answer = ["", "a", "b", "ba"] // "ab" is not a subsequence: order must be preserved
Every method returns the same multiset of strings; they differ in the order they produce them, which the article points out because each order is useful somewhere.
Approach 1: Bit masking
Intuition. A subsequence is a yes/no decision per character, so it is an n-bit number: bit i set means “take s[i]”. The numbers 0 .. 2^n - 1 enumerate every combination of decisions exactly once. For each number, walk the bits and append the chosen characters in index order, which keeps them in the original relative order.
Algorithm.
- For
mask = 0 .. 2^n - 1: - For
i = 0 .. n - 1, if bitiofmaskis set, appends[i]to a builder. - Add the builder’s string to the result.
static List<String> bitmask(String s) {
int n = s.length();
List<String> result = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) sb.append(s.charAt(i)); // bit i set: take s[i]
}
result.add(sb.toString());
}
return result;
}
Dry run on "abc":
| mask | binary (c b a) | subsequence |
|---|---|---|
| 0 | 000 | "" |
| 1 | 001 | "a" |
| 2 | 010 | "b" |
| 3 | 011 | "ab" |
| 4 | 100 | "c" |
| 5 | 101 | "ac" |
| 6 | 110 | "bc" |
| 7 | 111 | "abc" |
Complexity. Time O(2^n · n): 2^n masks, n bits each. Space O(n) besides the output. There is no recursion, so no stack concerns; the limit is 1 << n, which must stay inside an int (n ≤ 30).
Approach 2: Optimal, take / not-take recursion
Intuition. This is the recursion the whole “subsequences” section of the sheet is built on. Walk an index through the string. At each index make one of two choices, take s[index] into the current subsequence or leave it out, and recurse to index + 1. When the index reaches n, a decision has been made for every character and the current builder holds one complete subsequence. Two choices at each of n levels gives 2^n leaves, one per subsequence.
Algorithm.
generate(index, current): ifindex == n, recordcurrentand return.- Append
s[index], recurse onindex + 1, then remove the appended character (undo). - Recurse on
index + 1without appending.
static List<String> recursive(String s) {
List<String> result = new ArrayList<>();
generate(s, 0, new StringBuilder(), result);
return result;
}
static void generate(String s, int index, StringBuilder current, List<String> result) {
if (index == s.length()) { // base case: a decision was made for every character
result.add(current.toString());
return;
}
current.append(s.charAt(index)); // choice 1: take s[index]
generate(s, index + 1, current, result);
current.deleteCharAt(current.length() - 1); // undo, so the next branch starts from the same prefix
generate(s, index + 1, current, result); // choice 2: leave s[index] out
}
Dry run on "abc", as the recursion tree (left child = take, right child = not take):
"" index 0
├── "a" take a
│ ├── "ab" take b
│ │ ├── "abc" ✓ take c
│ │ └── "ab" ✓ not take c
│ └── "a" not take b
│ ├── "ac" ✓
│ └── "a" ✓
└── "" not take a
├── "b"
│ ├── "bc" ✓
│ └── "b" ✓
└── ""
├── "c" ✓
└── "" ✓
Leaves left to right: abc, ab, ac, a, bc, b, c, "". Swapping the two recursive calls would list the empty string first and s last.
Complexity. Time O(2^n · n): 2^n leaves, and copying a string of up to n characters at each. Space O(n) for the recursion depth and the shared builder.
The deleteCharAt line is the backtracking step: the builder is shared across all frames, so after exploring the take branch the character must be removed before the not-take branch runs. Forgetting it produces subsequences with extra characters.
Approach 3: Iterative doubling
Intuition. The power set of a longer string can be built from the power set of a shorter one. If P is the set of subsequences of the first i characters, then the subsequences of the first i + 1 characters are P together with every member of P extended by s[i]. Start with {""} and apply this n times; the list doubles each round, which is where 2^n comes from.
Algorithm.
result = [""].- For each character
cofs: letsizebe the current size ofresult; forj = 0 .. size - 1, addresult[j] + c. - Return
result.
static List<String> cascading(String s) {
List<String> result = new ArrayList<>();
result.add(""); // the power set of the empty prefix
for (int i = 0; i < s.length(); i++) {
int size = result.size(); // extend only the subsequences that existed before this character
for (int j = 0; j < size; j++) result.add(result.get(j) + s.charAt(i));
}
return result;
}
Complexity. Time O(2^n · n), space O(1) besides the output.
Taking size before the inner loop matters: the list grows while we iterate, and reading result.size() in the loop condition would never terminate. Interestingly this produces exactly the bit-mask order of Approach 1: adding s[i] to the first 2^i entries is the same as setting bit i.
The GeeksforGeeks output
GFG wants the non-empty subsequences sorted lexicographically. Generate with any of the three methods, drop the single empty string, and sort.
static List<String> sortedNonEmpty(String s) {
List<String> all = recursive(s);
all.remove(""); // exactly one empty subsequence exists
Collections.sort(all);
return all;
}
For "abc" this yields a, ab, abc, ac, b, bc, c. Sorting costs O(2^n · n · log 2^n) = O(2^n · n^2) in the worst case, so it is the dominant step, but with n ≤ 16 it is negligible.
Edge cases and pitfalls
- Empty string. The power set of
""is{""}: one element. All three methods return a list of size1, and the GFG wrapper returns an empty list. - Repeated characters.
"aab"has8subsequences as index sets but only6distinct strings;"a"and"ab"each appear twice. The problem as stated keeps them. If distinct strings are wanted, sort the input and skip a character equal to the previous one on the not-take side, which is the Subsets II technique. - Shift overflow.
1 << nforn ≥ 31wraps; the bit-mask method is only valid forn ≤ 30, and in practice the output size limitsnto about20anyway. - Order preservation.
"ba"never yields"ab". All three methods append characters in increasing index order, which is what preserves it. - Missing undo. In the recursive version, removing the appended character before the not-take call is essential.
- Reading
size()inside the loop. In the doubling version, cache the size before extending the list. - Output size.
2^nstrings of average lengthn / 2means about2^n · n / 2characters in memory.n = 20is roughly ten million characters; beyond that, process subsequences as they are generated instead of collecting them.
Related problems
- Learn All Patterns of Subsequences (Theory) is the next entry in the sheet and generalises Approach 2 to filtering, counting and early exit.
- Subsets I is the integer-array form of the same problem, and Subsets II adds duplicate removal.
- Power Set Bit Manipulation focuses on Approach 1 in the bit manipulation section.
- Subset Sums applies the take / not-take recursion to sums instead of strings.
- Generate Binary Strings Without Consecutive 1s is the same two-choice recursion with a constraint that prunes one branch.