2147. Number of Ways to Divide a Long Corridor
Problem Description
You are given a string corridor
representing a library corridor with seats ('S'
) and plants ('P'
). The corridor already has dividers at both ends (before index 0 and after index n-1).
Your task is to install additional room dividers to divide the corridor into sections, where:
- Each section must contain exactly 2 seats
- Each section can have any number of plants
- Dividers can only be placed between adjacent positions (between indices
i-1
andi
) - At most one divider can be installed at each position
You need to count the number of different ways to divide the corridor. Two ways are considered different if there exists at least one position where a divider is installed in one way but not in the other.
For example, if the corridor is "SSPPSPS"
:
- One valid way could be to place dividers to create sections like:
[SS][PP][SP][S]
- but this is invalid as the last section has only 1 seat - A valid division would ensure each section has exactly 2 seats
The solution uses dynamic programming with memoization. The function dfs(i, k)
represents:
i
: current position in the corridork
: number of seats in the current section being formed
The algorithm works by:
- Traversing each position and counting seats in the current section
- When a section has 2 seats, we can either:
- Continue adding to the current section (if we encounter plants)
- Start a new section (place a divider and reset seat count to 0)
- If we ever have more than 2 seats in a section, that path is invalid
The answer should be returned modulo 10^9 + 7
. If no valid division exists (e.g., odd number of total seats), return 0
.
Intuition
The key insight is that we need to make decisions about where to place dividers as we traverse the corridor from left to right. At each position, we need to track how many seats we've accumulated in our current section.
Think of it like walking down the corridor and counting seats. Every time we see a seat ('S'
), we add it to our current section's count. When we have exactly 2 seats in our section, we have a choice:
- We can close off this section by placing a divider (start fresh with 0 seats)
- We can continue without placing a divider (keep the 2 seats and continue)
The second option only makes sense if we encounter plants ('P'
) next, because if we encounter another seat without placing a divider, we'd have 3 seats in one section, which violates the constraint.
This naturally leads to a recursive approach where at each position i
with k
seats already counted:
- If we've reached the end of the corridor, we check if
k = 2
(valid section) - If we see a seat, increment
k
- If
k > 2
, this path is invalid (return 0) - Otherwise, we always try continuing without a divider:
dfs(i + 1, k)
- Additionally, if
k = 2
, we can also try placing a divider:dfs(i + 1, 0)
The beauty of this approach is that it automatically handles all the edge cases:
- Odd number of total seats will never produce valid divisions
- Sections with too many seats are pruned immediately
- Plant positions naturally become decision points when we have 2 seats
Memoization (@cache
) is crucial here because many subproblems repeat - we often reach the same position with the same number of seats through different division choices earlier in the corridor.
Learn more about Math and Dynamic Programming patterns.
Solution Approach
The solution uses memoization search (dynamic programming with recursion) to explore all valid ways to partition the corridor.
Implementation Details
We define a recursive function dfs(i, k)
where:
i
represents the current position in the corridor stringk
represents the number of seats in the current section being formed
Base Case:
When i >= len(corridor)
, we've traversed the entire corridor. We return 1
if k == 2
(indicating the last section has exactly 2 seats), otherwise return 0
.
Recursive Case:
- First, check if the current position has a seat: if
corridor[i] == 'S'
, incrementk
by 1 - If
k > 2
, the current section has too many seats, making this path invalid - return0
- Otherwise, we have two choices:
- Don't place a divider: Continue to the next position with the current seat count:
dfs(i + 1, k)
- Place a divider (only if
k == 2
): Start a new section with 0 seats:dfs(i + 1, 0)
- Don't place a divider: Continue to the next position with the current seat count:
The total number of ways is the sum of both choices (when applicable), taken modulo 10^9 + 7
.
Key Components
Memoization with @cache
:
The decorator automatically caches results of dfs(i, k)
calls. This prevents redundant calculations when we reach the same state (i, k)
through different partition choices.
Modulo Operation:
Since the answer can be very large, we apply modulo 10^9 + 7
when combining results: ans = (ans + dfs(i + 1, 0)) % mod
Algorithm Flow:
Start at position 0 with 0 seats
For each position:
- Count seats in current section
- If section valid (≤2 seats):
- Try continuing without divider
- If exactly 2 seats, also try placing divider
- Combine results with modulo
Return total number of valid partitions
The time complexity is O(n)
with memoization (each state (i, k)
is computed at most once, and k
can only be 0, 1, or 2). The space complexity is also O(n)
for the recursion stack and cache.
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 walk through the corridor "SSPPSPS"
to understand how the solution works.
Initial Setup:
- Start with
dfs(0, 0)
- position 0, with 0 seats in current section - We need to explore all valid ways to divide this corridor
Step-by-step traversal:
-
Position 0 ('S'):
dfs(0, 0)
- Current char is 'S', so k becomes 1
- k ≤ 2, so we continue:
dfs(1, 1)
-
Position 1 ('S'):
dfs(1, 1)
- Current char is 'S', so k becomes 2
- k = 2, we have two choices:
- Continue without divider:
dfs(2, 2)
- Place divider and start new section:
dfs(2, 0)
- Continue without divider:
-
Branch 1 - No divider after "SS":
dfs(2, 2)
- Position 2 ('P'): k stays 2 (plants don't add seats)
- k = 2, two choices again:
- Continue:
dfs(3, 2)
- Place divider:
dfs(3, 0)
- Continue:
Following Branch 1.1:
dfs(3, 2)
- Position 3 ('P'): k stays 2
- Continue with both choices...
Following Branch 1.2:
dfs(3, 0)
- We've divided as "SSP|P..."
- Position 3 ('P'): k stays 0
- Continue...
-
Branch 2 - Place divider after "SS":
dfs(2, 0)
- We've divided as "SS|P..."
- Position 2 ('P'): k stays 0
- k < 2, only one choice:
dfs(3, 0)
Continuing this process, let's trace one complete valid path:
- "SS|PPSPS" → First section "SS" has 2 seats ✓
- Continue from position 2 with k=0
- Positions 2-3 are plants, k stays 0
- Position 4 ('S'): k becomes 1
- Position 5 ('P'): k stays 1
- Position 6 ('S'): k becomes 2
- End of string: k = 2 ✓
- This gives us "SS|PPSPS" where second section has 2 seats
Another valid path:
- "SSPP|SPS" → First section "SSPP" has 2 seats ✓
- Divider placed after position 3
- Continue from position 4 with k=0
- Following similar logic leads to "SPS" with 2 seats ✓
The algorithm explores all such paths, counting only those where:
- Every section has exactly 2 seats
- The last section also has exactly 2 seats
Invalid paths (automatically pruned):
- Any path where k > 2 returns 0 immediately
- Any path ending with k ≠ 2 returns 0
The memoization ensures that when we reach the same (position, seat_count)
state through different earlier choices, we don't recalculate - we just reuse the cached result.
For this example, the function would return 2, representing the two valid ways to divide the corridor.
Solution Implementation
1class Solution:
2 def numberOfWays(self, corridor: str) -> int:
3 """
4 Calculate the number of ways to divide a corridor into sections
5 where each section contains exactly 2 seats.
6
7 Args:
8 corridor: String containing 'S' (seats) and 'P' (plants)
9
10 Returns:
11 Number of valid ways to divide the corridor, modulo 10^9 + 7
12 """
13 from functools import cache
14
15 MOD = 10**9 + 7
16
17 @cache
18 def count_divisions(index: int, seats_in_section: int) -> int:
19 """
20 Recursively count valid divisions starting from given index.
21
22 Args:
23 index: Current position in the corridor
24 seats_in_section: Number of seats in the current section (0, 1, or 2)
25
26 Returns:
27 Number of valid ways to divide from this position
28 """
29 # Base case: reached end of corridor
30 if index >= len(corridor):
31 # Valid only if current section has exactly 2 seats
32 return 1 if seats_in_section == 2 else 0
33
34 # Count seats if current position has a seat
35 if corridor[index] == "S":
36 seats_in_section += 1
37
38 # Invalid case: more than 2 seats in a section
39 if seats_in_section > 2:
40 return 0
41
42 # Option 1: Continue current section (don't place divider)
43 ways = count_divisions(index + 1, seats_in_section)
44
45 # Option 2: Place divider after current position if section has 2 seats
46 if seats_in_section == 2:
47 ways = (ways + count_divisions(index + 1, 0)) % MOD
48
49 return ways
50
51 # Calculate result starting from index 0 with 0 seats
52 result = count_divisions(0, 0)
53
54 # Clear cache to free memory
55 count_divisions.cache_clear()
56
57 return result
58
1class Solution {
2 private int corridorLength;
3 private char[] corridorArray;
4 private Integer[][] memoization; // memoization[position][seatsInCurrentSection]
5 private final int MOD = (int) 1e9 + 7;
6
7 public int numberOfWays(String corridor) {
8 // Convert string to char array for faster access
9 corridorArray = corridor.toCharArray();
10 corridorLength = corridorArray.length;
11
12 // Initialize memoization table
13 // memoization[i][j] represents the number of ways starting from position i
14 // with j seats already in the current section
15 memoization = new Integer[corridorLength][3];
16
17 // Start DFS from position 0 with 0 seats in the current section
18 return dfs(0, 0);
19 }
20
21 /**
22 * Recursively calculates the number of ways to divide the corridor
23 * @param currentPosition - current index in the corridor
24 * @param seatsInSection - number of seats in the current section (0, 1, or 2)
25 * @return number of valid ways to divide from current position
26 */
27 private int dfs(int currentPosition, int seatsInSection) {
28 // Base case: reached end of corridor
29 if (currentPosition >= corridorLength) {
30 // Valid only if the last section has exactly 2 seats
31 return seatsInSection == 2 ? 1 : 0;
32 }
33
34 // Check memoization cache
35 if (memoization[currentPosition][seatsInSection] != null) {
36 return memoization[currentPosition][seatsInSection];
37 }
38
39 // Update seat count if current position has a seat
40 int updatedSeatCount = seatsInSection;
41 if (corridorArray[currentPosition] == 'S') {
42 updatedSeatCount++;
43 }
44
45 // Invalid case: more than 2 seats in a section
46 if (updatedSeatCount > 2) {
47 return 0;
48 }
49
50 // Option 1: Don't place a divider, continue with current section
51 int totalWays = dfs(currentPosition + 1, updatedSeatCount);
52
53 // Option 2: Place a divider (only valid if current section has exactly 2 seats)
54 if (updatedSeatCount == 2) {
55 // Place divider and start new section with 0 seats
56 totalWays = (totalWays + dfs(currentPosition + 1, 0)) % MOD;
57 }
58
59 // Store result in memoization table and return
60 memoization[currentPosition][seatsInSection] = totalWays;
61 return totalWays;
62 }
63}
64
1class Solution {
2public:
3 int numberOfWays(string corridor) {
4 int n = corridor.size();
5
6 // dp[i][j] = number of ways to divide corridor from index i with j seats in current section
7 // j can be 0, 1, or 2 (representing seats count in current section)
8 int dp[n][3];
9 memset(dp, -1, sizeof(dp));
10
11 const int MOD = 1e9 + 7;
12
13 // Recursive function with memoization
14 // Parameters:
15 // - currentIndex: current position in corridor
16 // - seatsInSection: number of seats in current section (0, 1, or 2)
17 auto dfs = [&](this auto&& dfs, int currentIndex, int seatsInSection) -> int {
18 // Base case: reached end of corridor
19 if (currentIndex >= n) {
20 // Valid only if last section has exactly 2 seats
21 return seatsInSection == 2 ? 1 : 0;
22 }
23
24 // Return memoized result if already computed
25 if (dp[currentIndex][seatsInSection] != -1) {
26 return dp[currentIndex][seatsInSection];
27 }
28
29 // Update seats count if current position has a seat
30 int newSeatsCount = seatsInSection;
31 if (corridor[currentIndex] == 'S') {
32 newSeatsCount++;
33 }
34
35 // Invalid case: more than 2 seats in a section
36 if (newSeatsCount > 2) {
37 return dp[currentIndex][seatsInSection] = 0;
38 }
39
40 // Option 1: Continue with current section (don't place divider)
41 dp[currentIndex][seatsInSection] = dfs(currentIndex + 1, newSeatsCount);
42
43 // Option 2: If current section has 2 seats, we can place a divider
44 // and start a new section
45 if (newSeatsCount == 2) {
46 dp[currentIndex][seatsInSection] =
47 (dp[currentIndex][seatsInSection] + dfs(currentIndex + 1, 0)) % MOD;
48 }
49
50 return dp[currentIndex][seatsInSection];
51 };
52
53 // Start from index 0 with 0 seats in the first section
54 return dfs(0, 0);
55 }
56};
57
1/**
2 * Calculates the number of ways to divide a corridor with seats and plants into sections
3 * Each section must contain exactly 2 seats
4 * @param corridor - String representing the corridor where 'S' is a seat and 'P' is a plant
5 * @returns Number of ways to divide the corridor modulo 10^9 + 7
6 */
7function numberOfWays(corridor: string): number {
8 const corridorLength: number = corridor.length;
9 const MOD: number = 10 ** 9 + 7;
10
11 // Memoization table: dp[position][seatCount]
12 // dp[i][k] represents the number of ways to partition from position i with k seats in current section
13 const memoTable: number[][] = Array.from(
14 { length: corridorLength },
15 () => Array(3).fill(-1)
16 );
17
18 /**
19 * Dynamic programming with memoization to count valid partitions
20 * @param position - Current position in the corridor
21 * @param seatsInSection - Number of seats in the current section (0, 1, or 2)
22 * @returns Number of valid ways to partition from current position
23 */
24 const countWays = (position: number, seatsInSection: number): number => {
25 // Base case: reached end of corridor
26 if (position >= corridorLength) {
27 // Valid partition only if current section has exactly 2 seats
28 return seatsInSection === 2 ? 1 : 0;
29 }
30
31 // Return memoized result if already computed
32 if (memoTable[position][seatsInSection] !== -1) {
33 return memoTable[position][seatsInSection];
34 }
35
36 // Count seats: increment if current position has a seat
37 let currentSeatCount: number = seatsInSection;
38 if (corridor[position] === 'S') {
39 currentSeatCount++;
40 }
41
42 // Invalid state: more than 2 seats in a section
43 if (currentSeatCount > 2) {
44 memoTable[position][seatsInSection] = 0;
45 return 0;
46 }
47
48 // Option 1: Continue current section (don't place divider)
49 let totalWays: number = countWays(position + 1, currentSeatCount);
50
51 // Option 2: Place divider after current position (only if section has exactly 2 seats)
52 if (currentSeatCount === 2) {
53 totalWays = (totalWays + countWays(position + 1, 0)) % MOD;
54 }
55
56 // Memoize and return result
57 memoTable[position][seatsInSection] = totalWays;
58 return totalWays;
59 };
60
61 // Start from position 0 with 0 seats in the first section
62 return countWays(0, 0);
63}
64
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the corridor string. This is because the memoized DFS function has at most O(n)
unique states. Each state is defined by the pair (i, k)
, where i
ranges from 0
to n-1
and k
can only be 0
, 1
, or 2
. This gives us at most 3n
unique states. Each state is computed only once due to the @cache
decorator, and each computation takes O(1)
time (excluding recursive calls). Therefore, the overall time complexity is O(n)
.
The space complexity is O(n)
. This comes from two sources: the recursion call stack which can go up to depth n
in the worst case when traversing the entire corridor, and the cache storage which stores at most O(n)
states (specifically at most 3n
states as analyzed above). Both contribute to O(n)
space complexity, resulting in an overall space complexity of O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Divider Placement Logic
A critical misunderstanding is thinking that when we have 2 seats in a section, we must place a divider immediately. This leads to missing valid configurations where plants appear after the second seat but before the divider.
Wrong Approach:
if seats_in_section == 2: # Force divider placement - WRONG! return count_divisions(index + 1, 0)
Correct Approach:
if seats_in_section == 2: # We have TWO choices: place divider OR continue ways = (ways + count_divisions(index + 1, 0)) % MOD
For example, with "SSPP"
:
- We can divide as
[SS][PP]
(divider after position 1) - We can also divide as
[SSPP]
(only boundary dividers) Both are valid since each section has exactly 2 seats.
2. Mishandling the Base Case
Another common mistake is not properly validating the final section when reaching the end of the corridor.
Wrong Base Case:
if index >= len(corridor):
return 1 # Always returns 1 - WRONG!
Correct Base Case:
if index >= len(corridor):
# Only valid if the last section has exactly 2 seats
return 1 if seats_in_section == 2 else 0
3. Forgetting Edge Cases
The algorithm might seem to work but fail on edge cases like:
- Corridors with odd number of total seats (impossible to divide into sections of 2)
- Corridors with only plants
"PPP"
(returns 0 correctly) - Corridors with exactly 2 seats
"SS"
(should return 1)
Solution: The recursive approach naturally handles these cases:
- Odd seats: The base case will never have
seats_in_section == 2
for the final section - No seats: The function returns 0 as no valid division exists
- Exactly 2 seats: Returns 1 as there's exactly one valid way (no internal dividers)
4. Memory Limit Issues with Large Inputs
Without clearing the cache after computation, the memoization decorator can consume significant memory for large inputs.
Best Practice:
result = count_divisions(0, 0) count_divisions.cache_clear() # Clear cache to free memory return result
5. Integer Overflow Without Modulo
Forgetting to apply modulo at each step can cause integer overflow for large results.
Wrong:
ways = ways + count_divisions(index + 1, 0) # May overflow
Correct:
ways = (ways + count_divisions(index + 1, 0)) % MOD
Which of the following array represent a max heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
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!