Facebook Pixel

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 and i)
  • 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 corridor
  • k: number of seats in the current section being formed

The algorithm works by:

  1. Traversing each position and counting seats in the current section
  2. 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)
  3. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. If we've reached the end of the corridor, we check if k = 2 (valid section)
  2. If we see a seat, increment k
  3. If k > 2, this path is invalid (return 0)
  4. Otherwise, we always try continuing without a divider: dfs(i + 1, k)
  5. 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 string
  • k 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:

  1. First, check if the current position has a seat: if corridor[i] == 'S', increment k by 1
  2. If k > 2, the current section has too many seats, making this path invalid - return 0
  3. 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)

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 Evaluator

Example 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:

  1. Position 0 ('S'): dfs(0, 0)

    • Current char is 'S', so k becomes 1
    • k ≤ 2, so we continue: dfs(1, 1)
  2. 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)
  3. 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)

    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...
  4. 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:

  1. Every section has exactly 2 seats
  2. 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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More