【LeetCode ブラッシングノート】動的プログラミング集

1. 基本的な動的計画法: 1 次元

  • 表現すべきものを明確にするdp[i](二次元の場合dp[i][j]
  • dp[i]とのdp[i-1]関係によると状態遷移方程式を求める
  • 次のような初期条件を決定します。dp[0]
  • dp 配列を定義します。dp 配列の各要素はサブ問題に対応します

参照リンク:問題を解決するためのグラフィカルな動的プログラミングの 4 つのステップ (C++/Java/Python) - Robbery

70. 階段を上る

トピックの説明

あなたが階段を上っているとします。n建物の上に上がるには階段が必要です。

一度に登ったり1、。2建物の最上階に行く方法は何通りありますか?

入出力

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

答え

動的計画法は、主に状態

dp[i]最初のステップに進む方法の数を表す配列を定義しますi。これは、毎回 1 つまたは 2 つのステップに進むことができるため、最初のステップは1 つのステップと2 つのステップiを取ることができるため、状態遷移方程式、ここでi-1i-2dp[i] = dp[i-1] + dp[1-2]dp[1] = 1, dp[2] = 2

最初の方法では、配列を使用して、それぞれに必要なステップ数を格納します。

class Solution {
public:
    int climbStairs(int n) {
        //注意边界条件,类似于斐波那契数列
        //初始值dp[1] = 1, dp[2] = 2
        if (n <= 2)
            return n;
        vector<int> dp(n + 1, 1);
        for (int i = 2; i <= n; ++i) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

2 つ目は、スペース圧縮を実行することです。dp[i]のみに関連するため、2 つの変数に格納できます。dp[i-1]dp[i-2]

class Solution {
public:
    int climbStairs(int n) {
        //注意边界条件,类似于斐波那契数列
        //初始值dp[1] = 1, dp[2] = 2
        if (n <= 2)
            return n;
        int pre1 = 1; //i-2,初始值对应dp[1]
        int pre2 = 2; //i-1,初始值对应dp[2]
        int res = 0;
        for (int i = 2; i < n; ++i) {
            res = pre1 + pre2;
            pre1 = pre2;
            pre2 = res;
        }
        return res;
    }
};

198.強盗

トピックの説明

あなたは通りに沿って家を盗むことを計画しているプロの泥棒です。各部屋には一定量の現金が隠されています. 盗難に影響する唯一の制限要因は、隣接する家に相互接続された盗難防止システムが装備されていることです. 隣接する2つの家が同じ夜に泥棒によって侵入された場合, システムは、自動的に警察を呼びます。 .

各家に保管されている量を表す非負の整数の配列を指定して、アラームをトリガーせずに一晩で盗むことができる最大量を計算します。

入出力

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

答え

状態遷移方程式を見つけ、配列 を定義しますdpdp[i]これは、最初の家が強盗されたときに強盗できる強盗の最大数を表しますiためにdp[i]

  • 家が強盗されていない場合、累積額はdp[i-1]
  • この家が強盗された場合、累積額はdp[i-2]に現在の家を加えたものになります。

したがって、状態遷移方程式はdp[i] = max(dp[i - 1], nums[i] + dp[i-2])

dp 配列を定義する

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.empty())
            return 0;
        if (nums.size() == 1)
            return nums[0];
        vector<int> dp(nums.size() + 1, 0);
        dp[1] = nums[0];//第一个房子可以抢到的钱是nums[0]
        for (int i = 2; i <= nums.size(); ++i) {
            //第i个房子对应的是i-1,dp的下标从1开始,nums的下标从0开始
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[nums.size()];
    }
};

記憶を単純化する

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.empty())
            return 0;
        if (nums.size() == 1)
            return nums[0];
        int pre1 = nums[0];//第一个房子能抢到的钱
        int pre0 = 0;//第0个房子能抢到的钱
        int cur;//从第二个房子开始
        for (int i = 1; i < nums.size(); i++) {
            cur = max(pre1, pre0 + nums[i]);
            pre0 = pre1;
            pre1 = cur;
        }
        return cur;
    }
};

121. 株の売買に最適な時期

トピックの説明

配列の価格を指定すると、その i 番目の要素の価格 [i] は、i 日目の特定の株式の価格を表します。

ある日に株式を購入し将来の別の日に売却することしか選択。得ることができる最大の利益を計算するアルゴリズムを設計します。

この取引から得られる最大利益を返します。利益が出ない場合は 0 を返します。

入出力

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。。

答え

一次元動的計画法dp[i]

