算法:Dynamic Programming

今天在LeetCode做了一道动态规划的题,一直觉得动态规划的解题思路很有意思,借这道题来总结一下。

动态规划

我最初搞不懂动态规划是什么,那时候懂贪心算法,觉得贪心算法就是在求解问题的每一步都取全局最优,而动态规划在求解问题的每一步都取局部最优。后来做了一些动态规划的题才发现自己早就接触过了,还记得一道很经典的求从起点到终点有多少种走法的题就用了动态规划的思想。现在看来,动态规划就是缩小问题的规模,先在小问题中取得最优解,然后再不断增大问题的规模,通过小问题的最优解求得大问题的最优解。

上楼梯问题

我觉得有一道题能让人很容易理解动态规划,就是一个人上楼梯,一步上两级或三级,问n级楼梯最多有多少种不同的走法。很显然,如果n = 1,那么有0种走法,如果n = 2n = 3,那么有1种走法,也就是说,当n较小的时候我们很容易求得最优解,然后关键在于如何通过小问题的最优解求得大问题的最优解。当n > 3时,我们在上到第n级楼梯之前可能在第n-3n-2级楼梯上,因为我们最后一步可能走两级也可能走三级,那么如果我们知道n-3n-2级楼梯最多有多少种走法,我们就可以知道n级楼梯最多有多少种走法。设n级楼梯有f(n)种走法,那么f(1) = 0, f(2) = f(3) = 1, f(n) = f(n-3) + f(n-2), n > 3。在这个问题中,求解用了递归的方法,事实上,动态规划经常会用数组来存储小问题的最优解,以此来降低求解大问题的时间复杂度。

Longest Increasing Subsequence

问题

求一个无序数列中最长递增子数列的长度。(子数列指的是从数列中抽取任意元素按原本顺序排列得到的数列)

思路1

我的想法是一个长度为n的子数列可以由一个长度为n-1的子数列加上一个元素得到,为了使子数列尽可能长,相同长度的子数列中最后一个元素越小越好。因此我记录相同长度子数列最后一个元素中的最小值,然后遍历数列不断更新得到最优解。

代码1

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
	    //记录长度为下标值的子数列最后一个元素的最小值,初始化为无穷大,即第一次得到的任意子数列最后一个元素均为相同长度子数列中的最小值,子数列长度的最大可能值为数列的长度
        vector<int> v(nums.size()+1, INT_MAX);
        v[0] = INT_MIN;  //长度为0的子数列最后一个元素初始化为无穷小,即长度为0的子数列加上任意元素可以得到长度为1的数列
        int ans = 0;  //最长子数列的长度
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < ans; ++j) {
                if (nums[i] > v[j] && nums[i] < v[j+1]) v[++j] = nums[i];  //如果下标为i的元素大于长度为j的子数列最后一个元素并且小于长度为j+1的子数列最后一个元素的最小值,则更新为相同长度的长度为j+1的子数列的最后一个元素的最小值,即通过在长度为j的子数列后加上下标为i的元素得到长度为j+1的子数列
            }
            if (nums[i] > v[ans]) v[++ans] = nums[i];  //如果下标为i的元素大于当前最长子数列的最后一个元素,则通过在当前最长子数列后加上下标为i的元素得到新的最长子数列
        }
        return ans;
    }
};

思路2

我发现答案的思路更加清晰,记录以每一个元素为子数列中最后一个元素的所有子数列中的最大长度,然后取其中的最大值。假设以数列中第n个元素为子数列最后一个元素的所有子数列中的最大长度为Sn,数列中第m个元素小于第n个元素并且满足m < n,那么Sn = Sm + 1。我觉得这种思路更符合动态规划的思想,因为先求出了小问题的最优解,再求出大问题的最优解。

代码2

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> v(nums.size(), 1);  //记录以下标对应元素为最后一个元素的所有子数列的最大长度
        int ans = 0;  //子数列的最大长度
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) v[i] = max(v[i], v[j]+1);  //更新以当前元素为最后一个元素的所有子数列的最大长度
            }
            ans = max(ans, v[i]);
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值