動的計画法の線形DP

いわゆる線形DPは、漸化式が明らかな線形関係にあることを意味します。動的計画法の各状態は多次元状態であり、多次元状態には解の次数があります。たとえば、ナップサック問題は2次元問題であり、行ごとに計算されます。このようなDPは線形DPと呼ばれます。 。

デジタルトライアングル

問題の説明

下の図に示すように、上から順に各ノードでデジタル三角形が与えられた場合、左下のノードまたは右下のノードに移動し、下に歩いて、パスパス上の数値の合計を最大化するため。

        7
      3   8
    8   1   0
  2   7   4   4
4   5   2   6   5

入力形式
最初の行には、デジタル三角形の層数を表す整数nが含まれています。

次のn行では、各行にいくつかの整数が含まれています。ここで、i番目の行は、デジタル三角形のi番目の層に含まれる整数を表します。

出力フォーマット
最大パス番号の合計を表す整数を出力します

データ範囲1≤n≤500

-10000≤三角形の整数≤10000

入力サンプル:

5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

サンプル出力:

30

問題解決のアイデア:

ここに画像の説明を挿入します
上記の分析により、状態遷移方程式は、左上からのi-1、j-1の最大値、および右上からの最大i-1、jに位置の値を加えたものであると結論付けます。
動的計画法の時間計算量:状態の数に遷移の計算量を掛けます。ここでの合計時間計算量はO(n2)です。
ここでは下から上に実行するため、最初に境界問題を初期化します。つまり、最後の行の数値の合計は、すでにその位置の値になっています。次に、順番に上に移動します。最後の0、0の位置の値は、デジタル三角形の最大合計です。上から下の場合、最後の行の位置では、どちらが最大であるかも考慮する必要があります。明らかに、下から上にトラバースすると、コードはもう少し簡潔になります。

#include<algorithm>
#include<iostream>
using namespace std;
#define N 1010
int n;
int tri[N][N];
int f[N];
int main(){
    
    
    cin>>n;
    //读入数字三角形
    for(int i=0;i<n;i++){
    
    
        for(int j=0;j<=i;j++){
    
    
            cin>>tri[i][j];
        }
    }
    //处理边界问题,注意,这里使用到了滚动数组
    //我们从最后一行开始做
    for(int i=0;i<n;i++){
    
    
        f[i]=tri[n-1][i];
    }
    //状态转移方程
    for(int i=n-2;i>=0;i--){
    
    
        for(int j=0;j<=i;j++){
    
    
            f[j]=tri[i][j]+max(f[j],f[j+1]);
        }
    }
    cout<<f[0];
    return 0;
    
}

最長昇順部分列

問題の説明

長さNのシーケンスが与えられた場合、値が厳密に単調に増加しているサブシーケンスの最長の長さを見つけます。

入力形式
最初の行には整数Nが含まれています。

2行目には、完全なシーケンスを表すN個の整数が含まれています。

出力形式
最大長を示す整数を出力します

データ範囲
1≤N≤1000、
-109≤シーケンス内の数≤109
入力例:
7
3 1 2 1 8 5 6
出力例:
4
難易度:単純
時間/スペース制限:1秒/ 64MB
パスの
総数:9936合計試行:15166

問題解決のアイデア:

3 1 2 1 8 5 6の最長昇順部分列は1256で
ありこれはここでの次元を表します。答えを導き出すための原則に従う必要があります。これに基づいて、次元が少ないほど良いです。小さなものから大きなものまで状況を考えてみてください。
ここに画像の説明を挿入します
状態の数はO(N)であり、転送の時間計算量はO(N)であるため、時間計算量はO(N2)です。

#include<iostream>
#include<algorithm>
using namespace std;
#define N 1010
int arr[N];
int n;
int maxLen[N];
int main(){
    
    
    cin>>n;
    //初始化最长上升子序列为1
    for(int i=0;i<n;i++){
    
    
        cin>>arr[i];
        maxLen[i]=1;        //只有a[i]一个数的情况
    }
    //从前往后处理每一个状态
    for(int i=1;i<n;i++){
    
    
        for(int j=i-1;j>=0;j--){
    
    
            //只有aj<ai的时候,我们才计算最长上升子序列
            if(arr[i]>arr[j]){
    
    
                maxLen[i]=max(maxLen[i],maxLen[j]+1);
            }
        }
    }
    int res;
    //最后选取一个最长上升子序列
    for(int i=0;i<n;i++){
    
    
        res=max(res,maxLen[i]);
    }
    cout<<res;
    return 0;
}

最長共通部分列

問題の説明

2つの文字列text1とtext2が与えられた場合、これら2つの文字列の最長共通部分列の長さを返します。

文字列のサブシーケンスは、新しい文字列を参照します。これは、文字の相対的な順序を変更せずに、元の文字列から特定の文字を削除する(または文字を削除しない)ことによって形成される新しい文字列です。
たとえば、「ace」は「abcde」のサブシーケンスですが、「aec」は「abcde」のサブシーケンスではありません。2つの文字列の「共通サブシーケンス」は、2つの文字列によって共有されるサブシーケンスです。

