算法篇——动态规划 01背包问题 (js版)——更新新题

news2025/1/15 14:24:16

416. 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

链接:力扣

 

解题思路:

这道题看似是比较简单的背包问题:

首先可以通过判断数组和是否是偶数,因为如果是奇数是必然不可能拆分成两个数组的,直接返回false;

if(nums.length == 1) return false
var sum = 0
// 数组求和
for(var i = 0; i < nums.length; i++) {
    sum += nums[i]
}
// 如果相加不是偶数说明不可能拆成相等的两个数组
if(sum % 2 != 0) return false

接着获取sum / 2,也就是target,我们利用一个新的方法:getSum,对元素和它的索引指针进行遍历

// 目标值是当前和的一半
var target = sum / 2
// map 用来遍历元素,可记录元素状态,相比循环更快,防止超时
const map = new Map()

这里也是有两个临界判断的:指针是否会越界,数组中的数据项之和是否会超出 target,否则就是可以拆分的

// 越界
if(i == nums.length || cur > target) return false
// 可拆分
if(cur == target) return true

但同时还需要判断不连续的数据项是否满足

var key = cur + '+' + i
// 如果map中有对应的缓存值,直接get拿出使用
if(map.has(key)) {
   return map.get(key)
}
// 如果需要当前的项,就加入当前的和,移动指针到下一位
// 如果不需要当前的项,就直接移动指针到下一位
const res = getSum(cur + nums[i], i + 1) || getSum(cur, i + 1)
// 避免重复遍历元素,存入map中,可直接对应查找
map.set(key, res)
return res

这里调用方法时,主要考虑:如果需要当前的项,就加入当前的和,移动指针到下一位;如果不需要当前的项,就直接移动指针到下一位

并且利用map的好处就是可以防止重复遍历计算元素,提高了时间性能

下面是完整代码:

var canPartition = function(nums) {
    if(nums.length == 1) return false
    var sum = 0
    // 数组求和
    for(var i = 0; i < nums.length; i++) {
        sum += nums[i]
    }
    // 如果相加不是偶数说明不可能拆成相等的两个数组
    if(sum % 2 != 0) return false 
    // 目标值是当前和的一半
    var target = sum / 2
    // map 用来遍历元素,可记录元素状态,相比循环更快,防止超时
    const map = new Map()
    // cur: 当前和,i:指针
    const getSum = (cur, i) => {
        // 越界
        if(i == nums.length || cur > target) return false
        // 可拆分
        if(cur == target) return true
        var key = cur + '+' + i
        // 如果map中有对应的缓存值,直接get拿出使用
        if(map.has(key)) {
            return map.get(key)
        }
        // 如果需要当前的项,就加入当前的和,移动指针到下一位
        // 如果不需要当前的项,就直接移动指针到下一位
        const res = getSum(cur + nums[i], i + 1) || getSum(cur, i + 1)
        // 避免重复遍历元素,存入map中,可直接对应查找
        map.set(key, res)
        return res
    }
    return getSum(0, 0) // 递归入口,从第一个元素开始遍历
};

以上的代码时间消耗很大,下面利用动态规划的方法,与上面的思路类似,也是要进行如下步骤:(注意看图中的红框内容)

 

1.根据数组的长度 nums.length 判断数组是否可以被划分:如果 n=1,直接返回 false

2.计算整个数组的元素和 sum 以及最大元素 maxNum:如果 sum 是奇数,直接返回 false;反之,target= sum / 2

3.判断是否数组中是否存在元素的和等于 target。如果 maxNum > target,则除了 maxNum 以外的所有元素之和一定小于 target,直接返回false,完整代码如下:

var canPartition = function(nums) {
    // 一个元素无法拆分成两个数组
    if(nums.length == 1) return false
    var sum = 0, max = 0
    for(var i = 0; i < nums.length; i++) {
        sum += nums[i]
        // 得到最大元素
        max = max > nums[i] ? max : nums[i]
    }
    // 数组和是奇数
    if(sum % 2 != 0) return false 
    // 目标值是数组和的一半
    var target = sum / 2
    // 如果 max > target,则除了 max 以外的所有元素之和一定小于 target
    if(max > target) return false
    // 定义长度为 target+1 的数组,并赋值数组的初始值
    // 对于给定的数组 nums,能否选取其中一部分元素,使得它们的总和恰好等于 target 
    // 初始时,将数组 dp 初始化为全零,表示当前还没有任何元素可以选取
    const dp = new Array(target+1).fill(0)
    // 外层循环变量 i 表示遍历数组 nums 的索引,内层循环变量 j 表示当前的目标和(从大到小递减)
    for(var i = 0; i < nums.length; i++) {
        for(var j = target; j >= nums[i]; j--) {
            // 如果 nums[i] 可以选,则更新 dp[j],意味着 dp[j - nums[i]] + nums[i]:前一个目标和 j - nums[i] 的最优结果加上当前选的 nums[i]
            // 如果 nums[i] 不可选,则 dp[j] 保持不变
            dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i])
            // 判断 dp[j] 是否等于 target:如果相等,返回 true,否则继续遍历
            if(dp[j] == target) return true
        }
    }
    return dp[target] == target
}

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

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

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0。

