初级算法(动态规划篇):初级例题分析与总结

例1 爬楼梯

  • 题目描述

You are climbing a stair case. It takes n steps to reach to the top.
Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?
Note: Given n will be a positive integer.

Example 1:
Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1 .1 step + 1 step
2. 2 steps

Example 2:
Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
4. 1 step + 1 step + 1 step
5. 1 step + 2 steps
6. 2 steps + 1 step

  • 题目分析
    题意还是很清晰的,给定目标楼层,从底楼开始爬,每次爬一层或者两层,计算出一共有多少种爬法。
    举个例子来说,比如要爬到10层,那么只看最后一步的话,一共就有两种情况:先爬到8层,再爬2层;先爬到9层,再爬1层。同样,如果要爬到n层,那么只看最后一步的话,也只有两种情况:先爬到n-1层,再爬1层;先爬到n-2层,再爬2层,以f(n)代表爬到n层的爬法,那么很明显f(n)=f(n-1)+f(n-2),根据这个式子来看,要保证这个式子有意义,那么n≥3,而n实际上是n≥1的,因此还应当单独看n=1和n=2的情况,n=1时,很明显只有一种,n=2时很明显有两种,分析到这里,程序也就不难写出了。
  • 最终结果
    int climbStairs(int n) {
        int * d=new int[n+1];
        d[0]=0;
        d[1]=1;
        d[2]=2;
        for(int i=3;i<=n;i++)d[i]=d[i-1]+d[i-2];
        return d[n];
        }

例2 买卖股票最佳时机

  • 题目描述

Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.
Note that you cannot sell a stock before you buy one.

Example 1:
Input: [7,1,5,3,6,4]
Output: 5
Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5.
Not 7-1 = 6, as selling price needs to be larger than buying price.

Example 2:
Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

  • 题目分析
    这道题的题意也很清晰,给定一个数组,数组中每个元素表示在该天股票的价格,题目限定交易次数为1次,即是说在这么多天中,取一天买进股票,取一天卖出股票,求这买入卖出的利润最大值。首先,还是按照例1的思路,假设我第10天卖出,那么第10天卖出股票的利润是多少呢?很简单,第10天卖出股票就相当于我把第9天卖出的股票再买回来,然后拿到第10天卖,买进的时候利润相当于又减少了,卖出相当于利润增加了,那么根据这个意思即是profit[10]=profit[9] -price[9]+price[10]。
    同样,我要是第n天卖出,那么profit[n]=profit[n-1]-price[n-1]+price[n],和例1一样,这里要使n有意义,就必须n≥1,如果n=0天卖出的话,那么就只有一种情况,当天买当天卖,那么自然利润是0的,即是profit[0]=0。
    如果按照例1的思路分析的话,到这里也就结束了。
    那么真的结束了吗?显然是不对的,观察例1和例2,可以发现,例1问题的起点都是不变的,为第0层(尽管现实生活中不存在第0层),而在例2中,问题的起点是买进股票的时间,而买进股票的天数是会变的,因此这里如果只用profit[n]=profit[n-1]-price[n-1]+price[n],这就把买进股票的时间固定在了第0天,这显然是不对的,那么怎么去改呢?
    这里需要考虑到,为了达到利润最大,那么肯定要保证成本最小,如前所述,每一天的利润都是与第0天相比的,即是可以写成profit[n]=price[n]-price[0]; 如果有一天的profit[i]为负数,说明这一天的股票价格低于第0天,很明显,为了降低成本,就应当在这第i天买入股票了。

换句话说,如果第i天的profit[i]小于0,那么就应当让profit[i]=0,这就表示以第i天作为股票买入时间了。因此在最后,在程序中对每天的profit进行判断,如果profit小于0了,那么就把该天的股票价格作为买入股票价格,即将profit=0。最后要求max profit,那么就对各个profit取得其最大值即可。

  • 最终结果
int maxProfit(vector<int>& prices) {
        int *dp=new int[prices.size()];
        dp[0]=0;
        int res=0;
        for(int i=1;i<prices.size();i++)
        {
            dp[i]=max(0,dp[i-1]-prices[i-1]+prices[i]);
            if(dp[i]>res)res=dp[i];
        }
        return res;
    }

