算法自学__动态规划入门

参考资料:

算法引入

我们先考虑一个经典问题:
小明要爬上一个n级的台阶, 他每次可以走1级或2级, 求不同的走法数.

经典递归算法

代码:

int solve(int n) {
	if (n == 1)return 1;
	else if (n == 2)return 2;
	else return solve(n - 1) + solve(n - 2);
}
int main() {
	int n;
	cin >> n;
	cout << solve(n);
	return 0;
}

时间复杂度: O(2^n)
我们可以发现, 上述算法包含着大量的重复计算:
假设n=5, 根据上述算法, 我们需要先计算solve(4)solve(3)
而要计算solve(4), 必须先计算solve(3)solve(2)
我们注意到solve(3)被计算了两次.
当问题规模更大时, 重复计算会更多, 导致问题不能在理想的时间内得到解决.

动态规划算法

如果我们将已经解决的问题的结果保存下来, 在解决新问题时直接调取旧问题的结果, 就能节省不少时间, 这就是最简单的动态规划

#include<iostream>
#include<algorithm>
using namespace std;
int dp[100];
void solve(int n) {
	if (n <= 2) return;
	else {
		for (int i = 3; i <= n; i++) {
			dp[i] = dp[i - 1] + dp[i - 2];
		}
	}
}
int main() {
	dp[1] = 1, dp[2] = 2;
	int n;
	cin >> n;
	solve(n);
	cout << dp[n];
	return 0;
}

时间复杂度: O(n)

动态规划算法

能用动态规划解决的问题的特点
  • 最优化原理: 问题的最优解所包含的子问题的解也是最优的.
  • 无后效性: 某个阶段的决策一旦确定, 就不受后续决策的影响.
  • 有重叠的子问题(该性质不是必须的, 但是若没有这条性质, 动态规划算法将不具有优势).
算法实现的步骤
  1. 创建一个数组(数组的维数与问题相关), 用以保存子问题的结果.
  2. 设置数组的边界值.
  3. 求出状态转移方程, 即找到每个状态与上一个状态的关系.
  4. 返回需要的值.

经典例题

1.最长不连续递增子列

对应题目: LeetCode第300题

算法思想:

  • 定义状态dp[i]为: 以nums[i]尾元素的上升子序列的最长长度.
  • 状态转移: 对于每一个j属于[0, i), 若nums[i] > nums[j], 则dp[i] = max(dp[i], dp[j]+1)
  • 遍历dp数组, 数组中的最大元素即为原问题的答案.

代码实现:

int lengthOfLIS(int* nums, int numsSize){
    int dp[2505];
    for(int i=0;i<numsSize;i++){		//求以nums[i]为结尾的最长递增子列
        dp[i]=1;		//初值为1
        for(int j=0;j<i;j++){
            if(nums[i]>nums[j]){
                dp[i]=fmax(dp[i],dp[j]+1);		//状态转移方程
            }
        }
    }
    int max=0;
    for(int i=0;i<numsSize;i++){
        if(dp[i]>max) max=dp[i];
    }
    return max;
}
// 时间复杂度O(n^2)

算法改进:
定义tail数组, tail[i]表示: 长度为i+1的所有递增子列的最小尾元素.
我们容易知道, tail数组是严格单调递增的.
状态转移定义为:

  1. 若当前元素e大于tail[i], 则tail[i+1]=e(得到了一个长度为i+2的递增子列, 最小尾元素为e)
  2. 若当前元素e小于等于tail[i], 二分查找到tail数组中第一个大于e的数字, 并将其更新为e

当我们遍历整个数组后, tail数组中的元素个数即为最长递增子列的长度.

代码实现:

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int tail[2505];
        tail[0]=nums[0];
        int ans=1;
        int len=nums.size();
        for(int i=1;i<len;i++){
            if(nums[i]>tail[ans-1]){
                tail[ans++]=nums[i];
            }
            else if(nums[i]==tail[ans-1]) continue;
            else{
                int L=0,R=ans-1,flag=0;
                while(L<=R){
                    int mid=(L+R)>>1;
                    if(nums[i]>tail[mid]) L=mid+1;
                    else{
                        flag=mid;
                        R=mid-1;
                    }
                }
                if(nums[i]==tail[flag]) continue;
                tail[flag]=nums[i];
            }
        }
        return ans;
    }
};
//时间复杂度O(nlogn)
2. 数组最大连续子序列和

对应题目: LeetCode第53题

代码实现:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int len=nums.size();
        int dp[100005];
        dp[0]=nums[0];
        int max=dp[0];
        for(int i=1;i<len;i++){
            dp[i]=fmax(dp[i-1]+nums[i],nums[i]);
            if(dp[i]>max) max=dp[i];
        }
        return max;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值