Leetcode 分割等和子集

news2024/12/23 2:02:20

在这里插入图片描述
这段代码的目的是解决 LeetCode 416 问题:分割等和子集,即判断一个只包含正整数的数组,是否能够将其分割成两个子集,使得这两个子集的元素和相等。

算法思想(动态规划 - 背包问题)

该问题本质上是一个经典的动态规划问题,可以转化为“0-1 背包问题”,具体解释如下:

1. 总和判断:
  • 首先,我们需要计算数组中所有元素的总和 sum
  • 如果这个总和 sum奇数,那我们无法将其分为两个和相等的子集,因为奇数无法均匀分成两个整数。因此,直接返回 false
  • 如果 sum偶数,则问题转化为:我们能否找到一个子集,使得该子集的和等于 sum / 2。如果可以找到这样一个子集,那么剩余的元素自然也会构成另一个子集,它们的和也为 sum / 2
2. 动态规划:
  • 动态规划的核心思想是:我们用一个布尔数组 dp 来表示从数组中是否可以选出若干元素使得这些元素的和为 j。其中 dp[j] 表示数组中的某些子集是否可以构成和为 j 的子集。
  • 初始化:dp[0] = true,表示我们可以通过不选择任何元素来构成和为 0 的子集。
  • 遍历数组中的每个元素 num,从后向前更新 dp 数组。对于每一个 num,我们要判断是否能通过之前的选择和这个 num 构成和为 j 的子集。
3. 代码执行过程:
  • 首先计算数组元素的总和。
  • 如果总和是奇数,直接返回 false
  • 然后创建一个目标和 target = sum / 2,这也是我们希望找到的子集的和。
  • 初始化一个长度为 target + 1 的布尔数组 dp,用于记录是否可以通过某些元素构成特定的子集和。
  • 对数组中的每个元素 num,从后向前遍历 dp 数组,更新 dp[j],表示是否可以用之前的元素和当前元素 num 构成和为 j 的子集。
  • 最后,dp[target] 的值即为我们想要的结果,如果 dp[target]true,则表示可以找到这样的子集,返回 true;否则返回 false

动态规划的状态转移方程:

  • dp[j] = dp[j] || dp[j - num],即:
    • 如果在没有当前数字 num 的情况下可以构成和为 j 的子集,那么 dp[j]true
    • 或者,如果在没有当前数字 num 的情况下可以构成和为 j - num 的子集,那么加上当前数字 num 后也可以构成和为 j 的子集。

时间复杂度:

  • 时间复杂度为 O(n * target),其中 n 是数组中元素的个数,target 是总和的一半(即 sum / 2)。

总结:

这个问题使用动态规划解决,类似于“背包问题”的思路。通过判断是否可以找到和为 sum / 2 的子集,我们就可以判断数组是否能够被分割成两个和相等的子集。

java solution

class Solution {
    public boolean canPartition(int[] nums) {
        //只有nums所有元素和为偶数时,才有可能分割等和子集
        int sum = 0;
        for(int num : nums) {
            sum = sum + num;
        }

        if(sum % 2 != 0) {
            return false;
        }
        
        //如果所有元素和为偶数, 那么我们只要可以找到凑成和为 sum / 2 的子集,就可以返回 true
        int target = sum / 2;

        //创建boolean类型的dp数组, dp[i] 表示子集元素是否可以凑成和为 i
        // 通过 dp[target] 来判断是否凑成功, 所以创建  target + 1 个存储单元
        boolean[] dp = new boolean[target + 1];  //创建的boolean数组初始值默认为false
        dp[0] = true; //我们可以不选择任何元素来凑成 0

        //遍历每个数组元素,从后往前更新 dp
        for(int num : nums) {
            for(int j = target; j >= num; j--) {
                dp[j] = dp[j] || dp[j - num];
            }
        }

        return dp[target];
    }
}

如果 dp[j] 原本是 true,表示已经能够找到和为 j 的子集,保留 true
或者,如果 dp[j - num]true,表示可以通过之前的元素组合成和为 j - num 的子集,那么加上当前的 num,就可以组合成和为 j 的子集,所以 dp[j] 也应更新为 true

这部分代码片段,为什么是从后往前更新dp数组?

    // 遍历每个数字
    for (int num : nums) {
        // 从后向前更新 dp 数组
        for (int j = target; j >= num; j--) {
            dp[j] = dp[j] || dp[j - num];
        }
    }

这个代码片段中从 后往前 更新 dp 数组是为了防止在更新过程中产生错误的结果。原因与避免重复使用同一个元素有关,这是动态规划中处理“0-1 背包问题”时的常见技巧。

问题背景:

