当前位置: 首页 > article >正文

算法刷题打卡037 | 动态规划5

LeetCode 1049 最后一块石头的重量II

题目链接:1049. 最后一块石头的重量 II - 力扣(Leetcode)

看题目首先想到的是将所有石头放到一个有序集合里,不断取出两块重量接近的石头两相抵消,剩余部分放入集合中继续重复“相撞”的操作,主要就是模拟题目描述的过程:

import queue
class Solution:
    def lastStoneWeightII(self, stones: List[int]) -> int:
        s = queue.PriorityQueue()
        c = collections.Counter(stones)  # 去重(相同的两块石头优先一起粉碎,set中只加入奇数的数字)
        for st in stones:
            if c[st] % 2:
                s.put(-st)
        while s.qsize() > 1:
            max_1 = s.get()
            max_2 = s.get()
            if abs(max_2 - max_1) > 0:
                s.put(-abs(max_2 - max_1))
        if s.qsize() == 0:
            return 0
        else:
            return -s.get()

但这样模拟并不能解决问题,看似想用贪心,但并不能从局部最优推出全局最优。

运用动态规划01背包的思想,本质上就是“尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小”,和划分等和子集类似,但不强求划分为相等的两个集合。让两个集合尽可能接近,然后在这两个集合之间进行粉碎操作。这样最终剩余的石头重量就是和sum-2*dp[target](两个集合的差值):

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        int n = stones.size();
        int sum = 0;
        for (int i = 0; i < n; i++) {sum += stones[i];}
        int target = sum / 2;
        vector<int> dp(target + 1);
        for(int i=0; i < n; i++){
            for(int j=target; j >= stones[i]; j--)
            {
                dp[j] = max(dp[j], dp[j-stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }
};

LeetCode 494 目标和

题目链接:494. 目标和 - 力扣(Leetcode)

 看题目描述,其实也是在将数组分为两个集合(一个加正号,一个加负号),并且也是每个数只能用一次。不同的是这里求的是,给数值添加不同的符号之后,数组的和为target的数目。这就不能简单套用之前的dp数组定义方式,而是要将一维dp数组的含义dp[j]定义为装满容量为j的背包有多少种方法,同时还要重新考虑如何递推。递推公式有些类似于爬楼梯的方法数,倒序遍历容量j时,只要j能放下nums[i],dp[j-nums[i]]所包含的方法数就能累加到dp[j](累加是因为可以从不同的容量加上当前的nums[i],得到不同的方法)。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        int n = nums.size();
        for(int i=0;i<n;i++){sum += nums[i];}
        if (abs(target) > sum || (sum + target) % 2) return 0;
        int positive = (sum + target) / 2;  // 背包容量,加正号的部分数组
        vector<int> dp(positive+1);  //注意dp数组的含义是装满容量为j的背包有多少种方法
        dp[0] = 1;  //初始化
        for(int i=0; i<n; i++)
        {
            for (int j=positive; j>= nums[i]; j--)
            {
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[positive];
    }
};

自己实现时容易忽略一些边界条件(target绝对值大于数组总和,或者sum+target不能被2整除),导致提交出错。总的时间复杂度是O(n*m),空间复杂度是O(m),n为数组长度,m为背包容量。

LeetCode 474 一和零 

题目链接:474. 一和零 - 力扣(Leetcode)

 这一题的解题思路也是类似的,每个字符串相当于物品,只是背包的维度变成了二维:

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        // dp[i][j]:最多包含i个0和j个1的最大子集长度
        vector<vector<int>> dp(m+1, vector<int>(n+1));
        // 初始化,dp全为0即可
        for(int i=0; i < strs.size(); i++){
            // 字符串0 1 计数
            int c0 = 0, c1 = 0;
            for(int x=0; x < strs[i].size(); x++)
            {
                if (strs[i][x] == '0') c0 += 1;
                else c1 += 1;
            }
            for(int j=m; j>=c0; j--)
            {
                for (int k=n; k>=c1; k--)
                {
                    dp[j][k] = max(dp[j][k], dp[j-c0][k-c1] + 1);
                }
            }
        }

        return dp[m][n];

    }
};

背包的两个维度没有先后顺序之分,只需要维度内部保证是倒序遍历。也可以多加一个判断条件,当c0或c1超出m、n的大小时就能跳过,不用进行后续遍历(放不下)。总的时间复杂度是O(N*k*m*n),其中N是字符串的数量,k是字符串的平均长度,空间复杂度是O(mn)。


http://www.kler.cn/a/5274.html

相关文章:

  • C++ 复习总结记录六
  • 基于LabVIEW的BeamGage自动化接口应用
  • SQL概述
  • selenium合集
  • STM32Flash读写BUG,坑—————4字对齐
  • LeetCode 第34题:二分查找+扩展搜索
  • ThinkPHP大学生招聘管理系统
  • 读spring源码
  • Python3 os.close() 方法、Python3 File readline() 方法
  • POSTGRESQL 再说 PGBOUNCER 如何部署的问题
  • GoogleTest Advanced 官方doc 机翻
  • OSPF----优化
  • 永久免费CRM怎么选?有什么好用的功能?
  • 1663_MIT 6.828 JOS页面的分配与回收
  • 北大考研复试准备
  • 开源DataX集成可视化项目Datax-Web的使用
  • 膳食真菌在癌症免疫治疗中的作用: 从肠道微生物群的角度
  • 【HTTP详解】常用的14个HTTP状态码
  • ChatGPT开始威胁程序员的核心能力了!
  • Java设计模式(九)—— 中介者模式
  • 从NLP视角看电视剧《狂飙》,会有什么发现?
  • 15. 三数之和(Java)
  • 软件架构class-5-ORMapping思想
  • 发现一个白嫖GPT4.0的方法!真的是完胜3.5!
  • 二叉树练习题(数据结构系列10)
  • 【linu】ARM安装vscode服务器,本地vscode远程服务器开发