パンチカードの問題から派生したLeetCode-dp演習

前に書く

今日のLCパンチカードは簡単ですが、それはdpを使用して解決します。dp思考の実践に非常に役立つと思います。さらに、多くの大手がソリューションに同様の質問をたくさん挙げているので、一緒に書きますそしてそれを整理します。(格闘強盗シリーズ+醜悪シリーズ)

インタビューの質問17.16。マッサージ師

よく知られているマッサージ師は、アポイントメント要求の安定したフローを受け取り、各アポイントメントを選択するかどうかを選択できます。各予約サービスの間に休憩があるため、彼女は隣接する予約を受け入れることができません。予約要求シーケンスが与えられた場合、マッサージ師に最適な一連の予約(最長の合計予約時間)を見つけ、合計分数を返します。

例1:

入力:[1,2,3,1]

出力:4

説明:予約番号1と予約番号3を選択します。合計期間= 1 + 3 = 4。

解決策:前のビットのステータスは選択および非選択のみで、前のビットのステータスに従って現在の位置を見つけ、現在の選択および非選択の最大値を見つけることができます。

再帰方程式:dp [i] = max(dp [i-1]、dp [i-2] + nums [i])

この問題はdp配列を使用する必要がなく、2つのdp変数で実行できます。

コード:

class Solution {
public:
    int massage(vector<int>& nums) {
        if(nums.empty()) return 0;
        int dp0 = 0;
        int dp1 = nums[0];
        int i=1;
        while(i<nums.size()){
            int t0 = max(dp0,dp1);
            int t1 = max(dp0+nums[i],dp1);
            dp0 = t0;
            dp1 = t1;
            ++i;
        }
        return max(dp0,dp1);
    }
};

198.自宅での戦闘

あなたはプロの泥棒で、通りに沿って家を盗むことを計画しています。各部屋にはある程度の現金が隠されています。盗難に影響を与える唯一の制限要因は、隣接する家に相互接続された盗難防止システムが装備されていることです。2つの隣接する家が同じ夜にハッキングされた場合、システムは自動的に。

各家に保管されている金額を表す非負の整数配列を指定して、警報装置に触れずに盗むことができる最大量を計算します。

前の質問のプロトタイプは実際にはこの古典的な泥棒の問題であり、まったく同じなので、ここでは書きません。

213.家族の戦いII

あなたはプロの泥棒であり、通りに沿って家を盗む計画を立てています。各部屋には一定量の現金が含まれています。この場所にある家はすべて円になっています。つまり、最初の家と最後の家が隣り合っています。同時に、隣接する家には相互接続された盗難防止システムが装備されており、同じ夜に2つの隣接する家が泥棒に侵入された場合、システムは自動的に警察に通報します。

各家に保管されている金額を表す非負の整数配列を指定して、警報装置に触れずに盗むことができる最大量を計算します。

解決策:実際にはリングになり、頭と尾を特別に判断する必要があるため、2つの答えの大きい方の値に分けられます。頭なしと尾なしです。

コード:

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.empty()) return 0;
        if(nums.size()==1) return nums[0];
        int dp0 = 0;
        int dp1 = nums[0];
        int i=1;
        while(i<nums.size()-1){
            int t0 = max(dp0,dp1);
            int t1 = max(dp0+nums[i],dp1);
            dp0 = t0;
            dp1 = t1;
            ++i;
        }
        int res1 = max(dp0,dp1);
        dp0 = 0;
        dp1 = nums[1];
        i=2;
        while(i<nums.size()){
            int t0 = max(dp0,dp1);
            int t1 = max(dp0+nums[i],dp1);
            dp0 = t0;
            dp1 = t1;
            ++i;
        }
        int res2 = max(dp0,dp1);
        return max(res1,res2);
    }
};

337.家族の戦いIII

通りと家の輪の最後の強盗の後、泥棒は盗まれる可能性のある新しい領域を見つけました。このエリアには「ルート」と呼ばれる入り口が1つだけあります。「ルート」に加えて、各家には1つだけ「親」家が接続されています。いくつかの偵察の後、巧妙な泥棒は「この場所のすべての家の配置はバイナリツリーに似ている」ことに気付きました。直接接続された2つの家が同じ夜に強盗されると、家は自動的に警察に電話します。

泥棒がアラームをトリガーせずに夜を盗むことができる最大量を計算します。

解決策:考え方は同じですが、バイナリツリーストレージになっているため、ツリー状のdpになっています。

サブツリーの場合、2つのケースがあります。

  1. 現在のルートノードが含まれています
  2. 現在のルートノードを含まない

ケース1:ルートノードを含める

ルートノードが含まれているため、左右の息子ノードは選択できません。この場合の最大値は、
現在のノード+左の息子のケース2 +右の2番目の子のケース2です。

