Topics › Recursion and Backtracking › Subsequences Pattern
Generate Binary Strings Without Consecutive 1s
Java source ↗ Edit this article ↗
Jump to code:
Problem
Given an integer n, generate every binary string of length n that does not contain two consecutive 1s, in lexicographic order (which for fixed-length binary strings is the same as increasing numeric order).
Constraints: 1 ≤ n ≤ 20. The number of valid strings is a Fibonacci number, F(n + 2), which for n = 20 is 17711, so the output itself is small even though the search space 2^n is about a million.
Example 1
n = 3
answer = ["000", "001", "010", "100", "101"] // "011", "110", "111" contain "11"
Example 2
n = 2
answer = ["00", "01", "10"] // only "11" is excluded
This is the first problem in the sheet’s “subsequences pattern” section: build an answer one position at a time, and at each position make a small set of choices. The twist here is that the legal choices at a position depend on the previous choice, which is what makes pruning possible.
Approach 1: Brute force
Intuition. There are only 2^n binary strings of length n. Generate all of them by counting from 0 to 2^n - 1, write each number in binary with n digits, and keep the strings that have no "11" substring. Counting upward yields the strings in lexicographic order for free.
Algorithm.
- For
mask = 0 .. 2^n - 1: - Build the
n-character string whosei-th character from the left is bitn - 1 - iofmask. - If the string does not contain
"11", add it to the result.
static List<String> bruteForce(int n) {
List<String> result = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) { // counting upward is lexicographic order for fixed-width strings
StringBuilder sb = new StringBuilder();
for (int bit = n - 1; bit >= 0; bit--) sb.append((mask >> bit) & 1);
String s = sb.toString();
if (!s.contains("11")) result.add(s);
}
return result;
}
Complexity. Time O(2^n · n): every one of the 2^n strings is built and scanned. Space O(n) besides the output.
For n = 20 that is about twenty million character operations, which is fine, but the work is dominated by strings we throw away: fewer than 2 percent of the 2^20 candidates survive. The recursive approach avoids ever constructing an invalid prefix.
Approach 2: Optimal, recursion with pruning
Intuition. Fill the string from left to right. At position pos, placing a 0 is always legal. Placing a 1 is legal only if the previous character was not a 1. If we recurse on both legal choices and never on the illegal one, every leaf of the recursion tree is a valid string: no filtering is needed, and no work is spent on dead ends. Trying 0 before 1 produces the output in lexicographic order.
The information the next call needs is just “was the last character a 1?”, which travels as a boolean parameter.
Algorithm.
build(current, pos, lastWasOne): ifpos == n, recordcurrentand return.- Set
current[pos] = '0'and recurse withlastWasOne = false. - If
lastWasOneis false, setcurrent[pos] = '1'and recurse withlastWasOne = true.
static List<String> optimal(int n) {
List<String> result = new ArrayList<>();
build(new char[n], 0, false, result);
return result;
}
static void build(char[] current, int pos, boolean lastWasOne, List<String> result) {
if (pos == current.length) { // base case: every position is filled
result.add(new String(current));
return;
}
current[pos] = '0'; // choice 1: a 0 is always legal, and sorts first
build(current, pos + 1, false, result);
if (!lastWasOne) { // choice 2: a 1, only when the previous character was not 1
current[pos] = '1';
build(current, pos + 1, true, result);
}
}
Dry run for n = 3, as the recursion tree (each node is the prefix built so far; a node has one child if it ends in 1, two otherwise):
""
├── "0"
│ ├── "00"
│ │ ├── "000" ✓
│ │ └── "001" ✓
│ └── "01"
│ └── "010" ✓
└── "1"
└── "10"
├── "100" ✓
└── "101" ✓
Reading the leaves left to right gives 000, 001, 010, 100, 101, already sorted.
Complexity. Time O(F(n + 2) · n): the tree has exactly one leaf per valid string, every internal node has at least one child, and recording a leaf costs O(n). The output alone has F(n + 2) · n characters, so this is as good as any generator can be. Space O(n) for the recursion depth and the shared buffer.
Because the buffer is a char[] that is overwritten at current[pos] before every recursive call, there is no “undo” step; a position is always written before it is read. With a StringBuilder you would append before recursing and delete the last character afterwards.
Approach 3: Counting without generating
Intuition. Sometimes only the number of valid strings is wanted, and generating F(n + 2) strings to count them is wasteful. Classify valid strings of length len by their last character. A string ending in 0 can be extended by either 0 or 1; a string ending in 1 can only be extended by 0. So
endsWith0(len + 1) = endsWith0(len) + endsWith1(len)
endsWith1(len + 1) = endsWith0(len)
with endsWith0(1) = endsWith1(1) = 1. The total follows the Fibonacci recurrence and equals F(n + 2).
static long countOnly(int n) {
if (n == 0) return 1; // the empty string
long endsWith0 = 1, endsWith1 = 1; // the strings "0" and "1"
for (int len = 2; len <= n; len++) {
long nextEndsWith0 = endsWith0 + endsWith1; // a 0 may follow anything
long nextEndsWith1 = endsWith0; // a 1 may only follow a 0
endsWith0 = nextEndsWith0;
endsWith1 = nextEndsWith1;
}
return endsWith0 + endsWith1;
}
Complexity. Time O(n), space O(1).
This is also the proof of the time bound in Approach 2, and the test suite uses it as a cross-check: for every n up to 16 the two generators must produce identical lists whose size equals countOnly(n).
Edge cases and pitfalls
n = 0. Both generators return a list containing just the empty string, andcountOnly(0)returns1. The constraints start at1, but the base cases handle0naturally.n = 1. Both"0"and"1"are valid; a single1is not “consecutive” with anything.- Order. Place
0before1in the recursion, or the output comes out in a different order and needs sorting. - Shift overflow in the brute force.
1 << nis only meaningful forn < 31in anint. The recursive version has no such limit (other than output size). - Pruning condition. The check is on the previous character only. Checking the whole prefix for
"11"at every step is correct but turns anO(1)decision intoO(n). - Shared buffer. The
char[]is shared by all frames; copy it withnew String(current)at the leaf. Storing the array itself would make every recorded answer point at the same, later-overwritten, buffer. - Recursion depth. Exactly
nframes, trivial forn ≤ 20.
Related problems
- Generate Parentheses is the next problem in the sheet: the same “only recurse on legal choices” idea with two counters instead of one boolean.
- Power Set is the unconstrained version, where both choices are always legal and all
2^nstrings are produced. - Learn All Patterns of Subsequences (Theory) collects the take / not-take variants that these problems are built on.
- Fibonacci Number is the recurrence behind the count in Approach 3.