在这个问题中,每个元素只能使用一次。这与完全背包问题不同,后者允许一个元素多次使用。为了确保我们在更新 dp 数组时,使用的每个数字 num 只被处理一次,我们需要从后往前更新 dp 数组。

详细解释:

假设我们从前往后更新 dp 数组(即 for (int j = num; j <= target; j++))会导致的问题:

  • 例如,如果我们有一个数字 num = 5,当前目标和 target = 11
  • 当我们从前往后更新时,如果首先更新了 dp[5],假设原本 dp[5] = false,在这次更新中我们设置了 dp[5] = true
  • 但是,当我们接着更新 dp[10] 时,dp[10] 的更新依赖于 dp[5]。由于我们刚刚把 dp[5] 更新为了 true,此时 dp[10] 也会被更新为 true这样导致的问题是我们实际上用了两次 num = 5 来实现目标和,因为 dp[10] 是由新更新的 dp[5] 推导而来的,而这个 dp[5] 实际上是在同一轮循环中刚刚被更新过的。

因此,从前往后更新会导致我们错误地“多次使用”同一个元素。

为什么从后往前更新可以避免这个问题?

通过从后往前更新,可以确保每个元素 num 在每轮循环中只被使用一次。因为当我们更新 dp[j] 时,dp[j - num] 是基于 上一次循环的结果,而不是当前循环已经更新过的结果。具体原因如下:

  • 当我们从 targetnum 方向遍历时,dp[j] 只依赖于 dp[j - num],而 dp[j - num] 在当前循环之前还没有被更新过,因此不会重复使用同一个元素。

举例说明:

假设我们有数组 nums = [1, 5],目标和为 6,dp 数组的初始状态如下:

dp = [true, false, false, false, false, false, false]

其中 dp[0]true 表示可以通过不选任何元素实现和为 0

  • 首先处理 num = 1,从后往前更新 dp 数组:
    • 更新 dp[6],不变,因为 dp[5] 还是 false
    • 更新 dp[5],不变,因为 dp[4] 还是 false
    • 更新 dp[1] = dp[1] || dp[0] = true。更新后:
dp = [true, true, false, false, false, false, false]
  • 接下来处理 num = 5,从后往前更新 dp 数组:
    • 更新 dp[6] = dp[6] || dp[1] = true,因为 dp[1] 已经是 true(由之前的 num = 1 得到)。
    • 更新 dp[5] = dp[5] || dp[0] = true,因为 dp[0]true

最终得到:

dp = [true, true, false, false, false, true, true]

这说明我们可以找到两个子集,分别为 {1, 5},它们的和都是 6。

总结:

从后往前更新 dp 数组的目的是防止在同一次循环中重复使用同一个元素。这样确保了每个元素在一轮循环中只被考虑一次,从而正确解决问题。

为什么外层循环是遍历nums数组,而不是遍历j?

这段代码的外层循环遍历 nums 数组而不是直接遍历 j,是因为我们需要逐个处理数组中的每个数字,并根据这些数字来逐步更新 dp 数组。它遵循的算法思想是“0-1 背包问题”,每个数字只能使用一次,而不是可以无限制地使用。

具体原因和解释:

1. 动态规划的过程依赖于逐个数字的处理:
  • 这个问题的核心是寻找是否存在一个子集,其和等于目标值 target。为了实现这一点,我们必须逐个遍历数组中的每一个元素,检查当前元素 num 是否能与之前选择的数字组合成新的子集,从而更新是否能够构成特定的和。
  • 外层循环遍历 nums 数组,表示我们依次将数组中的每个元素加入到子集的选择中,然后根据这些元素更新 dp 数组,记录我们是否能够构成特定的子集和。
2. 动态规划状态的更新依赖于每个数字:
  • 对于每个数字 num,我们需要判断当前这个 num 能否帮助我们构成新的子集和,即从和为 j - num 的子集中,加上 num,能否构成和为 j 的子集。这是通过内层循环来完成的。
  • 外层循环遍历数组中的每个数字,每次拿到一个新的 num,我们就尝试更新所有可能的子集和(从 targetnum)。因此,外层循环必须遍历 nums 数组,以确保每个元素都被考虑一次。
3. 逐步构造子集和:
  • 动态规划的过程是一步步地构造所有可能的子集和,并用 dp[j] 来记录是否能构成和为 j 的子集。因此,必须逐个遍历每个 num,以确保每个数字都被正确处理,并且能基于前面的状态更新新的状态。
4. 确保每个数字只被使用一次:
  • 如果外层循环遍历 j,而不是遍历 nums 数组,我们就无法确保每个数字 num 只被使用一次。这是因为,如果直接遍历 j,那么在每一轮更新 dp 数组时,我们可能会使用同一个 num 多次。
  • 通过外层循环遍历 nums,我们保证每次只处理一个数字 num,并在内层循环中根据这个 num 更新 dp 数组。这符合“0-1 背包问题”的要求:每个元素最多只能使用一次。