dp過去 i 日間の最大利益を表す配列を定義しdp[i]、過去 i 日間の最低価格を記録します
dp [ i ] = max ( dp [ i − 1 ] , dp [ i ] − minprice ) dp[i] = max(dp[i -1], dp[i] - 最低価格)d p []=max ( d p [ i _1 ] d p []最小価格)コード_ _ _

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n <= 1)
            return 0;
        int minprice = prices[0];
        int pre1 = 0;
        int profit;
        //dp是从第二天开始,但是对应prices数组,下标是1
        for (int i = 2; i <= n; i++) {
            if (prices[i - 1] < minprice)
                minprice = prices[i - 1];
            profit = max(pre1, prices[i - 1] - minprice);
            pre1 = profit;
        }
        return profit;
    }
};

53. 最大部分配列合計

トピックの説明

integers の配列が与えられた場合nums、合計が最大になる連続部分配列 (部分配列には少なくとも 1 つの要素が含まれる) を見つけ、その最大合計を返します。

サブ配列は、配列の連続部分です。

入出力

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

答え

古典的な動的計画法の問題 (「残効なし」の理解) - サブアレイの最大和

dp[i]nums[i] :で終わる連続した部分配列の最大合計を表します。

つまり、nums[i]選択する必要があります

次に、2 つのケースがあります。dp[i-1]それが負の数である場合、つまり、nums[i-1]最後の連続するサブ配列の最大合計がまだ負の数である場合、nums[i]追加できずdp[i-1]、この時点で直接再起動します。

dp[i-1]負でない場合、それを dp [ i ] = { dp [ i − 1 ] + nums [ i ] で終わる部分配列の合計に追加します ( dp [ i − 1 ] >= 0 ) nums [ i ] のnums[i]場合
( dp [ i − 1 ] < 0 ) dp[i] = \begin{cases} dp[i-1]+nums[i], &if (dp[i-1]>=0) \\ nums[ i] ,&if (dp[i-1]<0) \end{cases}d p []={ d p [ i1 ]+n u m s [ i ] ,n u m s [ i ] ,f ( d p [1 ]>=0 )f ( d p [1 ]<0 )
あるいは直接
dp [ i ] = max ( dp [ i − 1 ] + nums [ i ] , nums [ i ] ) dp[i] = max(dp[i-1]+nums[i], nums[i])d p []=max ( d p [ i _1 ]+n u m s [ i ] ,[ i ] ) _ _

同時に、戻り値は ではなくdp[n]dp配列内の最大値であることに注意してください.スペースを最適化するために、各ループで最大値を保持する必要があります

コード

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0;
        int res = nums[0];
        for (int i = 0; i < nums.size(); i++) {
            pre = max(pre + nums[i], nums[i]);
            res = max(pre, res);
        }
        return res;
    }
};

413. 数列除算

トピックの説明

シーケンスに少なくとも 3 つの要素があり、隣接する 2 つの要素の差が同じである場合、そのシーケンスは算術シーケンスと呼ばれます。

たとえば、[1,3,5,7,9]、[7,7,7,7]、[3,-1,-5,-9] は等差数列です。
整数配列 nums を指定すると、配列 nums 内の算術配列である部分配列の数を返します。

サブ配列は、配列内の連続したシーケンスです。

入出力

输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。

答え

状態を定義する:算術数列部分配列の先頭から末尾までの数をdp[i]示すnums[0]nums[i]

最小の算術数列には 3 つの要素が必要なので、状態遷移式
dp [ i ] = dp [ i − 1 ] + 1 , if ( nums [ i ] − nums [ i − 1 ] = nums [ i − 1 ] − nums [ i − 2 ] ) dp[i] = dp[i -1] + 1,if (nums[i] - nums[i-1] = nums[i-1]-nums[i-2])d p []=d p [ i1 ]+1 i f ( n u m s [ i ]n u m s [ i1 ]=n u m s [ i1 ]n u m s [ i2 ])
上記の式は、算術シーケンスnums[i]できる末尾のすべての配列を結合して新しい算術シーケンスを形成できることを示しています. このとき、合計は 1 でありこの新しい配列が追加されます同時に合計する正しい dpi-1, i-2nums[i-1]nums[i]dp[i-1][i-2, i-1, i]

vector合計関数:accumulate

ヘッダーファイルは#include<numeric>sum = accumulate(nums.begin(),nums.end(), 0)(開始位置、終了位置、初期値)

コード

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int N = nums.size();
        if (N < 3)
            return 0;
        vector<int> dp(N, 0);
        for (int i = 2; i < N; i++) {
            if ((nums[i] - nums[i - 1]) == nums[i - 1] - nums[i - 2])
                dp[i] = dp[i - 1] + 1;
        }
        return accumulate(dp.begin(), dp.end(), 0);
    }
};

