动态数组怎么定义_动态规划|分割等和子集

news/2024/7/3 13:16:57

f6f0561eda84def9aeefe0c1f27b5f31.png

动态规划|分割等和子集

今天一起来学习Leetcode第 416 题:分割等和子集。

题目描述

f45738f63af4054983d361164c5099dd.png

题目分析

这是一道中等难度的题目。可能很多同学初看这道题目没有什么想法,但如果我们可以转变一下思路,这道题是不是说「我们是否可以从给定的数组中挑出一些数,这些数的总和恰好是数组总和的一半」

有没有感觉很熟悉,我们想一下背包问题的问题描述:「我们是否可以从给定的物品中挑出一些物品,使得这些物品恰好可以装满整个背包」

其实这道题本质上就是一道「背包问题」

既然我们可以将这道题转化为常见的背包问题,那么这道题的解题思路也就大概明确了,我们应该使用「动态规划」的思想来解决这道题。

我们首先定义一个二维的动态规划数组dp[n][m+1],并将其全部初始化为False。其中n 表示物品的总个数,而 m 表示数组总和的一半。dp 数组在容量这里多加一维是为了考虑容量为0 这个边界条件。

定义好了dp数组之后,我们看一下dp[i][j] 的含义,我们定义状态dp[i][j] 为在前i个物品中存不存在一种选择的可能性,使得它们的总和为j。如果存在,我们就将dp[i][j]的值置为True

定义好了状态dp[i][j],我们怎么进行状态的转移呢?也就是说我们怎么不断填满我们的dp数组呢?

其实这里的状态转移和我们人做选择时候很像,对于元素i, 只有两种选择的可能性,「放或者是不放」

如果我们如果选择不放第 i 个物品,那么dp[i][j]=dp[i-1][j]

如果我们选择放第 i 个物品,那么 dp[i][j]=dp[i-1][j-nums[i]],当然这里的前提条件是 j>=nums[i]

可能有些同学有些蒙,我们来手动画一下状态转移图来加深一下理解:

1b382f6e337ca5c63548a35885dd6c44.png

上面这个就是我一步一步推导的状态转移过程,有了上面的分析之后,我们可以写出代码

题目代码

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total_sum = sum(nums)
        n = len(nums)
        if total_sum%2==1:
            return False
        
        half_sum = total_sum//2
        dp = [[0]*(half_sum+1) for i in range(n)]
        if nums[0]<=half_sum:
            dp[0][nums[0]]=1
        
        for i in range(n):
            dp[i][0]=1

        for i in range(1,n):
            for j in range(half_sum+1):
                dp[i][j] = dp[i-1][j]
                if nums[i]<=j:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
        return dp[-1][-1]

当然,我们由上面的推导过程可以发现,只要我们当发现dp[i][-1]=1的时候,就说明前i个元素就可以等于我们的既定目标,那么我们就可以直接结束我们的程序,从而完成剪枝。所以我们对代码进行一些优化:

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total_sum = sum(nums)
        n = len(nums)
        if total_sum%2==1:
            return False
        
        half_sum = total_sum//2
        dp = [[0]*(half_sum+1) for i in range(n)]
        if nums[0]<=half_sum:
            dp[0][nums[0]]=1
        
        for i in range(n):
            dp[i][0]=1

        for i in range(1,n):
            for j in range(half_sum+1):
                dp[i][j] = dp[i-1][j]
                if nums[i]<=j:
                    dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
            if dp[i][-1]==1:
              return True
        return dp[-1][-1]

代码优化

当然,和背包问题一样,我们可以对算法的空间复杂度进一步优化。上述代码的空间复杂度为O(mn),我们可以将其降到O(m)

降低空间复杂度的核心motivation在于,我们发现,dp[i][:]的更新仅仅依赖于dp[i-1][:],和dp[i-2][:] 或者更往前的状态是无关的;并且dp[i][j]的状态仅仅和dp[i-1][j] 以及 dp[i-1][j-nums[i]] 相关,如下图所示:

56d1e8469cdf4569dc31b21c4183b7d1.png

