0

I have an assignment to solve the coin change problem which goes like this: i have 5 types of coins {1,5,10,25,50}, We want to make changes with these coins for a given amount of money, in how many ways can i form up 11 cents for example, or n cents in general ? my program should be able to handle up to 7489 cents as an input.

i used recursion and dynamic programming to solve the problem but i get a stack overflow error during runtime at (input >= 3980)

How do i solve this error ?

#include <iostream>
using namespace std;
typedef long long ll;
int Cents[5] = { 1,5,10,25,50 }, in, mem[5][7495];

ll CC(int i, int val) { // this is where the recursion happens
    if (val == 0)   return 1;
    if (i == 5) return 0;
    if (mem[i][val] != -1) return mem[i][val];
    long long op1 = CC(i + 1, val);
    long long op2 = Cents[i] <= val ? CC(i, val - Cents[i]) : 0;
    return  mem[i][val] = (op1+op2);
}

int main() { // here i pass an input and fill the mem 2d array with -1's
    scanf_s("%d", &in);
    memset(mem, -1, sizeof mem);
    printf("%lld\n", CC(0, in));
    system("pause");
    return 0;
}

if the input is 5 for example, then the output should be 2 ways (1),(5) if 11 , then 4 (1*11),(1*6,5*1),(10*1,1*1),(1*1,5*2) and so on.

  • Two solutions: 1) Use bottom-up approach, instead of top-down. 2) Check your course instructions about command line arguments. It's possible that they have stack size which is bigger than a default one (see this answer for example: https://stackoverflow.com/a/2275586/2956272). –  Aug 15 '19 at 03:48
  • 1
    The recursion depth for input `n` is proportional to `n`, as you are trying to make change in `n` pennies. Such an approach is unreasonable. – Igor Tandetnik Aug 15 '19 at 03:52
  • You could [remove the recursion](https://refactoring.com/catalog/replaceRecursionWithIteration.html). – user1118321 Aug 15 '19 at 04:04
  • `memset(mem, -1, sizeof mem)` sets each **byte** in `mem` to -1. There is no guarantee that this will result in each `int` value in the `mem` array having the value -1. – Pete Becker Aug 15 '19 at 13:03
  • Does your assignment require you to use recursion? Doing the calculation in a loop is much simpler and much more flexible. – Pete Becker Aug 15 '19 at 13:04
  • @dyukha Thankyou for your help, actually i've tried the second option after installing cygwin on windows to use the sys/resource header file in order to control the stack size, but what's really weird is that there was no problem apparently, for when i tried running the program on code blocks (prior to using it i was working on visual c++ 2017, and this was where the error occurred), the program worked smoothly without any errors..., but again, thank you very much for your help ! – Bassil Oraby Aug 15 '19 at 22:50
  • @user1118321@PeteBecker well unfortunately it's required to use the top down approach in dynamic programming, and as far as i know the top down approach requires a recursive type of code, or doesn't it ? also, as for the memset, actually i've used it as i've been told that it's better in time complexity than making a nested two level for loop for each dimension of the mem array, and yes i've actually tried it and it always outputs -1 at any location in the 2d array, Thank you very much for your help. – Bassil Oraby Aug 15 '19 at 22:58
  • @PeteBecker, can you clarify about `memset`? Each byte value is `111....11`, and therefore each int value is `1111.111`, which is `-1`. –  Aug 16 '19 at 00:54
  • @dyukha -- 111..11 is -1 in a twos-complement representation. It is not -1 in a ones-complement representation or in a sign-magnitude representation. Granted, twos-complement is the most common representation used, but it is not required by the standard. – Pete Becker Aug 16 '19 at 01:04

1 Answers1

1

You have a stack overflow because your recursion depth is limited by only the size of your array and the number of pennies. And your stack state is reasonable in size.

One solution is to go with an explicit stack.

enum coins {
  penny, nickle, dime, quarter, half,
  coin_count
};
enum {
  max_cash = 7495
};
struct coin_problem {
  coins lowest_coin = penny;
  int money = 0;
};
struct coin_cache {
  unsigned long long solution[coin_count][max_cash+1];
  coin_cache(){
    for(auto& row:solution)
      for(auto& val:row)
        val=-1;
  }
  unsigned long long& set(coin_problem p){
    return solution[p.lowest_coin][p.money];
  }
  unsigned long long get(coin_problem p)const{
    if(p.lowest_coin==coin_count) return 0;
    if(p.money==0) return 0;
    return solution[p.lowest_coin][p.money];
  }
};
unsigned long long count_solutions( std::vector<coin_problem>& vec, coin_cache& cache){
  unsinged long lon retval = 0;
  while(!vec.empty()){
    auto cur = vec.back();
    retval = cache.get[cur];
    if (retval != -1) {
      vec.pop_back();
      continue;
    }
    ++cur.lowest_coin;
    auto op1 = cache.get[cur];
    if (op1 == -1) vec.push_back(cur);
    --cur.lowest_coin;
    unsigned long long op2 = -1;
    if(Cents[cur.lowest_coin] <= cur.money){
      cur.money -= Cents[cur.lowest_coin];
      op2 = cache.get[cur];
      if (op2==-1) vec.push_back(cur);
      cur.money += Cents[cur.lowest_coin];
    } else {
      op2 = 0;
    }
    if (op1 != -1 && op2 != -1){
      retval = cache.set(cur) = op1+op2;
      vec.pop_back();
    }
  }
  return retval;
}
unsigned long long count_solutions( coin_problem p ){
  auto pcache = std::make_unique<coin_cache>();
  std::vector<coin_problem> todo = {p};
  return count_solutions( todo, *pcache );
}

I tore your recursive solution up and gave it a manual stack.

For each entry in the stack of problems, we first see if there is a solution waiting in the cache. If so, we calculate it, and store it in the cache, and pop the problem. If the stack of problems is empty, we return it.

If not, we push sub problems to solve first onto the top if the stack, leaving our current problem to be solved later, and loop until the stack is empty.

As our manual stack exists on the free store (aka the heap) we are unlikely to experience a stack overflow on a desktop system.

This isn't perfectly efficient, as we can have more than one entry on the stack for each problem. But that should be at most a constand factor, and can be likely fixed by reordering the "recursion" to do the "deepest" call first. Or making the stack of problems a set of problems ordered by depth.

Another approach is to just solve every entry in the table, deepest first.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524