55.ジャンピングゲーム

トピックの説明

負でない整数の配列 nums を指定すると、最初は配列の最初のインデックスにいます。

配列内の各要素は、その位置でジャンプできる最大の長さを表します。

最後の添え字に到達できるかどうかを判断します。

入出力

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

答え

ジャンプできる最も遠い位置、つまり動的計画法を記録する

配列をトラバースし、到達可能な最遠距離を記録しますmax。最遠距離が添え字 i にある場合はmax<i、戻りますfalse

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int m = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (m < i)
                return false;
            m = max(m, nums[i] + i);
        }
        return true;
    }
};

方法 2: 動的計画法

状態はdp[i]、i から始まると最も遠い場所にジャンプできることを示します

状態遷移式

dp[i-1]未満の場合i、つまり、前のものにまったく到達できない場合はi、戻りますfalse

否则dp [ i ] = max ( dp [ i − 1 ] , i + nums [ i ] ) dp[i]=max(dp[i-1], i+nums[i])d p []=max ( d p [ i _1 ] +[ i ] ) _ _

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int pre = 0;
        for (int i = 0; i < n; i++) {
            if (pre < i)
                return false;
            pre = max(pre, i + nums[i]);
        }
        return true;
    }
};

2. 基本的な動的計画法: 2 次元

64. パス合計の最小値

トピックの説明

負でない整数の mxn グリッドが与えられた場合、パス上の数値の合計を最小化する左上隅から右下隅へのパスを見つけます。

注: 一度に 1 ステップずつ下または右に移動してください。

入出力

【外部リンクの画像転送に失敗しました。ソースサイトに盗難防止リンクの仕組みがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-SiRij4bH-1658197012984) (https://raw.githubusercontent.com/Jolene- hust/Jolene/main/img/202207181335179.png)]

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

答え

状態を定義する:左上隅からその位置までの最適パスの数の合計をdp[i][j]表す2 次元配列[i, j]

状態遷移方程式を定義する: ポイントの場合[i, j][i-1,j]右に移動するか[i,j-1]下に移動するため、
dp [ i ] [ j ] = min ( dp [ i − 1 ] [ j ] , dp [ i ] [ j − 1 ] ) + nums [ i ] [ j ] dp[i][j] = min(dp[i-1][j],dp[i][j-1])+nums[i][j]d p [ i ] [ j ]=( d p [ i1 ] [ j ] d p [ i ] [ j1 ])+n u m s [ i ] [ j ]
は境界条件に注意する必要があり、変換の動きは上と左の 1 種類だけです。

コード

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int col = grid.size();//多少行
        int row = grid[0].size();//多少列
        vector<vector<int>> dp(col, vector<int>(row, 0));
        for (int i = 0; i < col; i++) {
            for (int j = 0; j < row; j++) {
                //注意边界条件
                //起点
                if (i == 0 && j == 0)
                    dp[i][j] = grid[i][j];
                    //最上面的行,只能向右
                else if (i == 0)
                    dp[i][j] = dp[i][j - 1] + grid[i][j];
                else if (j == 0)
                    dp[i][j] = dp[i - 1][j] + grid[i][j];
                else
                    dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[col - 1][row - 1];
    }
};

高度な最適化スペース

状態の値dp[i][j]は、列方向に対応する 1 つの次元に圧縮して、その上と左にのみ関連します。

行 1については、列 1iにトラバースするとき、列 1 は既に更新されているため左側の値である の値を表し、更新されるために、現在格納され行 1 で計算されるため、の、上記の値です。j j-1dp[j-1]dp[i][j-1] dp[j] dp[j] i-1dp[i-1][j]

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int col = grid.size();//多少行
        int row = grid[0].size();//多少列
        vector<int> dp(row, 0);
        for (int i = 0; i < col; i++) {
            for (int j = 0; j < row; j++) {
                if (i == 0 && j == 0)
                    dp[j] = grid[i][j];
                else if (i == 0)
                    dp[j] = dp[j - 1] + grid[i][j];
                else if (j == 0)
                    dp[j] = dp[j] + grid[i][j];
                else
                    dp[j] = min(dp[j - 1], dp[j]) + grid[i][j];
            }
        }
        return dp[row - 1];
    }
};

542.01 マトリックス

トピックの説明

0 と 1 で構成されるマトリックス マットが与えられた場合、同じサイズのマトリックスを出力してください。各グリッドは、マット内の対応する位置要素から最も近い 0 までの距離です。

