灵神笔记(1)----动态规划篇

这篇具有很好参考价值的文章主要介绍了灵神笔记(1)----动态规划篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

介绍

本篇文章主要是观看"灵茶山艾府"动态规划篇视频后,做出的笔记。
视频链接如下
[动态规划入门:从记忆化搜索到递推]
[0-1背包,完全背包]
[最长公共子序列,编辑距离]

动态规划入门:从记忆化搜索到递推

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

打家劫舍

对于第i间房有两种抉择,选或者不选。选的话对应的子问题就是前i-2间房,不选的话对应的子问题就是前i-1间房。从第一间房子或者最后一间房子考虑受到的约束最小。假设从最后一间房子开始考虑,形成的递归树如下
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

上述思考过程可以抽象为:

  • 当前操作?枚举i个房子选或者不选。
  • 子问题?从i个房子中得到的最大金额和
  • 下一个子问题:
    不选,从i-1个房子中得到的最大金额和
    选,从i-2个房子中得到的最大金额和

dfs(i) = max(dfs(i-1),dfs(i-1)+nums[i])
在定义dfs或者dp数组时,无法从单个元素中获取结果,而是从某一些元素中获取结果。

递归

class Solution {
public:
    int rob(vector<int>& nums) {
        //i表示第i件物品选还是不选
        //dfs(i)表示偷取前i家获得的最大金额
        int n=nums.size();
        return dfs(nums,n-1);
    }
private:
    int dfs(const vector<int>&nums,int i){
        if(i<0) return 0;
        return max(dfs(nums,i-1),dfs(nums,i-2)+nums[i]);
    }
};
  • 这份代码会超出时间限制。时间复杂度是指数级别的为O(2n)
  • 仔细观察上述的递归树就会发现有很多重复计算的地方,所以可以用一个数组用来存储计算过的值,这样下次直接拿来用即可。

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

记忆化递归

class Solution {
public:
    int rob(vector<int>& nums) {
        //i表示第i件物品选还是不选
        //dfs(i)表示偷取前i家获得的最大金额
        int n=nums.size();
        vector<int>cache(n,-1);
        return dfs(nums,n-1,cache);
    }
private:
    int dfs(const vector<int>&nums,int i,vector<int>&cache){
        if(i<0) return 0;
        //记忆化
        if(cache[i]!=-1) return cache[i];
        cache[i]=max(dfs(nums,i-1,cache),dfs(nums,i-2,cache)+nums[i]);
        return cache[i];
    }
    
};
  • 时间复杂度=状态个数乘以单个状态花费的时间O(n*1)
  • 空间复杂度O(n)

递推

通过递归树和代码可以发现cache[i]的实际计算发生在向上"归"的过程中,所以我们直接省去向下"递"的过程只留下向上归的过程,这被称之为递推。

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

  • 自顶向下算=记忆化搜索
  • 自低向上算=递推

递归与递推

  • dfs->数组
  • 递归->循环
  • 递归边界->数组初始值
class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        vector<int>f(n+2,0);
        for(int i=0;i<n;++i){
            f[i+2]=max(f[i+1],f[i]+nums[i]);
        }
        return f[n+1];
    }
};
  • 空间复杂度为O(n)
  • 时间复杂度为O(n)

滚动变量

class Solution {
public:
    int rob(vector<int>& nums) {
        int n=nums.size();
        int f0=0,f1=0,newf;
        for(int i=0;i<n;++i){
            newf=max(f1,f0+nums[i]);
            f0=f1;
            f1=newf;
        }
        return f1;
    }
};

背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

0-1 背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

递归写法

int zero_one(const vector<int>&w,const vector<int>&v,int i,int c){
        if(i<0) return 0;
        //不选
        if(c<w[i]) return zero_one(w,v,i-1,c);
        return max(zero_one(w,v,i-1,c),zero_one(w,i-1,c-w[i])+v[i]);
    }

记忆化递归

