[LeetCode] Partition to K Equal Sum Subsets 分割K個等和的子集


 

Given an array of integers nums and a positive integer k, find whether it's possible to divide this array into knon-empty subsets whose sums are all equal.

Example 1:

Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: True
Explanation: It's possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.

 

Note:

  • 1 <= k <= len(nums) <= 16.
  • 0 < nums[i] < 10000.

 

這道題給了我們一個數組nums和一個數字k,問我們該數字能不能分成k個非空子集合,使得每個子集合的和相同。給了k的范圍是[1,16],而且數組中的數字都是正數。這跟之前那道 Partition Equal Subset Sum 很類似,但是那道題只讓分成兩個子集合,所以問題可以轉換為是否存在和為整個數組和的一半的子集合,可以用dp來做。但是這道題讓求k個和相同的,感覺無法用dp來做,因為就算找出了一個,其余的也需要驗證。這道題我們可以用遞歸來做,首先我們還是求出數組的所有數字之和sum,首先判斷sum是否能整除k,不能整除的話直接返回false。然后需要一個visited數組來記錄哪些數組已經被選中了,然后調用遞歸函數,我們的目標是組k個子集合,是的每個子集合之和為target = sum/k。我們還需要變量start,表示從數組的某個位置開始查找,curSum為當前子集合之和,在遞歸函數中,如果k=1,說明此時只需要組一個子集合,那么當前的就是了,直接返回true。如果curSum等於target了,那么我們再次調用遞歸,此時傳入k-1,start和curSum都重置為0,因為我們當前又找到了一個和為target的子集合,要開始繼續找下一個。否則的話就從start開始遍歷數組,如果當前數字已經訪問過了則直接跳過,否則標記為已訪問。然后調用遞歸函數,k保持不變,因為還在累加當前的子集合,start傳入i+1,curSum傳入curSum+nums[i],因為要累加當前的數字,如果遞歸函數返回true了,則直接返回true。否則就將當前數字重置為未訪問的狀態繼續遍歷,參見代碼如下:

 

解法一:

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

 

我們也可以對上面的解法進行一些優化,比如先給數組按從大到小的順序排個序,然后在遞歸函數中,我們可以直接判斷,如果curSum大於target了,直接返回false,因為題目中限定了都是正數,並且我們也給數組排序了,后面的數字只能更大,這個剪枝操作大大的提高了運行速度,感謝熱心網友 hellow_world00 提供,參見代碼如下:

 

解法二:

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % k != 0) return false;
        sort(nums.begin(), nums.end(), greater<int>());
        vector<bool> visited(nums.size(), false);
        return helper(nums, k, sum / k, 0, 0, visited);
    }
    bool helper(vector<int>& nums, int k, int target, int start, int curSum, vector<bool>& visited) {
        if (k == 1) return true;
        if (curSum > target) return false;
        if (curSum == target) return helper(nums, k - 1, target, 0, 0, visited);  
        for (int i = start; i < nums.size(); ++i) {
            if (visited[i]) continue;
            visited[i] = true;
            if (helper(nums, k, target, i + 1, curSum + nums[i], visited)) return true;
            visited[i] = false;
        }
        return false;
    }
};

 

下面這種方法也挺巧妙的,思路是建立長度為k的數組v,只有當v里面所有的數字都是target的時候,才能返回true。我們還需要給數組排個序,由於題目中限制了全是正數,所以數字累加只會增大不會減小,一旦累加超過了target,這個子集合是無法再變小的,所以就不能加入這個數。實際上相當於貪婪算法,由於題目中數組數字為正的限制,有解的話就可以用貪婪算法得到。我們用一個變量idx表示當前遍歷的數字,排序后,我們從末尾大的數字開始累加,我們遍歷數組v,當前位置加上nums[idx],如果超過了target,我們掉過繼續到下一個位置,否則就調用遞歸,此時的idx為idx-1,表示之前那個數字已經成功加入數組v了,我們嘗試着加下一個數字。如果遞歸返回false了,我們就將nums[idx]從數組v中對應的位置減去,還原狀態,然后繼續下一個位置。如果某個遞歸中idx等於-1了,表明所有的數字已經遍歷完了,此時我們檢查數組v中k個數字是否都為target,是的話返回true,否則返回false,參見代碼如下:

 

解法三:

class Solution {
public:
    bool canPartitionKSubsets(vector<int>& nums, int k) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % k != 0) return false;
        vector<int> v(k, 0);
        sort(nums.begin(), nums.end());
        return helper(nums, sum / k, v, (int)nums.size() - 1);
    }
    bool helper(vector<int>& nums, int target, vector<int>& v, int idx) {
        if (idx == -1) {
            for (int t : v) {
                if (t != target) return false;
            }
            return true;
        }
        int num = nums[idx];
        for (int i = 0; i < v.size(); ++i) {
            if (v[i] + num > target) continue;
            v[i] += num;
            if (helper(nums, target, v, idx - 1)) return true;
            v[i] -= num;
        }
        return false;
    }
};

 

類似題目:

Partition Equal Subset Sum

 

參考資料:

https://leetcode.com/problems/partition-to-k-equal-sum-subsets/

https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/108730/javacstraightforward-dfs-solution

https://leetcode.com/problems/partition-to-k-equal-sum-subsets/discuss/108751/easy-to-understand-java-solution

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM