分区问题的递归函数

时间:2011-09-10 20:51:54

标签: c++ recursion

问题是找到数组中数字子集加起来到特定目标数的次数。

例如,有两种方法可以对集{1, 3, 4, 5}进行分区,以便其余元素加起来为5:

  • 选择14
  • 只选择5

相比之下,没有办法将集合{1, 3, 4, 5}分区为11。

#include "genlib.h"
#include <iostream>

void RecursePart(int[], int, int, int&);
int Wrapper(int[], int, int);


int main() {
    int arr[8] = {1,2,3,4,5,6,7,8};
    cout << Wrapper(arr, 8, 11);
}


void RecursePart(int arr[], int len, int target, int& ctr) {
    if (len == 1) {
        if (arr[0] == target) {
            ctr++;
        }
        return; 
    }

    int sum, temp;
    sum = temp = arr[0];

    for (int j = 1; j < len; j++) {
        if (sum == target) {
            ctr++;
            break;
        }

        sum = sum + arr[j];

        if (sum == target) {
            ctr++;
            sum = temp;
            continue;           
        }

        if (sum > target) {
            sum = temp;
            continue;
        }

        if (sum < target) {
            temp = sum;
            continue;
        }    
    }

    RecursePart(arr + 1, len - 1, target, ctr);
}

int Wrapper(int arr[], int len, int target) {
    int n = 0;
    RecursePart(arr, len, target, n);
    return n;
}

问题是我获得的输出是1,但是数组中加起来11的数字子集的次数大于1.我试图跟踪算法,我知道问题必须在for循环中。算法会跳过一些总和。我怎么能覆盖这个问题?

3 个答案:

答案 0 :(得分:3)

与其他人一样,这是子集和问题(NP-complete),这意味着你需要一个指数时间算法来解决它。
只需查看您的函数,就可以调用RecursePart一次,每次使用len-1,然后调用for-loop长度为n,这意味着您的计算为O(n^2) 。这显然无法解决O(2^n)问题。

以下是一个递归解决方案,它创建子集的总和,并尝试查看它们是否到达目标。如果当前子集没有选项等于目标,则停止当前子集的“创建”。

int RecursePart(int arr[], int len, int idx, int curr_sum, int target)     
{        
    int count = 0;

    // this subset is good
    if (curr_sum == target)
       return 1;

    // the sum of the current subset exceeds target, no point in continuing
    if (curr_sum > target || idx == len)
       return 0;

    count += RecursePart(arr, len, idx+1, curr_sum + arr[idx], target);
    count += RecursePart(arr, len, idx+1, curr_sum, target);

    return count;
}

这是我以前的解决方案,它会创建所有可能的子集,并计算与目标匹配的子集。

#include <iostream>

int Wrapper(int[], int, int);

int main() {
    int arr[8] = {1,2,3,4,5,6,7,8};
    std::cout << Wrapper(arr, 8, 11);
}

// counts the sum of a subset
int CountSet(int* arr, int* mask, int len)
{
    int sum = 0;

    for (int i=0; i < len; ++i)
    {
        if (mask[i])
        {
            sum += arr[i];
        }
    }
    return sum;
}


int RecursePart(int arr[], int idx, int len, int* subset_mask, int target) 
{
    int count = 0;

    if (idx == len)
    {
        if (CountSet(arr, subset_mask, len) == target)
            return 1;
        else
            return 0;
    }

    // create the subset "without" me
    subset_mask[idx] = 0;
    count += RecursePart(arr, idx+1, len, subset_mask, target);

    // now create the subset "with" me
    subset_mask[idx] = 1;
    count += RecursePart(arr, idx+1, len, subset_mask, target);

    return count;
}

int Wrapper(int arr[], int len, int target) {

    int* subset_mask = (int*)malloc(len*sizeof(int));
    int res = RecursePart(arr, 0, len, subset_mask, target);
    free(subset_mask);
    return res;
}

答案 1 :(得分:0)

既然您正在使用C ++,那么您还可以使用vector<int>存储中间解决方案,如下所示:

#include <iostream>
#include <vector>

using namespace std;

vector<int> RecursePart(int[], int, int, int&);
int Wrapper(int[], int, int);


int main() {
    int arr[8] = {8,2,3,4,5,6,7,1};
    cout << Wrapper(arr, 8, 11);
}


vector<int> RecursePart(int arr[], int len, int target, int& ctr) 
{
    vector<int> return_vec;

    if (len == 1)
    {
        if (arr[0] == target)
        {
            ctr++;
            return return_vec;
        }

        return_vec.push_back(arr[0]);
        return return_vec;
    }

    int current = arr[0];

    if (current == target)
        ctr++;

    if (current < target)
        return_vec.push_back(current);

    vector<int> temp;

    temp = RecursePart(arr + 1, len - 1, target, ctr);

    for (int i = 0; i < temp.size(); i ++)
    {
        if (temp[i] + current == target)
        {
            ctr++;
        }

        if (temp[i] + current < target)
            return_vec.push_back(temp[i] + current);

        if (temp[i] < target)
            return_vec.push_back(temp[i]);
    }

    /*Debug Print
    cout << "Current: " << current << endl;
    for (int i = 0 ; i < return_vec.size(); i++)
    {
        cout << return_vec[i] << ", ";
    }
    cout << endl;
    */

    return return_vec;
}

int Wrapper(int arr[], int len, int target) {
    int n = 0;
    RecursePart(arr, len, target, n);
    return n;
}

详细说明,我们正在使用递归将列表分解为最简单的部分,即单个数字(列表的最后一个元素),并使我们的返回类型的所有可能总和小于我们的目标。在基本情况下,它基本上是数组的最后一个元素,如果该数字等于目标,我们递增计数器,并返回一个空向量,因为返回向量,即所有可能的总和小于我们的目标,不应该有一个等于或大于我们目标的元素。在基本情况的所有先前递归步骤中,我们得到返回的向量,然后将当前值与返回向量中的所有元素进行比较,这再次表示直到该点的所有可能总和小于目标。我们检查是否有任何与目标匹配的总和,然后将任何总和复制到我们的返回向量中,小于目标。然后我们回到之前的递归步骤。

此算法的复杂性仍然呈指数级...如果启用debug print部分,您将看到每次迭代的向量长度增长将如何以指数方式增长,因此对于给定的一组大小为N,您必须检查的解决方案的数量将与O(2 ^ N)一致。这个版本的解决方案虽然通过确保我们只将有效的解决方案移动到下一次迭代而有所回复,但无需计算每个可能的子集。

答案 2 :(得分:0)

你的功能完全错了。您可以在Wrapper(arr,4,5)中看到相同的错误。只要它们不超过目标,您就可以从左侧总结元素,这样您就找不到仅涉及其中一些元素的解决方案。你必须重新考虑算法。