您的位置:首页 > 其它

leetcode:动态规划

2016-03-13 17:49 423 查看
动态规划是利用存储历史信息使得未来需要历史信息时不需要重新计算, 从而达到降低时间复杂度, 用空间复杂度换取时间复杂度目的的方法。可以把动态规划分为以下几步:

确定递推量。 这一步需要确定递推过程中要保留的历史信息数量和具体含义, 同时也会定下动态规划的维度;

推导递推式。 根据确定的递推量, 得到如何利用存储的历史信息在有效时间(通常是常量或者线性时间)内得到当前的信息结果;

计算初始条件。 有了递推式之后, 我们只需要计算初始条件, 就可以根据递推式得到我们想要的结果了。 通常初始条件都是比较简单的情况, 一般来说直接赋值即可;

动态规划的时间复杂度是O((维度)×(每步获取当前值所用的时间复杂度))。 基本上按照上面的思路, 动态规划的题目都可以解决, 不过最难的一般是在确定递推量, 一个好的递推量可以使得动态规划的时间复杂度尽量低。

96 独特的二叉搜索树

给定一个整数n,返回含有n个节点的结构不同的二叉搜索树的数目。

思路:以 1 为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是 0 个元素的树,右子树是 2 个元素的树。

以 2 为根的树的个数,等于左子树的个数乘以右子树的个数,左子树是 1个元素的树,右子树也是 1 个元素的树。依此类推。

当数组为 1; 2; 3; …..; n 时,基于以下原则的构建的 BST 树具有唯一性:以 i 为根节点的树,其左子树由 [1, i-1] 构成,其右子树由 [i+1, n] 构成。

定义 f (i) 为以 [1; i] 能产生的 Unique Binary Search Tree 的数目,则

如果数组为空,毫无疑问,只有一种 BST,即空树,f (0) = 1。

如果数组仅有一个元素 1,也只有一种 BST,单个节点,f (1) = 1。

……

class Solution {
public:
int numTrees(int n) {
vector<int> f(n+1,0);
f[0] = 1;
f[1] = 1;
for(int i = 2;i <= n;i++){
for(int j = 1;j <= i;j++){
f[i] += f[j-1] * f[i-j];
}
}
return f
;
}
};


70 爬楼梯的方式

思路:与上题非常类似。

class Solution {
public:
int climbStairs(int n) {
if(n==1) return 1;
if(n==2) return 2;
int sum=0,p=2,q=1;
for(int i=3;i<=n;i++){
sum=p+q;
q=p;
p=sum;
}
return sum;
}
};


337 House Robber iii

思路:求二叉树中不连续节点的最大和问题,也就是动态规划问题。

class Solution {
public:
int rob(TreeNode* root) {
vector<int> ret = getMoney(root);
return max(ret[0], ret[1]);
}
vector<int> getMoney(TreeNode* node) {
vector<int> ret(2, 0);
if(!node) return ret;
vector<int> lRet = getMoney(node->left);
vector<int> rRet = getMoney(node->right);
ret[0] = lRet[1] + rRet[1] + node->val;
ret[1] = max(lRet[0], lRet[1]) + max(rRet[0], rRet[1]);
return ret;
}
};


53 和最大的子串

思路:求连续的子串中和最大的那个,也可以用动态规划的思路解决。设r[i]表示以nums[i]结尾的子串中最大的那个和,则问题就变成求r中的最大值。

那么,r[i]怎么求呢?r[i]可以表示成

r[i]=max(r[i-1]+nums[i],nums[i]);


完整的解题代码为

class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> r(nums.size());
int result=nums[0];
r[0]=nums[0];
for(int i=1;i<nums.size();i++){//从第2个数开始,以nums[i]结尾的子串的最大值r[i]=max(r[i-1]+nums[i],nums[i])
r[i]=max(r[i-1]+nums[i],nums[i]);
if(result<r[i]) result=r[i];
}

return result;
}
};


309 买卖股票的最佳策略(带cooldown)

思路:因为今天买卖股票会受到之前买卖股票行为的影响,首先考虑到用DP解决。

对于第i天收盘时的状态,无非只有持仓(buy)和空仓(sell)两种状态。

如果空仓(sell),最大化收益就是

sell = max(pre_buy+prices[i],sell);


昨天还持仓,今天卖出了昨天就空仓,今天不操作这两种状态中较大的那个。

2. 如果持仓(buy),最大化收益就是

buy = max(pre_sell-prices[i],buy);


前天空仓,今天买入昨天就持仓,今天不操作这两种状态中的一种。注意到因为有cooldown的存在,今天如果买入,需要前天就已经空仓了。

理解空仓(sell)和持仓(buy)时的最大化收益的时候,稍微有所不同。空仓的最大化收益是我们实际要求得的,而持仓时的最大化收益,其实可以理解成用最少的钱买入,使得剩余现金最大。而这两种信息对于状态转移方程都是需要的。

完整代码如下:

class Solution {
public:
int maxProfit(vector<int>& prices) {
int pre_buy = INT_MIN;
int buy = INT_MIN;
int pre_sell = 0;
int sell = 0;
for(int i=0; i<prices.size();++i){
pre_buy = buy; //pre_buy记录buy[i-1]
buy = max(pre_sell-prices[i],buy);//buy计算buy[i],更新buy[i-1]到buy[i]
pre_sell = sell; //pre_sell,sell[i-2]更新到sell[i-1];
sell = max(pre_buy+prices[i],sell);//sell计算sell[i],更新sell[i-1]到sell[i]
}
return sell;
}
};


