动态规划算法及优化

数学归纳法

Step1:验证K0成立
Step2:证明如果K1成立,那么K(i+1)也成立
Step3:联合Step1与Step2,证明由K0->Kn成立

状态转移方程中的重点
状态:一个数学符号,外加一个语义描述
决策:从所有可能产生最优解的状态中,选择一个最大值
阶段:本阶段只依赖上一个阶段。

leetcode-714. 买卖股票的最佳时机含手续费

状态定义:
dp[i][0]代表不持有股票的最大收益
dp[i][1]代表持有股票的最大收益
状态转移方程:
**dp[i][0]来自:**
1.i-1天没买股票、第i天也没买股票;
最大值为dp[i-1][0]
2.i-1天有股票、第i天把股票卖了。
最大值为dp[i-1][1]+price[i]-fee
因此,第i天不持有股票的最大收益在两者之间取个最大值:
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+price[i]-fee)
**dp[i][1]来自:**
1.i-1天就持有股票且第i天也持有股票:
最大值为:dp[i-1][1]
2.i-1天没买股票,第i天买股票了
最大值为dp[i-1][0]-price[i]
因此,第i天持有股票的最大收益在两者之间取个最大值:
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-price[i])

代码演示:

class Solution {
public:
//dp[i][0]表示第i天不持有股票的最大收益 来自1.第i-1天没有股票 2.第i天将股票卖掉
//dp[i][1]表示第i天持有股票的最大收益 
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        int dp[n][2];
        dp[0][0]=0;//第0天不持有股票的最大收益
        dp[0][1]=-prices[0];
        for(int i=1;i<n;i++){
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]-fee);
            dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
        }
        return max(dp[n-1][0],dp[n-1][1]);//在最后一天持有股票和不持有股票之间的最大值取一个
    }
};

leetcode-213. 打家劫舍 II

状态定义:
dp[i][0]表示不偷第i家的最大值
dp[i][1]表示偷第i家的最大值
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 1) return nums[0];
        int dp[n][2];
        dp[0][0] = 0;
        dp[0][1] = nums[0];
        for(int i=1;i<n;i++){
            dp[i][0] = max(dp[i-1][1],dp[i-1][0]);
            dp[i][1] = dp[i-1][0]+nums[i];
        }
        int ans1=dp[n-1][0];//这是选择了最后一个没偷的,第一个偷不偷不重要
        
        dp[0][0] = 0;
        dp[0][1] = 0;//这是选择第一个一定不偷 最后一个偷不偷不重要
        for(int i=1;i<n;i++){
            dp[i][0] = max(dp[i-1][1],dp[i-1][0]);
            dp[i][1] = dp[i-1][0]+nums[i];
        }
        int ans2 = max(dp[n-1][1],dp[n-1][0]);
        return max(ans1,ans2);
    }
};

leetcode-416. 分割等和子集

可达数组概念:在某种情况下,可以到达某些状态或值的数组称为可达数组
二维状态定义:f[i][j]表示前i个数字是否能凑出j值
状态转移方程:
f[i][j] = f[i-1][j]|f[i-1][j-nums[i]] 两种状态满足一个 f[i][j]状态就等于1
降为一维:
因为f[i][j]只取决于第i-1行第j列和j-x的列(图中两块阴影部分的区域值)

一维数组表示
滚动数组可以存两行的值,我们现在可以只存一行
例如:i-1行变成第i行 :i-1行的第j列和j-x列转换成第i行的第j列。因此j将从后往前遍历 可以防止前面的j-x值没有被覆盖

class Solution {
public:
//可达数组:在某种情况下,可以到达某些状态|值
    bool canPartition(vector<int>& nums) {

        int sum = 0;
        for(auto x:nums)sum+=x;
        if(sum%2) return false;//奇数一定不能分成两个数组
        int f[sum+1];
        memset(f,0,sizeof(f));//一开始f数组每个位置都设定为不可达(每个位置拼凑不出来)
        f[0] = 1;//初始化0值是可以到达的
        sum = 0;//这里表示当前值x之前能拼凑出来的最大值是什么
        for(auto x:nums){
            sum+=x;
            for(int j=sum;j>=x;j--){//倒着扫描一遍
                f[j] = f[j]|f[j-x];
            }
        }
        return f[sum/2];
    }
};

leetcode-474. 一和零

