2147. Number of Ways to Divide a Long Corridor - Explanation
Description
The string corridor describes a row of seats (S) and plants (P). Its two ends are already closed by dividers. You may add at most one divider in any gap between adjacent characters. Count the ways to partition the entire row so that every section contains exactly two seats and any number of plants. Different sets of divider positions count as different ways. Return the count modulo 1000000007, or 0 if no valid partition exists.
Dashed lines mark possible divider positions between groups of two seats; choose one divider in each such gap.
Example 1:
Input: corridor = "SSPSS"
Output: 2Explanation: The two valid partitions are SS | PSS and SSP | SS. Each section has exactly two seats.
Example 2:
Input: corridor = "PSSP"
Output: 1Explanation: There are exactly two seats in the entire corridor, so the only valid choice is to add no divider.
Example 3:
Input: corridor = "SSS"
Output: 0Explanation: There are three seats. Sections of exactly two seats cannot cover an odd number of seats, so no partition is valid.
Constraints:
1 <= corridor.length <= 100000- Every character is
SorP.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Dynamic Programming - Used to count valid ways by tracking seat counts in each section
- Memoization - Caching subproblem results to avoid redundant computation
- Modular Arithmetic - Required for handling large results modulo 10^9+7
- Combinatorics - The optimal approach counts divider positions between seat pairs using multiplication
1. Dynamic Programming (Top-Down)
Intuition
We need to divide the corridor so each section has exactly two seats. The key insight is that after placing two seats in a section, we have a choice: place the divider immediately after the second seat, or delay it through any number of plants. Each plant between the second seat of one section and the first seat of the next section represents a possible divider position.
Algorithm
- Define a recursive function
dfs(i, seats)whereiis the current index andseatscounts how many seats are in the current section (0,1, or2). - Base case: If we reach the end of the corridor, return
1if we have exactly2seats (valid section), otherwise0. - If we already have
2seats in the current section:- If the current position is a seat, we must start a new section (reset
seatsto1). - If it's a plant, we can either place the divider here (reset to
0) or continue without dividing (keep at2).
- If the current position is a seat, we must start a new section (reset
- If we have fewer than
2seats, count the seat if present and continue. - Use memoization to cache results.
class Solution:
def numberOfWays(self, corridor: str) -> int:
mod = 10**9 + 7
cache = [[-1] * 3 for i in range(len(corridor))] # (i, seats) -> count
def dfs(i, seats):
if i == len(corridor):
return 1 if seats == 2 else 0
if cache[i][seats] != -1:
return cache[i][seats]
res = 0
if seats == 2:
if corridor[i] == "S":
res = dfs(i + 1, 1)
else:
res = (dfs(i + 1, 0) + dfs(i + 1, 2)) % mod
else:
if corridor[i] == "S":
res = dfs(i + 1, seats + 1)
else:
res = dfs(i + 1, seats)
cache[i][seats] = res
return res
return dfs(0, 0)Time & Space Complexity
- Time complexity:
- Space complexity:
2. Dynamic Programming (Bottom-Up)
Intuition
We can convert the top-down approach to bottom-up by iterating from the end of the corridor to the beginning. For each position, we compute the number of ways based on whether we have 0, 1, or 2 seats in the current section.
Algorithm
- Create a DP table where
dp[i][seats]represents the number of ways to divide from indexionward withseatsseats in the current section. - Initialize
dp[n][2] = 1(reaching the end with exactly2seats is one valid way). - Iterate backward through the corridor:
- For each position and seat count, apply the same transitions as the top-down approach.
- Return
dp[0][0](starting from index0with0seats).
class Solution:
def numberOfWays(self, corridor: str) -> int:
MOD = 1000000007
n = len(corridor)
dp = [[0] * 3 for _ in range(n + 1)]
dp[n][2] = 1
for i in range(n - 1, -1, -1):
for seats in range(3):
if seats == 2:
if corridor[i] == 'S':
dp[i][seats] = dp[i + 1][1]
else:
dp[i][seats] = (dp[i + 1][0] + dp[i + 1][2]) % MOD
else:
if corridor[i] == 'S':
dp[i][seats] = dp[i + 1][seats + 1]
else:
dp[i][seats] = dp[i + 1][seats]
return dp[0][0]Time & Space Complexity
- Time complexity:
- Space complexity:
3. Dynamic Programming (Space Optimized)
Intuition
Since each row of the DP table only depends on the next row, we can reduce space by maintaining just a single array of size 3 (for the three possible seat counts).
Algorithm
- Initialize a DP array of size
3withdp[2] = 1. - Iterate backward through the corridor:
- Create a new DP array and compute transitions based on whether the current position is a seat or plant.
- Replace the old DP array with the new one.
- Return
dp[0].
class Solution:
def numberOfWays(self, corridor: str) -> int:
MOD = 1000000007
dp = [0, 0, 1]
for i in reversed(corridor):
new_dp = [0] * 3
for seats in range(3):
if seats == 2:
new_dp[seats] = dp[1] if i == 'S' else (dp[0] + dp[2]) % MOD
else:
new_dp[seats] = dp[seats + 1] if i == 'S' else dp[seats]
dp = new_dp
return dp[0]Time & Space Complexity
- Time complexity:
- Space complexity:
4. Combinatorics
Intuition
Instead of DP, we can think combinatorially. First, collect all seat positions. If the count isn't even or is less than 2, there's no valid way. Otherwise, between every pair of seats (the 2nd and 3rd, 4th and 5th, etc.), we can place the divider at any position including those occupied by plants. The number of choices at each gap is the distance between consecutive pairs of seats.
Algorithm
- Collect the indices of all seats in a list.
- If the seat count is less than
2or odd, return0. - For every pair of adjacent seat groups (positions
1and2, positions3and4, etc.), multiplyresby the gap sizeseats[i+1] - seats[i]. - Return
resmodulo10^9 + 7.
class Solution:
def numberOfWays(self, corridor: str) -> int:
mod = 10**9 + 7
seats = [i for i, c in enumerate(corridor) if c == "S"]
length = len(seats)
if length < 2 or length % 2 == 1:
return 0
res = 1
for i in range(1, length - 1, 2):
res = (res * (seats[i + 1] - seats[i])) % mod
return resTime & Space Complexity
- Time complexity:
- Space complexity:
5. Combinatorics (Optimal)
Intuition
We can optimize the combinatorics approach by not storing all seat positions. Instead, we track the count of seats and the position of the previous seat. Whenever we complete a pair (seat count becomes even and greater than 2), we multiply by the gap between the current seat and the previous one.
Algorithm
- Initialize
count = 0,res = 1, andprev = -1to track the position of the last seat. - Iterate through the corridor:
- When a seat is found, increment
count. - If
count > 2andcount % 2 == 1(meaning we just started a new section), multiplyresby the distance fromprev. - Update
prevto the current position.
- When a seat is found, increment
- If
count >= 2andcount % 2 == 0, returnres; otherwise return0.
class Solution:
def numberOfWays(self, corridor: str) -> int:
mod = 1_000_000_007
count = 0
res = 1
prev = -1
for i, c in enumerate(corridor):
if c == 'S':
count += 1
if count > 2 and count % 2 == 1:
res = (res * (i - prev)) % mod
prev = i
return res if count >= 2 and count % 2 == 0 else 0Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Not Handling Edge Cases for Seat Count
If the total number of seats is odd or less than 2, there is no valid way to divide the corridor. Forgetting to check these conditions at the start leads to incorrect results or division by zero errors when computing gap products.
Misunderstanding Where Dividers Can Be Placed
Dividers can only be placed between sections, specifically in the gaps between the second seat of one section and the first seat of the next section. The number of valid positions for each divider equals the number of positions between consecutive seat pairs (the gap includes all plants plus the implicit space between seats).
Off-by-One Errors in Gap Calculation
The gap between the 2nd seat of one section and the 1st seat of the next is seats[i+1] - seats[i], not seats[i+1] - seats[i] - 1 or seats[i+1] - seats[i] + 1. This represents the number of possible divider positions, including placing it immediately after the 2nd seat.
Forgetting to Apply Modulo During Multiplication
The product of gaps can become extremely large. Applying modulo only at the end causes integer overflow. You must apply modulo after each multiplication step to keep the intermediate results within bounds.
Incorrect Loop Bounds in Combinatorics Approach
When iterating through seat positions to compute gap products, the loop should process pairs (seats[1], seats[2]), (seats[3], seats[4]), etc. Starting at the wrong index or using incorrect step sizes skips gaps or includes invalid pairs.
Sign in to join the discussion