【算法】【线性表】【数组】分割等和子集

1  题目

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

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100

2  解答

我最开始想的那个贪心思路不行,就是先升序排列,然后逐次求和,到了一半了,直接返回 true,否则返回 false。这个不行,比如 1,1,2,2 就行不通。

首先分析下题目,拆分等和,那就要知道目标是什么,是不是就是要先求下和?然后除以2,看看一半是多少。

那么和为奇数的话,是不是就可以直接返回 false((sum & 1) == 1)。比如和为 sum,sum 的一半 target = sum / 2,那么是不是我们只要能找出几个数的和为 target (剩下的加起来肯定也为 target,因为整体的和是 2*target 我都找到几个数的和为 target了,是不是)这道题就可以返回 true ,否则就返回 false。

那么这道题就变为 找几个数的和为 target。那么暴力的解法是什么?因为找几个数到底要找几个不固定,所以你即使嵌套的 for 循环都不好使,因为你不知道要嵌套几层。

我们知道对于每个数其实就是选或者不选,那么有多少个情况呢?是不是就是 2 的 n 次方 (n=数组的长度),比如下面这颗树:

每个都有两个分叉,选或者不选,所有的叶子节点就是所有的情况,4个数= 2的4次方 = 16种,那我们用代码写就是:

复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        // 和为奇数,直接返回 false
        if ((sum & 1) == 1) {
            return false;
        }
        // 砍半
        int target = sum / 2;
        // 然后暴力递归求解
        return recursionPartition(nums, 0, 0, target);
    }
    /**
     * @param nums 数组
     * @param depth 当前递归到第几层了
     * @param sum 当前的和为多少了
     * @return
     */
    public boolean recursionPartition(int[] nums, int depth, int sum, int target) {
        // 退出标志,没有别的数了 也就是 depth == nums.length 或者 sum 已经大于 target 那就不要继续了 这条路不通了
        if (depth == nums.length || sum > target) {
            return false;
        }
        // 退出标志,当前的sum 已经是target了
        if (sum == target) {
            return true;
        }
        // 对于当前的数 nums[depth] 可以选择取或者不取
        // 取就是 sum + nums[depth] 如果取返回的是 true 那么就直接返回 true
        boolean res = recursionPartition(nums, depth + 1, sum + nums[depth], target);
        if (res) {
            return true;
        }
        // 不取的情况下继续递归
        return recursionPartition(nums, depth + 1, sum, target);
    }
}
复制代码

怎么样,应该是对的,但是当你的数组比较大的时候,会出现超时,因为递归的情况太多了:

奶奶的,贪心不行,递归超时,哎不会又要用到动态规划吧,大问题拆小问题,小问题推导大问题的解。

复制代码
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = Arrays.stream(nums).sum();
        // 和为奇数,直接返回 false
        if ((sum & 1) == 1) {
            return false;
        }
        // 砍半
        int target = sum / 2;
        // 构造中间态记录中间结果
        boolean[][] arr = new boolean[nums.length + 1][target + 1];
        // 因为第0列表示 target 为0的话,不管你几个数 都不取即可,所以都为true
        for (int i = 0; i <= nums.length; i++) {
            arr[i][0] = true;
        }
        // 从加入第一个 第二个数 直到所有的数都参与进来
        for (int i = 1; i <= nums.length; i++) {
            // 取出当前要加入的数
            int num = nums[i - 1];
            // 构造 target 从 1 到 target
            for (int j = 1; j <= target; j++) {
                // 当前数大于目标 那肯定不要当前数
                if (num > j) {
                    arr[i][j] = arr[i-1][j];
                } else {
                    // 选取了当前数,当前数也可以不加或者加入
                    arr[i][j] = arr[i-1][j] | arr[i-1][j - num];
                }
            }
        }
        return arr[nums.length][target];
    }
}
复制代码

加油。

posted @   酷酷-  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2023-03-14 【排序算法】快速排序
2023-03-14 【排序算法】归并排序
2023-03-14 【常用命令】平时常用的一些命令
2023-03-14 【排序算法】希尔排序
2023-03-14 【排序算法】插入排序
2023-03-14 【排序算法】直接选择排序
2023-03-14 【排序算法】冒泡排序
点击右上角即可分享
微信分享提示