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

leedcode 算法刷题第三十一天

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

有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

  • 如果 x == y,那么两块石头都会被完全粉碎;
  • 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x

最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0

示例 1:

输入:stones = [2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。

示例 2:

输入:stones = [31,26,33,21,40]
输出:5

提示:

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

解题思路:

问题背景

问题描述:有一堆石头,每次从中挑选两块石头碰撞,碰撞后会得到一块新石头(重量为两块石头的差值),直到只剩下一块石头或没有石头。求最后可能剩下的最小石头重量。

核心思路转化

这个问题可以转化为:

  • 将石头分成两堆,使两堆的重量尽可能接近
  • 两堆重量的差值就是最后可能剩下的最小重量

这是因为每次碰撞相当于从总重量中减去 twice of 较小的那块石头的重量,最优策略是让两堆石头重量尽可能接近。

代码原理详解

  1. 动态规划数组定义
    dp[j]表示能从石头中选出若干块,使得它们的总重量最大为j

  2. 计算目标值
    sum是所有石头的总重量
    targetsum/2,我们尝试找到不超过这个值的最大子集和

  3. 0-1 背包问题求解

    • 外层循环遍历每块石头(物品)
    • 内层循环从target往当前石头重量遍历(0-1 背包的典型做法,避免重复使用同一物品)
    • dp[j] = max(dp[j], dp[j - stones[i]] + stones[i])表示对于重量j,要么不选当前石头保持dp[j],要么选当前石头得到dp[j - stones[i]] + stones[i]
  4. 计算结果
    sum - dp[target] - dp[target]表示总重量减去两倍的最大子集和(即两堆石头的重量差)

这个解法的时间复杂度是 O (n×target),空间复杂度是 O (target),其中 n 是石头数量,target 是总重量的一半。

494. 目标和

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int sum = 0;for (int i = 0; i < nums.size(); i++) sum += nums[i];if (abs(target) > sum) return 0; // 此时没有方案if ((target + sum) % 2 == 1) return 0; // 此时没有方案int bagSize = (target + sum) / 2;vector<int> dp(bagSize + 1, 0);dp[0] = 1;for(int i=0;i<nums.size();i++){for(int j = bagSize;j>=nums[i];j--){dp[j] += dp[j-nums[i]];}}return dp[bagSize];}
};

