Dynamic Programming | Set 9 (Binomial Coefficient)
Following are common definition of Binomial Coefficients.
1) A binomial coefficient C(n, k) can be defined as the coefficient of X^k in the expansion of (1 + X)^n.
2) A binomial coefficient C(n, k) also gives the number of ways, disregarding order, that k objects can be chosen from among n objects; more formally, the number of k-element subsets (or k-combinations) of an n-element set.
The Problem
Write a function that takes two parameters n and k and returns the value of Binomial Coefficient C(n, k). For example, your function should return 6 for n = 4 and k = 2, and it should return 10 for n = 5 and k = 2.
1) Optimal Substructure
The value of C(n, k) can recursively calculated using following standard formula for Binomial Cofficients.
C(n, k) = C(n-1, k-1) + C(n-1, k) C(n, 0) = C(n, n) = 1
2) Overlapping Subproblems
Following is simple recursive implementation that simply follows the recursive structure mentioned above.
// A Naive Recursive Implementation
#include<stdio.h>
// Returns value of Binomial Coefficient C(n, k)
int binomialCoeff(int n, int k)
{
// Base Cases
if (k==0 || k==n)
return 1;
// Recur
return binomialCoeff(n-1, k-1) + binomialCoeff(n-1, k);
}
/* Drier program to test above function*/
int main()
{
int n = 5, k = 2;
printf("Value of C(%d, %d) is %d ", n, k, binomialCoeff(n, k));
return 0;
}
It should be noted that the above function computes the same subproblems again and again. See the following recursion tree for n = 5 an k = 2. The function C(3, 1) is called two times. For large values of n, there will be many common subproblems.
C(5, 2)
/ \
C(4, 1) C(4, 2)
/ \ / \
C(3, 0) C(3, 1) C(3, 1) C(3, 2)
/ \ / \ / \
C(2, 0) C(2, 1) C(2, 0) C(2, 1) C(2, 1) C(2, 2)
/ \ / \ / \
C(1, 0) C(1, 1) C(1, 0) C(1, 1) C(1, 0) C(1, 1)
Since same suproblems are called again, this problem has Overlapping Subprolems property. So the Binomial Coefficient problem has both properties (see this and this) of a dynamic programming problem. Like other typical Dynamic Programming(DP) problems, recomputations of same subproblems can be avoided by constructing a temporary array C[][] in bottom up manner. Following is Dynamic Programming based implementation.
// A Dynamic Programming based solution that uses table C[][] to calculate the
// Binomial Coefficient
#include<stdio.h>
// Prototype of a utility function that returns minimum of two integers
int min(int a, int b);
// Returns value of Binomial Coefficient C(n, k)
int binomialCoeff(int n, int k)
{
int C[n+1][k+1];
int i, j;
// Caculate value of Binomial Coefficient in bottom up manner
for (i = 0; i <= n; i++)
{
for (j = 0; j <= min(i, k); j++)
{
// Base Cases
if (j == 0 || j == i)
C[i][j] = 1;
// Calculate value using previosly stored values
else
C[i][j] = C[i-1][j-1] + C[i-1][j];
}
}
return C[n][k];
}
// A utility function to return minimum of two integers
int min(int a, int b)
{
return (a<b)? a: b;
}
/* Drier program to test above function*/
int main()
{
int n = 5, k = 2;
printf ("Value of C(%d, %d) is %d ", n, k, binomialCoeff(n, k) );
return 0;
}
Time Complexity: O(n*k)
Auxiliary Space: O(n*k)
Following is a space optimized version of the above code. The following code only uses O(k). Thanks to AK for suggesting this method.
// A space optimized Dynamic Programming Solution
int binomialCoeff(int n, int k)
{
int* C = (int*)calloc(k+1, sizeof(int));
int i, j, res;
C[0] = 1;
for(i = 1; i <= n; i++)
{
for(j = min(i, k); j > 0; j--)
C[j] = C[j] + C[j-1];
}
res = C[k]; // Store the result before freeing memory
free(C); // free dynamically allocated memory to avoid memory leak
return res;
}
Time Complexity: O(n*k)
Auxiliary Space: O(k)
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
The equation
C(n, k) = C(n-1, k-1) + C(n-1, k)
has a nice intuitive interpretation.
To pick k elements from n elements [C(n, k)], you consider one element and either include it in the k chosen elements, or you don't. If you do, you have to now choose k-1 elements from the remaining n-1 elements[C(n-1, k-1)]; if you don't you need to choose k elements from the remaining n-1 elements [C(n-1, k)].
QED.
@geeksforgeeks,
for n<k;
we should give answer as 0 instead of garbage value,
because number of ways to choose k items from n items for n<k is 0 only.
Your program is missing that case.
Same is for n=0 and k=non zero.
/* Paste your code here (You may delete these lines if not writing code) */
and we should use memoization approach here as it will take O(n+k) time only.
please comment if i m wrong..
For the first DP approach Auxiliary Space should be O(n*k) instead of O(n^k).
Thanks for pointing this out. There was a typo. We have corrected it now. Keep it up!!
If you consider mathematically n(c, k) = (n*(n-1)*..*(n-(k-1)))/(k*(k-1)*...*1)
Here is the code
int mathematicalWay(int n, int k) { int val = 1; int div = 1; int i; for (i = 1; i <= k; i++) { val = val * (n-(i-1)); div = div * i; } return (val/div); }yeah, time O(k) and space O(1). definitely much better than DP
#include
#include
void find(int ,int, float);
int main()
{
int a,b;
scanf("%d %d",&a,&b);
find(a,b,1);
}
void find(int a,int b,float sum)
{
if(b==1)
{
printf("%f",sum*a);
exit(0);
}
else
{
sum=sum*((float)a/b);
find(a-1,b-1,sum);
}
}
~
Above recursive Tree for example C(5,2) is wrong it should be, C(5, 2) / \ C(4, 1) C(4, 2) / \ / \ C(3, 0) C(3, 1) C(3, 1) C(3, 2) / \ / \ / \ C(2, 0) C(2, 1) (2, 0) C(2, 1) C(2, 1) C(2, 2) / \ / \ / \ C(1, 0) C(1, 1) C(1, 0) C(1, 1) C(1, 0) C(1, 1)@Sandeep Vasani: Thanks for pointing this out. We have updated the post with the correct recursion tree.
If you just want to find C[n][k], here is a simple O(n*k) time and O(n) space method.
int binomialCoeff(int n, int k) { int* C = (int*)calloc(n+1,sizeof(int)); int i, j; C[0] = 1; for(i = 1; i <= n; i++) { for(j = i; j > 0; j--) C[j] += C[j-1]; } return C[k]; }@AK: Thanks for suggesting a space optimized method. The time complexity of this method is O(n^2) though. I think, the inner loop initialization statement can be modified to make it O(n*k).
int binomialCoeff(int n, int k) { int* C = (int*)calloc(n+1,sizeof(int)); int i, j; C[0] = 1; for(i = 1; i <= n; i++) { for(j = k; j > 0; j--) C[j] += C[j-1]; } return C[k]; }Even after the loop initialization changes, this method seems be doing O(nk) operations. But, the method given in the post doesn (k-1)k/2 + k(n-k) operations. Please correct me if I am wrong.
Thats a very minor speed-up and depends on input k. You can as well start j from min(i,k)
Starting from min(i,k) makes sense. So the final code would be.
int binomialCoeff(int n, int k) { // Only O(k) space needed int* C = (int*)calloc(k+1,sizeof(int)); int i, j; C[0] = 1; for(i = 1; i <= n; i++) for(j = min(i, k); j > 0; j--) C[j] += C[j-1]; return C[k]; }This DP method looks great. It uses O(k) space and same time complexity as the method given in the post. We will add it to the original post. Thanks for your time and effort. Keep it up!!
If you use calloc, you should call free(C) before returning; otherwise you have a leak.
@Frederic: Thanks for pointing this out. We have updated the code to avoid memory leak.
Why the inner loop is run backwards to 0 and not from 0?
@AK , Karthik Can You Explain this little bit more
C(n, k) = C(n-1, k-1) + C(n-1, k) ??
this recursion , please explain its meaning ?
@shankar: This follows the standard Binomial Coefficient formula. Please see the wiki page.