LeetCode 2222 - Number of Ways to Select Buildings
The problem asks us to count the number of ways to select exactly 3 buildings from a street represented as a binary string s, such that no two consecutive buildings among the selected ones have the same type. Here, 0 represents an office, and 1 represents a restaurant.
Difficulty: 🟡 Medium
Topics: String, Dynamic Programming, Prefix Sum
Solution
Problem Understanding
The problem asks us to count the number of ways to select exactly 3 buildings from a street represented as a binary string s, such that no two consecutive buildings among the selected ones have the same type. Here, 0 represents an office, and 1 represents a restaurant. Essentially, for the three selected buildings at positions i < j < k, the substring s[i]s[j]s[k] must be either "010" or "101".
The input s can be up to length 10^5, so any approach that examines all possible triplets individually (which would be O(n^3)) is infeasible. The expected output is the total number of valid triplets that satisfy the alternating type constraint.
Edge cases to consider include strings with all identical characters (no valid selections), strings of length exactly 3, and strings where valid sequences appear at the boundaries.
Approaches
The brute-force approach would iterate over all possible triplets (i, j, k) and check whether s[i] != s[j] != s[k] in the correct alternating pattern. This is correct but highly inefficient with a complexity of O(n^3), which is unacceptable for n = 10^5.
The key observation for an optimal solution is that a valid 3-building sequence must follow "010" or "101". We can precompute prefix sums of the counts of '0' and '1' up to each position. Then, for each position j chosen as the middle building, we can count valid left and right buildings based on the type of s[j]. Specifically, if s[j] == '0', the number of valid triplets with s[j] in the middle is (number of '1's to the left) * (number of '1's to the right). Similarly, if s[j] == '1', it is (number of '0's to the left) * (number of '0's to the right). Summing this over all positions gives the total number of valid triplets in O(n) time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^3) | O(1) | Enumerates all triplets explicitly |
| Optimal | O(n) | O(n) | Uses prefix sums to count valid triplets efficiently |
Algorithm Walkthrough
- Initialize two arrays,
prefix0andprefix1, of lengthn+1.prefix0[i]stores the number of'0's ins[0..i-1]andprefix1[i]stores the number of'1's ins[0..i-1]. - Fill in
prefix0andprefix1by iterating over the strings. For each positioni, increment the respective prefix sum. - Initialize a variable
total_waysto 0. This will accumulate the number of valid 3-building selections. - Iterate through each position
jin the string, treating it as the middle building. - If
s[j] == '0', calculateways = prefix1[j] * (prefix1[n] - prefix1[j+1]). This represents the number of valid triplets"101"withs[j]in the middle. - If
s[j] == '1', calculateways = prefix0[j] * (prefix0[n] - prefix0[j+1]). This represents the number of valid triplets"010"withs[j]in the middle. - Add
waystototal_ways. - After processing all positions, return
total_ways.
Why it works: By treating each position as the middle of the triplet, and using precomputed counts of characters on each side, we ensure that all valid sequences are counted exactly once. Prefix sums allow us to efficiently compute left and right counts in O(1) per position, leading to an overall O(n) algorithm.
Python Solution
class Solution:
def numberOfWays(self, s: str) -> int:
n = len(s)
prefix0 = [0] * (n + 1)
prefix1 = [0] * (n + 1)
for i in range(n):
prefix0[i+1] = prefix0[i] + (1 if s[i] == '0' else 0)
prefix1[i+1] = prefix1[i] + (1 if s[i] == '1' else 0)
total_ways = 0
for j in range(n):
if s[j] == '0':
ways = prefix1[j] * (prefix1[n] - prefix1[j+1])
else:
ways = prefix0[j] * (prefix0[n] - prefix0[j+1])
total_ways += ways
return total_ways
The implementation first builds prefix sums for both '0' and '1' characters. Then, for each potential middle building, it calculates the number of valid triplets efficiently using the prefix sums. The multiplication gives all combinations of left and right characters matching the required pattern.
Go Solution
func numberOfWays(s string) int64 {
n := len(s)
prefix0 := make([]int, n+1)
prefix1 := make([]int, n+1)
for i := 0; i < n; i++ {
prefix0[i+1] = prefix0[i]
prefix1[i+1] = prefix1[i]
if s[i] == '0' {
prefix0[i+1]++
} else {
prefix1[i+1]++
}
}
var totalWays int64 = 0
for j := 0; j < n; j++ {
var ways int
if s[j] == '0' {
ways = prefix1[j] * (prefix1[n] - prefix1[j+1])
} else {
ways = prefix0[j] * (prefix0[n] - prefix0[j+1])
}
totalWays += int64(ways)
}
return totalWays
}
The Go implementation mirrors the Python version. The main differences include explicit integer conversions for the result to int64 and use of make to initialize slices.
Worked Examples
Example 1: s = "001101"
| j (middle) | s[j] | prefix0[j] | prefix1[j] | left*right ways | Accumulated total |
|---|---|---|---|---|---|
| 0 | '0' | 0 | 0 | 0*3 = 0 | 0 |
| 1 | '0' | 1 | 0 | 0*3 = 0 | 0 |
| 2 | '1' | 2 | 0 | 2*1 = 2 | 2 |
| 3 | '1' | 2 | 1 | 2*1 = 2 | 4 |
| 4 | '0' | 2 | 2 | 2*1 = 2 | 6 |
| 5 | '1' | 3 | 2 | 3*0 = 0 | 6 |
Final answer: 6
Example 2: s = "11100"
All potential middle '1' or '0' characters lead to zero valid combinations because either left or right counts of the required opposite type are zero. Total ways = 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass to compute prefix sums + single pass to compute total ways |
| Space | O(n) | Storing prefix sums for '0' and '1' |
The algorithm scales linearly with the string length and efficiently handles the upper bound of 10^5 characters.
Test Cases
# Provided examples
assert Solution().numberOfWays("001101") == 6 # Mixed pattern
assert Solution().numberOfWays("11100") == 0 # No valid triplets
# Boundary cases
assert Solution().numberOfWays("010") == 1 # Minimal valid length
assert Solution().numberOfWays("000") == 0 # All same type
assert Solution().numberOfWays("111") == 0 # All same type
assert Solution().numberOfWays("01") == 0 # Too short
# Larger patterns
assert Solution().numberOfWays("010101") == 10 # Multiple alternating triplets
assert Solution().numberOfWays("101010") == 10 # Reverse alternating pattern
assert Solution().numberOfWays("000111") == 0 # No alternating triplets
| Test | Why |
|---|---|
| "001101" | Standard mixed pattern with multiple valid triplets |
| "11100" | No valid selections due to consecutive same types |
| "010" | Minimal valid triplet length |
| "000"/"111" | All identical, cannot select triplet |
| "01" | Too short to select 3 buildings |
| "010101"/"101010" | Multiple alternating patterns, stress test for correct counting |
| "000111" | Consecutive blocks, should yield zero |