目次
分割統治+最適な下部構造
1.動的計画法と再帰または分割統治の間に基本的な違いはありません(重要なのは最適な部分構造があるかどうかによって異なります)
2.共通性:重複するサブ問題を見つける
3.違い:最適な下部構造、次善のソリューションは途中で排除できます
動的計画法の要点
1.最適な下部構造
2.中間状態を保存します
3.漸化式(美しい名前:状態遷移方程式またはdp方程式)
トピック1:フィボナッチ数
(件名リンク:https://leetcode-cn.com/problems/fibonacci-number/)
通常F(n)で表されるフィボナッチ数で、形成されるシーケンスはフィボナッチ数列と呼ばれます。数列は0と1で始まり、その後の各番号は前の2つの番号の合計です。あれは:
F(0)= 0、F(1)= 1
F(n)= F(n-1)+ F(n-2)、ここでn> 1
はnを与えます、F(n)を計算してください
アイデア:動的再帰である動的計画法を使用する
class Solution {
public int fib(int n) {
if(n < 2){return n;}
int a = 0;
int b = 1;
int sum = 0;
for(int i =2;i<=n;i++){
sum = a+b;
a = b;
b = sum;
}
return sum;
}
}
トピック2:さまざまなパス
(件名リンク:https://leetcode-cn.com/problems/unique-paths/)
ロボットはmxnグリッドの左上隅にあります(開始点は下の図で「開始」とマークされています)。
ロボットは、一度に1ステップ下または右にしか移動できません。ロボットはグリッドの右下隅に到達しようとします(下の画像で「完了」とラベル付けされています)。
合計でいくつの異なるパスがありますか?
アイデア:出発点から進むには2つの方法があります:1)右に1歩進む; 2)さらに一歩進む
つまり、各グリッドにあるスキームの数は、右側のグリッドのスキーム+その下のグリッドのスキームの合計に等しくなります。
明らかに、一番下の行の各グリッドのスキームは1であり、右端の列の各グリッドのスキームも1です。
class Solution {
public int uniquePaths(int m, int n) {
int f[][] = new int[m][n];
for(int j =0;j<n;j++){
f[m-1][j] = 1;
}
for(int i =0;i<m;i++){
f[i][n-1] = 1;
}
for(int i =m-2;i>=0;i--){
for(int j = n-2;j>=0;j--){
f[i][j] = f[i+1][j]+f[i][j+1];
}
}
return f[0][0];
}
}
トピック3:最長共通部分列
(サブシーケンスリンク:https://leetcode-cn.com/problems/longest-common-subsequence/)
2つの文字列text1とtext2が与えられた場合、これら2つの文字列の最長共通部分列の長さを返します。
文字列のサブシーケンスは、新しい文字列を参照します。これは、文字の相対的な順序を変更せずに、元の文字列から一部の文字を削除する(または文字を削除しない)ことによって形成される新しい文字列です。
たとえば、「ace」は「abcde」のサブシーケンスですが、「aec」は「abcde」のサブシーケンスではありません。2つの文字列の「共通サブシーケンス」は、2つの文字列によって共有されるサブシーケンスです。
2つの文字列に共通のサブシーケンスがない場合は、0が返されます。
例1:
入力:text1 = "abcde"、text2 = "ace"
出力:3
説明:最長共通部分列は "ace"であり、その長さは3です。
アイデア:最長共通部分列問題。経験上、2つの文字列を2次元配列の行と列として扱います。
2次元配列の各ポイントは、2つの文字列に対応します。このポイントの前の共通文字列の長さ
もう1つの行と1つの列を持つ新しい配列を作成します。1つは0に初期化することであり、1つはインデックス0の行と列の空の文字列について個別に説明する必要はありません。
漸化式:
if(c1 == c2){ dp [i + 1] [j + 1] = dp [i] [j] +1; } else { dp [i + 1] [j + 1] = Math.max(dp [i + 1] [j]、dp [i] [j + 1]); }
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m+1][n+1];
for(int i=0;i<m;i++){
for(int j = 0;j<n;j++){
char c1 = text1.charAt(i);
char c2 = text2.charAt(j);
if(c1 == c2){
dp[i+1][j+1] = dp[i][j]+1;
}else{
dp[i+1][j+1] = Math.max(dp[i+1][j],dp[i][j+1]);
}
}
}
return dp[m][n];
}
}
トピック4:三角形の最小パス合計
(件名リンク:https://leetcode-cn.com/problems/triangle/)
三角形が与えられた場合、上から下への最小経路合計を見つけます。
各ステップは、次の行の隣接ノードにのみ移動できます。ここで隣接するノードとは、前のレイヤーのノードの添え字と添え字を指します
前のレイヤーの添え字+1と同じか等しい2つのノード。つまり、現在の行の添え字iにある場合は、次のステップを次の行の添え字iまたはi +1に移動できます。
例1:
入力:triangle = [[2]、[3,4]、[6,5,7]、[4,1,8,3]]
出力:11
説明:次の図に示すように:
2
3 4
6 5 7
4 1 8 3
上から下への最小パス合計は11です(つまり、2 + 3 + 5 + 1 = 11)。
アイデア:動的計画法、ボトムアップ、および層ごとの上昇を採用します。
各レイヤーは、対応する最小パスを取ります。
動的漸化式は次のとおりです。dp[j] = Math.min(dp [j]、dp [j + 1])+ triangle.get(i).get(j)
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int[] dp = new int[triangle.size()+1];
for(int i=triangle.size()-1;i>=0;i--){
for(int j =0;j<triangle.get(i).size();j++){
dp[j]= Math.min(dp[j],dp[j+1])+triangle.get(i).get(j);
}
}
return dp[0];
}
}
トピック5:最大サブシーケンス合計
(トピックリンク:https://leetcode-cn.com/problems/maximum-subarray/)
整数配列numsが与えられた場合、合計が最大のものを見つけます
連続するサブ配列(サブ配列には少なくとも1つの要素が含まれます)は、その最大合計を返します。
例1:
入力:nums = [-2,1、-3,4、-1,2,1、-5,4]
出力:6
説明:連続するサブ配列[4、-1,2,1]の合計は最大の6です
アイデア:漸化式を見つける:
最大サブオーダー合計=現在の要素自体の値または前の要素を含めた後の値。
dpの式は次のとおりです。f[i] = max(f [i-1]、0)+ a [i]
class Solution {
public int maxSubArray(int[] nums) {
int maxsum=nums[0];
for(int i =1;i<nums.length;i++){
nums[i] = nums[i]+Math.max(0,nums[i-1]);
if(nums[i]>maxsum){
maxsum = nums[i];
}
}
return maxsum;
}
}
トピック6:変更の変更
(件名リンク:https://leetcode-cn.com/problems/coin-change/)
異なる金種と合計金額のコインが与えられます。合計金額を構成するために必要なコインの最小数を計算する関数を記述します。
合計金額を構成できるコインの組み合わせがない場合は、-1を返します。各コインの数は無制限と考えることができます。
例1:
入力:コイン= [1、2、5]、金額= 11
出力:3
説明:11 = 5 + 5 + 1
アイデア:動的計画法を使用します。
dp方程式はf(n)= min {f(nk)、コイン配列のkの場合} +1です。
プログラミングのアイデア:
最初にdpテーブルの各要素を金額+1に初期化します(Integer.MAX_VALUEもOKです)。dp[amount]の最終値がまだ初期化された値である場合は、交換状況がないことを意味します。
変数dpにおいて、金額を補うことができる場合は回数が更新され、金額を補うことができない場合は更新されません。
class Solution {
public int coinChange(int[] coins, int amount) {
int maxvalue = amount+1;
int[] dp = new int[amount+1];
Arrays.fill(dp,maxvalue);
dp[0] = 0;
for(int i=1;i<amount+1;i++){
for(int j=0;j<coins.length;j++){
if(coins[j]<=i){
dp[i] = Math.min(dp[i],dp[i-coins[j]]+1);
}
}
}
return dp[amount] == amount+1 ? -1 : dp[amount];
}
}