Leetcodeのダイナミックプログラミングシリーズの古典的な質問に関するJavaの詳細な説明

家の伝染病の予防の間、リートコードは10以上の線形動的計画法アルゴリズムの質問をブラッシングしました。DPのアイデアを要約して洗練する時が来ました。
要約したトピックを2つのタイプのシーケンスマッチングとライフに分けました。
最初のカテゴリの代表的なホットトピック:
leetcode300。最長の昇順サブ
シーケンスleetcode53。最大サブシーケンスと
leetcode1143。最長の共通サブ
シーケンスleetcode72。編集距離

2番目のカテゴリーの代表的なホットな質問:
leetcode121。株の売買に最適な時期leetcode122。株式の売買に最適な時期
II
leetcode322。変更の変更
以下は、7つの質問のアイデアとコード実装です。

300.最長の昇順サブシーケンスここに画像の説明を挿入

  • アイデア:配列を使用して昇順のシーケンスを格納します。重要なのは、最後の出力がシーケンスの長さであるため、長さが保証され、常に実際のサブオーダーを記録する必要がないということです。
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length==0) return 0;
        for(int i=1; i<nums.length; i++){
            dp[1] = 1;
            for(int j=i-1; j>1; j--){
                 if(dp[j] < dp[i]) dp[j] = dp[j-1]+1; 
            }
        }
    }
}

私がこの質問を最初に書いた理由は、貪欲なアルゴリズムが以前に使用されたためであり、バイナリ検索は考えるのがより困難です

public static int lengthOfLIS(int[]nums) {
        if(nums.length < 2) return nums.length;
        //LIS数组存放最长子序列的数组,但并非时刻都存放的是最长子序列
        int[] LIS = new int[nums.length]; 
        LIS[0] = nums[0];//数组有负整数的情况
        int end = 0;
        for(int i=1; i<nums.length; i++){
            //如果该位元素大于子序列最后一位,上升子序列变长1
            if(nums[i]>LIS[end]){
                end++;   LIS[end]=nums[i];
            }
 //如果当前nums[i]小于子序列最后一位,则用二分法搜索子序列中比nums[i]大的最小数
            else{
                int left = 0,right =end;
                while(left<right){
                    int pivot = left+(right-left)/2;
                    if( LIS[pivot]< nums[i]){
                        left = pivot+1;
                    }
                    else{
                        assert LIS[pivot] >= nums[i];
                        right = pivot;
                    }
                }
                LIS[right]=nums[i];
            }
        }
        return end+1;
    }

LeetCode53。最大サブシーケンス合計

  • アイデア:サブシーケンス問題の難しさは、不連続なシーケンスです。定義dp [i]は、nums [i]で終わる最も増加するサブシーケンスの次数を示します。dp [i]はdp [i-1]の結果に依存し、各再帰はmaxで記録する必要があり、最終結果はボトムアップで計算されます。簡単な例を使用してプッシュしてみましょう。nums= [1、-2,3]、次にdp [0] = nums [0] = 1;
  • step1:dp [1] = dp [0] + nums [1] = -1;この時点ではmax = 1> dp [1]なので、現在のmaxは変更されません。
  • step2:dp [2] = dp [1] + nums [2] = 1;最大値<dp [2]になり、最大値がdp [2]に更新されます
  • このように、maxは最終的最大dp [i]
    アルゴリズムのアイデアのアニメーションです
   public int maxSubArray(int[] nums) {
		int[] dp = new int[nums.length];
		dp[0] = nums[0];   
		int max = nums[0];
		for (int i = 1; i < nums.length; i++) {
		//nums[i] > 0,说明对结果有增益,dp[i]再加当前遍历值
		//nums[i] <= 0,说明对结果无增益,dp[i]直接更新为当前遍历数字
			dp[i] = Math.max(dp[i- 1] + nums[i], nums[i]);	
			if (max < dp[i]) {      //关键步:取每次遍历的当前最大和
				max = dp[i];
			}
		}
		return max;
   }

LeetCode1143。最も長い共通のサブシーケンス

ここに画像の説明を挿入ここに画像の説明を挿入

  • アイデア:
    ここでは、YouTubeのホワイトボードでアルゴリズムのアイデアを示す弟をお勧めします。彼のビデオの説明は非常に詳細であり、完全に理解するのに役立つだけでなく、英語を改善することもできます。両方の世界で最も優れています:[LeetCodeアルゴリズムの詳細な説明]最も長い共通のサブシーケンスの
    中間状態は2次元配列では、dp [i] [j]は文字列1の最初のiビットと文字列2の最初のjビットの位置を表します。
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int n1 = text1.length(); int n2 = text2.length();
        int[][] dp = new int[n1+1][n2+1];
        for(int i=0; i<n1; i++) { dp[i][0] = 0; }
        for(int j =0; j<n2; j++){ dp[0][j] = 0; }
        for(int i=1; i<=n1; i++){
            for(int j =1; j<=n2; j++){
                if(text1.charAt(i-1)==text2.charAt(j-1))
                    dp[i][j] = dp[i-1][j-1] + 1;
                else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[n1][n2];
    }
}

