サブセットの完全な順列と組み合わせ数の問題(11のOJ質問を簡単に把握できます)

コンテンツ

1.サブセット

2.サブセットII

3.フルアレンジ

4.完全な順列II

5.キャラクターの完全な配置

6.ストリングサイズのフルアレンジメント

7.合計 

 8.複合合計II

9.合計III

10.合計IVの合計

11.サブシーケンスのインクリメント


1.サブセット

ソードポイントオファーII079。すべてのサブセット-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

1.暴力的な列挙で十分です。つまり、各要素には2つの選択肢があり、1つは必要であり、もう1つはそうではありません。それらすべてを列挙することができます。例として配列[1,2,3]を取り上げます。

要約すると:

①再帰ツリーを描画し、状態変数(バックトラッキング関数のパラメーター)を見つける、このステップは非常に重要
です*②質問の意味に従って、終了条件を確立します
③密接に関連する選択リスト(関数パラメーターに関連する)を見つけます最初のステップに関連する
*④判断する必要があるかどうか
⑤選択を行い、再帰的に呼び出して、次のレイヤーに入る✧選択を
元に戻す

対応するプロセス図:

対応するコード:

class Solution {
public:
           vector<vector<int>>ans;//记录答案
    vector<vector<int>> subsets(vector<int>& nums) {
                vector<int>tmp;
                process(nums,0,tmp);
                return ans;
    }
    void process(vector<int>&nums,int index,vector<int>&tmp){
                    if(nums.size()==index){
                       ans.push_back(tmp);
                       return;
                    }
                    //不选择当前的数
                    process(nums,index+1,tmp);
                    
                    tmp.push_back(nums[index]);
                    //选择当前的数
                    process(nums,index+1,tmp);
                    tmp.pop_back();//递归回来的时侯删掉
    }
};

2.サブセットII

90.サブセットII-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問と前の質問の違いは、配列に繰り返し要素があるため、数値が選択されているか、数値が選択されていないときにサブセットが繰り返される可能性があり、質問ではサブセットが繰り返されないようにする必要があることです。ここで整理する必要があります。これが配列の例です[1,2,2,3]

 ここには重複するサブセットがあるため、重複を除外する方法、つまり、同じ深さの2つの異なるブランチで、現在の要素が前の要素と同じである場合はスキップします。以下に示すように。

対応するコード:

class Solution {
public:
        vector<int>tmp;
        vector<vector<int>>ans;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(),nums.end());//注意一定要排序
        //方便去重
       process(nums,0,INT_MAX);
       return ans;
    }
    void process(vector<int>&nums,int index,int prev)
    //prev代表前一个已经被选择的元素
    {
        if(index==nums.size())//如果已经到了最后了
        {
            ans.push_back(tmp);
            return;
        }
        tmp.push_back(nums[index]);//选择当前的数
        process(nums,index+1,nums[index]);
        tmp.pop_back();//撤回选择
       if(nums[index]!=prev)//如果当前数和前一个选择的数不相等才进行下一个分支
        process(nums,index+1,prev);

    }
};

3.フルアレンジ

46.完全な配置-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

すべての要素はそれを試すことができる位置にあり、すべての位置が試されます。各要素は、それ自体とそれ自体の後ろの位置のみを試すことができます。以下は、配列[1,2,3]の完全な順列を示しています。

対応するコード:

class Solution {
public:
           vector<vector<int>>ans;
    vector<vector<int>> permute(vector<int>& nums) {
          process(nums,0);
          return ans;
    }
    void process(vector<int>&nums,int i)
    {
        if(i==nums.size())//不能交互
        {
            ans.push_back(nums);
            return;
        }
        for(int j=i;j<nums.size();j++)
        {
            swap(nums[i],nums[j]);//来到当前位置
            process(nums,i+1);
            swap(nums[i],nums[j]);//撤销决定
        }
    }
    
};

4.完全な順列II

対応するletecodeリンク:

47.フルアレンジメントII-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問と前の質問の唯一の違いは、配列内に要素が繰り返されていることです。したがって、重複排除処理を行わないと、完全な順列が確実に繰り返されます。では、重複排除する必要があります。どのように重複排除するのでしょうか。現在の位置を記録するためにハッシュテーブルを定義するだけでよく、到達した次の位置がすでにハッシュテーブルに記録されている場合はスキップします。

詳細については、コードを参照してください。

