LeetCode第 698 题:划分为k个相等的子集 (C++)

698. 划分为k个相等的子集 - 力扣(LeetCode)

注意提示
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000(正整数数组)

数组的和 / k 即可知道每个自己的和是多少,如果不能整除,则返回false。

参考Java(带返回值的)回溯法

// i:开始位置, cw:当前值,target:目标和值, k为组合的个数
// 一次只找到一个就行,找到之后标记位置(help数组)
class Solution {
public:
    bool backtrack(int i, int cw, int k, vector &nums, vector &help, int target){
	    if (k == 0) return true;
	    if (cw == target){ //找到了匹配
		    // 匹配下一个
		    return backtrack(0, 0, k-1, nums, help, target);
	    }
	    for (int j = i; j < nums.size(); ++j){
		    if (help[j] == 0 && cw + nums[j] <= target){
			    help[j] = 1; //做出选择
			    if (backtrack(j + 1, cw + nums[j], k, nums, help, target)) return true;
			    help[j] = 0; //撤销选择 
		    }
	    }
	    return false; 
    }
    bool canPartitionKSubsets(vector& nums, int k) {
        vector help(nums.size(), 0); //  用于记录nums中的第i个元素有没有被匹配过
        int sum = 0;
        for(const auto &c : nums){
            sum += c;
        }
        if(sum % k != 0)    return false;
        int target = sum / k; 

        return backtrack(0, 0, k, nums, help, target);
    }
};

回溯法很好理解,但是一开始自己设计的时候总是没法同意返回值的问题,总是最初的第一遍循环无法执行结束。。。

最后的代码,比上面的容易看懂一些,算法的重点是这一行:

if(cw+nums[j] == target)
                    flag = backtrack(0, 0, k-1, nums, help, target); //找到了一组,k=k-1,又回到最初开始循环

前两个参数i, cw一定要是0,0,才能回到起点重新开始,我们有help数组记录考察过的值,所以从起点开始并不会重复计算。说的不太清楚,我也是好久才弄明白这儿的。

class Solution {
public:
    //i:开始位置, cw:当前值
    bool backtrack(int i, int cw,int k, vector &nums, vector &help, int target){
        if(k == 0)   return true; //目标就是把size元素全装进去
        bool flag = false;
        for(int j = i; j < nums.size(); ++j){
            if(!help[j] && cw + nums[j] <= target){
                help[j] = 1; //做出选择
                if(cw+nums[j] == target)
                    flag = backtrack(0, 0, k-1, nums, help, target); //找到了一组,k=k-1,又回到最初开始循环
                else   
                     flag = backtrack(j+1, cw+nums[j], k, nums, help, target); //没找到
                if(flag) return true;
                help[j] = 0; //撤销选择
            }
        }
        return flag;
    }
    bool canPartitionKSubsets(vector& nums, int k) {
        int n = nums.size();
        vector help(n, 0); //  用于记录nums中的第i个元素有没有被匹配过
        int sum = 0;
        int max = 0;
        for(const auto &c : nums){
            if(max < c) max = c;
            sum += c;
        }
        if(sum % k != 0 || max > sum/k)    return false;
        int target = sum / k; 
        return backtrack(0, 0, k, nums, help, target);
    }
};

一个月之后回看,当时确实理解的不够,自己又写了一遍回溯:

class Solution {
public:
    int target = 0;
    vector help;//用于标记nums中对应下标的元素有没有被选取
    //idx:开始位置, cw:当前和, k : 还需要找的子集个数
    bool backtrack(vector &nums, int k, int idx, int cw){
        if(k == 0)  return true;

        if(cw == target) return backtrack(nums, k-1, 0, 0);//又回到数组的起点重新寻找下一个集合

        for(int i = idx; i < nums.size(); ++i){
            if(help[i] == 0 && cw + nums[i] <= target){
                help[i] = 1;
                if(backtrack(nums, k, i+1, cw+nums[i])) return true;
                help[i] = 0;
            }
        }
        return false;
    }
    bool canPartitionKSubsets(vector& nums, int k) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if(sum % k) return false;
        int target = sum / k;//目标和值
        this->target = target;
        help = vector(nums.size(), 0);
        return backtrack(nums, k, 0, 0);
    }
};

这类回溯不容易理解的地方在于带有返回值

你可能感兴趣的:(leetcode,leetcode)