int zero_one(const vector<int>&w,const vector<int>&v,int i,int c,vector<vector<int>>&cache){
        if(i<0) return 0;
        if(cache[i][c]!=-1) return cache[i][c];
        //不选
        if(c<w[i]) cache[i][c]=zero_one(w,v,i-1,c);
        else cache[i][c]=max(zero_one(w,v,i-1,c),zero_one(w,v,i-1,c-w[i])+v[i]);
        return cache[i][c];
    }

目标和

假设所有元素中,正数的和为p,所有元素的总和是s,目标和为t,则所有负数的和就是(s-p),这样一来就可以得到一个等式p-(s-p)=t。移项之后可以得到p=(s+t)/2;由于p是正数之和故(s+t)的值一定大于0.要想一分为二(s+t)的值一定要是偶数才可以。

记忆化搜索

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        //p个正值,s-p个负值
        //p-(s-p)=t p=(t+s)/2
        for(auto&n:nums) target+=n;
        if(target<0 || target&1) return 0;
        target/=2;
        vector<vector<int>> cache(nums.size(),vector<int>(target+1,-1));
        return zero_one(nums,nums.size()-1,target,cache);
    }
private:
    int zero_one(const vector<int>&nums,int i,int target,vector<vector<int>>&cache){
        if(i<0) {//所有的数都选过一遍
        /*
        还有一个条件就是目标和需要满足target。又由于target是倒着减的,所以target==0的时候就找到了一种目标方案,此时返回1.
        */
            if(target==0)  return 1;//找到一种方案
            return 0;
        }
        if(cache[i][target]!=-1) return cache[i][target];
        if(target<nums[i]) cache[i][target]=zero_one(nums,i-1,target,cache);
        else cache[i][target]=zero_one(nums,i-1,target,cache)+zero_one(nums,i-1,target-nums[i],cache);
        return cache[i][target];
    }             
};

递推

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        //p个正值,s-p个负值
        //p-(s-p)=t p=(t+s)/2
        for(auto&n:nums) target+=n;
        if(target<0 || target&1) return 0;
        target/=2;
        //递推
        int n=nums.size();
        vector<vector<int>> dp(n+1,vector<int>(target+1,0));
        dp[0][0]=1;   
        for(int i=1;i<=n;++i){
            for(int j=0;j<=target;++j){
                if(j<nums[i-1]) dp[i][j]=dp[i-1][j];
                else dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
            }
        }
        return dp[n][target];
    }        
};
  • 时间复杂度为O(n2)。
  • 空间复杂度为O((n+1)*(target+1))
    时间复杂度是不能再优化了,那空间复杂度可以优化吗?

两个数组

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        //p个正值,s-p个负值
        //p-(s-p)=t p=(t+s)/2
        for(auto&n:nums) target+=n;
        if(target<0 || target&1) return 0;
        target/=2;
        //递推
        int n=nums.size();
        vector<vector<int>> dp(2,vector<int>(target+1,0));
        //vector<int> dp(target+1,0);
        dp[0][0]=1;//递归边界就是dp数组的初始值   
        for(int i=1;i<=n;++i){
            //滚动数组是直接在原有的状态上进行覆盖,所以需要倒着覆盖 
            for(int j=0;j<=target;++j){
                if(j<nums[i-1]) dp[i%2][j]=dp[(i-1)%2][j];
                else dp[i%2][j]=dp[(i-1)%2][j]+dp[(i-1)%2][j-nums[i-1]];
            }
        }
        return dp[n%2][target];
    }        
    
};

一个数组

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        //p个正值,s-p个负值
        //p-(s-p)=t p=(t+s)/2
        for(auto&n:nums) target+=n;
        if(target<0 || target&1) return 0;
        target/=2;
        //递推
        int n=nums.size();
        vector<int> dp(target+1,0);
        dp[0]=1;   
        for(int i=1;i<=n;++i){
            //滚动数组是直接在原有的状态上进行覆盖,所以需要倒着覆盖
            for(int j=target;j>=nums[i-1];--j){
                dp[j]+=dp[j-nums[i-1]];
            }
        }
        return dp[target];
    }        
    
};