链接:力扣

这道题的思路和上一题的思路类似,让石头分成重量相同的两堆,相撞后剩下的石头最小 

var lastStoneWeightII = function(stones) {
    // 如果只有一块石头,直接返回当前石头的重量
    if(stones.length == 1) return stones[0]
    var sum = 0
    for(var i = 0; i < stones.length; i++) {
        sum += stones[i]
    }
    var target = Math.floor(sum / 2)
    var dp = new Array(target+1).fill(0)
    for(var i = 0; i < stones.length; i++) {
        for(var j = target; j >= stones[i]; j--) {
            dp[j] = Math.max(dp[j], dp[j- stones[i]] + stones[i])
        }
    }
    return sum - 2 * dp[target]
}

494. 目标和

给你一个整数数组 nums 和一个整数 target 。

向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目

链接:力扣 

解题思路:本题不同于上面两题的地方在于,这题需要求出有多少种方法,前面的递推公式都是

dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i])

但这道题的是把 所有的 dp[j - nums[i]] 累加起来,这也是组合类问题的共性,即 

dp[j] += dp[j - nums[i]]

如果数组长度是1 ,考虑这个元素与 target 的大小值,如果不等于 target 的绝对值,返回0,反之返回1

// 如果数组长度是1
if(nums.length == 1) {
   if(nums[0] == Math.abs(target)) return 1
   else return 0
}

如果 目标值的绝对值 大于 数组元素之和 sum,无法找到满足条件的方案;如果 目标值与元素之和 的和 取余为非零值,直接返回 0,因为如果将数组分为两个子集,它们的和必须相等,而加法操作结果除以 2 的余数只能是 0 或 1

// 目标值的绝对值大于数组元素之和 sum,无法找到满足条件的方案
// 如果 目标值与元素之和 的和 取余为非零值,直接返回 0
if(Math.abs(target) > sum || (target + sum) % 2) return 0
var mid = (target + sum) / 2
var dp = new Array(mid+1).fill(0)

下面是完整的代码:

var findTargetSumWays = function(nums, target) {
    // 如果数组长度是1
    if(nums.length == 1) {
        if(nums[0] == Math.abs(target)) return 1
        else return 0
    }
    var sum = 0
    for(var i = 0; i < nums.length; i++) {
        sum += nums[i]
    }
    // 目标值的绝对值大于数组元素之和 sum,无法找到满足条件的方案
    // 如果 目标值与元素之和 的和 取余为非零值,直接返回 0
    if(Math.abs(target) > sum || (target + sum) % 2) return 0
    var mid = (target + sum) / 2
    var dp = new Array(mid+1).fill(0)
    // 这里初值赋值为 1,是因为如果为 0,则递归下来,只可能有 1种方案,因为始终为 0
    dp[0] = 1
    for(var i = 0; i < nums.length; i++) {
        for(var j = mid; j >= nums[i]; j--) {
            dp[j] += dp[j - nums[i]]
        }
    }
    return dp[mid]
}

474. 一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。

请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。

如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。


链接:力扣

解题思路:

这里的二维数组定义dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j] 

01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

因此此题中 dp[i][j] 可以由前一个 strs 中的字符串推出,strs里的字符串有 num0 个0,num1 个1

dp[i][j] 是 dp[i - num0][j - num1] + 1。然后遍历取 dp[i][j] 的最大值,递推公式:

