LeetCode初级算法之动态规划

LeetCode初级算法之动态规划

    • 爬楼梯
    • 买卖股票的最佳时机
    • 最大子序和
    • 打家劫舍
    • 写在后面

爬楼梯

Question:
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

Solution:

	int climbStairs(int n) {
	    	if(n == 1) return 1;
	        if(n == 2) return 2;
	    	else return climbStairs(n - 1) + climbStairs(n - 2);
	    }

这里我用了递归的办法,但是很遗憾超时了,所以改成用迭代的方法试一试~

int climbStairs(int n) {
	vector s;
	s.push_back(1);
	s.push_back(2);
	if (n == 1) return 1;
	if(n == 2) return 2;
	for (int i = 2; i < n; i++)
	{
		s.push_back(s[i - 1] + s[i - 2]); 
	}
	return s[n - 1];
}

果然通过啦~上网查了一下为什么递归比迭代慢,特意贴在这里,希望以后别再忘了:
为什么递归效率比迭代差那么多?

买卖股票的最佳时机

Question:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

遍历肯定是能做出来的,但是我就不去做了,肯定超时),所以想一下动态规划,这道题其实算是非常典型的动态规划题目了,我每到一天,都看今天的价格是不是以往最低的,如果是最低的,我今天买入肯定不亏,以今天为开始对从今天到最后一天再进行一次规划,如果能够得到比当前预计利润更高,就更新:

int maxProfit(vector& prices) {
      if (prices.size() <= 1) {
            return 0;
        }
        int max = 0;    
        int min = prices[0];
        for(int i = 0; i 

因为我们是上帝视角,可以知道以后股票的价格,所以总可以记录一个利润的最大值max和一个直到今天价格的最低值min,这样我们每遇到一个价格就得出今天卖出股票的利润并与max相比,更新,最终得出的就是最大利润!

最大子序和

Question:
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

Solution:
做这题的时候思路有点不太清楚,可能是下午健身跑傻了,写了半天,最后的思路是这样的,是一个时间复杂度为O(n)的算法,在遍历数组的过程中,始终加上下一个元素,这里只要现在存储的和大于记录的max值,就更新max,如果是负值不用担心,照加不误,但这里我们要保证的是现在的nowmax是大于零的,这样不管怎么样,保留现在的这个序列,通俗一点的说,肯定不亏,它一定会为后面的序列做正贡献,反之,如果nowmax小于0,那么会造成负贡献,在这里就放弃当前序列,清零。
我刚开始是这样写的,但是发现自己忽略了一个小细节,如果这个数组的值都是负的,那么最后的结果会错误输出为0,所以我就加了一个,如果最后结果等于0,就在数组中再进行遍历,如果数组中全都是小于0的元素,那么最终结果应该是最大的那一个。这与之前大二算法设计里的子序列要求不同,那个题可以子序列长度为0,所以不需要加这一部分,通过做这道题又排了一个坑,开心~~

int maxSubArray(vector& nums) {
    int max = nums[0], nowmax = nums[0], min = nums[0];
    for(int i = 1; i < nums.size(); i++)
    {
       if(nowmax < 0) nowmax = 0;
       nowmax += nums[i];
       if(nowmax > max) max = nowmax;
     }
    if(max == 0) 
    {
        for(int i = 0; i < nums.size(); i++)
        {
    	  if(nums[i] >= 0)
    	  {
    		  min = 0;
    		  break;
    	  }
    	  if(min < nums[i])
    		  min = nums[i];
        }
        return min;
    }
    return max;
}

题目的进阶要求中还说道,让我们尝试使用更为精妙的分治法求解,额。。分治法我都忘掉了,赶紧去查一查:

分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。 From 百度百科

那分治法如何用的求最大子序列中呢?
通过观察可以发现最大子序列和只有三种情况:

  • 出现在数组的左半部分
  • 出现在数组的右半部分
  • 出现在数组的中间部分,横跨左右两部分
    利用这个特性,可以写出相应的递归,递归结束的条件是当只有一个元素时,判断这个元素是否大于零,大于零便返回这个数,否则返回零。 然后求出左边最大值,右边最大值和横跨两边的最大值,返回这三个值中的最大值 。这里代码留着下次再写~~不然今天完不成任务了哈哈哈,赶紧去写下一题了!

打家劫舍

Question:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
Solution:
这道题给人以和最大子序列和以异曲同工之妙,只是这里对序列的要求变了,是不能连续的序列,写到这里,我才知道回头去看一看动态规划的Basic knowledge :

动态规划算法:
1.全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解
2.动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优解
3.边界条件:即最简单的,可以直接得出的局部最优解

我们在抢劫的时候考虑的无非是两个选择, 抢这一家还是抢下一家,这里要动态维护一个maxpro[ ]数组来记录抢劫到某一家时的最大收入,如果抢第i家的话,就要判定maxpro[i - 2] + pro[i] 与maxpro[i -1]哪个大,即状态转移方程为:

maxpro[i] = max(pro[i] + maxpro[i - 2] , maxpro[i - 1])

代码如下:

     int rob(vector& nums) {
    	vector maxprofit;
        if(nums.size() == 0) return 0;
    	if(nums.size() == 1) return nums[0];
    	if(nums.size() == 2) return nums[1] > nums[0] ? nums[1] : nums[0];
    	maxprofit.push_back(nums[0]);
    	maxprofit.push_back(nums[1] > nums[0] ? nums[1] : nums[0]);
    	for(int i = 2; i < nums.size(); i++)
    	{
    		maxprofit.push_back(maxprofit[i - 1]);
    		if(maxprofit[i - 2] + nums[i] >= maxprofit[i - 1])
    			maxprofit[i] = maxprofit[i - 2] + nums[i];
    	}
    	return maxprofit[nums.size() - 1];
    }

写在后面

今天是2019.6.13,开始刷题的第二天,今天刷题的收获就是希望自己能够不要懒,看到题最好能用学过的算法或者数据结构的知识来有方法的解决这道题,就像打家劫舍这道题一样,能够找出状态转移方程才是关键所在,不要一头上去就开始coding,系统的复习一遍数据结构和算法 ,才是关键,明天的目标,做完剩下的初级算法!

你可能感兴趣的:(LeetCode)