leetcode 416——分割等和子数组

题目描述
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:

  • 每个数组中的元素不会超过 100
  • 数组的大小不会超过 200

示例 1:
在这里插入图片描述
示例 2:
在这里插入图片描述
解题思路:
这道题是0-1背包问题 我用到了动态规划中备忘录的方法 通过记录每种子集的情况来解题(其实就是枚举了所有的情况 不过复杂度没有那么高)

  • 首先进行两次特判 即原来数组长度为0,1,2时候的情况。0和1的时候不可能有子集,所以返回false,2的时候只需看两个元素是否相等就可以了
  • 两次特判后我们对原数组求和 如果总和为奇数的话也说明不能分成俩相等的子集,返回false
  • 接下来是动态规划备忘录的解法
    • 首先开辟一个长度为 (原数组求和)/2+1 长度的bool类型vis数组表示访问状态,这个数组用来表示子集的合的可能性。
      比如[1, 5, 11, 5] 当子集是[1,5]的时候,vis[6]就为1 记录下了这次子集的和 子集是[1,5,5]的时候 vis[11]就是1 以此类推(你可能还在代码中看到了max_mid max_max 待会就说)
    • 然后两重循环
      第一重遍历原来数组的全部元素,每次遍历都相当于向已经有的子集(也就是vis数组中值为1的地方)中添加了一个元素 第二重遍历vis,从0~max_mid,来遍历所有已经存在的子集。

max_mid是添加当前元素(nums[i])之前,上一轮循环中子集的最大和,这样就可以保证我不会遍历0~mid的全部元素,减少了遍历次数 而max_mid的求法则是在遍历vis的时候用max_max获得添加当前元素(nums[i-1])时子集的最大值,然后在退出这个vis循环后用max_max更新mid_max的值 这样在添加下一个元素(nums[i])的时候就可以得到上一次子集的最大值了。


两重循环的目的我通过举一个例子来讲 比如对于[1, 5, 11, 5] nums[0]=1 因此最开始的时候是vis[1]=1 然后进入两重循环

第一重循环中 上一次循环的最大值就是nums[0]=1 因此遍历0~1 在vis[1]处发现vis[1]=1 判断1+5不会超过11(也就是原数组和的一半),因此记录vis[1+5]=1 退出循环 更新max_mid = 6

第二重循环 元素为11 遍历vis中0~6 发现在vis[1]和vis[6]处都为1 但1+11>11,6+11>11因此不进行操作 max_mid 不更新

第三重循环 元素为5 遍历vis中0~6 发现在vis[1]和vis[6]处都为1 但1+5=6,6+5=11因此vis[6]=1 vis[11]=1 此时找到了合为11的子集 返回true

如果循环全部结束还没有找到子集 那就不存在了

代码实现:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n = nums.size();
        if (n < 2)
            return false;
        else if (n == 2)
            return nums[0] == nums[1];
        
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            sum += nums[i];
        }
        if (sum % 2 == 1) {
            return false;
        }
        
        int mid = sum / 2;
        vector<bool> accessed(mid + 1, false);

        for (int i = 0; i < n; ++i) {
            if (nums[i] <= mid) {
                swap(nums[0], nums[i]);
                break;
            }
        }
        
        accessed[nums[0]] = true;
        int max_mid = nums[0], max_max = nums[0];
        
        for (int i = 1; i < n; ++i)
        {
            for (int j = 0; j <= max_mid; ++j)
            {
                if (accessed[j] && j + nums[i] <= mid)
                {
                    accessed[j + nums[i]] = true;
                    if (j + nums[i] > max_max)
                        max_max = j + nums[i];
                }    
                if (accessed[mid])
                    return true;
            }
            max_mid = max_max;
        }
        
        return false;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值