这里解释一下,为什么第二层循环需要倒着遍历

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包
如果是正着遍历,在i层的状态上修改i+1层的数据,
f[2] = f[2]+f[0]=3+1=4;
f[3] = f[3]+f[1]=5+2=7;
f[4] = f[4]+f[2]=6+4这里原本应该要加的是3,但是由于f[2]已经被覆盖了所以会变成加4,导致这里计算错误。

现在来看看倒着遍历会不会发生错误。
f[6] = f[6]+f[4] = 9+6=15;
f[5] = f[5]+f[3]= 7+5=12;
f[4] = f[4]+f[2] = 6+3=9;
f[3] = f[3]+f[1]= 5+2=7
f[2] = f[2]+f[0] = 3+1=4;
会发现这样计算不会出现错误。

完全背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

记忆化递归搜索

int dfs(const vector<int>&w,const vector<int> &v,int i,int c,vector<vector<int>>&cache){
        if (i<0){
            return  0;
        }
        if(cache[i][c]!=-1) return cache[i][c];
        if(c<w[i]) cache[i][c]=dfs(w,v,i-1,c,cache);
        else cache[i][c]=max(dfs(w,v,i-1,c,cache),dfs(w,v,i,c-w[i],cache)+v[i]);//i表示选过了还可以再选
        return cache[i][c];
    }
  • 与01背包类似,唯一不同的地方就是选中当前物品之后,下一个子问题还可以选取当前物品

零钱兑换

记忆化递归

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n=coins.size();
        vector<vector<int>> cache(n,vector<int>(amount+1,-1));
        
        int ans = dfs(coins,n-1,amount,cache);
        return ans==INT_MAX-1?-1:ans;
    }
private:
    int dfs(const vector<int>&ws,int i,int c,vector<vector<int>>&cache){
        if (i<0){
            if(c==0) return 0;
            return INT_MAX-1;
        }
        if(cache[i][c]!=-1) return cache[i][c];
        if(c<ws[i]) cache[i][c]=dfs(ws,i-1,c,cache);
        else cache[i][c]=min(dfs(ws,i-1,c,cache),dfs(ws,i,c-ws[i],cache)+1);
        return cache[i][c];
    }
};

递推

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int n=coins.size();
        
        vector<vector<int>> dp(n+1,vector<int>(amount+1,INT_MAX-1));
        dp[0][0]=0;
        
        //vector<vector<int>> cache(n,vector<int>(amount+1,-1));
        for(int i=1;i<=n;++i){
            for(int j=0;j<=amount;++j){
                if(j<coins[i-1]) dp[i][j]=dp[i-1][j];
                else dp[i][j] = min(dp[i-1][j],dp[i][j-coins[i-1]]+1);
            }
        }
        return dp[n][amount]==INT_MAX-1?-1:dp[n][amount];
    }

背包问题变形[至多|恰好|至少]

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包
这三种变形的不同在于它们的递归终止条件或者说动态规划的初始化条件
至多
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

恰好
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

至少
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

最长公共子序列

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

记忆化搜索

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int l1=text1.size(),l2=text2.size();
        
        vector<vector<int>> cache(l1,vector<int>(l2,-1));
        return dfs(text1,text2,l1-1,l2-1,cache);
    }
private:
    int dfs(const string&s,const string&t,int i,int j,vector<vector<int>>&cache){
        if(i<0 || j<0) return 0;
        
        if(cache[i][j]!=-1) return cache[i][j];
        if(s[i]==t[j]) cache[i][j]=dfs(s,t,i-1,j-1,cache)+1;
        else cache[i][j]=max(dfs(s,t,i-1,j,cache),dfs(s,t,i,j-1,cache));
        return cache[i][j];
    }
};

