AlgoMooc
你已开通华为OD训练营权益,还差最后一步——完成入营激活(兑换课程 + 加飞书 + 登记服务群),即可解锁全部课程与专属服务。去激活 →
← OD 20 天课程

华为 OD 训练营 · 题解精讲

LC416.分割等和子集

LC416.分割等和子集

题目描述

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

题目解析

解题思路

本题等价于在数组中寻找一个子集,使其和为总和的一半。采用动态规划解决,将问题转化为 0-1 背包问题:每个数字选或不选,能否恰好凑出目标值 target = total // 2

定义 dp[i][j] 表示从数组前 i+1 个元素(下标 0~i)中能否选出若干元素使其和为 j。状态转移时,对于当前元素 nums[i],有两种情况:

  • 不选:dp[i][j] = dp[i-1][j]
  • 选:若 nums[i] <= j,则 dp[i][j] = dp[i-1][j - nums[i]]

两者取或运算即可。

关键步骤

1. 计算总和,若为奇数直接返回 False。 2. 初始化 dp[0][nums[0]] = True(前提 nums[0] <= target)。 3. 两层循环遍历 i 从 1 到 n-1,j 从 0 到 target:

  • 先继承上一行结果:dp[i][j] = dp[i-1][j]
  • nums[i] == j,直接置 True
  • nums[i] < j,则 dp[i][j] |= dp[i-1][j - nums[i]]

4. 返回 dp[n-1][target]

转移示意图(以 nums=[1,5,11,5], target=11 为例):


     j=0  1  2  3  4  5  6  7  8  9 10 11
i=0  F   T  F  F  F  F  F  F  F  F  F  F
i=1  F   T  F  F  F  T  T  F  F  F  F  F
i=2  F   T  F  F  F  T  T  F  F  F  F  T
i=3  F   T  F  F  F  T  T  F  F  F  F  T

复杂度分析

  • 时间复杂度:O(n × target),其中 n 为数组长度,target 为总和的一半。两层循环嵌套。
  • 空间复杂度:O(n × target),使用二维 dp 数组。可优化为一维,但参考代码未做此优化。

参考代码

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        # 使用 total 来保存数组中全部元素的和
        total = sum(nums)

        # 如果发现 sum 为奇数,那么必然无法拆分为两个相等的整数
        if total & 1:
            return False

        # 获取数组的长度
        n = len(nums)

        # 获取数组中全部元素之后的一半
        # 接下来需要在数组 nums 中寻找一些数字去拼凑为 target
        # 如果能找到一些数字之和为 target,那么剩下的数字之和也是 target
        target = total // 2

        # dp[i][j] 表示 nums 的前 i 个元素能否可以组成和为 j 的结果
        # dp[0][0] 表示 nums 的前 0 个元素能否可以组成和为 0 的结果
        # dp[2][6] 表示 nums 的前 2 个元素能否可以组成和为 6 的结果
        # dp[n - 1][target ] 表示 nums 的前 n - 1 个元素能否可以组成和为 target 的结果
        # i 的值从 0 到 n - 1
        # j 的值从 0 到 target
        dp = [[False] * (target + 1) for _ in range(n)]

        # 先初始化  dp[0][nums[0]]
        # 如果 nums 的第 0 个元素 nums[0] 小于 target
        if  nums[0] <= target : 
            # 那么 nums 的前 0 个元素能否可以组成和为 nums[0] 的结果是 true
            # 因为 nums 的前 0 个元素就是 nums[0]
            dp[0][nums[0]] = True
        

        # 接下来开始往 dp 数组中填充结果
        # i 的值从 1 到 n - 1
        for i in range( 1 , n ):
            # j 的值从 0 到 target
            for j in range( 0 , target + 1 ):
                # dp[i][j] 表示 nums 的前 i 个元素能否可以组成和为 j 的结果
                # dp[i - 1][j] 表示 nums 的前 i - 1 个元素能否可以组成和为 j 的结果
                # 对于 dp[i][j] 来说,如果 dp[i - 1][j] 为 true,那么 dp[i][j] 必然也为 true
                # 所以,先初始化 dp[i][j] 的值为 dp[i - 1][j] 的值
                # 再通过后面的代码修改 dp[i][j] 中还为 false 的值 
                dp[i][j] = dp[i - 1][j]

                # 如果此时 nums[i] 恰巧为 j
                # 那么对于 dp[i][j] 来说,nums 的前 i 个元素可以组成和为 j 的结果
                if nums[i] == j  : 
                    # 所以 dp[i][j] 为 true
                    dp[i][j] = True
                    # 同时继续
                    continue
                

                # 如果发现 nums[i] 小于 j
                if nums[i] < j : 
                    # dp[i][j] 表示 nums 的前 i 个元素能否可以组成和为 j 的结果
                    # dp[i - 1][j] 表示 nums 的前 i - 1 个元素能否可以组成和为 j 的结果
                    # dp[i - 1][j - nums[i]] 表示 nums 的前 i - 1 个元素能否可以组成和为 j - nums[i] 的结果
                    # 那么 dp[i][j] 的结果要为 true
                    # 1、nums 的前 i - 1 个元素可以组成和为 j 
                    # 2、nums 的前 i - 1 个元素可以组成和为 j - nums[i]
                    # 对于 1 来说,不用使用 nums[i] 就组成了 j
                    # 对于 2 来说,前 i - 1 个元素可以组成和为 j - nums[i],那么加上此时的值 nums[i],也组成了 j
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - nums[i]]


        # dp[n - 1][target ] 表示 nums 的前 n - 1 个元素能否可以组成和为 target 的结果
        # 返回这个结果
        return dp[n - 1][target]