dp[i][j] = max(dp[i][j], dp[i - num0][j - num1] + 1)
var findMaxForm = function(strs, m, n) {
    const dp = Array(m+1).fill(0).map(() => Array(n+1).fill(0)) 
    for(const str of strs) {
        let num0 = 0
        let num1 = 0
        for(const s of str) {
            // 对字符串中的 0 和 1 计数
            if(s == '0') num0++
            else num1++
        }
        // 使用两个倒序循环,从 m 到 numOfZeros,从 n 到 numOfOnes,更新 dp[i][j] 的值
        for(let i = m; i >= num0; i--) {
            for(let j = n; j >= num1; j--) {
                // 如果加入当前字符串,则更新 dp[i][j] 的值为 之前 dp[i - numOfZeros][j - numOfOnes] 的值加1
                // 如果不加入当前字符串,则 dp[i][j] 不变
                dp[i][j] = Math.max(dp[i][j], dp[i - num0][j - num1] + 1)
            }
        }
    }
    return dp[m][n]
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/667025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【ZenUML】时序图之ZenUML详解

时序图 序列图是一种交互图&#xff0c;显示进程如何彼此操作以及顺序。 Mermaid可以使用ZenUML渲染序列图。请注意&#xff0c;ZenUML使用的语法与mermaid中的原始序列图不同。 目前&#xff0c;最新版本mermaid v10.2.3 暂时不单独支持zenuml语法,需要配合mermaid-zenuml一…

动态规划_可视化校园导航Floyd算法应用

目录 引言 图片展示 视频展示 针对校园导航问题的分析 关键技术和算法介绍 详细介绍&#xff1a;算法的实现 总结 代码 附件&#xff1a;Map.png 引言 本文主要通过详细的程序打印和作者的推理过程&#xff0c;描述作者对Floyd算法的理解&#xff0c;阐述其中的动态规划思想是如…

突然发现CSDN变得不一样了【建议】【活动】

突然发现CSDN变得不一样了【活动】 前言推荐突然发现CSDN变得不一样了关于上传代码包关于上传视频关于运行代码关于插入代码1关于插入代码2关于社区的建立关于社区的管理关于此次活动的评选关于排行突然发现说明一下关于我 最后 前言 2023-6-19 23:34:04 本文章仅用于参加 20…

【Python 随练】年龄计算问题

题目&#xff1a; 有 5 个人坐在一起&#xff0c;问第五个人多少岁&#xff1f;他说比第 4 个人大 2 岁。问第 4 个人岁数&#xff0c;他说比第3 个人大 2 岁。问第三个人&#xff0c;又说比第 2 人大两岁。问第 2 个人&#xff0c;说比第一个人大两岁。最后问第一个人&#x…

C++基础(8)——类和对象(6)

前言 本文主要介绍了C中多态的基本知识 4.7.1&#xff1a;多态的基本概念和原理剖析 1&#xff1a;基本概念 静态多态&#xff1a;函数重载、运算符重载 动态多态&#xff1a;派生类和虚函数实现运行时多态 静态多态在编译阶段确定函数地址&#xff1b;动态多态在运行阶段…

微信小程序uniapp+springboot实现小程序服务通知

微信小程序uniappspringboot实现小程序服务通知 1. 实现效果 2. 模板选用及字段类型判断 2.1 开通订阅消息,并选用模板 如果点击订阅消息让开启消息订阅开启后就可以出现以下页面,我本次使用的模板是月卡到期提醒模板,点击选用即可 2.2 查看模板字段类型 TemplateId后续会使用…

面试官问:Redis 分布式锁如何自动续期?

资深面试官&#xff1a;你们项目中的分布式锁是怎么实现的&#xff1f; 老任&#xff1a;基于redis的set命令&#xff0c;该命令有nx和ex选项。 资深面试官&#xff1a;那如果锁到期了&#xff0c;业务还没结束&#xff0c;如何进行自动续期呢&#xff1f; 老任&#xff1a;…

第九章 番外篇:TORCHSCRIPT

下文中的代码都使用参考教程中的例子。 会给出一点自己的解释。 参考教程&#xff1a; 文章目录 Introduction复习一下nn.Module()Torchscripttorch.jit.ScriptModule()torch.jit.script()torch.jit.trace()一个小区别 使用示例tracing Modulesscripting ModuleMixing scripti…

乐鑫线上研讨会|探索 LCD 屏在物联网中的发展趋势

LCD 屏通过显示实时信息并提供交互式体验&#xff0c;现已成为各类设备的重要组成部分。在当下的 AIoT 时代&#xff0c;随着物联网技术的快速发展和应用场景的不断拓展&#xff0c;LCD 作为人机交互的主要输入输出设备&#xff0c;在智能家居、智能安防、工业控制、智慧城市等…

C#开发的OpenRA游戏之建造物品的窗口5

C#开发的OpenRA游戏之建造物品的窗口5 前面分析了TAB窗口的建立和运行,现在关注它的子窗口,也就是ProductionPaletteWidget类实现的窗口,这个窗口主要用来显示所有可以创建物品的ICON图标。用户可以通过这个窗口实现物品创建,如下图所示: 比如要创建电厂,就是点击上面…

【好书精读】网络是怎样连接的 之 创建套接字

&#xff08;该图由AI制作 学习AI绘图 联系我&#xff09; 目录 协议栈的内部结构 套接字的实体就是通信控制信息 真正的套接字 调用 socket 时的操作 从应用程序收到委托后 &#xff0c; 协议栈通过 TCP 协议收发数据的操作可以分为 4 个阶段 。 首先是创 建套接字 &…

SothisAI创建容器和conda环境

1.创建容器&#xff08;设置torch版本&#xff0c;cuda&#xff0c;python版本等等&#xff09;后进入web shell 2.shell里输入ssh username&#xff08;你自己的用户名&#xff09; IP&#xff08;你创建的实例的ip地址&#xff09; 3.在web平台创建你自己的文件夹 4.shel…

小程序请求封装、使用

小程序请求封装 1、要了解方法 1.1、wx.request() wx.request 发起 HTTPS 网络请求。&#xff08;详情点击wx.request查看官方文档&#xff09; 1.2、wx.showModal() wx.showModal 显示模态对话框。&#xff08;详情点击wx.showModal查看官方文档&#xff09; 1.3、wx.sho…

Swift 周报 第三十一期

文章目录 前言新闻和社区注册 WWDC23 实验室和活动Apple Vision Pro 和 visionOS 撼世登场App Store 中新增的隐私功能 提案正在审查的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组自主整理周报的第二十二期&#xff0c;每个模块已初步成型。各位读者如果…

奇数分频器电路设计

目录 奇数分频器电路设计 1、奇数分频器电路简介 2、实验任务 3、程序设计 3.1、7分频电路代码 3.2、仿真验证 3.2.1、编写 TB 文件 3.2.2、仿真验证 4、用状态机实现7分频电路设计 4.1、代码如下&#xff1a; 4.2、使用状态机的好处 奇数分频器电路设计 前面一节我…

前端JS限制绕过测试-业务安全测试实操(17)

前端JS限制绕过测试,请求重放测试 前端JS限制绕过测试 测试原理和方法 很多商品在限制用户购买数量时,服务器仅在页面通过JS脚本限制,未在服务器端校验用户提交的数量,通过抓取客户端发送的请求包修改JS端生成处理的交易数据,如将请求中的商品数量改为大于最大数限制的值…

Vue中使用分布式事务管理解决方案

文章目录 分布式事务管理是什么优点&#xff1a;缺点&#xff1a;弥补缺点的方法有&#xff1a; 解决方案 分布式事务管理是什么 分布式事务管理是指在分布式系统中对跨多个数据库或服务的操作进行协调和保证一致性的机制。在分布式环境下&#xff0c;由于涉及到多个独立的资源…

半年面试12家大厂,我总结出了这份2023版互联网大厂(Java岗)面试真题汇总

Java面试 现在互联网大环境不好&#xff0c;互联网公司纷纷裁员并缩减HC&#xff0c;更多程序员去竞争更少的就业岗位&#xff0c;整的IT行业越来越卷。身为Java程序员的我们就更不用说了&#xff0c;上班8小时需要做好本职工作&#xff0c;下班后还要不断提升技能、技术栈&am…

docker 命令解释 - nginx镜像制作

目录 Dockerfile 部分命令解释 1、ENTRYPOINT 而ENTRYPOINT 语言 CMD的区别 1、docker run 启动容器的时候&#xff0c;可以传递参数进入给ENTRYPOINT里面的命令&#xff08;-e&#xff09; 2、当2者都存在的时候&#xff0c;CMD里的内容会成为 ENTRYPOINT 里的参数&#x…

Pytest中断言的重要性

目录 前言 pytest断言 增加断言详细信息 异常断言 .type .value .traceback pytest常用断言 前言 在pytest中&#xff0c;断言是非常重要的一部分。断言可以帮助我们验证代码的正确性&#xff0c;检查函数返回的值是否符合要求&#xff0c;以及判断程序中预期行为是否发生。如…