首页 > 编程语言 >(算法)分割等和⼦集————<动态规划>

(算法)分割等和⼦集————<动态规划>

时间:2024-11-04 17:17:14浏览次数:3  
标签:分割 nums int sum 元素 算法 动态 dp 总和

1. 题⽬链接:416. 分割等和⼦集

2. 题⽬描述:

3. 解法(动态规划):

算法思路:

先将问题转化成我们「熟悉」的题型。 如果数组能够被分成两个相同元素之和相同的⼦集,那么原数组必须有下⾯⼏个性质:

        i. 所有元素之和应该是⼀个偶数;

        ii. 数组中最⼤的元素应该⼩于所有元素总和的⼀半;

        iii. 挑选⼀些数,这些数的总和应该等于数组总和的⼀半。 根据前两个性质,我们可以提前判断数组能够被划分。

根据最后⼀个性质,我们发现问题就转化成 了「01 背包」的模型:

        i. 数组中的元素只能选择⼀次;

        ii. 每个元素⾯临被选择或者不被选择的处境;

        iii. 选出来的元素总和要等于所有元素总和的⼀半。 其中,数组内的元素就是物品,总和就是背包。 那么我们就可以⽤背包模型的分析⽅式,来处理这道题。

请⼤家注意,「不要背」状态转移⽅程,因为题型变化之后,状态转移⽅程就会跟着变化。我们要 记住的是分析问题的模式。⽤这种分析问题的模式来解决问题。

1. 状态表⽰:

dp[i][j] 表⽰在前 i 个元素中选择,所有的选法中,能否凑成总和为 j 这个数。

2. 状态转移⽅程:

⽼规矩,根据「最后⼀个位置」的元素,结合题⽬的要求,分情况讨论:

        i. 不选择 nums[i] :那么我们是否能够凑成总和为 j ,就要看在前 i - 1 个元素中 选,能否凑成总和为 j 。根据状态表⽰,此时 dp[i][j] = dp[i - 1][j] ;

        ii. 选择 nums[i] :这种情况下是有前提条件的,此时的 nums[i] 应该是⼩于等于 j 。 因为如果这个元素都⽐要凑成的总和⼤,选择它就没有意义呀。那么我们是否能够凑成总和 为 j ,就要看在前 i - 1 个元素中选,能否凑成总和为 j - nums[i] 。根据状态表 ⽰,此时 dp[i][j] = dp[i - 1][j - nums[i]] 。

综上所述,两种情况下只要有⼀种能够凑成总和为 j ,那么这个状态就是 true 。

因此,状态转 移⽅程为: dp[i][j] = dp[i - 1][j] if(nums[i - 1] <= j) dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i]]

3. 初始化:

由于需要⽤到上⼀⾏的数据,因此我们可以先把第⼀⾏初始化。 第⼀⾏表⽰不选择任何元素,要凑成⽬标和 j 。只有当⽬标和为 0 的时候才能做到,因此第⼀ ⾏仅需初始化第⼀个元素 dp[0][0] = true

4. 填表顺序:

根据「状态转移⽅程」,我们需要「从上往下」填写每⼀⾏,每⼀⾏的顺序是「⽆所谓的」。

5. 返回值:

根据「状态表⽰」,返回 dp[n][aim] 的值。 其中 n 表⽰数组的⼤⼩, aim 表⽰要凑的⽬标和。

6. 空间优化:

所有的「背包问题」,都可以进⾏空间上的优化。 对于 01背包类型的,我们的优化策略是:

        i. 删掉第⼀维;

        ii. 修改第⼆层循环的遍历顺序即可。 

C++ 优化前的算法代码: 

class Solution 
{
public:
    bool canPartition(vector<int>& nums) 
    {
        //求和
        int sum=0;
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i];
        }
        //判断奇偶数,若是为奇数不可能分为两类,直接返回false
        if(sum%2==1)
        {
            return false;
        }
        //建表
        int m=nums.size();
        int n=sum/2;
        vector<vector<int>>dp(m+1,vector<int>(n+1));
        //初始化
        dp[0][0]=true;
        for(int i=1;i<=m;i++)
        {
            dp[i][0]=true;
        }
        //填表
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                //不选
                dp[i][j]=dp[i-1][j];
                //选
                if(j-nums[i-1]>=0)
                {
                    dp[i][j]=(dp[i-1][j]||dp[i-1][j-nums[i-1]]);
                }
            }
        }
        //返回值
        return dp[m][n];
    }
};

C++ 空间优化后的算法代码:

class Solution 
{
public:
    bool canPartition(vector<int>& nums) 
    {
        //求和
        int sum=0;
        for(int i=0;i<nums.size();i++)
        {
            sum+=nums[i];
        }
        //判断奇偶数,若是为奇数不可能分为两类,直接返回false
        if(sum%2==1)
        {
            return false;
        }
        //建表
        int m=nums.size();
        int n=sum/2;
        vector<int>dp(n+1);
        //初始化
        dp[0]=true;
        //填表
        for(int i=1;i<=m;i++)
        {
            for(int j=n;j>=1;j--)
            {
                if(j-nums[i-1]>=0)
                {
                    dp[j]=(dp[j]||dp[j-nums[i-1]]);
                }
            }
        }
        //返回值
        return dp[n];
    }
};

Java 优化前算法代码:

