• 416. Partition Equal Subset Sum


    今天又是刷leetcode的一天呀,开局不顺,今天做的第二道题就不会了,看了别人的回答才会的,记录一下。

    这道题是 416. Partition Equal Subset Sum

    就是能否将一个数组分成两个不相交的子集,使得两个子集之和相等。

    一开始我想的是首先判断数组之和是不是偶数,不是的话直接return false。然后再使用dfs寻找原数组中是否存在某个子集之和是原数组所有元素之和的一半,代码如下:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            int sum = accumulate(nums.begin(), nums.end(), 0);
            if(sum % 2 != 0) return false;
            return helper(nums, sum/2, 0, 0);//TLE
        }
        
        bool helper(vector<int>& nums, int target, int start, int sum){
            if(sum == target) return true;
            for(int i = start; i < nums.size(); i++){
                if(helper(nums, target, i+1, sum+nums[i])) return true;
            }
            return false;
        }
    };
    

    很明显,这种做法在nums数组中元素个数稍多的情况下就会超时,因为一个含有n个元素的数组的子集个数是 2n 个,这个复杂度是显而易见的高。

    于是我们需要使用dp去做。

    在网上搜了一下别人的讲解,这道题本质上是01背包问题,01背包问题是经典的dp问题,我们先简单回顾一下01背包问题:

    0-1 背包问题

    给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi

    问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

    01背包问题的状态转移方程为:

    if(j >= w[i])
        m[i][j] = max(m[i-1][j], m[i-1][j-w[i]] + v[i]);
    else
        m[i][j] = m[i-1][j];
    

    简单解释一下,
    m[i][j] 表示可选物品为 第1到第i件 物品,且背包容量为 j 时所能获得的最大价值

    • 如果当前背包容量能装下目前的第i个物品,那么m[i][j] = max(m[i-1][j], m[i-1][j-w[i]] + v[i])。其中m[i-1][j]表示不装第i个物品,m[i-1][j-w[i]] + v[i]表示装第i个物品
    • 如果当前背包容量不能装下目前的第i个物品,那么相当于目前第i个物品不在考虑范围内(因为光装它一个都装不下),这种情况下m[i][j]自然就等于m[i-1][j]

    回到我们这道题,其实我们也可以按照这种逻辑定义原问题的子问题:
    dp[i][j]表示可选数字为nums数组中第0个到第i(0 <= i < nums.size())个数字的时候,是否可以找到其中一个子集,使得子集各元素之和等于j(0 <= j <= sum/2, sum表示nums各元素之和)。

    根据这个定义,我们可以写出如下代码:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            int sum = accumulate(nums.begin(), nums.end(), 0);
            if(sum % 2 != 0) return false;
            
            vector<vector<bool>> dp(nums.size(), vector<bool>(sum/2+1, false));
            //dp[i][j]表示可选数字为nums数组中第0到第i个数字的时候,是否可以凑出来和为j的子集
            //其中0<= i <= nums.size()-1, 0 <= j <= sum/2
            
            for(int i = 0; i < nums.size(); i++) dp[i][0] = true;
            for(int i = 1; i <= sum/2; i++) dp[0][i] = nums[0]==i;
            
            for(int i = 1; i < nums.size(); i++){
                for(int j = 1; j <= sum/2; j++){
                    if(j >= nums[i]) dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
                    else dp[i][j] = dp[i-1][j];
                }
            }
            
            return dp[nums.size()-1][sum/2];
        }
    };
    

    其中下面代码是边界条件的初始化

    for(int i = 0; i < nums.size(); i++) dp[i][0] = true;//子集之和为0,相当于不选择任何元素
    for(int i = 1; i <= sum/2; i++) dp[0][i] = nums[0]==i;//可选数字只有第一个数字,直接判断该数字是不是j
    

    可以看到,每个状态值只依赖于上一行的状态值,所以事实上可以在空间上进行优化,不过时间上应该不能优化了。

    只有0和1的世界是简单的
  • 相关阅读:
    PHP实现微信小程序人脸识别刷脸登录功能
    thinkphp3.2.3中设置路由,优化url
    ThinkPHP URL 路由简介
    在 Linux 下搭建 Git 服务器
    图解 Android 广播机制 狼人:
    手机系统竞争背后的利益竞逐 狼人:
    你必须知道的Windows Phone 7开发 狼人:
    展望Android之前世今生 狼人:
    在美做开发多年,写给国内iPhone开发新手 狼人:
    NDK入门项目实战 狼人:
  • 原文地址:https://www.cnblogs.com/nullxjx/p/14368238.html
Copyright © 2020-2023  润新知