给每个数字添加+-,本质上是将数组分成两组:

  • 一组数字前面加+(和为left
  • 另一组数字前面加-(和为right

根据题意,我们有:

plaintext

left - right = target  // 表达式结果等于target
left + right = sum     // 所有数字的总和

将两式相加可得:2*left = target + sum,即 left = (target + sum) / 2

因此问题转化为:找到有多少种方法可以从数组中选出一些数字,使它们的和等于 left

代码解析

  1. 边界条件判断

    if (abs(target) > sum) return 0; // 目标值的绝对值大于总和,不可能实现
    if ((target + sum) % 2 == 1) return 0; // 和为奇数,无法被2整除,没有方案
    
  2. 计算背包大小

    int bagSize = (target + sum) / 2;
    
     

    这里的bagSize就是我们要找的left值,即需要选出和为bagSize的数字组合

  3. 动态规划初始化

     
    vector<int> dp(bagSize + 1, 0);
    dp[0] = 1; // 表示凑出和为0的方法有1种(什么都不选)
    
     

    dp[j]的含义是:有多少种方法可以选出和为 j 的数字组合

  4. 填充 DP 数组

     
    for (int i = 0; i < nums.size(); i++) { // 遍历每个数字for (int j = bagSize; j >= nums[i]; j--) { // 倒序遍历背包dp[j] += dp[j - nums[i]];}
    }
    
     
    • 对于每个数字,我们可以选择 "放入背包"(即该数字前面加+)或 "不放入背包"(即该数字前面加-
    • 状态转移方程dp[j] += dp[j - nums[i]]表示:凑出和为 j 的方法数 = 不选当前数字的方法数 + 选当前数字的方法数(此时需要加上凑出 j-nums [i] 的方法数)
    • 倒序遍历是为了保证每个数字只被使用一次(0-1 背包特性)
  5. 返回结果

    return dp[bagSize];
    
     

    dp[bagSize]就是能凑出和为bagSize的方法总数,也就是我们要求的答案

示例说明

nums = [1,1,1,1,1]target = 3为例:

  • 总和sum = 5
  • bagSize = (3 + 5) / 2 = 4
  • 问题转化为:有多少种方法从数组中选出和为 4 的数字
  • 答案是 5 种,对应 5 种不同的表达式

474. 一和零

class Solution {
public:int findMaxForm(vector<string>& strs, int m, int n) {vector<vector<int>> result(m+1,vector<int> (n+1,0));for(string str:strs){int onenumber = 0,zeronumber = 0;for (char c : str) {if (c == '0') zeronumber++;else onenumber++;}for(int i=m;i>=zeronumber;i--){for(int j=n;j>=onenumber;j--){result[i][j] = max(result[i][j],result[i-zeronumber][j-onenumber]+1);}}}return result[m][n];}};

问题理解

给定一组二进制字符串,每个字符串包含0和1。我们需要选择一些字符串,使得:

  • 所有选中字符串中0的总数不超过 m

  • 所有选中字符串中1的总数不超过 n

  • 选中的字符串数量尽可能多

动态规划思路

1. 状态定义

dp[i][j] 表示:使用最多 i 个0和 j 个1时,能够组成的最大字符串数量

2. 状态转移

对于每个字符串,我们有两种选择:

  • 不选这个字符串dp[i][j] 保持不变

  • 选这个字符串dp[i][j] = dp[i - zeros][j - ones] + 1

状态转移方程:

cpp

dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1);

3. 为什么从后往前遍历?

这是0-1背包问题的核心技巧:

  • 从后往前遍历:确保每个字符串只被使用一次(避免重复选择)

  • 如果从前往后遍历,会变成完全背包问题(每个物品可以选多次)

4. 具体步骤

  1. 初始化:创建 (m+1) × (n+1) 的二维数组,初始值为0

  2. 遍历每个字符串

    • 计算当前字符串的0和1的数量

    • 从 m 到 zeros,从 n 到 ones 反向更新dp表

  3. 返回结果dp[m][n] 就是最大数量

示例说明

假设 strs = ["10", "0001", "111001", "1", "0"]m = 5n = 3

对于字符串 "10":

  • zeros = 1, ones = 1

  • 更新所有 i >= 1 且 j >= 1 的位置

对于字符串 "0001":

  • zeros = 3, ones = 1

  • 更新所有 i >= 3 且 j >= 1 的位置

以此类推...

时间复杂度

  • 外层循环:O(S),S为字符串数量

  • 内层循环:O(M × N)

  • 总复杂度:O(S × M × N)

空间复杂度

  • O(M × N),即dp表的大小

这种解法巧妙地利用了动态规划和背包问题的思想,通过反向遍历确保每个字符串只被考虑一次,从而找到最优解。


文章转载自:

http://ekQvKhuq.tgmwy.cn
http://PbLvQMlW.tgmwy.cn
http://tr41HZbS.tgmwy.cn
http://YTsKH4DS.tgmwy.cn
http://V2iq1Xqz.tgmwy.cn
http://QJ8Hq6gz.tgmwy.cn
http://PFR8JmAB.tgmwy.cn
http://ZEUWYItZ.tgmwy.cn
http://GWfkrgUo.tgmwy.cn
http://A2xvfaHG.tgmwy.cn
http://3BHpxBT2.tgmwy.cn
http://6rOlqMnq.tgmwy.cn
http://ZW4tKHh9.tgmwy.cn
http://Ojd2h5cq.tgmwy.cn
http://l0BRB33y.tgmwy.cn
http://6xkUbMye.tgmwy.cn
http://5MNVnKZV.tgmwy.cn
http://81b94oXY.tgmwy.cn
http://C708kH5w.tgmwy.cn
http://rwtzrLKQ.tgmwy.cn
http://grQ40h81.tgmwy.cn
http://5LGwaST0.tgmwy.cn
http://bJRxPqWK.tgmwy.cn
http://5xKleAEO.tgmwy.cn
http://oyN0SGQO.tgmwy.cn
http://S209oZSb.tgmwy.cn
http://yvYwRa7y.tgmwy.cn
http://Iy8gWEdO.tgmwy.cn
http://KYtn1eA2.tgmwy.cn
http://NRsvHPNu.tgmwy.cn
http://www.dtcms.com/a/377002.html

相关文章:

  • IDEA下载安装图文教程(非常详细,适合新手)
  • Spark 性能优化全攻略:内存管理、shuffle 优化与参数调优
  • 老味道私房菜订餐系统的设计与实现(代码+数据库+LW)
  • 古董装载优化:30秒破解重量限制
  • Vue2手录02-指令
  • 爬虫逆向之瑞数6案例(深圳大学某某附属医院)
  • AWK工具使用与技巧指南
  • Java程序员职业发展路径与转型选择分析报告(2025年)
  • 资产管理软件哪家口碑好
  • 【实战中提升自己完结篇】分支篇之分支之无线、内网安全与QOS部署(完结)
  • 【Qt】PyQt、原生QT、PySide6三者的多方面比较
  • 多级缓存架构
  • 多模态对齐与多模态融合
  • 【MySQL】常用SQL语句
  • 教师节组诗-我不少年师已老,无报师恩仅遥忆
  • 手把手带你推导“逻辑回归”核心公式
  • 当按摩机器人“活了”:Deepoc具身智能如何重新定义人机交互体验
  • solidity得高级语法3
  • PLM 与机器视觉协同:三维模型驱动自动光学检测标准制定
  • vuecli中使用splitchunksplugin提取公共组件,vue单组件使用less scoped处理style,打包会有css顺序冲突警告
  • 元宇宙与旅游产业:沉浸式体验重构旅行全流程
  • 城市道路落叶清扫机设计【三维SW模型】【含9张CAD
  • apache poi 导出复杂的excel表格
  • 海外盲盒APP开发:如何用技术重构“惊喜经济”
  • Linux快速安装JDK1.8
  • 高可用消息队列线程池设计与实现:从源码解析到最佳实践
  • 使用nvm管理node多版本(安装、卸载nvm,配置环境变量,更换npm淘宝镜像)
  • Python 0909
  • 二进制安装MySQL 8.0指南:跨平台、自定义数据路径、安全远程访问配置
  • MySQL - 全表扫描 会发生死锁?