举个例子:

假设我们有数组 nums = [1, 5],目标和为 6,dp 数组初始为:

dp = [true, false, false, false, false, false, false]

此时:

  • 外层循环遍历 nums,首先处理 num = 1,它会从 target 开始逐步更新 dp 数组,确保它和之前的组合是否可以构成新的子集。
  • 接下来处理 num = 5,同样从 target 开始往前更新。

通过这种方式,我们保证每个 num 只被处理一次,避免重复使用同一个元素。

总结:

  • 外层循环遍历 nums 数组,是为了逐一处理数组中的每个数字,确保每个数字 num 都能正确参与到子集和的构造过程中。
  • 通过外层循环遍历 nums 数组,可以有效地控制每个数字 num 只使用一次,避免重复选择。这符合“0-1 背包问题”的要求,即每个元素只能使用一次。

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

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

相关文章

Facebook减肥产品广告投放攻略

有不少刚开始投放facebook广告的小伙伴会感到疑惑&#xff0c;为什么别人的减肥产品跑的风生水起&#xff0c;销量羡煞旁人&#xff0c;自己的广告要不就是被拒要不就是没有流量&#xff0c;甚至还可能被封号&#xff0c;如果你也有这样的困扰&#xff0c;那一定要看完这篇文章…

组合式API有什么好处

什么是组合式API&#xff1f; 组合式 API (Composition API) 是一系列 API &#xff08;响应式API、生命周期钩子、依赖注入&#xff09;的集合。它不是函数式编程&#xff0c;组合式 API 是以 Vue 中数据可变的、细粒度的响应性系统为基础的&#xff0c;而函数式编程通常强调…

使用Python进行Web开发的15个框架指南

引言 在Python Web开发领域&#xff0c;有许多不同类型的框架可供选择&#xff0c;从轻量级到全功能型&#xff0c;再到专注于异步处理的框架。本文将介绍多个Python Web框架&#xff0c;帮助开发者根据具体需求选择合适的工具。 1.Flask&#xff1a;轻量级Web框架 Flask是一…

【C语言】深入理解指针(三)(下)

本篇文章将讲解以下知识&#xff1a; 1、二维数组传参的本质 2、函数指针变量 3、函数指针数组 1、二维数组传参的本质 有了数组指针的理解&#xff0c;我们就能弄清楚二维数组传参的本质了 例如&#xff1a; 在一维数组中&#xff0c;数组名是数字首元素的地址。但有两个例外…

【进阶OpenCV】 (10)--光流估计--->描绘运动物体轨迹

文章目录 光流估计一、基本原理二、计算步骤三、实现步骤1. 处理第一帧2. 寻找特征点3. 创建全零掩膜4. 流光估计函数介绍5. 主循环处理视频的每一帧5.1 流光估计5.2 绘制轨迹5.3 生成最终图像5.4 更新旧灰度图和旧特征点 6. 释放资源 总结 光流估计 光流估计是计算机视觉中的…

操作系统——磁盘管理

目录 前言基础实例1.1读取磁盘数据计算时间1.2磁盘调度算法1.3单双缓冲区1.4磁盘基础知识 前言 本文简述操作系统中有关磁盘的相关知识点&#xff0c;作为软件设计师考试资料复习 基础 磁盘管理是指对计算机中的磁盘进行有效地管理和使用的过程。磁盘管理包括以下方面&#…

软件测试工程师面试整理 —— 操作系统与网络基础!

在软件测试中&#xff0c;了解操作系统和网络基础知识对于有效地进行测试工作至关重要。无论是在配置测试环境、调试网络问题&#xff0c;还是在进行性能测试和安全测试时&#xff0c;这些知识都是不可或缺的。 1. 操作系统基础 操作系统&#xff08;Operating System, OS&am…

【Redis】网络模型(day10)

在本篇文章中&#xff0c;主要是对五种网络模型进行一个简单的介绍&#xff0c;然后对Redis4.0和6.0的网络模型进行一个概述。 用户空间和内核空间 在Linux系统上&#xff0c;分为用户空间、内核空间和硬件设备。硬件设备主要包括CPU、内存、网卡等物体&#xff0c;内核应用去…

垃圾回收器和垃圾回收机制(简单介绍,用于回忆总结)

文章目录 垃圾回收机制1. 分代收集2. 标记复制3. 标记清除4. 标记压缩&#xff08;整理&#xff09; 垃圾回收器1. Serial / Serial Old2. Parallel Scavenge3. ParNew收集器4. CMS收集器5. G1收集器 参考链接 垃圾回收机制 1. 分代收集 分代收集&#xff08;Generational Co…

