【算法】【线性表】【数组】分割等和子集
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]; } }
加油。
分类:
算法 / 数组
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享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 【排序算法】冒泡排序