递推

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int l1=text1.size(),l2=text2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        for(int i=1;i<=l1;++i){
            for(int j=1;j<=l2;++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[l1][l2];
       
    }
};

两个一维数组

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int l1=text1.size(),l2=text2.size();
        vector<vector<int>> dp(2,vector<int>(l2+1,0));
        for(int i=1;i<=l1;++i){
            for(int j=1;j<=l2;++j){
                if(text1[i-1]==text2[j-1]) dp[i%2][j]=dp[(i-1)%2][j-1]+1;
                else dp[i%2][j]=max(dp[(i-1)%2][j],dp[i%2][j-1]);
            }
        }
        return dp[l1%2][l2];
       
    }
};

一维数组

由递推公式可以看出,f[i][j]由f[i-1][j-1]、f[i-1][j]、f[i][j-1]三个状态得出。先假设只用一个数组完成三个状态的转换,会发现f[i-1][j-1]的状态在使用的时候已经被更新了,所以需要先保存之前的状态。
灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int l1=text1.size(),l2=text2.size();
        vector<int> dp(l2+1,0);
        for(int i=1;i<=l1;++i){
            int pre=dp[0];
            for(int j=1;j<=l2;++j){
                int tmp=dp[j];
                if(text1[i-1]==text2[j-1]) dp[j]=pre+1;
                else dp[j]=max(dp[j],dp[j-1]);
                pre=tmp;
            }
        }
        return dp[l2];
       
    }
};

编辑距离

灵神笔记(1)----动态规划篇,算法与数据结构,笔记,动态规划,递归,记忆化递归,递推,01背包,完全背包文章来源地址https://www.toymoban.com/news/detail-846573.html

记忆化搜索

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size(),l2=word2.size();
        vector<vector<int>> cache(l1,vector<int>(l2,-1));
        return dfs(word1,word2,l1-1,l2-1,cache);
    }
private:
    int dfs(const string&w1,const string&w2,int i,int j,vector<vector<int>>&cache){
        if(i<0) return j+1;
        if(j<0) return i+1;
        if(cache[i][j]!=-1) return cache[i][j];
        if(w1[i]==w2[j]) cache[i][j]=dfs(w1,w2,i-1,j-1,cache);
        else cache[i][j]=min(dfs(w1,w2,i-1,j,cache),min(dfs(w1,w2,i,j-1,cache),dfs(w1,w2,i-1,j-1,cache)))+1;
        return cache[i][j];
    }
};

递推

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size(),l2=word2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        for(int i=1;i<=l2;++i) dp[0][i]=i;
        for(int i=1;i<=l1;++i) dp[i][0]=i;
        for(int i=1;i<=l1;++i){
            for(int j=1;j<=l2;++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],min(dp[i][j-1],dp[i-1][j-1]))+1;
            }
        }
        return dp[l1][l2];
    }
};
  • 时间复杂度为O(n*m)
  • 空间复杂度为O(n*m)

一个数组

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size(),l2=word2.size();
        vector<int> dp(l2+1,0);
        //for(int i=1;i<=l2;++i) dp[0][i]=i;
        //for(int i=1;i<=l1;++i) dp[i][0]=i;
        for(int i=1;i<=l2;++i) dp[i]=i;
        for(int i=1;i<=l1;++i){
            int pre=dp[0];
            dp[0]+=1;//相当于二维数组中的dp[i][0]=i;
            for(int j=1;j<=l2;++j){
                int tmp=dp[j];
                if(word1[i-1]==word2[j-1]) dp[j]=pre;
                else dp[j]=min(dp[j],min(dp[j-1],pre))+1;
                pre=tmp;
            }
        }
        return dp[l2];
    }
};
  • 时间复杂度为O(n*m)
  • 空间复杂度为O(m)