此题类似于背包问题,原因是固定总资源(背包容量)的大小选择不同的物品集合
dp[i][m][n]前i个字符串,最多m个0和n个1的数量
状态转移方程:
dp[i][m][n] = max(dp[i-1][m][n],dp[i-1][m-1i][n-0i]+1)
这里1i表示第i个字符串中1个数,同理0i。

这里使用的优化方法都是逆向刷表法 可以降维

class Solution {
public:
//此题类似于背包问题,原因是固定总资源(背包容量)的大小选择不同的物品集合
//dp[i][m][n]前i个字符串,最多m个0和n个1的数量
//状态转移方程:
//dp[i][m][n] = max(dp[i-1][m][n],dp[i-1][m-1i][n-0i]+1)
    int findMaxForm(vector<string>& strs, int m, int n) {
        int dp[m+1][n+1];
        memset(dp,0,sizeof(dp));
        for(auto x:strs){
            int cnt0 = 0,cnt1=0;//计算当前字符串中0的数量和1的数量
            for(auto y:x){
                if(y == '0') cnt0+=1;
                else cnt1+=1;
            }
            for(int i=m;i>=cnt0;i--){//倒着刷表
                for(int j=n;j>=cnt1;j--){
                    dp[i][j] = max(dp[i][j],dp[i-cnt0][j-cnt1]+1);
                }
            }
        }
        return dp[m][n];

    }
};

leetcode-494. 目标和

//这里f[i][j]状态定义为前i个数字能达到j的方法总数
//状态转移方程为:
//f[i][j] = f[i-1][j+x]+f[i-1][j-x]; 因为第i个数x可正可负所以应将二者方法数相加
/*这里需要注意:
    j在这里可正可负所以不能通过数组来进行存储 
    解决方法1.可以分成两个数组,正数数组+负数数组 2.可以使用hash表进行存储 3.偏移量
*/

偏移量图解:定义一个比原来sum大两倍加一大小的数组 让p指针指向中间位置,使得向左跑为负数,向右跑为正数,且二者范围都是sum。(可以理解为支持负数下标的数组)
数组偏移量解法

class Solution {
public:
//这里f[i][j]状态定义为前i个数字能达到j的方法总数
//状态转移方程为:
//f[i][j] = f[i-1][j+x]+f[i-1][j-x]; 因为第i个数x可正可负所以应将二者方法数相加
/*这里需要注意:
    j在这里可正可负所以不能通过数组来进行存储 
    解决方法1.可以分成两个数组,正数数组+负数数组 2.可以使用hash表进行存储 3.偏移量
这道题使用了三种优化方法:1偏移量2.滚动数组3.我到哪里去
*/
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0,n=nums.size();
        for(auto x:nums)sum+=x;
        int buff[2][2*sum+5],*f[2]={buff[0]+sum+2,buff[1]+sum+2};//这里声明的buff数组长度没有特别讲究 不小于2*sum+1即可且稍微大一些 buff的位置就相当于数组的0位置下标
        sum = 0;
        memset(buff,0,sizeof(buff));
        f[0][0] = 1;
        for(int i=1;i<=n;i++){
            int ind = i%2;
            int pre_ind = !ind;
            int x = nums[i-1];
            memset(buff[ind],0,sizeof(buff[ind]));
            for(int j=-sum;j<=sum;j++){
                f[ind][j+x]+=f[pre_ind][j];
                f[ind][j-x]+=f[pre_ind][j];
            }
            sum+=x;
        }
        return f[n%2][target];
    }
};

leetcode-322. 零钱兑换

