2222. Number of Ways to Select Buildings
Problem Description
You are given a binary string s
where each character represents a type of building along a street:
s[i] = '0'
means the building at positioni
is an offices[i] = '1'
means the building at positioni
is a restaurant
Your task is to select exactly 3 buildings for inspection with the constraint that among the 3 selected buildings, no two consecutive buildings (in the order they appear in the original string) can be of the same type.
For example, if s = "001101"
and you select buildings at positions 0, 2, and 4 (forming the pattern "010"), this is valid because no two consecutive buildings in your selection have the same type. However, selecting positions 1, 3, and 5 (forming "011") would be invalid because the second and third selected buildings are both restaurants.
The goal is to find the total number of valid ways to select 3 buildings that satisfy this constraint.
Since we need exactly 3 buildings with alternating types, there are only two valid patterns possible:
- "010" - office, restaurant, office
- "101" - restaurant, office, restaurant
The solution counts how many ways we can form these patterns by choosing any 3 positions from the string where the buildings at those positions match one of these alternating patterns.
Intuition
Since we need to select 3 buildings with alternating types, we can think about this problem differently - instead of trying all possible combinations of 3 buildings, we can focus on the middle building of our selection.
The key insight is that once we fix the middle building, the types of the first and third buildings are determined - they must be opposite to the middle one. If the middle building is type x
, then both the first and third buildings must be type x ^ 1
(the opposite type).
This leads us to a counting strategy: for each position i
in the string that could serve as the middle building:
- Count how many buildings of the opposite type exist to its left
- Count how many buildings of the opposite type exist to its right
- The number of valid selections with building
i
as the middle is simply the product of these two counts
For example, if building i
is a restaurant (1
), and there are 3 offices to its left and 2 offices to its right, then there are 3 × 2 = 6
ways to form a valid selection with this building in the middle.
To implement this efficiently, we can maintain two arrays:
l[0]
andl[1]
to track the count of offices and restaurants seen so far (to the left)r[0]
andr[1]
to track the count of offices and restaurants remaining (to the right)
As we traverse the string from left to right, for each building we encounter, we update these counts and calculate its contribution to the answer. This allows us to solve the problem in a single pass with O(n)
time complexity.
Learn more about Dynamic Programming and Prefix Sum patterns.
Solution Approach
The solution implements the counting strategy we identified in the intuition. Here's how the algorithm works:
Initialization:
l = [0, 0]
: Tracks the count of buildings of each type (0 for offices, 1 for restaurants) to the left of current positionr = [s.count("0"), s.count("1")]
: Initially contains the total count of all offices and restaurants in the entire string (representing everything to the right at the start)ans = 0
: Accumulator for the final answer
Main Algorithm:
We iterate through each character in the string s
, treating each position as a potential middle building:
-
Convert character to integer:
x = int(s[i])
gives us 0 for office, 1 for restaurant -
Update right count:
r[x] -= 1
- Since we're at position
i
, this building is no longer "to the right", so we decrement its count fromr
- Since we're at position
-
Calculate valid selections:
ans += l[x ^ 1] * r[x ^ 1]
- If current building is type
x
, we need buildings of typex ^ 1
on both sides x ^ 1
flips the type: ifx = 0
(office), thenx ^ 1 = 1
(restaurant), and vice versa- The number of valid ways with current building as middle = (count of opposite type on left) × (count of opposite type on right)
- If current building is type
-
Update left count:
l[x] += 1
- After processing position
i
, this building becomes part of the "left side" for future positions
- After processing position
Example Walkthrough:
For s = "001101"
:
- Initial:
l = [0, 0]
,r = [3, 3]
- Position 0 (office):
r[0] = 2
, calculate0 * 3 = 0
,l[0] = 1
- Position 1 (office):
r[0] = 1
, calculate0 * 3 = 0
,l[0] = 2
- Position 2 (restaurant):
r[1] = 2
, calculate2 * 1 = 2
,l[1] = 1
- Position 3 (restaurant):
r[1] = 1
, calculate2 * 1 = 2
,l[1] = 2
- Position 4 (office):
r[0] = 0
, calculate2 * 1 = 2
,l[0] = 3
- Position 5 (restaurant):
r[1] = 0
, calculate3 * 0 = 0
,l[1] = 3
- Total:
0 + 0 + 2 + 2 + 2 + 0 = 6
The time complexity is O(n)
as we make a single pass through the string, and space complexity is O(1)
as we only use fixed-size arrays.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a small example with s = "00110"
to understand how the algorithm counts valid selections.
Setup:
- We need to select 3 buildings with alternating types (either "010" or "101" pattern)
- Strategy: For each position, treat it as the middle building and count valid combinations
Initial State:
l = [0, 0]
(no buildings to the left initially)r = [3, 2]
(3 offices and 2 restaurants total in the string)ans = 0
Step-by-step Processing:
Position 0: '0' (office)
- Current building type:
x = 0
- Update right count:
r[0] = 3 - 1 = 2
(remove this office from right side) - Calculate combinations with this as middle:
- Need restaurants (type 1) on both sides
- Left restaurants:
l[1] = 0
- Right restaurants:
r[1] = 2
- Contribution:
0 × 2 = 0
- Update left count:
l[0] = 0 + 1 = 1
- Running total:
ans = 0
Position 1: '0' (office)
- Current building type:
x = 0
- Update right count:
r[0] = 2 - 1 = 1
- Calculate combinations:
- Left restaurants:
l[1] = 0
- Right restaurants:
r[1] = 2
- Contribution:
0 × 2 = 0
- Left restaurants:
- Update left count:
l[0] = 1 + 1 = 2
- Running total:
ans = 0
Position 2: '1' (restaurant)
- Current building type:
x = 1
- Update right count:
r[1] = 2 - 1 = 1
- Calculate combinations:
- Need offices (type 0) on both sides
- Left offices:
l[0] = 2
- Right offices:
r[0] = 1
- Contribution:
2 × 1 = 2
- This counts: (position 0, position 2, position 4) and (position 1, position 2, position 4)
- Update left count:
l[1] = 0 + 1 = 1
- Running total:
ans = 2
Position 3: '1' (restaurant)
- Current building type:
x = 1
- Update right count:
r[1] = 1 - 1 = 0
- Calculate combinations:
- Left offices:
l[0] = 2
- Right offices:
r[0] = 1
- Contribution:
2 × 1 = 2
- This counts: (position 0, position 3, position 4) and (position 1, position 3, position 4)
- Left offices:
- Update left count:
l[1] = 1 + 1 = 2
- Running total:
ans = 4
Position 4: '0' (office)
- Current building type:
x = 0
- Update right count:
r[0] = 1 - 1 = 0
- Calculate combinations:
- Left restaurants:
l[1] = 2
- Right restaurants:
r[1] = 0
- Contribution:
2 × 0 = 0
- Left restaurants:
- Update left count:
l[0] = 2 + 1 = 3
- Running total:
ans = 4
Final Answer: 4
The 4 valid selections are:
- Positions (0, 2, 4): "010" pattern
- Positions (1, 2, 4): "010" pattern
- Positions (0, 3, 4): "010" pattern
- Positions (1, 3, 4): "010" pattern
Solution Implementation
1class Solution:
2 def numberOfWays(self, s: str) -> int:
3 # Track count of 0s and 1s to the left of current position
4 left_count = [0, 0] # [count_of_0s, count_of_1s]
5
6 # Track count of 0s and 1s to the right of current position (initially all)
7 right_count = [s.count("0"), s.count("1")] # [count_of_0s, count_of_1s]
8
9 total_ways = 0
10
11 # Iterate through each character in the string
12 for digit in map(int, s):
13 # Remove current digit from right count (as we're now at this position)
14 right_count[digit] -= 1
15
16 # Count valid 3-character subsequences with current digit in the middle
17 # If current digit is 0, we need 1s on both sides (010 pattern)
18 # If current digit is 1, we need 0s on both sides (101 pattern)
19 # XOR with 1 flips the bit: 0^1=1, 1^1=0
20 opposite_digit = digit ^ 1
21 total_ways += left_count[opposite_digit] * right_count[opposite_digit]
22
23 # Add current digit to left count for next iterations
24 left_count[digit] += 1
25
26 return total_ways
27
1class Solution {
2 public long numberOfWays(String s) {
3 int length = s.length();
4
5 // Arrays to track count of 0s and 1s to the left and right of current position
6 // leftCount[0] = count of '0's to the left, leftCount[1] = count of '1's to the left
7 int[] leftCount = new int[2];
8 // rightCount[0] = count of '0's to the right, rightCount[1] = count of '1's to the right
9 int[] rightCount = new int[2];
10
11 // Initialize rightCount with total counts of 0s and 1s in the entire string
12 for (int i = 0; i < length; i++) {
13 int digit = s.charAt(i) - '0';
14 rightCount[digit]++;
15 }
16
17 long totalWays = 0;
18
19 // Iterate through each position as the middle element of a 3-character subsequence
20 for (int i = 0; i < length; i++) {
21 int currentDigit = s.charAt(i) - '0';
22
23 // Remove current element from right count (moving it from right to current position)
24 rightCount[currentDigit]--;
25
26 // For valid alternating patterns (010 or 101):
27 // - If current is 0, we need 1s on both sides
28 // - If current is 1, we need 0s on both sides
29 // XOR with 1 flips the bit: 0^1=1, 1^1=0
30 int oppositeDigit = currentDigit ^ 1;
31
32 // Count ways by multiplying left opposite digits with right opposite digits
33 totalWays += (long) leftCount[oppositeDigit] * rightCount[oppositeDigit];
34
35 // Add current element to left count (moving it from current position to left)
36 leftCount[currentDigit]++;
37 }
38
39 return totalWays;
40 }
41}
42
1class Solution {
2public:
3 long long numberOfWays(string s) {
4 int n = s.size();
5
6 // Arrays to count '0's and '1's on the left and right sides
7 // leftCount[0] = count of '0's to the left of current position
8 // leftCount[1] = count of '1's to the left of current position
9 int leftCount[2] = {0, 0};
10 int rightCount[2] = {0, 0};
11
12 // Initialize right counts with total counts of '0's and '1's in the string
13 rightCount[0] = ranges::count(s, '0');
14 rightCount[1] = n - rightCount[0];
15
16 long long totalWays = 0;
17
18 // Iterate through each position as the middle element of a subsequence
19 for (int i = 0; i < n; ++i) {
20 int currentDigit = s[i] - '0'; // Convert char to int (0 or 1)
21
22 // Remove current element from right count since we're at this position
23 rightCount[currentDigit]--;
24
25 // If current element is the middle of a valid subsequence,
26 // we need different digits on both sides
27 // XOR with 1 flips the bit: 0^1=1, 1^1=0
28 int oppositeDigit = currentDigit ^ 1;
29
30 // Count valid subsequences with current position as middle element
31 // leftCount[oppositeDigit] * rightCount[oppositeDigit] gives all combinations
32 totalWays += static_cast<long long>(leftCount[oppositeDigit]) * rightCount[oppositeDigit];
33
34 // Add current element to left count for next iterations
35 leftCount[currentDigit]++;
36 }
37
38 return totalWays;
39 }
40};
41
1/**
2 * Counts the number of ways to select 3 indices i, j, k from string s
3 * such that i < j < k and s[i] != s[j] != s[k]
4 * This forms alternating patterns like "010" or "101"
5 *
6 * @param s - Binary string containing only '0' and '1'
7 * @returns Number of valid subsequences of length 3 with alternating bits
8 */
9function numberOfWays(s: string): number {
10 const length: number = s.length;
11
12 // Track count of 0s and 1s to the left of current position
13 const leftCount: number[] = [0, 0]; // [count of 0s, count of 1s]
14
15 // Initialize right counts with total counts in the string
16 const rightCount: number[] = [0, 0]; // [count of 0s, count of 1s]
17
18 // Count total number of 0s in the string
19 rightCount[0] = s.split('').filter((char: string) => char === '0').length;
20 // Calculate total number of 1s
21 rightCount[1] = length - rightCount[0];
22
23 let totalWays: number = 0;
24
25 // Iterate through each character as the middle element
26 for (const char of s) {
27 // Convert character to index: '0' -> 0, '1' -> 1
28 const currentBit: number = char === '0' ? 0 : 1;
29
30 // Decrement right count as we move this element from right to current
31 rightCount[currentBit]--;
32
33 // Calculate ways with current element as middle
34 // XOR with 1 flips the bit (0^1=1, 1^1=0) to get opposite bit
35 // Count ways = (opposite bits on left) * (opposite bits on right)
36 totalWays += leftCount[currentBit ^ 1] * rightCount[currentBit ^ 1];
37
38 // Increment left count as we move past this element
39 leftCount[currentBit]++;
40 }
41
42 return totalWays;
43}
44
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string s
. This is because:
- The
count()
operations each scan through the entire string once, contributingO(n)
time - The main loop iterates through each character in the string exactly once, performing constant-time operations (
O(1)
) for each character: decrementingr[x]
, calculating the XOR operations, updatingans
, and incrementingl[x]
- Overall:
O(n) + O(n) + O(n)
=O(n)
The space complexity is O(1)
. The algorithm uses:
- Two fixed-size arrays
l
andr
, each with 2 elements - A constant number of variables (
ans
,x
) - The space usage does not depend on the input size, remaining constant regardless of the length of string
s
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Problem - Selecting Non-Adjacent Buildings
A common misinterpretation is thinking that the selected buildings must be physically adjacent in the original string. This leads to incorrect solutions that only check consecutive triplets.
Incorrect Approach:
def numberOfWays(self, s: str) -> int:
count = 0
# Wrong: Only checking consecutive buildings
for i in range(len(s) - 2):
substring = s[i:i+3]
if substring == "010" or substring == "101":
count += 1
return count
Why it's wrong: For s = "00110"
, this would return 1 (finding "011" doesn't match, "110" doesn't match), but the correct answer is 4 because we can select non-adjacent buildings like positions [0,2,4] forming "010".
Correct Understanding: We select any 3 buildings from anywhere in the string, maintaining their relative order but not requiring adjacency.
2. Overcounting by Not Maintaining Order
Another pitfall is counting combinations instead of subsequences, ignoring the order constraint.
Incorrect Approach:
def numberOfWays(self, s: str) -> int:
zeros = s.count('0')
ones = s.count('1')
# Wrong: This counts unordered combinations
return zeros * ones * zeros + ones * zeros * ones
Why it's wrong: This counts the same selection multiple times in different orders. For positions [1,3,5], it might count both as firstsecondthird and as thirdfirstsecond.
Solution: The correct approach maintains order by considering each position as a middle element and counting valid left-right pairs.
3. Off-by-One Errors in Count Management
A subtle but critical error occurs when the right count isn't updated before calculating valid ways.
Incorrect Approach:
def numberOfWays(self, s: str) -> int:
left = [0, 0]
right = [s.count("0"), s.count("1")]
ans = 0
for digit in map(int, s):
opposite = digit ^ 1
# Wrong: Calculating before updating right count
ans += left[opposite] * right[opposite]
right[digit] -= 1 # Should be done BEFORE calculation
left[digit] += 1
return ans
Why it's wrong: When we're at position i, that building shouldn't be counted as "to the right". Including it in the right count leads to overcounting because we're essentially counting the current building as both the middle element and a right element.
Solution: Always update the right count first to exclude the current position before calculating valid selections.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!