Loading

[LeetCode] 416. Partition Equal Subset Sum(划分和相等的子集)

Description

Given a non-empty array nums containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
给定一个只包含正整数的非空数组 nums,判断这个数组能否被分成两部分,每一部分的和相等。

Examples

Example 1

Input: nums = [1,5,11,5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].

Example 2

Input: nums = [1,2,3,5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.

Constraints

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

Solution

本人学艺不精,第一个想到的是以下的暴力搜索方法,结果自然是华丽丽的 TLE 了😂

class Solution {
    fun canPartition(nums: IntArray): Boolean {
        if (nums.size == 1) {
            return false
        }
        val sum = nums.sum()
        if (sum % 2 != 0) {
            return false
        }

        return backtrack(nums, 0, 0, sum / 2)
    }

    private fun backtrack(nums: IntArray, i: Int, curSum: Int, target: Int): Boolean {
        if (curSum == target) {
            return true
        }
        if (curSum > target) {
            return false
        }
        if (i == nums.size) {
            return curSum == target
        }
        val newSum = curSum + nums[i]
        val include = backtrack(nums, i + 1, newSum, target)
        if (include) {
            return true
        }
        return backtrack(nums, i + 1, curSum, target)
    }
}

似乎凡是涉及到大量递归的地方都会出现不可避免的重复计算?祭出 memo 大法试试,竟然 AC 了(别打我😂):

class Solution {
    private val memo = hashMapOf<Pair<Int, Int>, Boolean>()

    fun canPartition(nums: IntArray): Boolean {
        if (nums.size == 1) {
            return false
        }
        val sum = nums.sum()
        if (sum % 2 != 0) {
            return false
        }

        return backtrack(nums, 0, 0, sum / 2)
    }

    private fun backtrack(nums: IntArray, i: Int, curSum: Int, target: Int): Boolean {
        if (memo.containsKey(Pair(i, curSum))) {
            return memo.getValue(Pair(i, curSum))
        }
        if (curSum == target) {
            memo[Pair(i, curSum)] = true
            return true
        }
        if (curSum > target) {
            memo[Pair(i, curSum)] = false
            return false
        }
        if (i == nums.size) {
            memo[Pair(i, curSum)] = curSum == target
            return curSum == target
        }
        val newSum = curSum + nums[i]
        val include = backtrack(nums, i + 1, newSum, target)
        if (include) {
            memo[Pair(i, curSum)] = true
            return true
        }
        val exclude = backtrack(nums, i + 1, curSum, target)
        memo[Pair(i, curSum)] = exclude
        return exclude
    }
}

当然,这题动态规划的解法应该是 0-1 背包问题的变种,本题要求的是找出这样一个子集,使得其和等于原数组和的一半。如同前面的暴力搜索一样,对于一些情况可以特判。对于数组里的每个数,都有两种选择,选或不选。记 dp[i][j] 表示“前 i 个数里能否凑出和为 jtrue 表示能,false 表示不能”,对于每个数选或不选,有以下两种情况:

  • 选择这个数,说明只要前 i - 1 个数里能凑出 j - nums[i] 即可,即 dp[i][j] = dp[i - 1][j - nums[j]]

  • 不选这个数,说明只要前 i - 1 个数里能凑出 j 即可,即 dp[i][j] = dp[i - 1][j]

最后,本题要求解的实际上是 dp[nums.size][nums.sum() / 2]

以上思路来自 discussion(因为我自己都忘了 0-1 背包咋写了),代码如下:

class Solution {
    fun canPartition(nums: IntArray): Boolean {
        if (nums.size == 1) {
            return false
        }
        val sum = nums.sum()
        if (sum % 2 != 0) {
            return false
        }
        val dp = Array(nums.size + 1) { BooleanArray(sum / 2 + 1) }

        dp[0][0] = true
        for (i in 1..nums.size) {
            dp[i][0] = true
        }
        for (i in 1..(sum / 2)) {
            dp[0][i] = false
        }

        for (i in 1..nums.size) {
            for (j in 1..(sum / 2)) {
                dp[i][j] = dp[i - 1][j]
                if (j >= nums[i - 1]) {
                    dp[i][j] = (dp[i][j] || dp[i - 1][j - nums[i - 1]])
                }
            }
        }

        return dp.last().last()
    }
}
posted @ 2020-11-25 11:19  Zhongju.copy()  阅读(72)  评论(0编辑  收藏  举报