2つの文字列に共通のサブシーケンスがない場合は、0が返されます。
例1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace",它的长度为 3

例2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3

例3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0

問題解決のアイデア

最長共通部分列(最長共通部分列、LCSと呼ばれる)は非常に古典的なインタビューの質問です。その解決策は典型的な2次元動的計画法であるため、より難しい文字列の問題のほとんどはこの質問と同じです。たとえば、編集距離。さらに、このアルゴリズムは、わずかな変更で他の問題を解決するために使用できるため、LCSアルゴリズムを習得する価値があります。

いわゆるサブシーケンスは元の順序を維持することですが、不連続になる可能性があります。質問を確認した後、質問があるかもしれませんが、なぜこの問題は動的計画法によって解決されるのですか?サブシーケンスタイプの問題のため、考えられるすべての結果を列挙することは容易ではなく、動的計画法アルゴリズムが行うことは、枯渇+プルーニングであり、それらはペアで生まれます。したがって、部分列の問題が関係している限り、それを解決するには動的計画法が必要であると言えます。

最初のステップは、dp配列の意味を明確にすることです。

2つの文字列の動的計画問題の場合、ルーチンは普遍的です。

たとえば、文字列s1とs2の場合、それらの長さはそれぞれmとnです。一般的に、次のようなDPテーブルを作成する必要があります:int [] [] dp = new int [m + 1] [n +1]。

ここに1を追加する理由は、1を追加できないためですが、1を追加しない場合は、他の制限を使用してインデックスが有効であることを確認します。1を追加する場合は、判断する必要はありません。インデックスを0にするだけです。の行と列は空の文字列を表します。

2番目のステップは、境界条件を定義することです。
具体的には、インデックス0の行と列が空の文字列を表すようにします。dp[0] […]とdp […] [0]の両方を0に初期化する必要があります。これがベースです。場合。

3番目のステップは、状態遷移方程式を見つけることです。
これは、動的計画法で最も難しいステップです。ケースを通して導き出しましょう。

text1:abcdeおよびtext2:ace 2文字列の場合、iとjをトラバースするための2つのポインターを定義します。

トラバーサルtext1の長さはmであ​​り、ポインターiは0からmまで定義されます。iポインターの位置を固定し(i == 1)、text2の長さをnとしてトラバースし始め、ポインターjを0からnまで定義します。
ここに画像の説明を挿入します
最初のトラバーサルi = 1、j = 1、2つは同じであるため、dp [1] [1] = 12
番目のトラバーサルi = 1、j = 2、aとcは等しくなく、0にすることはできません。 、ここでは、aとacの最長部分列に変換する必要があります。ここでは、前の関係を渡す必要があるため、dp [1] [2] = 1および
3番目の走査i = 1、j = 3、aとeが同じではない場合は、前の関係を変更します。関係が渡されるため、dp [1] [3] = 1
text2:aceが最初のラウンドを完了し、text1:abcdeがb文字になります。

4番目のトラバーサルi = 2、j = 1は、abとaの最長の部分文字列を比較し、前の関係を渡すことです。したがって、dp [2] [1] = 1
などになります...(上の図を参照してください)詳細)

2つの文字列をトラバースする場合、2レベルのトラバースフロント値(関係転送)が異なる場合、つまり左側と上部の値が大きい場合を考慮する必要があることがわかります。同じように、それぞれに現在の文字列が含まれていないことを考慮する必要があります。サブシーケンスの長さに1を加えたものです。

したがって、
比較されている2つの文字は同じではないと結論付けることができます。その場合、「text1が1スペース進むか、text2が1スペース進むか、2つの最大値」
dp [i +1]を取る必要があります。 [j + 1] = Math.max(dp [i + 1] [j]、dp [i] [j + 1]);

比較する2つの文字は同じです。前のスペースの値に移動し、1を追加します。dp[i + 1] [j + 1] = dp [i] [j] + 1;

class Solution {
    
    
    public int longestCommonSubsequence(String text1, String text2) {
    
    
        int m=text1.length(),n=text2.length();
        int [][] cs=new int[m+1][n+1];
        //设置边界条件
        for(int i=0;i<=m;i++){
    
    
            cs[i][0]=0;
        }       
        for(int i=0;i<=n;i++){
    
    
            cs[0][i]=0;
        }
        for(int i=1;i<=m;i++){
    
    
            for(int j=1;j<=n;j++){
    
    
                //状态转移方程
                if(text1.charAt(i-1)==text2.charAt(j-1)){
    
    
                    cs[i][j]=cs[i-1][j-1]+1;
                }else{
    
    
                    cs[i][j]=Math.max(cs[i-1][j],cs[i][j-1]);
                }
            }
        }
        return cs[m][n];
    }
}

おすすめ

転載: blog.csdn.net/qq_39736597/article/details/114261553