zoukankan      html  css  js  c++  java
  • 动态规划 回溯和较难题

    背包问题

    01背包

    每个物品选择一次,选或者不选
    dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); //背包容量是减少的

    • 二维初始化
      因为是从i-1转换来的,所以初始化i=0的情况
    // 倒叙遍历 正序则第一个重复放置  
    for (int j = bagWeight; j >= weight[0]; j--) {
        dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况
    }
    
    
    • 转换一维
      dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。
      dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    //顺序不可颠倒,先物品后容量,并且容量倒叙  
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    
        }
    }
    
    完全背包

    每个物品可以多次选取

    字符串问题

    lt1143 最长公共子序列(长度)

    dp[i][j]

      int longestCommonSubsequence(string text1, string text2) {
            int m = text1.size();
            int n = text2.size();
            int dp[m+1][n+1];
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=m;i++)
                for(int j=1;j<=n;j++)
                {
                    if(text1[i-1]==text2[j-1])
                    {
                        dp[i][j] = dp[i-1][j-1]+1;
                    }
                    else{
                        dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                    }
                }
            return dp[m][n];
    
    牛客 最长公共子序列(具体序列)

    得到dp数组后,从后向前推导
    图片

     string LCS(string s1, string s2) {
            // write code here
            //不符合条件返回-1
            if(s1.size()==0||s2.size()==0) return "-1";
            int m = s1.size();
            int n = s2.size();
             string res = "";
            int dp[m+1][n+1];
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=m;i++)
                for(int j= 1;j<=n;j++)
                {
                    if(s1[i-1]==s2[j-1])
                        dp[i][j] = dp[i-1][j-1]+1;
                    else
                    {
                        dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                    }
                }
            //没有公共子序列 返回"-1"
            if(dp[m][n]==0) return "-1";
            while(dp[m][n])
            {
                if(dp[m-1][n]==dp[m][n-1]&&dp[m][n]>dp[m-1][n-1])
                {
                    res+=s1[m-1];
                    //res.push_back(s1[m-1]);
                    m--;
                    n--;
                }
                else if(dp[m-1][n]>dp[m][n-1])
                    m--;
                else{
                    n--;
                }
            }
            reverse(res.begin(),res.end());
            return res;
        }
    
    牛客 最长公共子串(具体序列)

    子串是连续的,判断简单,找到最长和i或者j位置,反推

     string LCS(string str1, string str2) {
            // write code here
            //保留最长子串的值和位置
            int m = str1.size();
            int n = str2.size();
            if(m==0||n==0)return "-1";
            int dp[m+1][n+1];
            memset(dp,0,sizeof(dp));
            int len = 0,end = 0;
            for(int i=1;i<=m;i++)
                for(int j=1;j<=n;j++)
                {
                   if(str1[i-1]==str2[j-1])
                   {
                        dp[i][j] = dp[i-1][j-1]+1;
                            //判断是否更大
                   }
                   if(dp[i][j]>len)
                        {
                            end = i;
                            len = dp[i][j];
                         }
                }
            if(len==0)return "-1";
            //本质end-start+1 = len 得到 start= end-len+1  因为end是下标后面一位,所以start = end-len
            return str1.substr(end-len,len);
        }
    
    编辑字符串

    字节二面
    给定两个字符串str1和str2,再给定三个整数ic,dc和rc,分别代表插入、删除和替换一个字符的代价,请输出将str1编辑成str2的最小代价。

    "abc","adc",5,3,2 返回2

    注意是把str1编辑成str2,开始理解错题意了
    dp[i][j] 是str1前i个字符转换到str2前j个字符需要的次数
    1.dp[i][j-1] 是str1前i和str2前j-1子问题,对于str2的第j个字符,str1插入一个即可,所以dp[i][j] = dp[i][j-1]+ic
    2.dp[i-1][j] 是str1前i-1和str2前j子问题,对于str1的第i个字符,删除第i个,所以 dp[i][j] = dp[i-1][j]+dc
    3.dp[i-1][j-1] 同理 dp[i][j] = dp[i-1][j-1]+rc

    int minEditCost(string str1, string str2, int ic, int dc, int rc) {
            // write code here
           int m = str1.size();
           int n = str2.size(); 
           vector<vector<int>>dp(m+1,vector<int>(n+1,0));
            //str1的前n个
           for(int i=0;i<=m;i++)
           {
               dp[i][0] = i*dc;
           }
           for(int j=0;j<=n;j++)
           {
               dp[0][j] = j*ic;
               
           }
            
           for(int i=1;i<=m;i++)
               for (int j=1;j<=n;j++)
               {
                  //取出为i-1和j-1,因为 dp[i][j]代表i个j个,索引位置是从0开始的
                  char one = str1[i-1];
                  char two = str2[j-1];
                  if(one==two)
                      dp[i][j] = dp[i-1][j-1];
                  else{
                      //理解这部分 和j-1 匹配少一个,和j匹配多一个
                      int insert = dp[i][j-1]+ic;
                      int del = dp[i-1][j]+dc;
                      int replace = dp[i-1][j-1]+rc;
                      dp[i][j] = min(insert,min(del,replace));
                  }
               }
            return dp[m][n];
        }
    
    lt72 编辑距离

    单词word1到word2 需要的最少编辑次数

    int minDistance(string word1, string word2) {
        int m = word1.size();
        int n = word2.size();
        //dp[i][j]代表word1的前i转变为word2前j需要的最少次数
        vector<vector<int>>dp(m+1,vector<int>(n+1));
        
        for(int i=0;i<=m;i++)
        {
            dp[i][0] = i;
        }
        for(int j = 0;j<=n;j++)
        {
            dp[0][j] = j;
        }
    
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                //相等不改变
                if(word1[i-1]==word2[j-1])
                {
                    dp[i][j] = dp[i-1][j-1];
                }
                else{//不等取最小的
                    dp[i][j] = min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;
                }
                
            }
        }
         return dp[m][n];
        }
    
    offer19 正则表达匹配

    .代表匹配任意字符
    *代表前面字符的0次或者多次(可以和前面的字符一起消失)

     bool isMatch(string s, string p) {
            int m = s.size();
            int n  =p.size();
            vector<vector<bool>>dp(m+1,vector<bool>(n+1));
            
            for (int i=0;i<=m;i++){
                for(int j=0;j<=n;j++){
                    //匹配串为空
                    if(j==0)
                    {
                        dp[i][0] = i==0;
                    }else{
                        //非空串  
                        if(p[j-1]!='*'){
                            if(i>0&&(s[i-1]==p[j-1]||p[j-1]=='.'))
                            dp[i][j] = dp[i-1][j-1];
                        }else{
                            //去掉*和它前面的字符
                            if(j>=2)
                            dp[i][j] = dp[i][j]||dp[i][j-2];
    
                            //*前字符和i位置相等或者*前为.
                            if(i>0&&j>=2&&(s[i-1]==p[j-2]||p[j-2]=='.'))
                            dp[i][j] = dp[i][j]||dp[i-1][j];
                            
                            
                        }
                    }
                }
            }
            return dp[m][n];
        }
    
    通配符匹配

    ? 代表任意字符

    • 代表任意字符串包括空串
      dp[i][j]代表s的前i和p的前j是否匹配
    bool isMatch(const char *s, const char *p) {
            int m = strlen(s);
            int n = strlen(p);
            vector<vector<bool>>dp(m+1,vector<bool>(n+1));
            //特殊情况 p全是*时是可以匹配的
             for(int i=1;i<=n;i++)
            {
                if(p[i-1]!='*')
                break;
                dp[0][i] = true;
            }
            
            for(int i=0;i<=m;i++)
            {
                for(int j=0;j<=n;j++)
                {
                    if(j==0)
                    {
                        dp[i][j]=(i==0);
                    }else{
                        if(p[j-1]!='*')
                        {
                            if(i>0&&(p[j-1]=='?'||p[j-1]==s[i-1]))
                                dp[i][j] = dp[i-1][j-1];
                        }else{
                            //直接去除  
                            if(i>=1)
                                dp[i][j] = dp[i][j]||dp[i-1][j]||dp[i][j-1];
                        }
                    }
                }
            }
            return dp[m][n];
        }
    
    lt76 最小覆盖子串

    输入:s = "ADOBECODEBANC", t = "ABC"
    输出:"BANC"
    滑动窗口 两个map分别记录t串和s中t字符出现次数

     //hash t已经存在的   count 是记录过程
        unordered_map<char,int>hash,count;
        bool check()
        {
            //从已经计算好的t串hash判断count
            for (unordered_map<char,int>::iterator iter = hash.begin();iter!=hash.end();iter++){
                if(count[iter->first]<iter->second)
                    return false;
            }
            return true;
        }
    
        string minWindow(string s, string t) {
                int m = s.size();
                int n = t.size();
                //t串放入hash
                for(int i=0;i<n;i++)
                {
                    hash[t[i]]++;
                }
    
                int len=INT_MAX,resL=-1,l = 0,r = -1;
                while(r<m)
                {
                    //访问的字符在hash存在则记录字符出现个数
                    if(hash.find(s[++r])!=hash.end())
                    {
                        count[s[r]]++;
                    }
                    //符合条件,则l移动缩短字符串长度
                    while(check()&&l<=r)
                    {
                        if(r-l+1<len)
                        {
                            len = r-l+1;
                            resL = l;
                        }
                        //向右移动时,当前字符在hash中则count数减少
                        if(hash.find(s[l])!=hash.end())
                         {
                                count[s[l]]--;
                        }
                         l++;
                    }
                }
                return resL==-1?"":s.substr(resL,len);
        }
    
    

    排列组合回溯

    lt22括号生成

    输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]
    回溯,左括号数量大于右括号数量则不符合规则

    vector<string>res;
        string path;//全局变量
        vector<string> generateParenthesis(int n) {
            string path = "";
            backtrack(n,n);
            return res;
        }
        //回溯剪枝
        void backtrack(int left,int right)
        {
            if(left>right)
            return ;
    
            if(left==0&&right==0)
            {
                res.push_back(path);
                return ;
            }
            if(left>0)
            {
                path.push_back('(');
                backtrack(left-1,right);
                path.pop_back();
            }
            if(right>0)
            {
                path.push_back(')');
                backtrack(left,right-1);
                path.pop_back();
            }
        }
    
    • 全排列问题
      使用used标识是否访问过,每层从0开始不用start,可能需要对字符进行排序
      两种,一种可以重复,第二种不可重复,假设abb,全排列只要一个abb,两种used标识方法不同

    子集问题: 需要使用start标识层数
    N皇后,每层需要start确定层数

    lt46 无重复数组全排列

    回溯,没有重复数组的全排列

    vector<vector<int>>res;
        vector<vector<int>> permute(vector<int>& nums) {
            if(nums.size()==0)return {};
            int m = nums.size();
            vector<bool>used(m+1,false);
            vector<int>path;
            backtrack(nums,path,used);
            return res;
        }
    
        //每次从0开始,不需要计算层数的标识start
        void backtrack(vector<int>nums,vector<int>&path,vector<bool>used)
        {
            if(path.size()==nums.size())
            {
                res.push_back(path);
                return ;
            }
    
            for(int i=0;i<nums.size();i++)
            {
                if(!used[i])
                {
                    path.push_back(nums[i]);
                    used[i] = true;
                    backtrack(nums,path,used); 
                    used[i] = false;
                    path.pop_back();
                }
            }
        }
    
    lt47 有重复数组全排列

    有重复需要排序,并且判断是否重复

     vector<vector<int>> permuteUnique(vector<int>& nums) {
            //有序,判断前置,同层不计算
            vector<int>path;
            vector<bool>used(nums.size(),false);
            sort(nums.begin(),nums.end()); //有重复需要排序
            backtrack(nums,path,used);
            return res;
            
        }
        
        //每次从0开始,不需要计算层数的标识start
        void backtrack(vector<int>nums,vector<int>&path,vector<bool>used)
        {
            if(path.size()==nums.size())
            {
                res.push_back(path);
                return ;
            }
    
            for(int i=0;i<nums.size();i++)
            {
                
                if(!used[i])
                {
                    //两个重复前一个没使用则表示重复,跳过这次
                    if(i>0&&nums[i]==nums[i-1]&&!used[i-1])
                    continue;
                    path.push_back(nums[i]);
                    used[i] = true;
                    backtrack(nums,path,used); 
                    used[i] = false;
                    path.pop_back();
                }
            }
        }
    
    

    golang版本

    var res [][]int
    var path []int
    func permuteUnique(nums []int) [][]int {
        if len(nums)==0{
            return res
        }
        res = make([][]int,0)
        path = make([]int,0)
    
        used:=make([]bool,len(nums))
        sort.Ints(nums)
        dfs(nums,used)
        return res
    }
    
    func dfs(nums []int, used []bool){
        if len(path)==len(nums){
            ret:=make([]int,len(path))
            copy(ret,path)
            res = append(res,ret)
            return 
        }
    
    
        for i:=0;i<len(nums);i++{
            if i>0&&nums[i-1]==nums[i]&&used[i-1]==false{
                continue
            }
    
            if used[i]{
                continue
            }
            path = append(path,nums[i])
            used[i] = true
            dfs(nums,used)
            used[i] = false
            path = path[:len(path)-1]
        }
    }
    
    
    牛客 字符串的排列

    abc 所有排列方式,按照字典序

    • set排序递归
      时间O(n!)
    set<string>hash;
        vector<string> Permutation(string str) {
            if(str=="")return vector<string>();
            dfs(str,0);
            return vector<string>(hash.begin(),hash.end());
        }
        void dfs(string &str,int start)
        {
            if(start==str.size())
            {
                hash.insert(str);
                return ;
            }
            for(int i = start;i<str.size();i++)
            {
                swap(str[start],str[i]);
                dfs(str,start+1); 
                swap(str[start],str[i]);
            }
        }
    
    
    • 回溯使用used标记
      每层从0开始,不可重复2需要对初始字符串排序if(i>0&&nums[i]nums[i-1]&&!used[i-1])
      **子集问题判断条件为if(i>start&&nums[i]
      nums[i-1])**
    vector<string>res;//全局记录
        vector<string> Permutation(string str) {
            if(str.size()==0) return {};
            string path = "";
            
            vector<bool>used(str.size());
            backtrack(str,path,used);
            return res;
        }
        
        void backtrack(string str,string &path,vector<bool>&used)
        {
            if(path.size()==str.size())
            {
                res.push_back(path);
                return ;
            }
            
            for(int i=0;i<str.size();i++)
            {
                if(!used[i])
                {    //之前记录过一次不用再次重复记录
                    if(i>0&&str[i]==str[i-1]&&!used[i-1])
                    {
                        continue;
                    }
                    path.push_back(str[i]);
                    used[i]=true;
                    backtrack(str,path,used);
                    used[i]=false;
                    path.pop_back();
                }
            }
        }
    

    较难

    牛客 最长的括号子串
    • 栈的方式
      时空 O(n) O(n)
    int longestValidParentheses(string s) {
            // write code here
            stack<int>sta;
            int last = -1,maxVal = 0;
            for(int i=0;i<s.size();i++)
            {
                if(s[i]=='(') sta.push(i);
                else{
                    //没有(重新确定起始位置
                    if(sta.empty())last = i;
                    else{
                        sta.pop();
                        maxVal = sta.empty()?max(maxVal,i-last):max(maxVal,i-sta.top());
                    }
                }
            }
            return maxVal;
        }
    
    • 动态规划
    牛客 数组中的逆序对

    数组前面一个数字大于后面的数字,则为逆序对,求所有逆序对
    归并的时候计算

    var sum  int = 0
    func reversePairs(nums []int) int {
    	sum = 0
    	Sort(nums,0,len(nums)-1)
    	return sum
    }
    
    func Sort(nums []int,left,right int){
    	if left>=right{
    		return
    	}
    	mid:=left+(right-left)/2
    	Sort(nums,left,mid)
    	Sort(nums,mid+1,right)
    	merge(nums,left,mid,right)
    }
    
    //归并,计算,当前面的数据大于后面则,left剩余的与right当前都为逆序对
    func merge(nums []int,start,mid,end int){
    	var tmp []int
    	l:=start
    	r:=mid+1
    	for l<=mid&&r<=end{
    		if nums[l]<=nums[r]{
    			tmp =  append(tmp,nums[l])
    			l++
    		}else{
    			sum+=mid-l+1
    			tmp = append(tmp,nums[r])
    			r++
    		}
    	}
    	for l<=mid{
    		tmp = append(tmp,nums[l])
    		l++
    	}
    	for r<=end{
    		tmp = append(tmp,nums[r])
    		r++
    	}
    	//start 开始的一段数据,不能覆盖nums太多
    	for i:=0;i<len(tmp);i++{
    		nums[i+start] = tmp[i]
    	}
    }
    
    牛客 汉诺塔

    每个步骤字符串,递归

    var res []string
    func getSolution( n int ) []string {
        // write code here
        res=make([]string,0)
        move(n,"left","mid","right")
        return res
    }
    
    // 从left 借助mid到right
    func move(n int,left string,mid string,right string){
        if n==0{
            return 
        }
        move(n-1,left,right,mid)
        res  = append(res,"move from "+left+" to "+right)
        move(n-1,mid,left,right)
    }
    
  • 相关阅读:
    【CF741D】Arpa’s letter-marked tree and Mehrdad’s Dokhtar-kosh paths(dsu on tree)
    【CF600E】Lomsat gelral(dsu on tree)
    【Vijos】lxhgww的奇思妙想(长链剖分)
    【HDOJ6586】String(枚举)
    【HDOJ6578】Blank(DP)
    【HDOJ6583】Typewriter(SAM,DP)
    JAVA 集合 List 分组的两种方法
    HashMap的实现原理
    HashMap, HashTable, CurrentHashMap的区别
    java ThreadLocal(应用场景及使用方式及原理)
  • 原文地址:https://www.cnblogs.com/9527s/p/14388128.html
Copyright © 2011-2022 走看看