class Solution {
public:
           vector<vector<int>>ans;//记录答案
    vector<vector<int>> permuteUnique(vector<int>& nums) {
       sort(nums.begin(),nums.end());//排序更容易去重不排序也行
              process(nums,0);
              return ans;
    }
    void process(vector<int>&nums,int i)
    {
        if(i==nums.size())
        {
            ans.push_back(nums);
        }
        unordered_set<int>Hash;//记录访问过了的位置
        for(int j=i;j<nums.size();j++)
        {
            if(!Hash.count(nums[j]))//查看上次来到的位置和这次来到位置是否相同
            {
                Hash.insert(nums[j]);
                swap(nums[i],nums[j]);
                process(nums,i+1);//去下一个位置
                swap(nums[i],nums[j]);//撤销决定
            }
        }
    }
    
};

5.キャラクターの完全な配置

ソードポイントオファー38.文字列の配置-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問と前の質問の間に違いはありませんが、一方が数字でもう一方が文字である点が異なります。このアイデアはすでに質問で言及されており、ここではコードのみが示されています。

対応するコード:

class Solution {
public:
       vector<string>ans;
    vector<string> permutation(string s) {
           process(s,0);
           return ans;
    }
    void process( string&str,int i)
    {
        if(i==str.size())
        {
            ans.push_back(str);
        }
        vector<bool>isVisit(256,false);
        for(int j=i;j<str.size();j++)
        {
           if(!isVisit[str[j]-'a'])
           {
           isVisit[str[j]-'a']=true;
            swap(str[i],str[j]);
            process(str,i+1);
            swap(str[i],str[j]);
           }

           
        }
    }
};

6.ストリングサイズのフルアレンジメント

784.大文字と小文字の完全な配置-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

各位置が文字の場合、2つのオプションがあります。1。変更なし、2は大文字または小文字になります。キャラクターでない場合は、直接次の位置に移動します。

7.合計 

39.合計-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問の同じ2つの要素について、2つのオプションがあります。選択するか選択しないかですが、この質問の各要素は何度でも選択できます。前の方法に従ってください。

①再帰ツリーを描画し、状態変数(バックトラッキング関数のパラメーター)を見つける、このステップは非常に重要
です*②質問の意味に従って、終了条件を確立します
③密接に関連する選択リスト(関数パラメーターに関連する)を見つけます最初のステップに関連する
*④判断する必要があるかどうか
⑤選択を行い、再帰的に呼び出して、次のレイヤーに入る✧選択を
元に戻す

この質問では、最初に配列を並べ替えて、再帰プロセスでのプルーニングを容易にすることができます。詳細については、コードを参照してください。 

対応するコード:

class Solution {
public:
        vector<vector<int>>ans;//记录答案
        vector<int>tmp;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
           sort(candidates.begin(),candidates.end());//排序方便后序剪枝优化
           dfs(candidates,target,0);
           return ans;
    }
    void dfs(vector<int>&nums,int target,int index)
    {
        if(target==0)//是否已经找到答案了
        {
         ans.push_back(tmp);
         return;
        }
       
       for(int i=index;i<nums.size();i++)
       {
           if(nums[i]>target)//如果当前数大于target由于数组有序所以后面的数都是大于target
           //不可能在凑出target
           {
              return;
           }
           tmp.push_back(nums[i]);//选择当前的数
           dfs(nums,target-nums[i],i);//注意不是i+1而是i因为一个数可以选择多次
           tmp.pop_back();//撤销决定
       }
    }
};

 8.複合合計II

40.結合された合計II-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問と前の質問の間に基​​本的に変更はありませんが、重複排除するためのもう1つのステップがあります。

対応するコード:

class Solution {
public:
          vector<vector<int>>ans;
          vector<int>tmp;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
              process(candidates,target,0);
              
              return ans;
    }
    void process(vector<int>&nums,int target,int index)
    {
        if(target==0)
        {
           ans.push_back(tmp);
           return; 
        }
        for(int i=index;i<nums.size();i++)
        {
            if(i>index&&nums[i]==nums[i-1])//防止出现重复元素
            {
                continue;
            }
            if(nums[i]>target)//有序数组后面不可能搞出target了
            {
                return;
            }
            tmp.push_back(nums[i]);//选择当前的数
            process(nums,target-nums[i],i+1);
            tmp.pop_back();//撤销选择

        }
    }
};

9.合計III

216.結合された合計III-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この質問の問題解決の考え方は、基本的に前の質問と同じであり、繰り返す必要はありません。これは、前の質問の弱体化されたバージョンです。詳細については、コードを参照してください。

対応するコード:

class Solution {
public:
  vector<int>tmp;
  vector<vector<int>>ans;
    vector<vector<int>> combinationSum3(int k, int n) {
        process(k,1,n);
        return ans;
    }
    void process(int k,int index,int taraget)
    {
        if(k==0&&taraget==0)//说明已经找到了
        {
            ans.push_back(tmp);
            return;
        }
        for(int i=index;i<=9;i++)//从index到9;
        {
            tmp.push_back(i);//选择当前的数
            process(k-1,i+1,taraget-i);
            tmp.pop_back();//撤回选择

        }
    }
};

