二次元グリッドの最小経路を求める動的計画法の実装

問題の概要

左上のセル (0, 0) から右下のセル (m-1, n-1) まで、右または下のみ移動して到達する経路のうち、通過したセルの値の合計が最小となるものを求めます。

DP の定式化

dp[i][j] を「(i, j) まで到達する経路の最小値」とします。到達できる移元は上方向か左方向のみなので、次の漸化式が成り立ちます。

dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]

初期条件は、最初の行・列は一直線にしか進めないため累積和で埋めます。

実装例 1:メモ化再帰

トップダウンで同じ計算を防ぎます。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int h = grid.size();
        int w = grid[0].size();
        memo.assign(h, vector<int>(w, -1));
        this->g = &grid;
        return dfs(0, 0, h, w);
    }

private:
    vector<vector<int>> memo;
    vector<vector<int>>* g;
    static constexpr int INF = 1e9;

    int dfs(int y, int x, int h, int w) {
        if (y >= h || x >= w) return INF;
        int& res = memo[y][x];
        if (res != -1) return res;
        if (y == h - 1 && x == w - 1) return res = (*g)[y][x];

        int down = dfs(y + 1, x, h, w);
        int right = dfs(y, x + 1, h, w);
        return res = min(down, right) + (*g)[y][x];
    }
};

実装例 2:2 次元 DP

初期行・列を先に確定させ、残りを反復的に埋めます。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows = grid.size();
        int cols = grid[0].size();

        vector<vector<int>> cost(rows, vector<int>(cols, 0));
        cost[0][0] = grid[0][0];

        for (int c = 1; c < cols; ++c) {
            cost[0][c] = cost[0][c - 1] + grid[0][c];
        }
        for (int r = 1; r < rows; ++r) {
            cost[r][0] = cost[r - 1][0] + grid[r][0];
        }

        for (int r = 1; r < rows; ++r) {
            for (int c = 1; c < cols; ++c) {
                cost[r][c] = min(cost[r - 1][c], cost[r][c - 1]) + grid[r][c];
            }
        }

        return cost[rows - 1][cols - 1];
    }
};

実装例 3:1 次元 DP(空間圧縮)

各行の更新は左隣の値と前回の値だけで足りるため、配列を 1 本に削減できます。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows = grid.size();
        int cols = grid[0].size();

        vector<int> cur(cols, 0);
        cur[0] = grid[0][0];
        for (int c = 1; c < cols; ++c) {
            cur[c] = cur[c - 1] + grid[0][c];
        }

        for (int r = 1; r < rows; ++r) {
            cur[0] += grid[r][0];
            for (int c = 1; c < cols; ++c) {
                cur[c] = min(cur[c - 1], cur[c]) + grid[r][c];
            }
        }

        return cur[cols - 1];
    }
};

計算量

  • 時間計算量:O(m × n)
  • 空間計算量:
    • メモ化再帰・2 次元 DP:O(m × n)
    • 1 次元 DP:O(n)

タグ: LeetCode 動的計画法 C++ 最小経路 DPの空間圧縮

10月12日 23:10 投稿