Photo by MARIOLA GROBELSKA on Unsplash
MCM based DP or Partition DP
Problems that involve repeatedly placing partition between start and end indices
These problems can be formulated as increasing order of lengths between intervals they involve creating some specific set of partitions between a given range
Number of ways to evaluate Boolean expression to true
the question can be understood as number of ways we can place partitions in this complete string such that it evaluates to true
METHOD 1 : RECURSION AND MEMOIZATION
We will try to place 'k' partitions b/w range [s,e]
Partitions will be done on each `operand`
The answer to each range should consists of a pair of values , the number of way this subrange can evaluate to true and number of ways this subrange can evaluate to false. We can use both these numbers to calculate the answer for bigger subrange using the solutions of smaller subranges - OPTIMAL SUBSTRUCTURE
Lets start by creating an utility function for above table logic
We can start building recursive solution now
Since we have a lot of repeated subproblems , the above will give TLE unless memoized
METHOD 2 : DYNAMIC PROGRAMMING APPROACH
Let dp[i][j] represent a pair of numbers , which represent in how many ways can string expression between [i,j] indices can evaluate to true or false
We can store both true ways or false ways in same matrix or create separate matrices also , one storing number of true ways and other storing number of ways of marking range eval to false
We can setup the recurrence relation now
typedef long long ll; typedef pair<ll, ll> pll; constexpr ll MOD = 1e9 + 7; ll mod(ll x) { return (x % MOD + MOD) % MOD; } pll calc(ll ltc, ll lfc, ll rtc, ll rfc, const char& operand) { ll totalWaysToCombine = mod(ltc + lfc) * mod(rtc + rfc); ll numberOfWaysToMakeTrue, numberOfWaysToMakeFalse; switch (operand) { case '|': numberOfWaysToMakeFalse = mod(lfc * rfc); return { mod(totalWaysToCombine - numberOfWaysToMakeFalse), numberOfWaysToMakeFalse }; case '&': numberOfWaysToMakeTrue = mod(ltc * rtc); return { numberOfWaysToMakeTrue, mod(totalWaysToCombine - numberOfWaysToMakeTrue) }; case '^': numberOfWaysToMakeTrue = mod((ltc * rfc) + (lfc * rtc)); return { numberOfWaysToMakeTrue, mod(totalWaysToCombine - numberOfWaysToMakeTrue) }; default: return { 0, 0 }; } } int evaluateExp(string & exp) { size_t n= exp.size(); vector<vector<pll>> dp(n,vector<pll>(n,{0,0})); for(int gap=0;gap<n;gap+=2){// gap is always last-first we are solving for [0,n-1] =>gap (n-1) for(int s=0;s<=n-gap;s+=2){// (n-1)-x+1=gap int e=s+gap;// e-s=gap if(gap==0){ exp[s]=='T' ? dp[s][e].first=mod(dp[s][e].first + 1) : dp[s][e].second=mod(dp[s][e].second + 1); continue; } ll allWaysToMakeTrue=0 ; ll allWaysToMakeFalse=0; for(int k=s+1;k<e;k++){// all valid operand positions pll leftans=dp[s][k-1]; pll rightans=dp[k+1][e]; pll numberOfWaysToMakeTruenFalse=calc(leftans.first,leftans.second,rightans.first,rightans.second,exp[k]); // adding for the entire range allWaysToMakeTrue=mod(allWaysToMakeTrue + numberOfWaysToMakeTruenFalse.first); allWaysToMakeFalse=mod(allWaysToMakeFalse + numberOfWaysToMakeTruenFalse.second); } dp[s][e]=make_pair(allWaysToMakeTrue, allWaysToMakeFalse); } } return mod(dp[0][n-1].first); }
Matrix Chain Multiplication
Like earlier problem this problem also involves placing partitions between matrices such that they result in minimum operations
METHOD 1: Recursion and Memoization
We will place partitions one by one, compute operations for each and find minimum operations among them
METHOD 2: Tabulation
If we have solution for each subrange , we can form the solution for parent range from them . This gives us an hint that we can form our solution on basis of increasing length or gap
Rod Cutting Problem
METHOD-1 Recursion and Memoization
We can easily see how formulating this question in the basis of length can make our problem easier
Base cases : a zero length rod cannot be sold, a single unit rod can only be sold whole
The best price of any subsequent length, would be function solutions of all smaller lengths which are recursively similar
METHOD -2 Bottom up , Tabulation
The price array maps `length` to rod price
We can create a dp array of size n+1 , at each index of this array we will store the maximum price attainable for rod of length i by any means
ie dp[i] represents the maximum possible profit attainable for a rod of length i by any means (selling whole/cutting)
Base cases : a zero length rod cannot be sold, a single unit rod can only be sold whole
The best price of any subsequent length, would be function of pre-cauluated solutions of all smaller lengths
TODO: This problem can also be done via Unbounded -knapsack concept
Minimum Cost to cut a stick
The diagram in the question gives a great way to emulate the problem, we can try choosing any partition first among possible partitions and find cost of each choice
METHOD 1: RECURSION AND MEMOIZATION
METHOD 2: BOTTOM UP DP / Tabulation
Burst Balloons
- For example for [2,3,5] there are 3! ways to burst baloons, each way will lead to a different coins value
METHOD 1- RECURSION AND MEMOIZATION
At each balloon we have a choice to make it as the LAST one to burst, this simplifies calculation for recursive solution
METHOD 2- TABULATION
Above cells, for example cell[1][4] (3,1,5,6) represents the BEST price bursting these balloons in any order can give , in context of the entire array ie its actually 2,3,1,5,6,4
We can fill the gap=0 cells easily (base case)
-
For all subsequent cells we can reuse table and calculate
Palindrome Partitioning II
10:53
Method 1 : Recursion and Memoization
Wherever we find a valid palindromic prefix/suffix- we can place a partition at that point that way we know the answer of the left recursive call already and the problem reduces to the right recursive call
We can easily check if for a given string [s,e] is a palindrome again using gap method (tabulation)
Method-2 Tabulation/ Bottom Up DP
Dp[i] stores the number of cuts required in string [0,i] if we do via finding palindrome suffix
When we find a palindromic suffix we know the answer to the right recursive call (ZERO as its a palindromic suffix), and left recursive calls answer is precomputed in the tabulation
Partition Array for Maximum Sum
Method 1: Recursion and Memoization
We start with writing a simple recursive solution - handling for a given index , assuming we have the solution for subsequent index, we can then proceed to memorize the calls too
Method 2: Tabulation
Let dp[i] be the maximum sum obtained by partitioning the array of size i
We will use the dp approach very similar to palindrome partitioning problem, as we know the solution to suffix problem and we have the prefixes already pre-computed
We formulate the problem in such a way that its subproblem solution is already stored with us
Like above we have solved with suffix , we can solve with prefix too
More problems to be added soon ....