Unique Paths II

障害物のあるグリッドでの経路数計算をDynamic Programmingで効率的に解く

アルゴリズム概要

特徴

  • Dynamic Programming(動的計画法)を使用
  • O(m×n)の時間計算量で効率的
  • 障害物を考慮した経路数計算
  • 空間最適化で O(n) メモリ使用

適用場面

  • ロボット・パス探索問題
  • 組み合わせ数学の応用
  • 制約のある経路計数問題
  • グリッドベースのゲーム

Example 1

Input: [[0,0,0],[0,1,0],[0,0,0]]
Output: 2

3×3グリッドで中央に障害物がある場合。2通りの経路が存在。

Example 2

Input: [[0,1],[0,0]]
Output: 1

2×2グリッドで右上に障害物がある場合。1通りの経路のみ。

インタラクティブ解説

Step 1: 初期化

DPテーブルを初期化し、開始点を設定します

Step 2: 最初の行

最初の行の経路数を計算します

Step 3: 最初の列

最初の列の経路数を計算します

Step 4: DP計算

各セルの経路数を計算します

Step 5: 結果取得

右下の値が答えです

可視化エリア

初期化: DPテーブル作成

1
0
0
0
X
0
0
0
0

実装コード

Python Solution (LeetCode Format)

from typing import List

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        """
        障害物のあるグリッドでのユニークパス数計算

        Args:
            obstacleGrid: 障害物グリッド (0: 通路, 1: 障害物)

        Returns:
            ユニークパス数

        Time Complexity: O(m*n)
        Space Complexity: O(n) - 1D DP最適化版
        """
        if not obstacleGrid or not obstacleGrid[0] or obstacleGrid[0][0] == 1:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])

        # 終点が障害物の場合
        if obstacleGrid[m - 1][n - 1] == 1:
            return 0

        # 1D DP(空間最適化)
        dp = [0] * n
        dp[0] = 1  # スタート地点

        for i in range(m):
            # 各行の最初の列
            if obstacleGrid[i][0] == 1:
                dp[0] = 0

            # 残りの列
            for j in range(1, n):
                dp[j] = 0 if obstacleGrid[i][j] == 1 else dp[j] + dp[j - 1]

        return dp[n - 1]


# 可読性重視の2D DP版(参考実装)
class SolutionReadable:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        """
        2D DP実装(理解しやすい版)

        Time Complexity: O(m*n)
        Space Complexity: O(m*n)
        """
        if not obstacleGrid or obstacleGrid[0][0] == 1:
            return 0

        m, n = len(obstacleGrid), len(obstacleGrid[0])

        # 2D DPテーブル初期化
        dp = [[0] * n for _ in range(m)]
        dp[0][0] = 1

        # 最初の行を初期化
        for j in range(1, n):
            dp[0][j] = 0 if obstacleGrid[0][j] == 1 else dp[0][j - 1]

        # 最初の列を初期化
        for i in range(1, m):
            dp[i][0] = 0 if obstacleGrid[i][0] == 1 else dp[i - 1][0]

        # メインのDP計算
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 1:
                    dp[i][j] = 0
                else:
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

        return dp[m - 1][n - 1]

重要ポイント

  • 1D DP配列で空間効率化
  • インプレース更新で最適化
  • 障害物チェックを統合

最適化のコツ

  • 2D→1D配列で省メモリ
  • エッジケースの早期判定
  • 三項演算子で簡潔に

注意点

  • 開始・終了点の障害物チェック
  • 空のグリッドの処理
  • インプレース更新の順序

アルゴリズムフロー

START Input: obstacleGrid Input Valid? Empty or Start blocked? No Return 0 Yes Initialize DP dp[0] = 1, others = 0 For each row i i = 0 to m-1 First Column Obstacle? Yes dp[0] = 0 No For each column j dp[j] = obstacle? 0 : dp[j] + dp[j-1] All rows done Return dp[n-1]

計算量解析

時間計算量

O(m × n)

各セルを1回ずつ処理

グリッド走査 O(m×n)
各セル計算 O(1)
初期化 O(n)

空間計算量

O(n)

1D DP配列のみ使用

DP配列 O(n)
変数 O(1)
入力データ O(m×n)

手法比較

手法 時間計算量 空間計算量 可読性 実装難易度
1D DP (採用) O(m×n) O(n) ★★★☆☆ ★★★☆☆
2D DP O(m×n) O(m×n) ★★★★★ ★★☆☆☆
再帰+メモ化 O(m×n) O(m×n) ★★★★☆ ★★★★☆
DFS/BFS O(2^(m+n)) O(m+n) ★★★☆☆ ★★★☆☆

1D DP採用理由

  • 空間効率: O(m×n) → O(n) に削減
  • 実行速度: メモリアクセスパターンが最適
  • キャッシュ効率: 連続したメモリアクセス
  • 拡張性: 大きなグリッドでも安定動作