吉时利KEITHLEY 2657A源表keithley2651A数字源表

Keithley 2657A 源表是一款高电压、高功率、低电流源测量单元 (SMU) 仪器&#xff0c;可提供前所未有的功率、精度、速度、灵活性和易用性&#xff0c;以提高研发、生产测试和可靠性环境中的生产力。 Keithley 2657A SourceMeter 仪器专门设计用于表征和测试高压电子器件和功率…

【隐私计算篇】一种批量匿踪查询友好算法PIRANA的原理分析

1. 背景分析 前段时间开展了批量匿踪查询算法迭代优化的工作&#xff0c;取得了一些进展。不得不说&#xff0c;甲方爸爸永远会提出非常有挑战性的目标&#xff0c;push你去想各种解决方案。在实际的算法研发落地上&#xff0c;我们会结合算法本身的机制改进以及工程优化这两方…

力扣hot100--二叉树

目录 二叉树 1. 94. 二叉树的中序遍历 2. 98. 验证二叉搜索树 3. 101. 对称二叉树 二叉树 1. 94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2]示…

[ComfyUI]最好用的图像提示词反推工具发布 2.0 版本啦!更好用了!

图像提示词反推工具我也介绍了好一些了&#xff0c;但是架不住技术一直在迭代啊&#xff01;过一段时间就出一个新的&#xff0c;或者是升级版&#xff0c;所以我们的分享也不能停&#xff01; 前段时间 joy_caption 蛮火的&#xff0c;不过后来也陆陆续续出了一些比较好用的反…

JMeter性能测试时,如何做CSV参数化

在现代软件开发中&#xff0c;性能测试是保证应用程序在高负载条件下稳定运行的重要环节。为了实现真实场景的测试&#xff0c;参数化技术应运而生。其中&#xff0c;CSV参数化是一种高效且灵活的方法&#xff0c;可以让测试人员通过外部数据文件驱动测试脚本&#xff0c;从而模…

U-Boot阶段系统全量更新固件包制作杂记

背景&#xff1a;有一个在 U-Boot 阶段做系统全量自更新的需求&#xff0c;要制作系统的全量固件包&#xff08;U-Boot.img、kerlen.img、rootfs.img&#xff09;。大体分为三个主要部分&#xff1a;U-Boot-shell 脚本编写、打包各镜像为一个固件包、固件包的加密和解密 一、U…

电采暖集控系统陕西高陵体育馆应用项目案例

电采暖集控系统是一种集监测、控制和管理于一体的智能管理系统&#xff0c;旨在提高采暖效率、降低能耗和运营成本&#xff0c;同时提升用户的舒适度。该系统利用先进的计算机控制技术和系统集成技术&#xff0c;实现对电热采暖设备的集中管理和远程操控。 陕西高陵体育馆 是…

四川方维嘉术科技有限公司简介

四川方维嘉术科技有限公司 公司简介 四川方维嘉术科技有限公司成立于2023年&#xff0c;注册资本100万元整&#xff0c;位于中国西南地区的中心位置&#xff0c;是一家专注于供应医疗设备、高值耗材并提供医疗方面解决方案的企业。 【主要代理产品】 湖南瑞康通 &#xff1…

Alberta Wells数据集:首个包含超过213,000个油气井的大规模高质量基准数据集,它们是温室气体和其他污染物的重要来源,助力环境监测与气候变化。

2024-10-11&#xff0c;由Mila – Quebec AI Institute和McGill University等机构创建了首个大规模油井检测数据集&#xff0c;这个数据集的意义在于提供了一个工具&#xff0c;能够通过卫星图像识别和定位全球数以百万计的废弃油气井&#xff0c;这对于减少温室气体排放和保护…

数据结构与算法:堆与优先队列的深入剖析

数据结构与算法&#xff1a;堆与优先队列的深入剖析 堆是一种特殊的树形数据结构&#xff0c;广泛应用于优先队列的实现以及各种高效的算法中&#xff0c;如排序和图算法。通过深入了解堆的结构、不同堆的实现方式&#xff0c;以及堆在实际系统中的应用&#xff0c;我们可以掌…

使用js和canvas实现简单的网页打砖块小游戏

玩法介绍 点击开始游戏后&#xff0c;使用键盘上的←→控制移动&#xff0c;小球会不停移动&#xff0c;板子触碰小球时会反弹&#xff0c;碰撞到砖块时会摧毁砖块&#xff0c;如果没有用板子接住小球就游戏失败 代码实现 代码比较简单&#xff0c;直接阅读注释即可&#x…