隣接する 2 つの要素間の距離は 1 です。

入出力

画像-20220718155427617

输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]

画像-20220718155502181

输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]

答え

4方向なので動的計画法を2回使います.1回目は左上から右下への移動で右と下への移動のみ、2回目は右下から左上への移動で左への移動のみです.そして上向き

状態を定義する:dp[i][j]現在のポイント [i, j] から最も近い 0 までの距離を示します

状態遷移方程式を定義する

  • 左上から右下への動的計画法の場合、現在のポイントは左または上からのみ見ることができます

もしmat[i][j]==0、それからdp[i][j]=0、そうでなければdp[i][j]=min(dp[i][j-1], dp[i-1][j])+1

  • 右下から左上への動的計画法の場合、現在のポイントは右または下からのみ見ることができます

もしmat[i][j]==0、それからdp[i][j]=0、そうでなければdp[i][j]=min(dp[i+1][j], dp[i][j+1])+1

int の最大値は INT_MAX です

dp 配列の初期値が最大値であるため、配列が範囲外であることに注意してください。

コード

class Solution {
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        int col = mat.size();
        int row = mat[0].size();
        vector<vector<int>> dp(col, vector<int>(row, INT_MAX - 1));
        //先从左上角到右下角
        for (int i = 0; i < col; ++i) {
            for (int j = 0; j < row; ++j) {
                if (mat[i][j] == 0) {
                    dp[i][j] = 0;
                    continue;
                }
                if (i == 0 && j == 0);
                else if (i == 0)
                    dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
                else if (j == 0)
                    dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1);
                else
                    dp[i][j] = min(dp[i][j], min(dp[i][j - 1] + 1, dp[i - 1][j] + 1));
            }
        }
        //从右下角到左上角
        for (int i = col - 1; i >= 0; i--) {
            for (int j = row - 1; j >= 0; j--) {
                if (mat[i][j] == 0) {
                    dp[i][j] = 0;
                    continue;
                }
                if (i == col - 1 && j == row - 1);
                else if (i == col - 1)
                    dp[i][j] = min(dp[i][j], dp[i][j + 1] + 1);
                else if (j == row - 1)
                    dp[i][j] = min(dp[i][j], dp[i + 1][j] + 1);
                else
                    dp[i][j] = min(dp[i][j], min(dp[i][j + 1] + 1, dp[i + 1][j] + 1));
            }
        }
        return dp;
    }
};

62. 異なる道

トピックの説明

ロボットは、mxn グリッドの左上隅に配置されます (開始点は、下の図で「開始」とマークされています)。

ロボットは一度に 1 ステップだけ下または右に移動できます。ロボットは、グリッドの右下隅に到達しようとします (下の画像で「終了」とマークされています)。

全部でいくつの異なる道があるか尋ねる

入出力

画像-20220719091339416

输入:m = 3, n = 7
输出:28
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

答え

状態を定義する:から へのパスがいくつあるかを示しますdp[i][j][0, 0] [i, j]

状態遷移方程式を定義する[0, 0] :到着から[i, j]、下または[i-1, j]から移動するので、 dp [ i ] [ j ] = dp [ i − 1 ] [ j ] + dp [ i ] [ j − 1 ] dp[i][j ]=dp[ i-1][j]+dp[i][j-1][i, j-1]
d p [ i ] [ j ]=d p [ i1 ] [ j ]+d p [ i ] [ j1 ]
一般に 2 次元の境界条件を考慮することに注意してください。

if(i == 0 && j == 0)
    {}
else if(i == 0)
    {}
else if(j == 0)
	{}
else
	{}

コード

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i == 0 && j == 0)
                    dp[i][j] = 1;
                else if (i == 0)
                    dp[i][j] = dp[i][j - 1];
                else if (j == 0)
                    dp[i][j] = dp[i - 1][j];
                else
                    dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
            }
        }
        return dp[m - 1][n - 1];
    }
};

画像-20220719100547432

スペースの最適化

状態を定義する: dp[j]、行 i の場合、dp[j-1] は行 i の列 j-1 を表し、dp[j] は行 i-1 の列 j を表します

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> dp(n, 0);
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i == 0 && j == 0)
                    dp[j] = 1;
                else if (i == 0)
                    dp[j] = dp[j - 1];
                else if (j == 0);
                else
                    dp[j] = dp[j - 1] + dp[j];
            }
        }
        return dp[n - 1];
    }
};

画像-20220719100619668

おすすめ

転載: blog.csdn.net/xqh_Jolene/article/details/125866398