序文
この記事では、動的プログラミングのアイデアに関する 2 つのトピックについて説明します。
1. 動的計画法の 5 つのステップ
- 1. 状態表現の決定 (dp 配列の意味の決定)
- 2. 状態遷移方程式を求める(dpの漸化式を求める)
- 3. 初期化方法を決定します (フォームに正しく入力されていることを確認するために初期化します)。
- 4. 走査順序を決定する
- 5. 戻り値
2.ダンジョンゲーム
トピック分析
タイトルによると、各位置は次の 3 つの状況に対応しています:
(d[i][j] はタイトルで示されています)。
- 1.d[i][j] < 0: この位置には悪魔がいて、騎士は悪魔を倒すと失血します。
- 2.d[i][j] = 0: この位置は通路であり、血液を消費しません。
- 3.d[i][j] > 0 : この位置に血液袋があり、血液袋を食べることで血液を増やすことができます。
特定の地位に到達するには、騎士の血液量が 1 より大きいことを確認する必要があることはわかっています。そうでないと、たとえその地位が大きな血の袋であっても、騎士はそれを楽しむことができません。
アイデア: 動的プログラミング
- 1. 状態表現 (dp 配列の意味を決定する)
各 dp 質問の最初のポイントは多くの場合最も重要であり、経験と質問要件に基づいて決定する必要があります。
一般に、経験に基づいて、決定論的な状態表現には 2 つのタイプがあります。
(1) 位置 [i, j], ... で終了
(2) 位置 [i, j], ... で開始
私は多くの dp 質問を書いてきましたが、ほとんどの質問タイプは [i,j] の位置で終わります... そこで、最初のケースを最初に見てみましょう。
-
- (1) dp[i][j] は、始点から開始して [i, j] の終点に到達する、必要な最小健康値は dp[i][j] であることを意味します。次に 2 番目のステップを見てください
。再帰式はどうやって決めるのでしょうか?
下の図を見てください。
図の位置 [i, j] を例にとると、この位置に到達するには上または左から来る必要があるため、上と左の 2 つの状況を考慮する必要があります。
タイトルの意味通り、どの位置に到達するにも最低必要な血液量は1以上。
したがって、現在の位置だけでなく、上の位置と左の位置も考慮する必要があります。その後、上の位置に到達するためにさらに考慮する必要があります。つまり、[i, j] 位置に到達するには、次のことが必要です。以前のすべてのポジションを考慮します。そして、[i,j] の位置に到達した後、王女の位置に到達しない場合は、さらに次の位置を考慮する必要があります。そして、初期位置はタイトルが求めるものでもあるので、この状態はそれが機能しないことを意味します。
2 番目を見てみましょう。
- (1) dp[i][j] は、始点から開始して [i, j] の終点に到達する、必要な最小健康値は dp[i][j] であることを意味します。次に 2 番目のステップを見てください
-
- (2) dp[i][j] は、位置 [i,j] から開始して終点に到達する、必要な最小ヘルス値は dp[i][j] であることを意味します。これは機能します
。
右と下の位置だけを考慮する必要があります。
- (2) dp[i][j] は、位置 [i,j] から開始して終点に到達する、必要な最小ヘルス値は dp[i][j] であることを意味します。これは機能します
-
2. 状態遷移方程式 (再帰式の決定)
状態表現とトピックの説明によれば、[i,j]
位置の次のステップは、その位置に移動する[i+1,j]
か、[i,j+1]
その位置に移動する必要があることがわかります。したがって、これら 3 つの関係を考慮する必要があります。
ナイトが任意のポジションに到達するには、必要な最小 HP が 1 以上である必要があることがわかっています。
したがって、位置 [i, j] から開始し、位置[i+1,j]
またはまで歩いた後[i,j+1]
、2 つの位置のいずれかで、血液量が 1 以上でなければなりません。そうしないと、たとえこの位置に大きな血の袋があっても、騎士はそれを楽しむことができません。
次に、次のステップに進むことを検討するときは、まず自分のポジションの価値を考慮する必要があります。はい、+d[i][j]
次のポジションに進む場合、現在のポジションの最小ヘルス ポイントは最小ヘルス以上である必要があります。次の位置にスムーズに移動できるように、次の位置のポイントを確認します。
したがって、再帰式は次のとおりです。
dp[i][j] + d[i][j] >= dp[i+1][j]
またはdp[i][j] + d[i][j] >= dp[i][j+1]
シフトを実行して、次を取得します。
dp[i][j] >= dp[i+1][j] - d[i][j]
またはdp[i][j] >= dp[i][j+1] - d[i][j]
最小のヘルス値が必要であるため、等号が確立されると、それが最低のヘルス ポイントになります。
dp[i][j] = min(dp[i][j+1],dp[i+1][j]) - d[i][j]
ただし注意が必要で、d[i][j]が非常に大きな数の場合、d[i][j]を引いた後のdp[i][j]が非正の整数になる場合があります。 , これは、騎士が [i,j] の位置に行って電話が切られたことを意味しますが、まだ現在の位置で血液バッグを食べて次の位置にスムーズに移動できますが、これは明らかに非論理的です。
したがって、dp[i,j] が正ではない整数の場合、現在位置を通過するために必要な最小ヘルス値は 1 です。
つまり、 dp[i][j] = max(1,dp[i][j])
コードは以下のように表示されます:
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) - d[i][j];
dp[i][j] = max(1,dp[i][j]);
- 3. 初期化方法を決定する
上記を長い間分析した結果、騎士が王女の位置から開始して王女の位置に到着するとき、つまり [i+] の位置を知る必要があることがわかりました
。 1,j] および [i,j+1]。
したがって、初期化するときに、より適切に初期化するために、仮想空間の追加の行と列を開く必要があります。詳細は以下のとおりです。
この時点で注意する必要があるのは、
仮想空間の初期化では、通常の結果が影響を受けないようにする必要があるということです。
以前に書いた dp のトピックでは、仮想空間も 2 番目の
添字のマッピング関係に注意する必要がありますが、この質問では、仮想空間は添字に影響を与えないため、注意する必要はありません。
したがって、dp[m][n+1] と dp[m+1][n] の位置は 1 に初期化する必要があります。その位置では、最小ヘルス値が 1 以上でなければならないため、最小値は 1 以上である必要があります。値は 1 です。以前の経験によれば、他の値が影響を受けないように、他の位置は正の無限大に初期化する必要があります。
-
4. 走査順序
下から上に走査し、各行は右から左に走査する必要があります。 -
5. 戻り値
dp[0][0]の値を返します。
具体的なコードは以下の通り
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& d)
{
//1.确定dp数组的含义
//dp[i,j]表示:以[i,j]位置为起点,到达终点位置所需要的最小健康点数为dp[i][j]
//2.确定递推公式
//dp[i][j] = min(dp[i+1][j],dp[i][j+1]) - d[i][j]
//但是有可能算出来是非正整数,意味着走到这个位置已经是死了的状态,不符合题目要求。所以如果是非正整数的话,必须最小健康点数为1.
//dp[i][j] = max(1,dp[i][j]);
//3.确定如何初始化
//由于dp数组的含义是以[i,j]位置为起点的,所以我们必须从终点位置开始初始化,那么应该多开一行一列的虚拟空间来更好地初始化。
//注意:1.虚拟空间的初始化不能影响结果。
//这道题不同于之前的题,这里无需再注意下标的映射关系
//4.遍历顺序(从下到上遍历,每行从右往左遍历)
//5.返回值
//dp[0][0]
int m = d.size();
int n = d[0].size();
vector<vector<int>> dp(m+1,vector<int>(n+1,INT_MAX));
dp[m-1][n] = dp[m][n-1] = 1;
for(int i = m-1;i>=0;i--)
{
for(int j = n-1;j>=0;j--)
{
dp[i][j] = min(dp[i][j+1],dp[i+1][j]) - d[i][j];
//如果dp[i][j] <=0 ,则必须保证到[i,j]位置最低血量为1
dp[i][j] = max(1,dp[i][j]);
}
}
return dp[0][0];
//时空复杂度O(m*n)
}
};
时间复杂度O(m*n),空间复杂度O(m*n)
要約する
今日は難しい問題を書き、新しいタイプの dp 問題を学びました。