频率很高的笔试题--动态规划类型(上)

本文详细解析了10道常见的动态规划题目,包括最长上升子序列、零钱兑换、最大子序列之和、单词拆分、最大正方形等问题。通过实例和C++代码展示了解题思路和动态规划的解决方案,帮助读者掌握动态规划的应用技巧。
摘要由CSDN通过智能技术生成

1、最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度

示例
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4

方法一:典型的动态规划,dp[i] 表示到第 i 位时最长上升子序列的长度,为了更新 dp[i],需要找到以 num[i] 为最后一个元素的所有上升子序列,求出最长的长度

状态转移方程为:p[i] = max(dp[j]) + 1 , 其中 0 < j < i , 且 nums[i] > nums[j],若不存在 nums[i] > nums[j] , 其中0 < j < i , 则 dp[i] = 1

    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        n = len(nums)
        dp = [1]*len(nums)
        for i in range(1,n):
            cout = 0
            for j in range(i):
                if nums[j]<nums[i]:
                    cout = max(dp[j],cout)
            if cout:
                dp[i] = cout+1
        return max(dp)

C++ 写法

    int lengthOfLIS(vector<int>& nums) {
   
        int n = nums.size();
        if(n==0){
   
            return 0;
        }
        vector<int>dp(n,1);
        int resmax = 1;
        for(int i = 1;i<n;++i){
   
            int temp = 0;
            for(int j = 0;j<i;++j){
   
                if(nums[j]<nums[i]){
   
                    temp = max(temp,dp[j]);
                }
            }
            if(temp){
   
                dp[i] = temp + 1;
            }
            resmax = max(resmax,dp[i]);
        }
        return resmax;
    }

方法二:上面时间复杂度为O(N2),利用二分查找可以优化时间复杂度为 O(N*log2N)

    def lengthOfLIS(self, nums: List[int]) -> int:
        dp  = [0] * len(nums)
        res = 0
        for num in nums:
            left, right = 0, res
            while left < right:
                mid = (left + right) // 2
                # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可
                if dp[mid] < num:
                    left = mid + 1
                else:
                    right = mid
            dp[left] = num
            if right == res:
                res += 1
        return res

2、零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount,编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

示例1
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1

示例2
输入: coins = [2], amount = 3
输出: -1

错误思路:先将 coins 排序,然后依次取出coins 最后一个数字和amount 比较,如果大于amount 则弹出最后一个元素,如果小于amount 则将最后一个元素添加到返回列表中,最后看amount 是否为0,如果等于 0 则返回列表长度,否则返回 -1

正确思路:利用动态规划,从 1 到 amount 这个范围,需要一个一个去判断它最小的个数,dp[i] 表示凑成总金额 i 需要的最少硬币个数

如例 1 中 coins=[1,2,5],dp[1] , dp[2] 和 dp[5] 都等于 1,因为用相应金额的一枚硬币就能组成 i

对于其他的 i ,例如 i = 11

dp[11] = min(dp[11-1], dp[11-2], dp[11-5])+1

dp[i] 只有这两种更新方式 ,有可能 amount 无法用 coins 里的硬币构成,那么,初始化每个 dp[i] 的时候都为 amount,若 dp[amount] > amount 返回 -1

C++ 版本

    int coinChange(vector<int>& coins, int amount) {
   
        vector<int>dp(amount+1);
        sort(coins.begin(),coins.end());
        unordered_map<int,int>um;
        for(auto&e: coins){
   
            um[e] = 1;
        }
        for(int i = 1;i<amount+1;++i){
   
            if(um[i]){
   
                dp[i] = 1;
                continue;
            }
            int min_val = amount;
            for(int j = 0;j<coins.size();++j){
   
                if(i>=coins[j]){
   
                    min_val = min(min_val,dp[i-coins[j]]);
                }
            }
            dp[i] = min_val+1;
        }
        if(dp[amount]>amount){
   
            return -1;
        }
        return dp[amount];
    }

方法二:

int coinChange(vector<int>& v, int aim,int n) {
   
    vector<int>dp(aim+1);
    // dp[i] 表示到达 i 目标值的最小货币个数
    for(int i = 1;i<aim+1;++i){
   
        int min_val = aim;
        for(int j = 0;j < n;++j){
   
            if(i >= v[j]){
   
                min_val = min(min_val,dp[i-v[j]]);
            
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿的温柔香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值