class Solution
{
 public boolean canPartition(int[] nums)
 {
	int n = nums.length, sum = 0;
	for(int x : nums) sum += x;
	if(sum % 2 == 1) return false; // 如果不能平分,直接返回 false
	int aim = sum / 2;
	boolean[][] dp = new boolean[n + 1][aim + 1]; // 建表
	for(int i = 0; i <= n; i++) dp[i][0] = true; // 初始化
	for(int i = 1; i <= n; i++) // 填表
	for(int j = 1; j <= aim; j++){
		dp[i][j] = dp[i - 1][j];
		if(j >= nums[i - 1])
			dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i - 1]];
	}
	return dp[n][aim];
 }
}

Java 空间优化后的算法代码:

class Solution
{
 public boolean canPartition(int[] nums)
 {
	int n = nums.length, sum = 0;
	for(int x : nums) sum += x;
	if(sum % 2 == 1) return false; // 如果不能平分,直接返回 false
	int aim = sum / 2;
	 boolean[] dp = new boolean[aim + 1]; // 建表
	 dp[0] = true; // 初始化
	 for(int i = 1; i <= n; i++) // 填表
		 for(int j = aim; j >= nums[i - 1]; j--) // ?定要注意修改遍历顺序
			 dp[j] = dp[j] || dp[j - nums[i - 1]];
	return dp[aim];
 }
}

标签:分割,nums,int,sum,元素,算法,动态,dp,总和
From: https://blog.csdn.net/2301_79580018/article/details/143487613

相关文章

  • (算法) ⽬标和————<动态规划>
    1.题⽬链接:494.⽬标和2.题⽬描述: 3.解法(动态规划):算法思路:本题可以直接⽤「暴搜」的⽅法解决。但是稍微⽤数学知识分析⼀下,就能转化成我们常⻅的「背包模型」的问题。设我们最终选取的结果中,前⾯加+号的数字之和为a,前⾯加-号的数字之和为b,整个数组的总和......
  • NSET or MSET算法--原理解析
    1.背景NSET/MSET是一种非线性的多元预测诊断技术,广泛应用于系统状态估计、故障诊断和预测等领域;相比于传统的线性模型和方法,NSET/MSET能够更好地处理非线性系统,并提供更准确的预测和诊断能力。在早期,MSET融合了模式识别技术和序贯概率比检验方法,主要应用于核电厂信号验证、......
  • 简单介绍机器学习与深度学习以及相关算法
    机器学习算法线性回归解释:线性回归是一种简单的预测算法,它通过寻找输入变量和输出变量之间的线性关系来进行预测。例子:假设你想预测一个房子的价格,可以根据房子的面积(输入)和价格(输出)画一条直线,线性回归就是找到这条最合适的直线。逻辑回归解释:尽管名字中有“回归”,逻......
  • 【算法设计】
    二分治策略2.1最大子数组问题分治算法分治法是一种将问题分解成子问题递归解决的算法思想,用于求解连续最大子数组问题非常合适。该问题的目标是找到数组中连续子数组的最大和。算法解释分解:将数组划分为左右两个子数组,分别求解左右子数组的最大子数组和。合并:最大子数......
  • 烟雾检测识别智慧矿山一体机水仓水位异常识别针对环境不安全因素的算法保障
    在现代矿业生产中,安全始终是最为关键的议题之一。为了提升矿山的安全监管水平,降低生产风险,智慧矿山一体机应运而生。这款设备融合了最新的人工智能技术,为矿山提供了一个全面、高效、智能化的安全解决方案。以下是对智慧矿山一体机的详细介绍,包括其产品特性、环境不安全因素的识别......
  • 越界检测视频分析网关视频智能分析网关周界入侵检测算法:智能安防的新高度
    在当今数字化和智能化快速发展的时代,安全监控系统正经历着一场技术革命。越界检测视频分析网关周界入侵检测算法,作为这场革命的前沿技术之一,正逐渐改变我们对安全监控的认知和实践。本文将详细介绍这一算法的技术特点、应用场景及其在提升安防效率和准确性方面的重要性,展示它是如......
  • 智慧园区算法视频分析服务器烟雾识别智慧园区安防视频监控及动态布控预警方案
    智慧园区安防视频监控及动态布控预警方案是一种综合性的安全管理解决方案,智慧园区算法视频分析服务器通过结合视频监控技术、人工智能算法、大数据分析等技术,实现对工厂区域内人、车、物的全面监控和管理。一、需求和目标1、系统建设目标:目标是构建一个重点区域的动态人脸识别......
  • CSS:实现动态流光线条效果/动态流光线条颜色渐变效果
    需求分析需要实现类似下图中的动态流光线条效果:思路提到这种动态绘制矢量图形的需求,一般会想到使用canvas;由于笔者不太熟悉canvas动画也可以考虑用CSS来实现,这里先记录使用CSS实现此效果的尝试过程:①实现一条带有静态“流光”效果的边,参考CSS渐变背景;②实现静态线条的“流光......
  • 算法专题:哈希表
    目录1、1.两数之和1.1算法原理1.2算法代码2、面试题01.02.判定是否互为字符重排2.1算法原理 2.2算法代码3、217.存在重复元素3.1算法原理  3.2算法代码4、219.存在重复元素II4.1算法原理4.2算法代码5、49.字母异位词分组5.1算法原理 5.2算......
  • 基于改进多目标灰狼优化算法的考虑V2G技术的风、光、荷、储微网多目标日前优化调度研
    ......