分割等和子集算法:动态规划思路与实现解析
在算法的学习过程中,“分割等和子集”是一道经典的动态规划题目,它主要考察我们对问题的转化能力以及动态规划思想的运用。接下来,我们将深入剖析这一算法,从解题思路的梳理,到代码实现,再到对算法结构和优点的解读,带你掌握解决这类问题的关键。
一、题目理解与需求分析
(一)题目描述
给定一个只包含正整数的非空数组 nums
,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。例如,数组 [1,5,11,5]
可以分割成 [1,5,5]
和 [11]
,两者和相等,返回 true
;数组 [1,2,3,5]
无法分割,返回 false
。
(二)核心矛盾提炼
- 和的计算:首先需要计算数组的总和,如果总和是奇数,那么肯定无法分割成两个和相等的子集(因为奇数不能被 2 整除 )。如果总和是偶数,问题就转化为是否能从数组中选出一些元素,使得它们的和等于总和的一半。
- 元素选择:这类似于一个背包问题,即从数组中选择若干元素(每个元素只能选一次 ),使得它们的和恰好为目标值(总和的一半 )。
二、解题思路:动态规划的应用
(一)问题转化
首先计算数组 nums
的总和 total
:
- 如果
total
是奇数,直接返回false
,因为无法分割成两个和相等的子集。 - 如果
total
是偶数,计算目标值target = total / 2
,问题就转化为:是否能从数组中选取若干元素,使得它们的和等于target
。这就变成了一个典型的 0-1 背包问题(每个元素只能选或不选 )。
(二)状态定义
定义 dp[i][j]
表示在前 i
个元素中,是否能选取若干元素使得它们的和等于 j
。其中 i
表示元素的索引(从 1 开始 ),j
表示目标和。
(三)状态转移方程推导
对于第 i
个元素(对应数组中的 nums[i - 1]
,因为 i
从 1 开始 ),有两种选择:
- 不选第
i
个元素:那么dp[i][j]
的值就等于dp[i - 1][j]
,即前i - 1
个元素能否组成和为j
的子集。 - 选第
i
个元素:如果j >= nums[i - 1]
,那么dp[i][j]
的值就等于dp[i - 1][j - nums[i - 1]]
,即前i - 1
个元素能否组成和为j - nums[i - 1]
的子集,加上当前这个元素后和为j
。
所以,状态转移方程为:
dp[i][j] = dp[i - 1][j] or (j >= nums[i - 1] and dp[i - 1][j - nums[i - 1]])
(四)初始条件确定
dp[0][0] = true
:表示前 0 个元素(没有元素 )时,和为 0 是可以实现的(选 0 个元素 )。- 对于
j > 0
,dp[0][j] = false
:因为没有元素可选,无法组成和大于 0 的子集。
(五)遍历求解
首先计算 total
,判断是否为奇数。如果是,直接返回 false
。否则,计算 target = total / 2
,然后创建二维 dp
数组(也可以优化为一维数组 ),按照状态转移方程进行遍历计算。最终,dp[n][target]
(n
是数组长度 )的值就是答案,如果为 true
,说明可以分割,否则不能。
以示例 1 nums = [1,5,11,5]
为例:
- 计算
total = 1 + 5 + 11 + 5 = 22
,是偶数,target = 11
。 - 初始化
dp
数组,dp[0][0] = true
。 - 处理第一个元素
1
(i = 1
):- 对于
j
从1
到11
:- 当
j = 1
时,dp[
- 当
- 对于