10.合計IVの合計

377.組み合わせ合計IV-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

この問題の問題解決のアイデアは、依然として激しい再帰です。前の問題との違いは、毎回位置0から開始できることです。詳細については、コードを参照してください。

対応するコード:

int combinationSum4(vector<int>& nums, int target) {
      
          return process(nums,target);
    }

    int process(vector<int>&nums,int target)
    {
        if(target<0)//如果target小于0那么没有方法数
        {
            return 0;
        }

        if(target==0)//到0了
        {
            return 1;
        }
        int ans=0;
        for(int i=0;i<nums.size();i++)
        {
            ans+=process(nums,target-nums[i]);//选择当前的数
        }
      
        return ans;//返回答案即可
    }

ただし、計算が何度も繰り返されるため、このメソッドが直接タイムアウトするのは非常に残念です。

この問題を解決するには、メモ化された検索と動的計画法の2つの方法があります。両方の方法を以下に示します。

メモ化された検索:実際には非常に簡単です。dpテーブルを定義して、回答を返す前に事前に記録することができます。回答が記録されていることがわかった場合は、再帰的な繰り返し計算を呼び出さずに直接戻ることができます。

class Solution {
public:
    vector<int>dp;
    int combinationSum4(vector<int>& nums, int target) {
      
            dp.resize(target+1);
          return Dp(nums,target);
    }

    int process(vector<int>&nums,int target)
    {
       
        if(target==0)
        {
            return 1;
        }
        if(target<0)
        {
            return 0;
        }
         if(dp[target]!=-1)//说明之前已经计算过了
        {
            return dp[target];
        }
        int ans=0;
        for(int i=0;i<nums.size();i++)
        {
            ans+=process(nums,target-nums[i]);
        }
        dp[target]=ans;//提前将答案记录一下
        return ans;//返回答案
    }

動的計画法のバージョンは、再帰に基づいて登場します。

class Solution {
public:
    vector<int>dp;
    int combinationSum4(vector<int>& nums, int target) {
      
              dp.resize(target+1);
          return Dp(nums,target);
    }

    int process(vector<int>&nums,int target)
    {
       
        if(target==0)
        {
            return 1;
        }
        if(target<0)
        {
            return 0;
        }
         if(dp[target]!=-1)//说明之前已经计算过了
        {
            return dp[target];
        }
        int ans=0;
        for(int i=0;i<nums.size();i++)
        {
            ans+=process(nums,target-nums[i]);
        }
        dp[target]=ans;//提前将答案记录一下
        return ans;//返回答案
    }
    int Dp(vector<int>&nums,int target)
    {
        vector<int>dp(target+1);
        dp[0]=1;//根据递归函数的base填的
        for(int i=1;i<=target;i++)
        {
         long long  ans=0;
        for(int j=0;j<nums.size();j++)//这一部分直接抄递归函数
        {
            if(i-nums[j]>=0)//组合数小于0
            {
              ans+=dp[i-nums[j]];
            } 
        }
        dp[i]=ans;
        }
        return dp[target];//根据递归函数的调用确定返回值
    }

};

11.サブシーケンスのインクリメント

491.インクリメンタルサブシーケンス-LeetCode(leetcode-cn.com)

トピックの説明:

 問題解決のアイデア:

要素の選択と非選択の選択も同じですが、この質問には増やす必要があるため、再帰関数を定義するときに、配列内の前の要素を表すパラメーターを追加して、この数が選択されているかどうか。この番号が選択されている場合、重複排除が実行されます。例として[6、7、7、7]を取り上げます。

 対応するコード:

class Solution {
public:
     vector<int>tmp;
     vector<vector<int>>ans;
    vector<vector<int>> findSubsequences(vector<int>& nums) {
          dfs(nums,0,INT_MIN);
          return ans;
    }
    void dfs(vector<int>&nums,int index,int pre)
    {
        if(index==nums.size())//达到了答案
        {
            if(tmp.size()>=2)//判断是否满足至少有两个数
            {
                ans.push_back(tmp);
            }
            return;
        }

       
        if(nums[index]>=pre)//判断是否满足条件
        {
          tmp.push_back(nums[index]);
          dfs(nums,index+1,nums[index]);
          tmp.pop_back();
        }

        if(nums[index]!=pre)//出重
        {
             dfs(nums,index+1,pre);
        }
    }
};

おすすめ

転載: blog.csdn.net/qq_56999918/article/details/123925989