Combinatorics
Overview
Combinatorics is primarily involved with art of counting. This includes counting number of ways for a certain position to occur, to arrange some objects according to given rules or choosing some objects from a collection. Quite often, the required number takes a very large value, thus it is a very common practice to return the answer by taking modulo with some prime number (which is quite often, and quite recently also has become prominent). You can read more about modulo here.
Finding
Pronounced "n choose r", this is the mathematical notation to represent number of ways to choose r objects out of a collection of n objects. The analytical formula can be written as .
This leads to a neat recurrence relation: +
We can precompute all the required values using the above formula in and then perform lookup in time. The resulting values also form Pascal's Triangle.
Example #1: 2221 - Find Triangular Sum of an Array
The important insight here is that the figure provided is nothing but an inverted Pascal's Triangle and contribution of each cell in the final sum is the value of cell multiplied by the binomial coefficient at the particular position in Pascal's Triangle.
Thus for the cell at index in the topmost row, it's value is multiplied by and added to the final sum . Time Complexity of the program is for computing the binomial coefficient and Space complexity.
- C++
class Solution {
public:
int triangularSum(vector<int>& nums) {
int n = nums.size();
vector<int> pascalTriangleRow = {1};
// calculate the ith row using (i - 1)th row
for (int i = 0; i < n; i++) {
vector<int> nextRow = {1};
for(int j = 1; j < i; j++){
nextRow.push_back((pascalTriangleRow[j] + pascalTriangleRow[j - 1]) % 10);
}
nextRow.push_back(1);
pascalTriangleRow = nextRow;
}
// calculate the final answer as discussed above
int ans = 0;
for (int i = 0; i < n; i++) {
ans += (nums[i] * pascalTriangleRow[i]) % 10;
}
return ans%10;
}
};
Sometimes it is not possible to calculate the entirety of Pascal's Triangle due to larger values of . In this case, we begin by precomputing x!$$\, \forall$$\,$$x \in [{0, n}]. Similarly, we will also precompute the modular inverses. This can be achieved in time. Thus we can now compute using the analytical equation presented earlier. You can read about modular inverses here
The implementation of above can be as follows:
- C++
struct comb{
int mod;
// make arrays to store the factorial and inverse factorial modulo m
vector<long long int> factorial;
vector<long long int> inverse_factorial;
// N is the maximum value possible of input
comb (int N, int mod_in = 1e9 + 7) {
// calculate values for factorial
mod = mod_in;
factorial.push_back(1);
for(int i = 1; i <= N; i++) factorial.push_back((factorial.back() * i) % mod);
// calculate values for inverse factorial
vector<long long int> inverse;
inverse.push_back(1);
inverse.push_back(1);
inverse_factorial.push_back(1);
inverse_factorial.push_back(1);
for (int i = 2; i <= N; i++) {
inverse.push_back((mod - ((mod/i) * inverse[mod%i]) % mod) % mod);
inverse_factorial.push_back((inverse_factorial[i - 1] * inverse[i]) % mod);
}
}
// function to calculate nCr(n, r)
long long int nCr(int n, int r){
if ((r < 0) || (r > n)) return 0;
return ((factorial[n] * inverse_factorial[r]) % mod * inverse_factorial[n - r]) % mod;
}
};
For further reading, you can visit cp-algorithms.
Finding the catalan number
This is a very famous sequence of natural numbers and has a variety of applications.
- Number of ways to make balanced bracket sequences using left and right brackets.
- Number of ways to make binary trees
- Number of ways to form a mountain range with upstrokes and downstrokes.
Here is a more exhastive list.
The Catalan number can be found using the formula:
Example #2: 1863 - Sum of All Subset XOR Totals
This is an example of a very tricky problem which heavily simplifies after using some Combinatorics and Bit Manipulation
Here we will consider the bit from the right. Let's say that the bit is set in out of numbers in some given subset. If is odd, then bit is set in the XOR of all numbers of the subset, otherwise, it is not set.
Hence if there are numbers out of with bit set, then the contribution of the bit is
Thus we can find for all odd values of , which comes out to . Furthermore, we can choose the remaining elements in the subset in ways by similar logic. Hence total ways to get odd values of are , which is independent of both and .
Hence all we need to do is find bits which are set atleast once (by computing OR) and then multiply the final answer with . Time Complexity of the program is with Space Complexity.
- C++
class Solution {
public:
int subsetXORSum(vector<int>& nums) {
int arrayOR = 0;
// do OR of whole array to obtain bits which are set atleast once
for (int num : nums) arrayOR |= num;
// compute the final answer using the formula discussed
return arrayOR * (1 << (nums.size() - 1));
}
};
Example #3: 0062 - Unique Paths
Here our robot always goes either down or right. We know that we have to go down times and go left times. Thus we need to find the number of ways to arrange these. One way to visualize this is if we have blank spaces, and we have to fill of them using (representing going right) and remaining using (representing going down). Then we can just choose the number of spaces to fill with from total number of spaces. The the final solution is simply .
Notice that we are not required to return the value after taking modulo and the constraints allow for a precomputation. Thus, we will simply construct the entire Pascal's Triangle and query it everytime to calculate the answer.
- C++
class Solution {
public:
int uniquePaths(int m, int n) {
// the upper limit is m + n - 2 = 198
vector<vector<long long int>> PascalTriangle(199, vector<long long int> ());
PascalTriangle[0] = {1};
// calculating every row of the triangle
for (int i = 1; i <= 198; i++) {
PascalTriangle[i].push_back(1);
// using the recurrence relation.
for (int j = 1; j < i; j++) {
// take mod with INT_MAX as otherwise some values may overflow.
PascalTriangle[i].push_back((PascalTriangle[i - 1][j] + PascalTriangle[i - 1][j - 1]) % INT_MAX);
}
PascalTriangle[i].push_back(1);
}
// query the final answer
return PascalTriangle[m + n - 2][n - 1];
}
};
NOTE: Since every testcase only asks us to find for particular values of and , we can instead of precomputing the entire Pascal's Triangle, just compute the paricular value of using the recurrence relation and memoization. This will lead to less time and space complexity, as we only calculate the values we need. Also, then we no longer need to take modulo with INT_MAX as all the values will fit in the "int" type as mentioned in the question.
You can check the complete solution for this problem here
Example #4: 2400 - Number of Ways to Reach a Position After Exactly k Steps
Let's represent going left as and going right as . Thus, following the same idea as before, we have blanks to fill with and such that there sum is equal to .
Here we can immediately see that such will be impossible in only 2 cases:
- The parity of and is different.
- The magnitude of is less than magnitude of .
After checking for above 2 cases, we know for sure that there exists a solution. Now we can just find the number of and required to sum to . Expressing this as an equation:
, such that
Here represents the number of , i.e., the right steps and similarly represents number of , i.e., the number of left steps. We are now interested in finding the number of possible values of and such that the above equations are satisfied.
Adding both equations,
Thus,
Similarly, by subtracting the equations and simplifying,
Then the solution is as we need to find number of ways to choose or moves, out of moves.
Thus, we need to find .
To implement this, you can both precompute the entire Pascal's Triangle, or use concept of mudular inverses to find the required value.
You can check the complete solution for this problem here
Suggested Problems
Problem Name | Difficulty | Solution Link |
---|---|---|
920 - Number of Music Playlists | Hard | N/A |
1916 - Count Ways to Build Rooms in an Ant Colony | Hard | View Solutions |
1467 - Probability of a Two Boxes Having The Same Number of Distinct Balls | Hard | N/A |