因此,之前的历史状态其实是可以不保存的;我们可以仅仅维护一个动态数组dp[m+1],而其中最核心的技巧就是将j从大到小更新,这样上一轮dp[j-nums[i]]的值就可以在这一轮更新dp[j]的时候保持不变,代码如下:

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total_sum = sum(nums)
        n = len(nums)
        if total_sum%2==1:
            return False 
        half_sum = total_sum//2
        dp = [0]*(half_sum+1)
        dp[0]=1
        if nums[0]<=half_sum:
            dp[nums[0]]=1
        for i in range(1,n):
            for j in range(half_sum,-1,-1):
                if nums[i]<=j:
                    dp[j] = dp[j] or dp[j-nums[i]]
        return dp[-1]

总结

以上就是利用动态规划的思想解决分割等和子集问题,其本质还是背包问题。背包问题及其变体是笔面试中非常高频的问题,大家还是要掌握的,关于背包问题更详细的讲解,大家可以阅读《背包问题九讲》。


http://www.niftyadmin.cn/n/4085527.html

相关文章

透明背景情况下遮罩出不规则图片

我们在处理图片&#xff0c;比如用户头像的时候&#xff0c;通常上传的都是矩形图片 例如&#xff1a; 有时候根据设计师的需求&#xff0c;会要求是圆形或者带圆角&#xff0c;这时候我们通常使用css border-radius来达到这一效果&#xff1a; css.avatar {width: 50px;height…

vue 引入pdf跨域问题怎么解决_word转pdf排版变了怎么办?一招帮你解决排版问题!...

众所周知&#xff0c;想要编辑好一个文件&#xff0c;一篇文章&#xff0c;需要的不仅仅是丰富的脑力和想象力&#xff0c;还需要一个完美的排版。如果仅仅是word排版出现问题&#xff0c;那么问题也不是很大&#xff0c;但如果是word转PDF排版变了&#xff0c;就会显得有点棘手…

CentOS离线安装GCC编译环境

gcc编译环境rpm下载 安装相关的rpm包&#xff0c;具体版本可能随时间变化而变化&#xff0c;可以去以下地址下载&#xff1a; 重庆大学镜像&#xff1a;http://b.mirrors.lanunion.org/CentOS/中国科学技术大学镜像&#xff1a;http://centos.ustc.edu.cn/centos/上海交通大学镜…

opencv两个图像相减_图像技术检测图像中的条形码

编辑&#xff1a;zero关注 CV工程师的一技之长&#xff0c;AI算法与图像处理 微信公众号&#xff0c;研究好玩又有用的技术第005期在学习中发现快乐&#xff0c;在应用找到价值。这是我第五期分享图像技术应用的文章。前四期欢迎阅读和分享&#xff1a;扫描全能王&#xff1f;原…

用例图、类图中有哪些关系

用例图中的关系有关联&#xff08;Association)、泛化&#xff08;Generalization&#xff09;、包含(Include)、扩展(Extend)。 类图中&#xff0c;常见的有以下几种关系: 泛化&#xff08;Generalization&#xff09;, 实现&#xff08;Realization&#xff09;, 关联&…

js webpack 配置路径_Vue学习笔记之Webpack的使用

一、webpack的使用&#xff1a;项目结构&#xff1a;src&#xff1a;源码dist&#xff1a;打包后的文件&#xff0c;用于发布的文件(bundle.js就是打包后的文件&#xff0c;然后在index.html引用即可)同cmd进行项目打包&#xff1a;打包完&#xff0c;在项目dist目录下就会生成…

python安装打开_python安装超详细

安装python Python是跨平台的&#xff0c;它可以运行在Windows、Mac和各种Linux/Unix系统上。在Windows上写Python程序&#xff0c;在Linux上也是可以运行的。要学习编程&#xff0c;首先要安装工具&#xff0c;下面为大家演示python的安装。 安装Python 3.8 目前&#xff0c;P…

JavaEE(15) - JPA实体继承

1. 实体继承映射的三种策略 #1. 整个类层次对应一张表 #2. 连接子类 #3. 每个具体类对应一张表 2. 使用抽象实体 3. 使用非实体父类 4. 重定义子类实体的外键列 ----------------------------------------------------- 1. 实体继承映射的三种策略 #1. 整个类层次对应一张表 #2…