DSA: Shortest Path with Constraints

7 min read5.5k

Solution to Last Week's Challenge

Last week's problem was: DSA: Tree DP Explained

The optimal solution uses dsa: tree dp explained pattern with careful state management.

Complexity Analysis:

  • Time Complexity: O(n)
  • Space Complexity: O(1)

In technical interviews at top-tier companies like Google or Uber, standard graph traversal problems are rarely as simple as finding the shortest path between two nodes. Interviewers often introduce constraints—such as a limited number of stops, a specific budget, or the requirement to visit certain milestones. These constraints transform a basic Dijkstra's or Breadth-First Search (BFS) problem into a challenge of state-space search. Understanding how to augment your graph state is the difference between a brute-force approach that hits a Time Limit Exceeded (TLE) error and an optimized solution that scales.

In real-world systems, shortest path algorithms with constraints are the backbone of logistics and navigation. When a delivery app calculates the best route for a driver, it isn't just looking for the shortest distance; it is balancing fuel efficiency, delivery windows, and vehicle capacity. Mastering these patterns allows you to model complex, multi-dimensional problems into a graph structure that standard algorithms can solve efficiently.

Theory

The core concept in solving shortest path problems with constraints is State Augmentation. In a standard Dijkstra algorithm, the state is simply the current node. When constraints are added, the state must expand to include the current value of the constraint. For example, if you are limited by $K$ stops, your state becomes (node, stops_remaining).

When we augment the state, we are essentially creating a layered graph. If there are $N$ nodes and the constraint can take $K$ values, the new state space has $N \times K$ nodes. We then apply standard algorithms like BFS (for unweighted edges) or Dijkstra (for weighted edges) on this expanded graph.

Complexity Analysis:

  • Standard Dijkstra: $O(E \log V)$
  • Dijkstra with Constraints: $O((E \times K) \log (V \times K))$, where $K$ is the constraint limit.
  • Space Complexity: $O(V \times K)$ to store the distances for each state.

Pattern Visualization

To solve these problems, you must decide which algorithm to use based on the edge weights and the nature of the constraint. Use the following logic to determine your approach:

Implementation

Let's look at a classic interview problem: Cheapest Flights Within K Stops. We need to find the cheapest price from a source to a destination with at most $K$ stops. This is a weighted graph (prices) with a constraint (stops).

python
import heapq
from collections import defaultdict

def findCheapestPrice(n, flights, src, dst, k):
    # Build adjacency list: adj[u] = [(v, weight), ...]
    adj = defaultdict(list)
    for u, v, w in flights:
        adj[u].append((v, w))
    
    # Priority Queue stores: (total_cost, stops_made, current_node)
    # We use stops_made because it is easier to compare against k
    pq = [(0, 0, src)]
    
    # track_stops[node] stores the minimum stops taken to reach 'node' 
    # with the current minimum cost. This helps in pruning.
    track_stops = [float('inf')] * n
    
    while pq:
        cost, stops, u = heapq.heappop(pq)
        
        # If we reached destination, this is the cheapest due to Min-Heap
        if u == dst:
            return cost
        
        # If we have exceeded stops or found a path to 'u' with fewer stops
        # at a lower or equal cost previously, skip this path.
        if stops > k or stops >= track_stops[u]:
            continue
            
        track_stops[u] = stops
        
        for v, weight in adj[u]:
            heapq.heappush(pq, (cost + weight, stops + 1, v))
            
    return -1

# Time Complexity: O(E * K * log(V * K))
# Space Complexity: O(V * K)

Complexity Analysis Table

AlgorithmBest Use CaseTime ComplexitySpace Complexity
BFS + StateUnweighted edges, $K$ constraints$O(V \times K + E \times K)$$O(V \times K)$
Dijkstra + StateWeighted edges, no negative cycles$O(EK \log(VK))$$O(VK)$
Bellman-FordNegative weights allowed$O(K \times E)$$O(V)$
Dynamic ProgrammingDirected Acyclic Graphs (DAG)$O(V + E)$$O(V)$

Common Patterns

1. The Bitmask Constraint

When you must visit specific "checkpoints" or collect "keys," use a bitmask to represent the set of items collected.

python
# State: (row, col, mask)
# If mask == (1 << total_keys) - 1, we found all keys
state = (r, c, mask | (1 << key_id))

2. The "At Most K" Constraint

This is usually handled by adding a dimension to the distance array or by pruning paths in Dijkstra.

python
# Instead of dist[v], use:
if cost < dist[v][stops]:
    dist[v][stops] = cost
    heapq.heappush(pq, (cost, stops + 1, v))

Practice Problems

The following chart maps common problems by their difficulty and how often they appear in FAANG interviews.

This Week's Interview Challenge

Problem: The Oxygen-Limited Dungeon

You are in a 2D grid dungeon. Some cells are walls (1), and some are empty paths (0). You start at the top-left corner (0, 0) and want to reach the bottom-right corner (rows-1, cols-1). However, you have a limited oxygen tank that allows you to pass through at most K wall cells by breaking them.

Find the minimum number of steps to reach the destination. If it is impossible, return -1.

Example 1:

  • Input: grid = [[0,0,0],[1,1,0],[0,0,0],[0,1,1],[0,0,0]], k = 1
  • Output: 6
  • Explanation: You can remove one wall at (1,0) or (1,1) to create a path of length 6.

Example 2:

  • Input: grid = [[0,1,1],[1,1,1],[1,0,0]], k = 1
  • Output: -1

Constraints:

  • m == grid.length, n == grid[i].length
  • 1 <= m, n <= 40
  • 1 <= k <= m * n

Hints:

  1. Since the step cost is uniform (1 per move), which algorithm is more efficient than Dijkstra?
  2. How can you incorporate the "oxygen" (walls broken) into your BFS state?
  3. What should you store in your visited set to avoid redundant processing?
  4. If you reach a cell with more oxygen remaining than a previous visit, is it worth re-exploring?

Template:

python
def shortestPath(grid, k):
    rows, cols = len(grid), len(grid[0])
    # Your code here
    pass

# Submit your solution and check back next week for the detailed answer!

Conclusion

Mastering shortest path problems with constraints requires a shift in perspective. You are no longer just navigating a physical map; you are navigating a "state map." By augmenting your state and choosing the right algorithm (BFS for steps, Dijkstra for costs), you can solve a wide array of complex problems. In interviews, always start by identifying the edge weights and the specific constraints to determine the dimensions of your state space.

https://learning.oreilly.com/library/view/introduction-to-algorithms/9780262046305/ https://visualgo.net/en/sssp https://leetcode.com/discuss/general-discussion/456910/introduction-to-dijkstras-algorithm