LeetCode 力扣热题100 分割等和子集

题目解析

题目:给定一个正整数数组 nums,判断是否可以将数组分成两个和相等的子集。

等价问题

• 计算 nums 的总和 S

• 如果 S 是奇数,直接返回 false(因为不能均分)

• 目标是找到一个子集,使得子集和等于 S / 2

• 这相当于 0-1 背包问题:是否能从 nums 中选取一些数,使其和为 S / 2

解法:动态规划

1. 状态定义

使用 一维 DP 数组 dp[j]:

• dp[j] = true 表示存在一个子集,使得该子集的和为 j

• dp[j] = false 表示不存在这样的子集

目标:判断 dp[target] 是否为 true,其中 target = S / 2

2. 递推公式

• 遍历数组 nums,对于当前元素 num

倒序遍历 j = target 到 num,更新 dp[j]

• dp[j] = dp[j] || dp[j - num]

如果 dp[j - num] 为 true,说明 j - num 可达,那么 j 也可达

如果 dp[j] 原本为 true,则仍为 true

3. 初始化

• dp[0] = true(空集的和为 0)

代码分析

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int s = accumulate(nums.begin(), nums.end(), 0); // 计算总和
        if (s % 2 != 0) return false; // 总和为奇数,无法均分
        int target = s / 2; // 目标和

        vector<bool> dp(target + 1, false); // DP 数组,表示是否能组成某个和
        dp[0] = true; // 0 直接可达

        for (int num : nums) { // 遍历每个数
            for (int j = target; j >= num; j--) { // 逆序遍历
                dp[j] = dp[j] || dp[j - num];
            }
        }
        return dp[target]; // 看 target 是否可达
    }
};

详细运行步骤

示例 1

输入

nums = {1, 5, 11, 5}

计算总和

• S = 1 + 5 + 11 + 5 = 22

• target = 22 / 2 = 11

• 需要找到一个子集和为 11

DP 过程

初始化:

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

(下标 0 代表和为 0 可达)

遍历 1

dp[1] = dp[1] || dp[1 - 1]  (true)

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

遍历 5

dp[6] = dp[6] || dp[6 - 5] (true)
dp[5] = dp[5] || dp[5 - 5] (true)

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

遍历 11

dp[11] = dp[11] || dp[11 - 11] (true)

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

最终 dp[11] = true,返回 true

案例分析

我们用一个不同的测试案例,详细跟踪 dp 数组的变化。

示例 2

输入

nums = {3, 3, 3, 4, 5}

计算总和

• S = 3 + 3 + 3 + 4 + 5 = 18

• target = 18 / 2 = 9

• 目标是找到一个子集,使得该子集的和为 9

初始化

我们初始化 dp 数组:

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

• dp[0] = true,表示和为 0 的子集是可行的(空集)

遍历每个数

遍历 3(第一个)

更新 dp[j],从 target = 9 逆序到 num = 3

dp[3] = dp[3] || dp[3 - 3] = true

dp 变化

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

表示可以用 {3} 得到和 3

遍历 3(第二个)

dp[6] = dp[6] || dp[6 - 3] = true
dp[3] = dp[3] || dp[3 - 3] = true (已经是 true)

dp 变化

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

表示可以用 {3, 3} 得到和 6

遍历 3(第三个)

dp[9] = dp[9] || dp[9 - 3] = true
dp[6] = dp[6] || dp[6 - 3] = true (已经是 true)
dp[3] = dp[3] || dp[3 - 3] = true (已经是 true)

dp 变化

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

表示可以用 {3, 3, 3} 得到和 9 ✅ 目标达成

遍历 4

dp[9] = dp[9] || dp[9 - 4] = true (已经是 true)
dp[7] = dp[7] || dp[7 - 4] = true
dp[4] = dp[4] || dp[4 - 4] = true

dp 变化

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

表示可以用 {4} 得到和 4,可以用 {3, 4} 得到 7

遍历 5

dp[9] = dp[9] || dp[9 - 5] = true
dp[8] = dp[8] || dp[8 - 5] = true
dp[5] = dp[5] || dp[5 - 5] = true

dp 变化

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

可以用 {5} 得到 5,用 {3, 5} 得到 8

最终结果

✅ dp[9] = true,说明可以找到一个子集使得总和为 9,返回 true

完整执行过程

dp 状态

0

1

2

3

4

5

6

7

8

9

初始

T

F

F

F

F

F

F

F

F

F

处理 3

T

F

F

T

F

F

F

F

F

F

处理 3

T

F

F

T

F

F

T

F

F

F

处理 3

T

F

F

T

F

F

T

F

F

T

处理 4

T

F

F

T

T

F

T

T

F

T

处理 5

T

F

F

T

T

T

T

T

T

T

dp[9] = true,所以可以划分两个子集和相等。

复杂度分析

时间复杂度:O(n × target) 其中 target = S/2

空间复杂度:O(target) 只用了一维 dp

总结

思路

1. 计算 S,如果 S 为奇数直接返回 false

2. 定义 dp[j]:表示能否找到一个子集,使得总和为 j

3. 遍历 nums,使用 逆序 更新 dp,确保不会重复使用数字

4. 最终 dp[target] 是否为 true,决定是否能划分

关键优化点

• 使用 一维 DP 数组(空间优化)

倒序遍历 j,避免同一轮重复使用 nums[i]

时间复杂度

• O(n × sum/2),适用于 sum 适中的情况(sum < 10^5)

空间复杂度

• O(sum/2),降低空间占用

<think>好的,我现在需要处理用户关于LeetCode100的查询。首先,用户希望找到相关的解思路讲解。根据用户提供的引用内容,特别是引用[1]引用[2],我需要先确认这些资源是否足够详细,或者是否需要补充其他信息。 用户提到引用[1]中有一个解列表,包括LeetCode 35. 搜索插入位置,这涉及二分查找的变种。用户可能希望得到具体的解方法,比如如何处理目标值不存在的情况,返回left指针的位置。同时,引用[2]链接到一篇可能更详细的文章,但需要确认链接是否有效,或者是否有其他资源可用。 接下来,我需要考虑用户的需求是否被现有引用内容满足。如果现有的解列表文章足够,那么可以直接推荐这些资源。但可能还需要补充一些常见的解策略,比如双指针、动态规划等,这些在100中常见的方法。 另外,用户的问中还可能涉及不同型的分类,比如九、双指针/三指针(共3),这可能意味着用户希望按型分类学习。因此,在回答中需要将问按类型归类,并提供每个类型的典型思路。 还需要检查是否有其他常见的100目未被提及,比如两数之、链表反转等,确保回答的全面性。同时,要确保回答符合之前的系统级指令,正确使用LaTeX格式,并生成相关问。 最后,生成的相关问需要围绕用户可能的后续疑问,比如如何准备刷、时间空间复杂度优化等,帮助用户进一步学习。</think>[用户可见层] # LeetCode100思路与资源指引 ## 一、核心解方法分类 根据目类型可分为: 1. **二分查找**(如第35) - 变种型处理技巧:当$target$不存在时返回插入位置,需理解循环终止时$left$$right$的关系[^1] - 示例代码框架: ```python def searchInsert(nums, target): left, right = 0, len(nums)-1 while left <= right: mid = (left + right) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 else: right = mid - 1 return left # 关键点 ``` 2. **双指针技巧** - 三数之类问:通过排序+双指针将时间复杂度从$O(n^3)$降为$O(n^2)$ - 快慢指针应用:链表环检测(Floyd算法) 3. **动态规划** - 背包问变形:使用二维状态数组处理子集 - 状态转移方程构建:如最长递增序列的$dp[i] = max(dp[i], dp[j]+1)$结构 ## 二、优质资源推荐 1. 型分类详解(含Python/Java实现): - [LeetCode100型分类汇总](https://juejin.cn/post/7140176848285073438) [^2] - 包含动态规划、回溯算法等12类型的代码模板 2. 高频目精讲: - 第5(最长回文串)的中心扩展法实现 - 第15(三数之)的去重技巧 - 第21(合并两个有序链表)的递归写法 3. 刷路径建议: ```mermaid graph TD A[掌握基础数据结构] --> B[学习算法模板] B --> C{按类型专项训练} C --> D[数组/字符串] C --> E[链表操作] C --> F[树形结构] ``` ## 三、典型目解析示例 ### 例:62. 不同路径 **动态规划解法**: - 状态定义:$dp[i][j]$表示到达$(i,j)$的路径数 - 转移方程:$$dp[i][j] = dp[i-1][j] + dp[i][j-1]$$ - 空间优化:可将二维数组压缩为一维数组,时间复杂度保持$O(mn)$ ```python def uniquePaths(m, n): dp = [1] * n for _ in range(1, m): for j in range(1, n): dp[j] += dp[j-1] return dp[-1] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值