LeetCode72。距離の編集

タイトル

  • アイデア:2つの文字列の動的プログラミング問題に対するこのタイプの解決策は、2つのポインターiとjを使用して2つの文字列の終わりを指し、段階的に問題を絞り込むために、最長の昇順サブシーケンスと同じ方法を使用できます。スケール。
    dp [i] [j]-word1の最初のiビットをword2の最初のjビットに変換するために必要な最小ステップ数
public int minDistance(String word1, String word2) {
        int n1 = word1.length(); int n2 = word2.length();
        int[][] dp = new int[n1+1][n2+1];
        for(int i=1; i<=n1; i++) dp[i][0] = dp[i-1][0] +1;   
        for(int j=1; j<=n2; j++) dp[0][j] = dp[0][j-1] +1;  
        for(int i=1; i<=n1; i++){
            for(int j=1; j<=n2; j++){
            //word1和word2的该位字符相同,不需要改动。
                if(word1.charAt(i-1)==word2.charAt(j-1))
                   dp[i][j] = dp[i-1][j-1];
            //如果字符不同,则取该步之前的状态基础上做删除,修改,插入中的最小改动
                else 
                   dp[i][j] = Math.min(Math.min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;
            }
        }
        return dp[n1][n2];
    }

株式シリーズの問題は、よく要約された考え方を推奨しています。6つの株式問題を排除する方法
ここに画像の説明を挿入

  • アイデア:前のi-dayで得られた利益は、前のi-dayでの利益です。i番目の日は、「株式を購入」の場合は利益から価格[i]を差し引き、「株式を販売」の場合は利益を与えます。価格を上げる[i]。この時点での最大利益は、2つの選択肢のうち大きい方です。
    したがって、最適な部分構造が存在する可能性があります:
    dp [i] [0]は、在庫がi日目に保持されていない場合に所有される利益を表します; dp [i] [0] = Math.max(dp [i-1] [0]、dp [i-1] [1] +価格[i]);
    およびdp [i] [1]は、i日目に株式を保持することによる利益を表しますdp [i] [1] = Math.max(dp [i-1 ] [1]、-prices [i]);
class Solution {
   public int maxProfit(int[] prices) {
        int n = prices.length;
        if(prices.length==0)return 0;
        int[][] dp = new int[n][2]; //行表示第 i天,列表示是否买入当天股票
        dp[0][0] = 0; //i = 0 时 dp[i-1] 是不合法的。
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
        }
        return dp[n - 1][0];
    }
}

ここに画像の説明を挿入
+アイデア:アイデアは基本的に前の質問と同じです。違いは、トランザクションの数が1から無制限に変化することです。
その場合、dp [i] [1]はdp [i-1] [1]およびdp [i-1] [0と等しくなります。 ] -prices [i]より大きな結果。過去i日間の購入トランザクションもあるため、つまり、i日に株を購入したときに得られる利益は、i-1に直接関連しています。

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length==0) return 0;
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0; 
        dp[0][1] = -prices[0];
        for(int i=1; i<prices.length;i++){
              dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);
              dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0]-prices[i]);
        } 
        return dp[prices.length-1][0];
    }
}

leetcode322。変更変更

ここに画像の説明を挿入

  • アイデア:最適な状態dp [i]は、金額iに必要なコインの最小数を表します。注意すべき問題は、各dp []数は、初期化中の合計金額よりも大きくなければならないということです。これは、コインがすべて1である極端な場合です。
class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount+1];
      // 注意:因为要比较的是最小值,不可能是结果的初始化值就得赋一个最大值
        Arrays.fill(dp, amount + 1);
        dp[0] =0;
        for(int i=1; i<=amount; i++){
            for(int coin: coins){
        //如果可包含coin,那么剩余钱是i−coins,要兑换的硬币数是 dp[i−coins]+1
                if(coin <= i)
                   dp[i] = Math.min(dp[i],dp[i-coin]+1);
            }
        }
        return dp[amount]<=amount ? dp[amount] : -1;
    }
}

この記事があなたに役立つなら、それを好きで私に知らせてください、私はあなたと良いコンテンツを共有するために一生懸命努力します、ありがとう〜

元の記事を27件公開しました 賞賛されました4 訪問2178

おすすめ

転載: blog.csdn.net/smile001isme/article/details/105472635