class Solution {
public:
//dp[n]=p 表示拼凑n大小的面额最少所需要的的面额数
//也就是dp[n] = min(dp[n-Vi]+1) ,Vi表示第i种钱币的面额 ,但要确保dp(Vi)状态可达
    int coinChange(vector<int>& coins, int amount) {
        // vector<int>dp(amount+1);
        // dp[0] = 0;
        // //设定不可达的值为-1
        // for(int i=1;i<=amount;i++)dp[i] = -1;
        // for(int i=1;i<=amount;i++){
        //     for(auto x:coins){
        //         if(i<x)continue;//如果硬币所剩面额太小
        //         if(dp[i-x]==-1)continue;//如果前序列拼凑不出来
        //         if(dp[i] == -1 || dp[i]>dp[i-x]+1)dp[i]=dp[i-x]+1;//如果此时第一次拼凑这个面额或者还有更少的拼凑面额的方法
        //     }
        // }
        // return dp[amount];
        

        //方法二:dp[i][j]使用前i种硬币,拼凑j元钱最少使用的多少个
        /*
        dp[i][j] = min(dp[i-1][j],dp[i][j-x]+1) 从没有使用第i中硬币和使用了第i种硬币这里1是1个硬币
        */
        int dp[amount+1];
        //因为需要找到最少的零钱个数开始将每个位置都设置为极大值(题目要求-1)
        for(int i=0;i<=amount;i++) dp[i] = -1;
        dp[0] = 0;//拼凑0元钱最少使用0个硬币
        for(auto x:coins){//先遍历每一种硬币
            for(int j=x;j<=amount;j++){//使用正向刷表法
                if(dp[j-x] == -1)continue;
                if(dp[j] == -1|| dp[j-x]+1<dp[j])dp[j] = dp[j-x]+1;
                dp[j] = min(dp[j],dp[j-x]+1);
            }
        }
        return dp[amount];

    }
};

leetcode-518. 零钱兑换 II

class Solution {
public:
    int change(int amount, vector<int>& coins) {
//f[i][j]前i种硬币拼凑j元钱的方法总数
        //f[i][j] = f[i-1][j]+f[i][j-x]
        int f[amount+1];
        memset(f,0,sizeof(f));
        f[0] = 1;
        for(auto x:coins){
            for(int j=x;j<=amount;j++){
                f[j]+=f[j-x];//没加之前f[j]相当于f[i-1][j] 加过之后成了f[i][j]
            }
        }
        return f[amount];
    }
};

leetcode-377. 组合总和 Ⅳ

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        unsigned f[target+1];
        memset(f,0,sizeof(f));
        f[0] = 1;
        for(int i=1;i<=target;i++){
            for(auto x:nums){
                if(x>i) continue;
                f[i]+=f[i-x];
            }
        }
        return f[target];
    }
};

leetcode-382. 链表随机节点
方法一:将链表圈成一个圈,每次随机向前走几步获取随机节点值

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    /** @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node. */
    int n;
    ListNode *head;
    Solution(ListNode* head):n(0),head(head) {
        ListNode *p = head,*q;
        //将源链表圈成一个圈
        
        while(p)q=p,p=p->next,n+=1;
        //此时q指向链表的最后一个节点
        q->next = head;

    }
    
    /** Returns a random node's value. */
    int getRandom() {
        int x = rand()%n;
        while(x--)head = head->next;
        return head->val;
    }
};

leetcode-77. 组合

class Solution {
public:
//深搜
    void dfs(int i,int n,int cnt,
    int k,vector<int>&buff,
    vector<vector<int>>&ret){
    /*i:当前可以选择的第一个数字是什么,
    n:最多选择的数字是什么
    cnt:已经选择了几个,k:最多选择几个,buff:当前选择的集合,
    ret:当前存储结果的数组
    */
        if(k == cnt){
            ret.push_back(buff);
            return ;
        }
        if(n-i+1<k-cnt){
            //可以选择的比还需要选多少个( 能选的数字比要选的数字都少)
            return ;
        }
        //如果选了第i个数字
        buff[cnt] = i;
        dfs(i+1,n,cnt+1,k,buff,ret);
        //如果没选第i个数字
        dfs(i+1,n,cnt,k,buff,ret);

        return ;

    }
    vector<vector<int>> combine(int n, int k) {
        vector<int>buff(k);
        vector<vector<int>>ret;
        dfs(1,n,0,k,buff,ret);
        return ret;
    }
};

leetcode-234. 回文链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
//翻转链表
    ListNode *reverse(ListNode *head){
        ListNode ret,*p = head,*q;
        while(p){
            q = p->next;
            p->next = ret.next;
            ret.next=p;
            p = q;
        }
        return ret.next;
    }
    bool isPalindrome(ListNode* head) {
        if(head == nullptr) return false;
        ListNode *p=head,*q=head;
        while(q->next && q->next->next){
            p = p->next;
            q = q->next->next;
        }
        p->next = reverse(p->next);
        q = p->next;
        p = head;
        //此时再让两个指针一起往后去
        while(q){
            if(p->val!=q->val) return false;
            p=p->next;
            q=q->next;
        }
        return true;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值