You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.8 KiB

impl Solution {
    pub fn can_partition(nums: Vec<i32>) -> bool {
        let sum: i32 = nums.iter().sum();
        if sum % 2 != 0 {
            return false;
        }
        let target = sum / 2;
        let mut dp = vec![false; (target + 1) as usize];
        dp[0] = true;
        for num in nums {
            for i in (num..=target).rev() {
                dp[i as usize] = dp[i as usize] || dp[(i - num) as usize];
            }
        }
        dp[target as usize]
    }
}

The task is to determine whether a given set can be partitioned into two subsets such that the sum of elements in both subsets is equal. The dynamic programming solution works as follows:

  1. First, it calculates the sum of all elements in the set. If the sum is odd, it immediately returns false because it's impossible to split an odd number evenly.

  2. If the sum is even, it sets a target as half of the sum. The goal is to find a subset of the original set that sums up to this target.

  3. It then initializes a DP array of size target + 1 with all elements set to false, except for the first one which is set to true. This DP array is used to keep track of the possible sums that can be formed using the elements of the set. The index i in the DP array represents a possible sum, and the value at dp[i] is true if it's possible to form this sum using the elements of the set, and false otherwise.

  4. The algorithm then iterates over each number in the set. For each number, it iterates over the DP array in reverse order from target to num. This is done to avoid using the same element multiple times.

  5. For each i in the range from num to target, it updates dp[i] to be true if dp[i] is already true (meaning we can form the sum i without the current number), or if dp[i - num] is true (meaning we can form the sum i by adding the current number to a previously possible sum i - num).

  6. Finally, it returns the value of dp[target]. If this is true, it means we can partition the set into two subsets with equal sum.

Complexity analysis

  • Time Complexity: O(n*sum/2) where n is the number of elements in the input array and sum is the sum of all elements. This is because, for each element in the array, the code iterates up to sum/2 times.

  • Space Complexity: O(sum/2) because we use a DP array of size sum/2.

Improvement

The given implementation is already optimal in terms of time and space complexity. However, some minor improvements can be made

  1. You could use an early return if you find a number in the array that is greater than the target sum. Such a number could not be part of any subset that adds up to the target sum.

  2. You could sort the array in descending order before starting the main loop. This way, you would start with the largest numbers, and if you can't reach the target sum with them, you can return false earlier.

  3. If the input array is very large, you could use a bitset instead of a boolean array for the dp array to save space.

impl Solution {
    pub fn can_partition(mut nums: Vec<i32>) -> bool {
        let sum: i32 = nums.iter().sum();
        if sum % 2 != 0 {
            return false;
        }
        let target = sum / 2;
        nums.sort_unstable_by(|a, b| b.cmp(a));
        if nums[0] > target {
            return false;
        }
        let mut dp = vec![false; (target + 1) as usize];
        dp[0] = true;
        for num in nums {
            for i in (num..=target).rev() {
                dp[i as usize] = dp[i as usize] || dp[(i - num) as usize];
            }
            if dp[target as usize] {
                return true;
            }
        }
        dp[target as usize]
    }
}

Note that we are using sort_unstable_by instead of sort_by because it can be faster for certain inputs, and we don't care about preserving the relative order of equal elements (i.e we don't need a stable sort). The |a, b| b.cmp(a) part is a closure that compares two elements, and it's written in such a way that it sorts the array in descending order. The cmp method compares two values and returns an ordering, and by comparing b with a instead of a with b, we get the elements in descending order.

Extra

We can use a DP approach to find the actual subsets that add up to the target sum. The idea is as follows:

  1. Instead of a boolean DP array, we use a DP array of vectors. Each dp[i] will store all the subsets that sum up to i.

  2. Initialize dp[0] with an empty subset (since there's one way to sum up to 0, which is by taking no elements).

  3. For each number in the array, we iterate over the DP array in reverse order from target to num. For each i in this range, if dp[i - num] is not empty, add num to all subsets in dp[i - num] and append these new subsets to dp[i].

  4. Finally, dp[target] will contain all the subsets that sum up to the target.

Here's a Python code snippet that implements this idea:

fn can_partition(nums: Vec<i32>) -> Vec<Vec<i32>> {
    let total: i32 = nums.iter().sum();
    if total % 2 != 0 {
        return vec![];
    }
    let target = total / 2;
    let mut dp = vec![vec![]; (target + 1) as usize];
    dp[0] = vec![vec![]];
    for &num in nums.iter() {
        for i in (num as usize..=target as usize).rev() {
            let subsets = dp[(i - num as usize)].clone();
            for mut subset in subsets {
                subset.push(num);
                dp[i].push(subset);
            }
        }
    }
    dp[target as usize].clone()
}

This function returns all the subsets that sum up to the target. If there are no such subsets, it returns an empty list.

Note that this solution has a higher time and space complexity than the original solution, as it needs to store all possible subsets instead of just a boolean value for each sum.