Back to blog
Nov 22, 2024
5 min read

Dynamic Programming: Counting Possible Bus Routes

Solving the bus route counting problem using dynamic programming

Problem Description

Imagine a city divided into a grid of districts. Each district has bus routes that can connect it to other districts, but with some constraints on how these routes can be established. The goal is to count the total number of possible ways to travel from one corner of the city to another using these bus routes.

Problem Constraints

  1. The city is represented as an n × n grid of districts
  2. From each district, buses can only travel:
    • To the district directly to the right
    • To the district directly below
  3. Each district has at most 5 possible outgoing routes
  4. We need to count all possible ways to reach the bottom-right district from the top-left district

Example

For a 4 × 4 city grid, some sample routes might look like:

Route 1: [1,1] → [1,2] → [2,2] → [2,3] → [3,3] → [4,4]
Route 2: [1,1] → [2,1] → [2,2] → [3,2] → [3,3] → [4,3] → [4,4]
...and so on

Understanding the Solution

Dynamic Programming Approach

This problem is perfect for dynamic programming because:

  • It has optimal substructure (ways to reach any district include ways to reach districts before it)
  • It has overlapping subproblems (many routes share common sub-routes)

Let’s break down the solution step by step.

1. State Definition

Let m[i][j] represent the number of possible ways to reach district (i,j) from the starting district (1,1).

2. Recurrence Relation

For each district (i,j), we can reach it from two possible previous districts:

m[i][j]=m[i1][j]+m[i][j1]m[i][j] = m[i-1][j] + m[i][j-1]

This formula captures that:

  • We can arrive from the district above (m[i-1][j])
  • We can arrive from the district to the left (m[i][j-1])
  • Total ways is the sum of ways from both possible previous districts

3. Base Cases

  • m[1][1] = 1 (starting point)
  • m[i][1] = 1 for i>1 (only one way to reach first column)
  • m[1][j] = 1 for j>1 (only one way to reach first row)

Solution Implementation

def count_possible_routes(n):
    """
    Count the number of possible bus routes from top-left to bottom-right
    in an n × n city grid.
    
    Args:
        n (int): Size of the city grid
        
    Returns:
        int: Total number of possible routes
    """
    # Initialize the DP table
    m = [[0 for _ in range(n+1)] for _ in range(n+1)]
    
    # Initialize base cases
    for i in range(1, n+1):
        m[i][1] = 1  # First column
    for j in range(1, n+1):
        m[1][j] = 1  # First row
        
    # Fill DP table using recurrence relation
    for i in range(2, n+1):
        for j in range(2, n+1):
            m[i][j] = m[i-1][j] + m[i][j-1]
            
    return m[n][n]

def visualize_dp_table(m, n):
    """
    Visualize the DP table showing number of ways to reach each district.
    
    Args:
        m (List[List[int]]): The filled DP table
        n (int): Size of the grid
    """
    print("\nDP Table (number of ways to reach each district):")
    for i in range(1, n+1):
        for j in range(1, n+1):
            print(f"{m[i][j]:6d}", end=" ")
        print()

# Example usage
def main():
    n = 4  # 4×4 city grid
    m = [[0 for _ in range(n+1)] for _ in range(n+1)]
    
    # Initialize and fill the table
    for i in range(1, n+1):
        m[i][1] = 1
    for j in range(1, n+1):
        m[1][j] = 1
        
    for i in range(2, n+1):
        for j in range(2, n+1):
            m[i][j] = m[i-1][j] + m[i][j-1]
    
    print(f"Total number of possible routes: {m[n][n]}")
    visualize_dp_table(m, n)

if __name__ == "__main__":
    main()

Complexity Analysis

Time Complexity

  • O(n2)O(n^2) where n is the size of the grid
  • We visit each district exactly once
  • At each district, we perform constant time operations (addition)

Space Complexity

  • O(n2)O(n^2) for the DP table
  • We need to store the number of ways for each district in the grid

Mathematical Insight

An interesting observation is that this problem is related to the mathematical concept of “lattice paths”. The number of possible routes in an n × n grid is actually equal to the central binomial coefficient:

(2n2n1){2n-2 \choose n-1}

This gives us a direct mathematical formula for the solution, though the dynamic programming approach is often more practical for implementation and can be more easily modified to handle additional constraints.