記事ディレクトリ
動的プログラミング (単純なマルチステート)
1. マッサージ師
-
ステータス表示
dp[i]
位置 i に到着したときの予定の最長時間を示します。しかし、私たちはこの質問に答えるかどうかを選択できます。したがって、さらに 2 つのサブ状態に分けることができます。f[i]
表示: i 位置に到達するまでに許容される最大時間g[i]
示す: i 位置に到達したときに受け入れられない最大継続時間
-
状態遷移方程式
-
初期化
このトピックは比較的単純であるため、仮想ノードの方法を使用する必要はなく、後でフォームを入力するときに境界を越えないように初期化されます。
f[0] = nums[0], g[0] = 0
-
フォームに記入
左から右へ
-
戻り値
最後の値を受け入れるか、最大値を受け入れません
ACコード:
class Solution
{
public:
int massage(vector<int>& nums)
{
int n = nums.size();
if (n == 0) return 0;
vector<int> f(n);
auto g = f;
f[0] = nums[0], g[0] = 0;
for (int i = 1; i < n; i++)
{
f[i] = g[i - 1] + nums[i];
g[i] = max(f[i - 1], g[i - 1]);
}
return max(f[n - 1], g[n - 1]);
}
};
2.強盗||
分析します:
部屋は連続している、つまりリングであるため、分類して議論することができます。
- 1番目を盗んだ場合、2番目と最後は盗めません
- 最初のものを盗むのではなく、2番目と最後のものを盗むことができます
したがって、2 つの場合の最大値のみが必要です。
-
ステータス表示
dp[i]
盗まれた場合の上限額を示していますが、それでも盗む場合と盗まない場合の2つに分けられます。f[i]
iを盗む際の最大量を示します。g[i]
iを盗まない場合の上限を示します。 -
状態遷移方程式
-
初期化
後続のフォーム入力が境界を越えないようにする
-
フォームに記入
両方を左から右に入力してください
-
戻り値
最大値
ACコード:
class Solution
{
public:
int rob(vector<int>& nums)
{
int x = 0, y = 0;
int n = nums.size();
x += nums[0];
x += recursion(2, n - 2, nums);
y += recursion(1, n - 1, nums);
return max(x, y);
}
int recursion(int left, int right, vector<int> &v)
{
if (left > right) return 0;
int n = v.size();
vector<int> f(n);
auto g = f;
f[left] = v[left]; // 初始化
for (int i = left + 1; i <= right; i++)
{
f[i] = g[i - 1] + v[i];
g[i] = max(g[i - 1], f[i - 1]);
}
return max(f[right], g[right]);
}
};
3. 削除してポイントを獲得する
分析: すべての数値のポイントの合計を配列に入れて、住宅強盗を実行します。
元の配列を新しい配列に変換します。新しい配列の添字i
に対応する値はi
、元の配列内の元の配列の要素の合計です。たとえば[2, 2, 3, 3, 3, 4]
、元の配列は新しい配列に変換されます[0, 0, 4, 9, 4]
。新しい配列の添字は、0和1
元の配列 に0和1
これら 2 つの2
数値が存在しないことを示し、新しい配列の添字の値は であり4
、元の配列では2
すべての合計が であることを示します4
。変換の目的は、新しい配列から削除して得られるポイントnums[i]
、つまり奪える量を取得することです。nums[i]
削除後は合計を削除する必要があるためnums[i] + 1
、nums[i] - 1
新しい配列に隣接する要素を取り込むことができなくなり、隣接する要素を取り込めないことは強盗と同じになります。次に、家を強盗する方法を使用して問題を解決できます
ACコード:
class Solution
{
public:
const int N = 10001;
int deleteAndEarn(vector<int>& nums)
{
vector<int> arr(N);
for (auto e : nums) arr[e] += e;
vector<int> g(N);
auto f = g;
for (int i = 1; i < N; i++)
{
f[i] = g[i - 1] + arr[i];
g[i] = max(g[i - 1], f[i - 1]);
}
return max(g[N - 1], f[N - 1]);
}
};
4. 家にペンキを塗る
-
ステータス表示
dp[i]
iに到達する際に最低限必要なコストを示します。しかし、i に関しては 3 つの状況が考えられ、3 つのサブ状態に分割する必要があります。dp[i][0], dp[i][1], dp[i][2]
-
状態遷移方程式
-
初期化
仮想ノードの使用
-
フォームに記入
-
戻り値
3 つのテーブルの最小値を返します
ACコード:
class Solution
{
public:
int minCost(vector<vector<int>>& costs)
{
// 0: 红色 1:蓝色 2:绿色
int n = costs.size();
vector<vector<int>> dp(n + 1, vector<int>(3));
for (int i = 1; i <= n; i++)
{
dp[i][0] = min(dp[i - 1][1], dp[i - 1][2]) + costs[i - 1][0];
dp[i][1] = min(dp[i - 1][0], dp[i - 1][2]) + costs[i - 1][1];
dp[i][2] = min(dp[i - 1][0], dp[i - 1][1]) + costs[i - 1][2];
}
int ret = INT_MAX;
for (int i = 0; i < 3; i++)
{
ret = min(ret, dp[n][i]);
}
return ret;
}
};
5. 株式の売買に最適な時期には凍結期間が含まれます
-
ステータス表示
dp[i]
位置 i に到達したときの最大利益を示しますが、位置 i に到達した時点でもまだ 3 つのサブステートが存在します。dp[i][0]
、その後購入状態であることを示しますdp[i][1]
は、その後取引可能な状態になることを意味します。dp[i][2]
、その後、私がフリーズ状態にあることを示します
-
状態遷移方程式
このような状態を相互に変換できる場合は、次の方法を使用して分析できます。
-
初期化
dp[0][0] = -prices[0], dp[0][1] = 0, dp[0][2] = 0
-
フォームに記入
3 つのフォームに同時に記入する
-
戻り値
3 つの状態の最後の最大値を返します。
ACコード:
class Solution
{
public:
int maxProfit(vector<int>& prices)
{
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(3));
dp[0][0] = -prices[0];
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][2]);
dp[i][2] = dp[i - 1][0] + prices[i];
}
return max(max(dp[n - 1][0], dp[n - 1][1]), dp[n - 1][2]);
}
};
6. 株式の売買に最適な時期には手数料が含まれます
-
ステータス表示
dp[i]
i ポジションに到達したときの最大利益を示しますが、i ポジションに到達したときの状態は 2 つありますdp[i][0]
:購入状態であることを示します。dp[i][1]
販売状況を示します -
状態遷移方程式
-
初期化
初めは購入状態であれば
dp[0][0] = -prices[0]
-
フォームに記入
-
戻り値
ACコード:
class Solution
{
public:
int maxProfit(vector<int>& prices, int fee)
{
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2));
dp[0][0] = -prices[0];
for (int i = 1; i < n; i++)
{
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
}
return max(dp[n - 1][0], dp[n - 1][1]);
}
};
7. 株を売買するのに最適な時期|||
-
ステータス表示
dp[i]
ポジション i への最大利益を示しますが、いくつかの状態にも分かれていますf[i][j]
i までが j 回目の購入の最大利益になることを示しますg[i][j]
i までが j 回目の購入の最大利益になることを示します -
状態遷移方程式
-
初期化
f[0][0] = -prices[0], g[0][0] = 0
-
フォームに記入
上から下、各行は左から右へ
-
戻り値
売り状態の最後の数件の最大値
ACコード:
class Solution
{
public:
const int N = 0x3f3f3f3f;
int maxProfit(vector<int>& prices)
{
int n = prices.size();
vector<vector<int>> f(n, vector<int>(3, -N));
auto g = f;
f[0][0] = -prices[0], g[0][0] = 0;
for (int i = 1; i < n; i++)
{
for (int j = 0; j < 3; j++)
{
f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
g[i][j] = g[i - 1][j];
if (j - 1 >= 0) g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
}
}
int ret = 0;
for (int i = 0; i < 3; i++)
{
ret = max(ret, g[n - 1][i]);
}
return ret;
}
};
8. 株を売買するのに最適な時期 IV
-
ステータス表示
または 2 つのサブ状態
f[i][j]
ポジション i での購入状態での j 番目の株式購入の最大利益を示しますg[i][j]
ポジション i での売り状態での j 番目の株式購入の最大利益を示します -
状態遷移方程式
-
初期化
f[0][0] = -prices[0], g[0][0] = 0
-
フォームに記入
上から下、左から右へ
-
戻り値
すべての行の最大値を返します
ACコード:
class Solution {
public:
const int N = 0x3f3f3f3f;
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector<vector<int>> f(n, vector<int>(k + 1, -N));
auto g = f;
f[0][0] = -prices[0], g[0][0] = 0;
for (int i = 1; i < n; i++)
{
for (int j = 0; j <= k; j++)
{
f[i][j] = max(f[i - 1][j], g[i - 1][j] - prices[i]);
g[i][j] = g[i - 1][j];
if (j - 1 >= 0) g[i][j] = max(g[i][j], f[i - 1][j - 1] + prices[i]);
}
}
int ret = 0;
for (int i = 0; i <= k; i++)
{
ret = max(ret, g[n - 1][i]);
}
return ret;
}
};