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

news2024/11/20 22:38:38

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/669249.html

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

相关文章

MVC框架的model,view,controllr如何运作

第一步&#xff1a; MVC 是&#xff1a;model&#xff0c;view&#xff0c;controller 的缩写。 第二步&#xff1a; view负责界面显示&#xff0c;也就是jsp&#xff0c;html页面 controller是控制业务流程&#xff0c;也就是servlet&#xff0c;service等java文件 model是…

AI绘图网站 AI绘图生成器推荐

一、怎样设置关键词才能制作出美观高端的图片&#xff1f; 要在使用AI绘画软件时生成高端的优质图片&#xff0c;关键词的设置是不可或缺的重要因素。以下是一些关键词设置的建议。 确立你想要呈现的主题或题材 在设定关键字之前&#xff0c;你必须确定你所要表现的主题或题材…

基于pix实现无人机编队表演

文章目录 前言一、飞控LED灯光控制二、飞控路径控制三、飞控和地面站通信接口四、舞步设计五、gazebo仿真 前言 编队灯光表演没有什么高深的技术&#xff0c;主要是一些应用层的开发&#xff0c;事实上即使没有任何编程基础&#xff0c;按本教程操作也可以实现。 硬件准备&am…

spring security oauth2整合SSO(单点登录)

1.流程 用户在访问应用程序时&#xff0c;将被重定向到身份认证服务器进行身份验证。用户输入他们的凭据&#xff08;通常是用户名和密码&#xff09;&#xff0c;身份认证服务器对其进行验证。身份认证服务器向用户颁发一个令牌&#xff0c;该令牌表示用户已经通过身份验证。…

正则表达式(1)

文章目录 正则表达式一.基础命令1.grep命令1.1grep格式1.2grep命令选项 2.特殊的符号2.1空行——^$2.2以什么为开头—^,以什么为结尾—$2.2.1以什么为开头的格式&#xff1a;2.2.2以什么为结尾的格式&#xff1a; 3.只匹配单行——^匹配的字符$ 二.文本处理命令1.sort命令1.1命…

使用大型语言模(LLM)构建系统(七):评估1

今天我学习了DeepLearning.AI的 Building Systems with LLM 的在线课程&#xff0c;我想和大家一起分享一下该门课程的一些主要内容。之前我们已经学习了下面这些知识&#xff1a; 使用大型语言模(LLM)构建系统(一)&#xff1a;分类使用大型语言模(LLM)构建系统(二):内容审核、…

Java 10 新特性解读

前言  2018年3月21日&#xff0c;Oracle官方宣布Java10正式发布。  需要注意的是 Java 9 和 Java 10 都不是 LTS (Long-Term-Support) 版本。和过去的 Java 大版本升级不同&#xff0c;这两个只有半年左右的开发和维护期。而未 来的 Java 11&#xff0c;也就是 18.9 LTS&am…

SpringBoot基于Aop实现自定义日志注解(提供Gitee源码)

前言&#xff1a;日志在我们的日常开发当中是必定会用到的&#xff0c;在每个方法的上都会习惯性打上Log注解&#xff0c;这样系统就会自动帮我们记录日志&#xff0c;整体的代码结构就会非常优雅&#xff0c;这边我自己搭建了一个demo去实现了一些这个项目当中必定会用的功能。…

根据jar名称动态打包带版本的镜像, 并创建对应容器的脚本实现

根据jar名称动态打包带版本的镜像以及容器 利用shell脚本, 实现根据jar名称中的项目名和版本号来动态制作带版本的Docker镜像以及带版本的容器 背景 人人都逃不过的墨菲定律 事情的原因来自最近发生的一次生产环境事故: 我们在甲方那里环境中有两个服务器, 一个用于灰度测试另…

WPF开发txt阅读器16:自动编码检测

文章目录 更改编码重新载入自动编码检测更改编码并保存 txt阅读器系列&#xff1a; 需求分析和文件读写目录提取类&#x1f48e;列表控件与目录&#x1f48e;快捷键翻页字体控件绑定&#x1f48e;前景/背景颜色书籍管理系统&#x1f48e;用树形图管理书籍语音播放&#x1f48e…

Cortext-M3系统:储存器系统(2)

1、存储系统功能概览 Cortext-M3储存器有如下特点&#xff1a; 存储器映射是预定义的&#xff0c;并且还规定好了哪个位置使用哪条总线。 存储器系统支持所谓的“位带”&#xff08;bit-band&#xff09;操作。通过它&#xff0c;实现了对单一比特的原子操作&#xff0c;位带操…

STM32G0+EMW3080+阿里云实现单片机WiFi智能联网功能(一)EMW3080实现和PC之间的串口通讯

项目描述&#xff1a;该系列记录了STM32G0EMW3080实现单片机智能联网功能项目的从零开始一步步的实现过程&#xff1b; 硬件环境&#xff1a;单片机为STM32G030C8T6&#xff1b;物联网模块为EMW3080V2-P&#xff1b;网联网模块的开发板为MXKit开发套件&#xff0c;具体型号为XC…

基于tensorflow深度学习的猫狗分类识别

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Triton教程 --- 速率限制器

Triton教程 — 速率限制器 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理 速率限制器 速率限制器管理 Triton 在模型实例上调度请求的速率。 速率限制器在 Triton 中加载的所有模型上运行&#xff0c;以允许跨模型优…

带你用Python制作7个程序,让你感受到端午节的快乐

名字&#xff1a;阿玥的小东东 学习&#xff1a;Python、C/C 主页链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 前言 程序1&#xff1a;制作粽子 程序2&#xff1a;龙舟比赛 程序3&#xff1a;艾草挂 程序4…

基于Java高校共享单车管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

《网络安全0-100》网络安全的未来趋势

网络安全的未来趋势 网络安全是一个永恒的话题&#xff0c;随着技术的发展 和应用&#xff0c;网络安全也面临着新的挑战和威胁。 以下是网络安全未来的趋势&#xff1a; 人工智能和机器学习&#xff1a;人工智能和机器学习已 经成为网络安全领域的热门技术。未来&#xff…

编译原理笔记11:自上而下语法分析(1)基础概念、左递归和公共左因子处理、递归下降分析(咕咕咕)

目录 自上而下分析的一般方法用推导的方法分析输入序列左递归问题及其消除&#xff08;消除左递归&#xff09;消除直接左递归消除间接左递归左递归消除算法 公共左因子问题及其消除&#xff08;提取左因子&#xff09;提取左因子 递归下降分析 词法分析&#xff0c;是把源程序…

基于物联网及云平台的光伏运维系统

系统结构 在光伏变电站安装逆变器、以及多功能电力计量仪表&#xff0c;通过网关将采集的数据上传至服务器&#xff0c;并将数据进行集中存储管理。用户可以通过PC访问平台&#xff0c;及时获取分布式光伏电站的运行情况以及各逆变器运行状况。平台整体结构如图所示。 光伏背景…

Cortext-M3系列:调试组件(9)

1、调试组件简介 在 CM3 中有很多调试组件&#xff0c;使用它们可以执行各种调试功能&#xff1a;断点、数据观察点、闪存地址重载以及各种跟踪等。软件开发人员也许永远无需了解调试组 的细节&#xff0c;因为它们通常只是由调试器及其周边工具使用的。 本文对每种调试组件做一…