ケース2:ルートノードが含まれていない

この場合、左右の息子ノードを選択できるため、4つの可能性があります。

  1. 左息子の場合1 +右息子の場合1
  2. 左息子の場合1 +右息子の場合2
  3. 左の息子の場合2 +右の息子の場合1
  4. 左の息子のケース2 +右の息子のケース2は、
    max(左の息子のケース1、左の息子のケース2)+ max(右の息子のケース1、右の息子のケース2)の組み合わせです。

これら2つのケースを組み合わせると、dfsはバイナリツリーを走査します。

ソース:https://leetcode-cn.com/problems/house-robber-iii/solution/cdong-tai-gui-hua-si-xiang-shi-xian-xiang-xi-shuo-/

コード:

/**
* Definition for a binary tree node.
* struct TreeNode {
*     int val;
*     TreeNode *left;
*     TreeNode *right;
*     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
    pair<int, int> dfs(TreeNode *root) {
        if (root == nullptr) {
            return { 0, 0 };
        }
        auto left_pair = dfs(root->left);
        auto right_pair = dfs(root->right);
        return {root->val + left_pair.second + right_pair.second, 
                max(left_pair.first, left_pair.second) + max(right_pair.first, right_pair.second)};
    }
    int rob(TreeNode* root) {
        auto p = dfs(root);
        return max(p.first, p.second);
    }
};

戦いと略奪のシリーズを終えた後、同様の単純なdp質問を一緒に練習することをお勧めします。

1262。3で割り切れる最大の合計

整数配列numを与えます。3で割ることができる要素の最大合計を見つけて返します。

例1:

入力:nums = [3,6,5,1,8]

出力:18

説明:数値3、6、1、8を選択し、それらの合計は18(3で割り切れる最大の合計)です。

解決策:

dp [i]を選択した数の累積数とモジュロ3 = i数と

し、nums [i]%3 = 1 仮定します。次に、以前に選択した数とモジュロ3 = 2数を加算します。モジュラス3は0で、dp [0] = max(dp [0]、nums [i] + dp [2])の

ように表されます。dp 配列が常に更新されている限り、それに注意し、更新時に保存します。後続の更新中に繰り返される影響を回避するための状態の値。

コード:

class Solution {
public:
    int maxSumDivThree(vector<int>& nums) {
        vector<int> dp={0,0,0};
        for(int i=0; i<nums.size(); i++){
            int mod = nums[i] % 3;

            int a = dp[(3+0-mod)%3];
            int b = dp[(3+1-mod)%3];
            int c = dp[(3+2-mod)%3];

            if( a || mod==0) dp[0] = max(dp[0],a+nums[i]);
            if( b || mod==1) dp[1] = max(dp[1],b+nums[i]);
            if( c || mod==2) dp[2] = max(dp[2],c+nums[i]);
        }
        return dp[0];
    }
};

801.シーケンスを増やす交換の最小数

長さが等しく、空ではない2つの整数配列AとBがあります。

A [i]とB [i]の要素を交換できます。これらの2つの要素は、それぞれのシーケンスで同じ位置にある必要があります。

いくつかの要素を交換した後、配列AとBの両方が厳密に増加するはずです(厳密に増加する配列の条件は、A [0] <A [1] <A [2] <…<A [A.length-1]のみです。 )。

配列AおよびBが与えられた場合、両方の配列を厳密に増加させ続ける交換の最小数を返してください。与えられた入力は常に有効であると想定されています。

例:

入力:A = [1,3,5,4]、B = [1,2,3,7]

出力:1

説明:

A [3]とB [3]を交換すると、2つの配列は次のようになります:

A = [1、3、5、7]、B = [1、2、3、4]

どちらの配列も厳密に増加しています。

注:

  1. 2つの配列AとBの長さは常に等しく、長さの範囲は[1、1000]です。
  2. A [i]、B [i]はすべて[0、2000]の範囲の整数です。

解決策:

列番号ごとに、変化するか変化しないかの2つの状態があるため、状態を格納する2つのdp変数を定義します。

初期化:列が1つしかない場合:dp = [0、1]

dp [0] = 0、交換なし、合計交換数は0です

。dp[1] = 1、交換、合計交換数は1であり、配列には要素が1つしかないため、最後の要件は増分シーケンスです。


2つの列がある場合、a1 = A [i-1]、b1 = B [i-1]およびa2 = A [i]、b2 = B [i]、i = 1と
すると、4つのケースがあります

(1) a1 <a2およびb1 <b2

は、条件が満たされているため、現在の列が前の列と交換されないことを示します。

(2)a1 <b2およびb1 <a2

は、現在の列が前の列に対して相対的に交換できることを示します。交換後に条件が満たされるためです。

(3)非a1 <a2または非b1 <b2

は(1)否定されます。現在の列は前の列に対して相対的に交換する必要があります。ここでの交換は相対的です。前の列が既に交換されている場合、現在の列は移動しない場合があります。

(4)a1 <b2またはnot b1 <a2でない場合

(2)否定されている場合、現在の列を前の列に対して変更しないでください。


(3)

new_dp [0] = dp [1]の場合、現在の列が移動しない場合は、前の列を変更する必要があります。
new_dp [1] = dp [0] + 1現在の列を変更するには、前の列を移動しないでください。つまり、(3)の場合、前の列の反対が正しいです。

(4)の場合

new_dp [0] = dp [0]。現在の列が移動しない場合、前の列は移動してはなりません。
new_dp [1] = dp [1] + 1現在の列を変更するには、前の列も変更する必要があります。つまり、(4)の場合は、前のコラムと同じ操作で問題ありません。

ただし、(3)(4)はすべてのケースをカバーしているわけではありません。(1)と(2)は変更可能か変更不可で、貪欲の原則により変更可能なら変更はありません。

new_dp [0] = min(dp [0]、dp [1])、以前に変更したかどうかに関係なく、小さい方の数値を使用しましたが、変更しません。

new_dp [1] = min(dp [0]、dp [1])+ 1、以前に変更したかどうかに関係なく、dp [1]が現在の列に対応しているため、小さい方の数値を取得してから変更しました交換の状況。したがって、この値は現在の列のスワップ操作に対応している必要があります。

コード:

class Solution {
public:
    int minSwap(vector<int>& a,vector<int>& b) {
        vector<int> dp = {0,1};
        for(int i=1; i<a.size(); i++){
            int a1 = a[i-1];
            int a2 = a[i];
            int b1 = b[i-1];
            int b2 = b[i];

            if(a1>=a2 || b1>=b2){
                int t = dp[0];
                dp[0] = dp[1];
                dp[1] = t + 1;
            }
            else if(a1>=b2 || b1>=a2){
                ++dp[1];
            }
            else{
                int m = min(dp[0],dp[1]);
                dp[0] = m;
                dp[1] = m+1;
            }
        }
        return min(dp[0], dp[1]);
    }
};

上記の問題を実践した後、別の古典的なシリーズを作成します。醜い数字

263.醜い数

与えられた数が醜いかどうかを判断するプログラムを書いてください。

醜い数は、素因数2、3、および5のみを含む正の整数です。

解決策:最初のものは言うまでもなく、普通の暴力です。

コード:

class Solution {
public:
    bool isUgly(int num) {
        if(num==0) return false;
        while(num%2==0) num /= 2;
        while(num%3==0) num /= 3;
        while(num%5==0) num /= 5;
        return num==1;
    }
};

264.醜い番号II

n番目の醜い数を見つけるプログラムを書いてください。

醜い数は、素因数2、3、および5のみを含む正の整数です。

例:

入力:n = 10

出力:12

説明:1、2、3、4、5、6、8、9、10、12は最初の10個の醜い数字です。

解決策:2、3、5をそれぞれ表す3つのポインターを使用し、n番目の数がカウントされるまで、ポインターの位置に対応する数値に、対応する倍数の最小値である選択されたポインター+1を掛けた値を記録します。

コード:

class Solution {
public:
    int nthUglyNumber(int k) {
        if(k==1) return 1;
        int a(0),b(0),c(0);//记录2,3,5对应的位置
        vector<int> dp;
        dp.push_back(1);
        int s = 1;
        while(dp.size()<k){
            int next = min(2*dp[a],min(3*dp[b],5*dp[c]));
            if(next==2*dp[a]) a++;//选中的指针要+1
            if(next==3*dp[b]) b++;
            if(next==5*dp[c]) c++;
            dp.push_back(next);
        }
        return dp.back();
    }
};

インタビューの質問17.09。K番目の数

一部の数値の素因数は3、5、7のみです。k番目の数値を見つけるアルゴリズムを設計してください。これらの素因数を持つ必要はありませんが、他の素因数を含んではいけないことに注意してください。たとえば、最初のいくつかの番号は、1、3、5、7、9、15、21の順にする必要があります。

これは前の質問とまったく同じですが、3つの要因の変化が比較的素数である限り、以前の解決策は実行可能です。

313.超醜い数

n番目の超醜い数を見つけるプログラムを書いてください。

超醜い数は、すべての素因数が長さkの素数リスト素数の正の整数であることを意味します。

例:

入力:n = 12、素数= [2、7、13、19]

出力:32

説明:長さ4の素数のリストを指定すると、素数= [2、7、13、19]、最初の12の超醜い数シーケンスは[1,2,4,7,8,13,14,16,19,26,28,32]です。

説明:

  1. 1は与えられた素数の非常に醜い数です。
  2. 指定された素数の数値は昇順でソートされます。
  3. 0 <k≤100、0 <n≤106、0 <primes [i] <1000。
  4. n番目の非常に醜い数値は、32ビットの符号付き整数の範囲内にあることが保証されています。

解決策:dpのアイデアは最後の醜い数字と同じですが、ここでの因数は配列になるため、対応するポインターの位置を格納するための配列も必要になります。位置を記録しますが、重複の問題があるため(素数の数が増えるため、同じ次の値で乗算できる複数の要因があり、これらのポインターはこの時点で+1である必要があります)、ベクトル判定を使用すると、タイムアウトになります。だから私はセットを考えましたが、セットの添え字は整数で記録できないので、イテレータが使用されています。これもポインタの概念を統合するためのものです。

コード:

class Solution {
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) {
        if(primes.empty()) return 0;
        int l = primes.size();
        set<int> dp;
        dp.insert(1);
        vector<set<int>::iterator> points;//point数组记录dp集合的迭代位置
        for(int i=0; i<l; i++)
            points.push_back(dp.begin());
        while(dp.size()<n){
            int minv = INT_MAX;
            int mini = 0;
            for(int i=0; i<l; i++) 
                minv = min(minv,*points[i]*primes[i]);//算出next值
            dp.insert(minv);
            for(int i=0; i<l; i++)
                if(*points[i]*primes[i]==minv) points[i]++;//迭代位置+1
        }
        return *dp.rbegin();//set的最后一位索引,取值。
    }
};

セットはあまり使用されていません。この問題は、セットのトラバースとイテレータの使用を完全に理解させ、多くのメリットをもたらすためです。

1201.醜い数III

n番目の醜い数を見つけるプログラムの設計を手伝ってください。

醜い数は、aまたはbまたはcで割り切れる正の整数です。

例:

入力:n = 3、a = 2、b = 3、c = 5

出力:4

説明:醜い数字のシーケンスは2、3、4、5、6、8、9、10で、3番目の数字は4です。 。

ヒント:

  1. 1 <= n、a、b、c <= 10 ^ 9
  2. 1 <= a * b * c <= 10 ^ 18
  3. この質問の結果は[1、2 * 10 ^ 9]の範囲です

解決策:データ範囲は10 ^ 9です。これはdpとは関係ありません。名前はまだ醜いです=。=これ

は、偉大な神の解決策への直接の参照です。実際、これは、古典的な公差と除外の原理を使用した数学的問題です。

リンク:https://leetcode-cn.com/problems/ugly-number-iii/solution/er-fen-fa-si-lu-pou-xi-by-alfeim/

コード:

class Solution {
public:
    using LL = long long;
    int nthUglyNumber(int n, int a, int b, int c) {
        //看到n的范围应该马上联想到是,典型的二分思路
        LL low = min(min(a,b),c);                           //下边界显然是a、b、c中最小者
        LL high = static_cast<LL>(low) * n;                 //上边界是这个最小者的n倍    
        LL res = Binary_Search(low,high,a,b,c,n);
        LL left_a = res%a;
        LL left_b = res%b;
        LL left_c = res%c;
        return res - min(left_a,min(left_b,left_c));
    }
    //二分搜索
    LL Binary_Search(LL low,LL high,int a,int b,int c,LL n){
        if(low >= high) return low;
        LL mid = (low + high)>>1;
        LL MCM_a_b = MCM(a,b);
        LL MCM_a_c = MCM(a,c);
        LL MCM_b_c = MCM(b,c);
        LL MCM_a_b_c = MCM(MCM_a_b,c);
        //独立的丑数个数为,当前数分别除以a、b、c的和,减去当前数除以a、b、c两两间最小公倍数的和,再加上当前数除以 a、b、c三者的最小公倍数
        LL count_n = mid/a + mid/b + mid/c - mid/MCM_a_b - mid/MCM_b_c - mid/MCM_a_c +  mid/MCM_a_b_c;
        if(count_n == n) return mid;
        if(count_n < n) return Binary_Search(mid + 1,high,a,b,c,n);
        return Binary_Search(low,mid-1,a,b,c,n);
    }
    //求最小公倍数:两数乘积除以最大公约数
    LL MCM(LL a,LL b){
        LL Multi = a * b;
        while(b > 0){
            LL tmp = a % b;
            a = b;
            b = tmp;
        }
        return Multi/a;
    }
};
元の記事を13件公開 27 件を獲得 2649件を表示

おすすめ

転載: blog.csdn.net/u011708337/article/details/105105850