到了这里,关于灵神笔记(1)----动态规划篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构与算法-动态规划

    (我猜是做的多了背的题多了就自然懂了) 迭代法一般没有通用去重方式,因为已经相当于递归去重后了 这两个问题其实是一个问题,一般直接写出的没有去重的递归法,复杂度很高,此时需要使用备忘录去重,而备忘录去重时间复杂度和使用dp数组进行迭代求解时间复杂度相同

    2024年02月04日
    浏览(36)
  • python算法与数据结构---动态规划

    记不住过去的人,注定要重蹈覆辙。 对于一个模型为n的问题,将其分解为k个规模较小的子问题(阶段),按顺序求解子问题,前一子问题的解,为后一子问题提供有用的信息。在求解任一子问题时,通过决策求得局部最优解,依次解决各子问题。最后通过简单的判断,得到

    2024年02月20日
    浏览(40)
  • 数据结构与算法之贪心&动态规划

            一:思考         1.某天早上公司领导找你解决一个问题,明天公司有N个同等级的会议需要使用同一个会议室,现在给你这个N个会议的开始和结束 时间,你怎么样安排才能使会议室最大利用?即安排最多场次的会议?电影的话 那肯定是最多加票价最高的,入场

    2024年02月09日
    浏览(38)
  • 数据结构与算法 | 动态规划算法(Dynamic Programming)

    上一篇文末已经提到了记忆化搜索是动态规划(Dynamic Programming)的一种形式,是一种自顶向下(Top-Down)的思考方式,通常采用递归的编码形式;既然动态规划有自顶向下(Top-Down)的递归形式,自然想到对应的另外一种思考方式 自底向上( Bottom-Up ) ,也就是本篇要写的内

    2024年02月05日
    浏览(35)
  • Java数据结构与算法----动态规划(背包篇)

    1.1.算法思路 0/1背包是动态规划、背包问题中最经典的问题啦!它主要的问题是: 给定n种物品、这n种物品的重量分别是,价值分别是 ,而你有一个容量为C的背包,请问如何求出所能拿的最大价值呢? 对于动态规划,我们先需要找到一条推导公式,然后确定边界: 我们设

    2024年02月07日
    浏览(42)
  • 数据结构与算法:动态规划(Dynamic Programming)详解

    动态规划(Dynamic Programming,简称DP) 是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划经常被用于求解优化问题。 动态规划的核心思想是将复杂问题分解为更小的子问

    2024年04月25日
    浏览(40)
  • python数据结构与算法-动态规划(最长公共子序列)

    一个序列的子序列是在该序列中删去若干元素后得 到的序列。 例如:\\\"ABCD”和“BDF”都是“ABCDEFG”的子序列。 最长公共子序列(LCS) 问题: 给定两个序列X和Y,求X和Y长度最大的公共子字列。 例:X=\\\"ABBCBDE”Y=\\\"DBBCDB”LCS(XY)=\\\"BBCD\\\" 应用场景:字符串相似度比对 (1)问题思考 思考: 暴

    2024年02月08日
    浏览(42)
  • 【夜深人静学数据结构与算法 | 第十篇】动态规划

    目录 前言: 动态规划: 常见应用: 解题步骤:  动态规划的简化步骤: 案例: 509. 斐波那契数 - 力扣(LeetCode) 70. 爬楼梯 - 力扣(LeetCode) 62. 不同路径 - 力扣(LeetCode) 总结:         本文我们将为大家讲解一下动态规划的理论知识,并且会讲解几道力扣的经典例题。

    2024年02月11日
    浏览(47)
  • ​Python—数据结构与算法​---动态规划—DP算法(Dynamic Programing)

    目录 我们一路奋战, 不是为了改变世界, 而是为了不让世界改变我们。 动态规划——DP算法(Dynamic Programing) 一、🏔斐波那契数列(递归VS动态规划) 1、🐒斐波那契数列——递归实现(python语言)——自顶向下 2、🐒斐波那契数列——动态规划实现(python语言)——自底

    2024年02月10日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包