121 买卖股票的最佳策略(只能买卖一次)

思路:假设最佳策略是在第i天做出的,那么该天的最大收益是“在第1到i-1天中价格最低的时候买入,当天卖出”得到的。从前到后遍历prices,取可能的最大收益的最大值。

class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1)
return 0;
int lowest=prices[0];
int m=0;

for(int i=1;i<prices.size();i++){
m=max(m,prices[i]-lowest);
lowest=min(lowest,prices[i]);
}
return m;
}
};


312. Burst Balloons(戳穿气球)

Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.

Find the maximum coins you can collect by bursting the balloons wisely.

思路:dp[l][r]表示扎破(l, r)范围内所有气球获得的最大硬币数,不含边界

一开始我们对input的nums数组左右两边分别加上1,最后求d[0][n-1]就行。

l与r的跨度k从2开始逐渐增大;对于d矩阵我们初始化为0,逐个斜对角线赋值,即l - r == k. 因为只有在k == 2的时候,即[l, r]区间内有一个气球的时候,才会有值,所以从k == 2开始。

我们如何求dp[l][r]呢?这里我们用最后一个气球作为分类,在(l,r)区间burst 气球的方案可以按最后剩下的那个气球是哪个来分类,如果最后剩下的是第
l<i<r
个气球,那么dp[l][r](关于i) = nums[l] * nums[i] * nums[r] + dp[l][i] + dp[i][r]。因此我们可以选取这些方案中最大的。

然后再要循环l-r了,这里从第k == 2开始,即在nums上,l从0开始一直到n-k-1, 对应r 从k到n-1。 模拟如下:

当k == 2时,l = 0, r = 2; l = 1, r = 3; ……

当k == 3时,l = 0, r = 3; l = 1, r = 4;…..

…..

当k==(n-1)时,l=0,r=n-1。

此时的dp[0][n-1]即为题目所求。

形象地说,用(n-1)*(n-1)的矩阵理解的话,就是从左下到右上角,不断给对角线赋值。最右上角的dp[0][n-1]即为所求。

class Solution {
public:
int maxCoins(vector<int>& nums) {
int arr[nums.size()+2];

for(int i=1;i<nums.size()+1;++i)arr[i] = nums[i-1];//把nums拷贝到arr里去
arr[0] = arr[nums.size()+1] = 1;

int dp[nums.size()+2][nums.size()+2]={};
int n = nums.size()+2;

for(int k=2;k<n;++k)//第一重循环,决定left和right之间的跨度k
{
for(int left = 0;left<n-k;++left){//第二重循环,决定left的取值
int right = left + k;
for(int i=left+1;i< right; ++i){//第三重循环,决定指定(left,right) 范围内,如何决策可以使得dp[left][right]最大
dp[left][right] = max(dp[left][right],arr[left]*arr[i]*arr[right] + dp[left][i] + dp[i][right]);
}
}
}
return dp[0][n-1];
}
};


64 最小路径和

给定一个只含非负整数的m*n网格,找到一条从左上角到右下角的可以使数字和最小的路径。

注意 ,你在同一时间只能向下或者向右移动一步。

思路:构建一个m*n的矩阵,每个点表示从左上角到该位置的最小路径和。然后,递推式是
sum[i][j]=min(sum[i-1][j],sum[i][j-1])+grid[i][j];


值得注意的是,当i=0或j=0时,要考虑下边界情况。

class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size();
int n=grid[0].size();
if(m==0 && n==0) return 0;

int sum[m]
={0};
sum[0][0]=grid[0][0];

for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(i==0) sum[i][j]=sum[i][j-1]+grid[i][j];
else if(j==0) sum[i][j]=sum[i-1][j]+grid[i][j];
else sum[i][j]=min(sum[i-1][j],sum[i][j-1])+grid[i][j];
}
}
return sum[m-1][n-1];

}
};


300 Longest Increasing Subsequence

求一个未排序序列中的最长上升子序列

思路:建立一个数组lol
,初始化为1,用于存储到当前元素为止,以当前元素结尾的子序列的长度。当数组全部计算完毕后,找出其中的最大值,即为所求。状态转移方程为
lol[i]=max[lol[i],lol[j]+1


代码:

class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size(),i,j;
if(n<=1) return n;
int lol
,maxx=0;;
for(i=0;i<n;i++)
{
lol[i]=1;
}
for(i=1;i<n;i++)
{
for(j=0;j<i;j++)
{
if(nums[i]>nums[j])
lol[i]=max(lol[i],lol[j]+1);
}
if(maxx<lol[i]) maxx=lol[i];
}
return maxx;
}
};


279. Perfect Squares

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, …) which sum to n.

For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.

思路:考察动态规划。如果一个数x可以表示为一个任意数a加上一个平方数b∗b,也就是x=a+b∗b,那么能组成这个数x最少的平方数个数,就是能组成a最少的平方数个数加上1(因为b∗b已经是平方数了)。

class Solution {
public:
int numSquares(int n) {
vector<int> dp(n+1,0);
for(int i=0;i<n+1;i++)
dp[i]=i;  //最多都由1组成
for(int i=0;i<=n;i++)
for(int j=1;i+j*j<=n;j++)
dp[i+j*j]=min(dp[i+j*j],dp[i]+1);//要么本身,要么加一个平方数

return dp
;
}
};


#

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: