動的計画法の基本的な考え方
動的計画法アルゴリズム
通常、いくつかの最適なプロパティの問題を解決するために使用されます。このタイプの問題では、多くの実行可能な解決策があるかもしれません。各解は値に対応しており、最適な値を持つ解を見つけたいと考えています。
分割統治法とは異なり、分解によって得られるサブ問題は、動的計画法による解決に適した問題について、互いに独立していないことがよくあります。このような問題を分割統治法で解決すると、分解によって得られるサブ問題の数が多すぎて、何度も繰り返し計算されるサブ問題もあります。解決されたサブ問題への回答を保存し、必要なときに解決策を見つけることができれば、多くの二重計算を回避し、時間を節約できます。
テーブルを使用して、解決されたすべてのサブ問題に対する回答を記録できます。サブ問題が後で使用されるかどうかに関係なく、それが計算されている限り、テーブルにその結果を入力します。これが動的計画法の基本的な考え方です。特定の動的計画法アルゴリズムはさまざまですが、それらはすべて同じフォーム入力形式を持っています。
簡単な質問
記事のタイトルはLeeCodeから来ています
1.フィボナッチ数列
フィボナッチの考え方は動的計画法では非常に一般的です。いくつかの古典的な問題は、フィボナッチ数列の適用の拡張です。後で、そのような問題の基本構造は同じであることがわかります。
関数(入力n)を記述して、フィボナッチ数列のn番目の項(つまりF(N))を見つけます。フィボナッチ数列は次のように定義されます。
F(0)= 0、F(1)= 1
F(N)= F(N-1)+ F(N-2)、ここでN>1。
フィボナッチ数列は0と1で始まり、後続のフィボナッチ数は前の2つの数を加算することによって取得されます。
入力:n = 2出力:1
----------------------------------分析--------------- -------------------------------
フィボナッチ数の境界条件は、F(0)= 0およびF(1)=1です。n> 1n> 1n> 1の場合、各項目の合計は前の2つの項目の合計に等しいため、次の漸化式があります。
F(n)= F(n-1)+ F(n-2)
再帰に関しては、再帰またはループで解決できます。ここで、操作に必要なのはF(n)、F(n-1)、およびF(n-2)の3つの値のみであることに注意してください。 、したがって、格納に使用できる変数は3つだけです。つまり、ローリング配列です。
class Solution {
public int fib(int n) {
int a=0;
int b=1;
int c=0;
if(n==0) return a;
else if(n==1) return b;
for(int i=n-1;i>0;i--){
c=a+b
a=b;
b=c;
}
return c;
}
}
このような質問には、サイクルが始まる条件に注意を払う必要があります。
2.カエルのジャンプステップ
カエルは一度に1ステップ、または一度に2ステップジャンプできます。n
カエルが一歩上にジャンプできる方法をいくつ見つけてください。
入力:n = 2出力:2
----------------------------------------------分析--- ------------------------------------------------
ここで、カエルのジャンプステップの選択は順番に行われることに注意してください。たとえば、最初に1ステップジャンプしてから、2ステップジャンプします。これは、最初に2ステップジャンプしてから1ステップジャンプするのとは異なります。
カエルが1歩なのか2歩なのかを考え始めると、行き止まりに陥りやすく、逆の方向に考えることができます。
カエルがn番目のステップにジャンプしたとします。2ステップまたは1ステップのどちらでジャンプしたかを振り返って考えます。1ステップジャンプした場合は、ジャンプする前にn-1ステップでした。同様に、ジャンプする前は、n-2ステップである可能性があり、最初に言ったことと組み合わせると、異なるシーケンスを1つのステップとしてカウントすることはできず、最後のステップは異なります。カエルは以前のものから完全に独立しています。
二分木
明らかに、カエルのn番目のステップは、n-1ステップにn-2ステップを加えたものに等しく、これはフィボナッチ数列です。
上記の解決策と同じですが、a、b、つまりスクロール配列の先頭の位置によって、ループが開始する位置が決まることに注意してください。
class Solution {
public int numWays(int n) {
if(n==0) return 1;
int count=1;
int a=1;
int b=2;
int c=1;
if(n==2) return 2;
for(int i=n-2;i>0;i--){
c=a+b
a=b;
b=c;
}
return c;
}
}
3.連続するサブアレイの最大合計
整数配列を入力します。配列内の1つ以上の連続する整数がサブ配列を形成します。すべてのサブ配列の合計の最大値を見つけます。必要な時間計算量はO(n)です。
入力:nums = [-2,1、-3,4、-1,2,1、-5,4] 出力:6 説明:連続するサブ配列[4、-1,2,1]の最大合計は6です。 。
----------------------------------------分析--------- -------------------------------------------------- -----
この種の問題に初めて遭遇したとき、誤解に陥る可能性があります。たとえば、配列をトラバースすると、数値は負ですが、配列の値は小さくなりますが、背後には比較的大きな正の値があります。上限の正の値は負の値を完全に相殺する可能性があります.2つ以上の値を同時に見てトレードオフを行うには、前後の境界をどこに置く必要がありますか?
この種のジレンマが先を見据えることと振り返ることの間にある場合、問題を分解することを考え、動的計画法または分割統治アルゴリズムを使用して問題を置き換えることができます。
答えには最大の合計のみが必要であり、連続したサブ配列は必要ないことがわかります。したがって、変数を使用して最大値を格納し、すべてのサブ配列をトラバースして見つけることができます。明らかに不可能です。次に、それを拡張できます。
すべてのサブアレイ、およびほとんどのサブアレイには、2つのサブアレイ[1,2]と[1,2,3]など、多くの重複部分があります。現時点では、後者は1つだけであることがわかります。前者より3だけ大きく、3は正の値であるため、後者のサブ配列は最初のサブ配列よりも大きくする必要があります。
動的計画法のアイデアと組み合わせて、3をiと見なし、f(i)を使用して、i番目の数で終わる連続するサブ配列の最大合計を表します。
また、前の説明によれば、f(i)の値は、[1,2]や[1,2,3]などのf(i-1)から導出できます。
⮺:f(i)= max {f(i-1)+ nums [i]、nums [i]}
num[i]の意味はnums[i]+0であり、さらに前のサブ配列はそれ自体よりも小さく、直接破棄されます。
次に、配列をトラバースして各f(i)を見つけます。これには、フィボナッチ数列のメソッドが含まれ、最後に最大のf(i)を取得します。
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, 0+x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
難しい質問
1.株式からの最大利益
株式の価格が時系列で配列されていると仮定すると、株式を一度売買することで得られる最大の利益はどれくらいですか?
入力:[7,1,5,3,6,4]
出力:5
説明:2日目に購入(株価= 1)、5日目に販売(株価= 6)、最大利益= 6-1 = 5 。
売り価格は買い価格よりも高くする必要があるため、利益を7-1=6にすることはできないことに注意してください。
-------------------------------------------------- ---分析---------------------------------------------- -------
考え方は前の質問と似ています
まず、力ずくの方法を実行できますが、各値をトラバースしてから、この値の前の最小値をトラバースし、それぞれの違いを比較して結論を導き出し、徐々に進行するなど、非常に面倒です。トラバーサルは時間の問題を解決することができます。
動的計画法の観点から、ブルートフォース方式を変更でき、以前のすべての値の最小値をトラバーサルなしで取得できます。それ以外の場合は完全です。次に、全体をトラバースするときに最小値を格納する変数を定義します。配列、次に最悪の値の最大値の定義で、差を比較するために使用されます。
重要なのは、2つの値を完璧かつエレガントに割り当てて消費する方法です
public class Solution {
public int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
}
}
2.ギフトの最大値
m * nチェス盤の各正方形にはギフトがあり、各ギフトには特定の値(0より大きい値)があります。ボードの左上隅からギフトの受け取りを開始し、ボードの右下隅に到達するまで、一度に1マスずつ右または下に移動できます。チェス盤とその上の贈り物の価値を考えて、あなたが得ることができる贈り物の最大値を計算しますか?
入力:
[
[1,3,1]、
[1,5,1] 、[4,2,1
]
]
出力:12
説明:パス1→3→5→2→1は最も価値のある贈り物を得ることができます
-----------------------------分析-------------------- --------------------------------------
まず、最大値を取得するには、配列全体をトラバースする必要があると判断できます。次に、左上隅から考え始めると、どのステップを実行するかがわからず、簡単に作成できません。もちろん、配列全体をトラバースすることもバイナリツリーに保存できます。問題を解決します。
したがって、右下隅から考え始めると、右下隅に到達する方法は2つしかないことがわかります。どちらを選択するかは、どちらの方法がより多くの贈り物を持っているかによって異なりますが、前のすべてを見ることができない場合道路、左上隅からと同じになります。窮地?
上記の考え方に従って考え始めることができます。各ステップは前のすべてのステップに関連しており、各ステップは前のステップから導出できます。これはフィボナッチ数列の方法ではありません。この問題は2つと見なすことができます。 -の連続するサブ配列の合計の次元
最初に方程式を導き出し、境界問題を検討します
i = 0の場合、これは開始要素です
。i= 0およびj≠0の場合、これは行列の最初の行要素であり、左からのみ到達できます
。i≠0およびj = 0の場合、これは次のようになります。行列の最初の列要素は、上からのみ到達できます
。i≠0およびj≠0の場合、左または上から到達できます。
次に、コードの問題があります
class Solution {
public int maxValue(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
int max=grid[0][0];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0 && j==0){
continue;
}else if(i==0){
grid[0][j]=grid[0][j-1]+grid[0][j];
}
else if(j==0){
grid[i][0]=grid[i-1][0]+grid[i][0];
}else{
grid[i][j]=Math.max(grid[i-1][j],grid[i][j-1])+grid[i][j];
}
}
}
return grid[m-1][n-1];
}
}
3.数値を文字列に変換します
数値を指定すると、次のルールに従って文字列に変換されます。0は「a」に変換され、1は「b」に変換され、...、11は「l」に変換され、...、25は「z」に変換されます。番号には複数の翻訳がある場合があります。数が持つ異なる翻訳方法の数を計算する関数をプログラムしてください
入力:12258
出力:5
説明:「bccfi」、「bwfi」、「bczi」、「mcfi」、「mzi」の5つの異なる翻訳を含む12258
-------------------------------------分析------------ -------------------------------------------------- ------------
明らかに、1つまたは2つ、つまり、カエルのジャンプステップの拡張バージョンを選択する必要があります。違いは、25を超える「2つのステップ」をキャンセルする必要がある場合、スクロール配列を使用して次のこともできることです。スペースを節約します。
class Solution {
public int translateNum(int num) {
String src = String.valueOf(num);
int p = 0, q = 0, r = 1;
for (int i = 0; i < src.length(); ++i) {
p = q;
q = r;
r = 0;
r += q;
if (i == 0) {
continue;
}
String pre = src.substring(i - 1, i + 1);
if (pre.compareTo("25") <= 0 && pre.compareTo("10") >= 0) {
r += p;
}
}
return r;
}
}