例3 最大子序和

  • 题目描述

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:
Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

  • 题目分析
    这道题的的要求即是找出一个序列中,和最大的那个子序列,并返回和。与前面例子类似,先从最后来看:把以a[i]作为一个连续子序列最后一个元素,记这个子序列的最大子序和为f[i],那么f[i]是多少呢?按照前面两个例子,这个时候就应该找f[i]与前面f[i-1]…的关系,这里很容易知道,这里的f[i]只有两种情况,即是要么这个子序列中只有a[i],要么就是前面的子序列后面加上a[i],这样才能保证以a[i]作为末尾元素,因此要么f[i]=f[i-1]+a[i],要么f[i]=a[i],因此最终f[i]=max(f[i-1]+a[i],a[i]);这样就找到了f[i]和f[i-1]的关系,然后再看i≥1时这个关系式才有意义,因此还要单独讨论i=0的情况,很明显,f[0]=a[0],这样即可得出最终结果。最后要求max f,那么即对各个f取最大值即可。
  • 最终结果
int maxSubArray(vector<int>& nums) {
        int m=nums[0];
        int *dp=new int[nums.size()];
        dp[0]=nums[0];
        for(int i=1;i<nums.size();i++)
        {
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            if(dp[i]>m)m=dp[i];
        }
        return m;
    }

例4 打家劫舍

  • 题目描述

You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

Example 1:
Input: [1,2,3,1]
Output: 4
Explanation: Rob house 1 (money = 1) and then rob house 3 (money = 3).
Total amount you can rob = 1 + 3 = 4.

Example 2:
Input: [2,7,9,3,1]
Output: 12
Explanation: Rob house 1 (money = 2), rob house 3 (money = 9) and rob house 5 (money = 1).
Total amount you can rob = 2 + 9 + 1 = 12.

  • 题目分析
    这道题的最终目的即是对于一个数组,找出其互不相邻的元素相加后最大的和。还是从后面往前看,以a[i]表示第i个房间的钱,f[i]表示小偷在前i个房间中所取得的最大金额,由于每个房间的钱肯定都至少为0,只要房间能进去那就肯定得进去,因此这里也只有两种情况:小偷偷第i个房间和不偷第i个房间,如果是偷第i个房间的话,那说明小偷已经光顾过前面0~ i-2号房间了,这种情况下f[i]=f[i-2]+a[i];如果不偷第i个房间,说明小偷光顾了前面0~i-1房间,第i间不能偷了,因此f[i]=f[i-1],因此最终f[i]=max(f[i-2]+a[i],f[i-1])。这里注意到i≥2时才有意义,因此需要单独讨论i=0和i=1的情况:f[0]=a[0],f[1]=max(a[0],a[1]);最终结果如下:
  • 最终结果
int rob(vector<int>& nums) {  
        if(!nums.size())return 0;
        int * dp=new int[nums.size()+1];
        dp[0]=0;
        dp[1]=nums[0];
        dp[2]=max(nums[0],nums[1]);
        for(int i=3;i<nums.size()+1;i++)
            dp[i]=max(dp[i-2]+nums[i-1],dp[i-1]);
        return dp[nums.size()];
    }

总结

根据上面4个例子,大概也能总结出对于这类比较简单的动态规划的解题方法:
第一:建立问题模型。如果连问题的模型不能抽出来,那后面都进行不下去了;
第二:确定f[n]的意义,一般来说都是题目所需要求的或是极其相关的,比如说在爬楼梯中f[n]表示爬到第n层的方法种数;买股票中f[n]表示第n天卖出股票的利润;在最长子序和中f[n]表示以第n个元素为末尾的序列的最大子序和;在打家劫舍中f[n]表示在前n间房间中获的最大金额;
第三:确定f[n]的递推关系,即f[n]与f[n-1]或f[n-2]等的关系,将f[n]的问题缩小到f[n-i]中去解决,并根据具体问题进行f[n]优化;
第四:确定使f[n]有意义的n的范围,对其范围之外的n进行单独考虑。
第五:根据f[n]递归或循环求解。

总体来说,在这几步之中,确定f[n]以及找出f[n]的递推关系是最关键的,也是最困难的,需要针对题目具体的特点进行f[n]的选取,一定要牢牢记住动态规划的主要思想是将大问题简化到相似的小问题上去,然后从小问题开始解决,最终解决大问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值