Tuesday, June 21, 2011

Print all the combination from a candidate set that sum to a target value

Problem: Given a target set of integers (may contain negative integers) and a target value, print all the combinations that sum to the target value. Each element can only be used once in a combination. For the same combination, only print once.

  • Example:  candidate set [-1, 3, 2, 1, 5], target value 4. Then we can have 1+3, -1+2+3

Solution: Recursion is used to solve this problem. Also first sort the candidate set.

  • Assume we have a prev_sum (the sum of the previous integers we had added up) and current integer we want to add, if prev_sum + current integer > target value, we return.
  • if prev_sum + current integer = target value, we print out the combination and also return. No need to explore further, since the candidate set is sorted.
  • if prev_sum + current integer < target value, we take the current integer, update prev_sum, and also update an array that is used to track the combination, then we make a recursive function call
  • an array, int index[] is used to track the integers we had used.  The advantage of this array is simplicity. Definitely we can use STL vectors.
  • If the target value is negative, taking the above approach naively will have some problem (see chunjie's comment below). There are different ways to handle this case. One way used below is to convert the target value into positive and also convert the whole set of integers by multiplying with a "-1".
The related code is as follow:




void print_comb(int index[], int n, vector<int> set, int negative)
{
   for(int i =0; i<=n; i++)
     cout << (negative * set[index[i]]) << ((i<n)?'+':' ');
     cout << endl;
}

//assume a sorted list
void _print_all_combine_n(int target, int negative, int n_prev, int index[], vector<int> set, int pos, int n)
{

   for(int i = pos; i < set.size(); i++)
   {
     if(n_prev + set[i] == target) 
      {
         index[n] = i;
         print_comb(index, n, set, negative);
         return;
      }
      
      // we don't need to look at later cases, since they are not possible
      if(n_prev + set[i] > target)        
            return;

      // skip duplicate numbers before advancing, since we only use each number once
      int m = i+1;
      while(set[m] == set[i] && m < set.size())
           m++;
      i = m-1;

      if(n_prev + set[i] < target)
      {
         index[n] = i;
         print_all_combine_n(target, n_prev+set[i], index, set, i+1, n+1);

      }

   }

}

//assume a sorted list
void print_all_combine_n(int target)
{

   int index[10000];
   index[0] = 0;
   vector<int> set(myints, myints + sizeof(myints) / sizeof(int) );
   
   int negative = 1;

   if(target < 0) 
   {
      for(int i=0; i<set.size(); i++)
         set[i] = (-1)*set[i];

      negative = -1;
   }

   sort(set.begin(), set.end());

   print_all_combine_n(target * negative, negative, 0, index, set, 0, 0);
}

int main()
{

   int myints[] = {10, 1, 2, 7, 6, 1, 5, -3};

   
   print_all_combine_n(8);

}


7 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi, input as {-3, -2, -1, 0, 1} and target = -5
    your program will exit right after "if(n_prev + set[i] > target)".

    I have my own version of implementation but it seems little bit ugly.
    The basic idea is the same

    template
    void PrintVector(vector &v){
    for(int i = 0; i < v.size(); i++)
    cout< &path, bool &bChanged){
    if(sum == t){
    if(path.size() > 0 && bChanged){//bChanged: whether it has been change since last output
    PrintVector(path);
    bChanged = false;
    }
    }
    if(i == n) return;
    int j = i;
    while(j+1 < n && a[j] == a[j+1]) j++;
    // cnt = j-i+1
    for(int k = 0; k <= j-i+1; k++){
    if(k != 0){
    path.push_back(a[i]);
    bChanged = true;// mark it as changed since we just insert a new element
    }
    get_all_comb(a, n, j+1, t, sum+k*a[i], path, bChanged);
    }
    for(int k = 1; k <= j-i+1; k++) path.pop_back();
    }

    ReplyDelete
    Replies
    1. Thanks so much for your comment. It is true that my original implementation failed to handle the case where the "target" is negative. I updated the implementation. Basic idea is to convert the target to a positive one.

      Delete
    2. what about this one? {-3,-2,-1,0, 1, 1, 1}; target = 2

      I just began to browse your website, it is quite helpful to me. After going all through this site. I might consider to look for a job in USA, hope to get some help if possible by then. haha~ BTW, i just found i know you : ) you are famous in our University , ZJU right?

      Delete
    3. It is nice that this website can be helpful to you. You definitely can try to apply for a job in US. Though I have some friends from ZJU, actually I am not from ZJU.

      Delete
  3. my bad, I thought you are Ma Xiao -___- haha

    ReplyDelete