I wrote a recursive solution for the longest increasing subsequence and it worked perfectly fine. But when I applied dp on the same code it gives different answers. Problem Link: https://practice.geeksforgeeks.org/problems/longest-increasing-subsequence-1587115620/1 Recursive code:
int LISrecursive(int arr[], int n, int currIndex, int maxVal) {
if (currIndex == n) {
return 0;
}
int included = 0, notIncluded = 0;
if (arr[currIndex] > maxVal) {
included = 1 LISrecursive(arr, n, currIndex 1, arr[currIndex]);
}
notIncluded = LISrecursive(arr, n, currIndex 1, maxVal);
return max(notIncluded, included);
}
DP Code:
int LISdp(int arr[], int n, int currIndex, int maxVal, vector<int> &dp) {
if (currIndex == n) {
return 0;
}
if (dp[currIndex] != -1) return dp[currIndex];
int included = 0, notIncluded = 0;
if (arr[currIndex] > maxVal) {
included = 1 LISdp(arr, n, currIndex 1, arr[currIndex], dp);
}
notIncluded = LISdp(arr, n, currIndex 1, maxVal, dp);
return dp[currIndex] = max(notIncluded, included);
}
int32_t main() {
int n;
cin >> n;
int arr[n];
vector<int> dp(n, -1);
for (int i = 0; i < n; i ) {
cin >> arr[i];
}
cout << LISrecursive(arr,n,0,-1);
cout << LISdp(arr, n, 0 , -1, dp);
return 0;
}
I cannot figure out what I did wrong? For this test case 6 (n) 6 3 7 4 6 9 (arr[]) Recursive code gives 4 answer(correct) But DP code gives 3 answer(incorrect)
CodePudding user response:
When I think of dynamic programming, I usually break it down into two steps:
Solve the recursion with "including the current element before recursing again" compared to "not including the current element before recursing again". This is exactly what you did with your recursive solution.
Take the recursive solution from step 1 and add a cache of previous computed results to avoid repetitive recursion. The cache, can be conceptualized as a multidimension matrix that maps all the non-const variable parameters passed to the recursive function to the final result.
In your case, each recursive step has two variables, currIndex
, and maxVal
. a
and n
are actually constants throughout the entire recursion. The number of non-const parameters of the recursive step is the number of dimensions in your cache. So you need a two dimensional table. We could use a big 2-d int array, but that would take a lot of memory. We can achieve the same efficiency with a nested pair of hash tables.
Your primary mistake is that your cache is only 1 dimension - caching the result compared to currIndex
irrespective of the value of maxVal
. The other mistake is using a vector instead of a hash table. The vector technique you have works, but doesn't scale. And when we add a second dimension, the scale in terms of memory use are even worse.
So let's defined a cache type as an unordered_map (hash table) that maps currIndex
to another hash table that maps maxVal
to the result of the recursion. You could also use tuples, but the geeksforgeeks coding site doesn't seem to like that. No bother, we can just define this:
typedef std::unordered_map<int, std::unordered_map<int, int>> CACHE;
Then your DP solution is effectively just inserting a lookup into the CACHE at the top of the recursive function and an insertion into the CACHE at the bottom of the function.
int LISdp(int arr[], int n, int currIndex, int maxVal, CACHE& cache) {
if (currIndex == n) {
return 0;
}
// check our cache if we've already solved for currIndex and maxVal together
auto itor1 = cache.find(currIndex);
if (itor1 != cache.end())
{
// itor1->second is a reference to cache[currIndex]
auto itor2 = itor1->second.find(maxVal);
if (itor2 != itor1->second.end())
{
// itor2->second is a reference to cache[n][maxVal];
return itor2->second;
}
}
int included = 0, notIncluded = 0;
if (arr[currIndex] > maxVal) {
included = 1 LISdp(arr, n, currIndex 1, arr[currIndex], cache);
}
notIncluded = LISdp(arr, n, currIndex 1, maxVal, cache);
// cache the final result into the 2-d map before returning
int finalresult = std::max(notIncluded, included);
cache[currIndex][maxVal] = finalresult; // cache the result
return finalresult;
}
Then the initial invocation with the input set to solve for is effectively passing INT_MIN as the intial maxVal
and an empty cache:
int N = 16
int A[N]={0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15};
CACHE cache;
int result = LISdp(A, N, 0, INT_MIN, cache);
A minor optimization is to make a
, n
, and cache
a member variable of the C class encapsulating your solution so that they don't have to be pushed onto the stack for each step of the recursion. The cache is getting passed by reference, so it's not that big of a deal.
CodePudding user response:
You have 2 problems in your code:
Mistake 1
First in C , the size of an array must be a compile-time constant.So, take for example the following code snippets:
int n = 10;
int arr[n]; //INCORRECT because n is not a constant expression
The correct way to write the above would be:
const int n = 10;
int arr[n]; //CORRECT
Similarly, the following(which you did in your code example) is incorrect:
int n;
cin >> n;
int arr[n];// INCORRECT because n is not a constant expression
Mistake 2
Second in your function LISdp
, it seems to me that there is no need of the statement
if (dp[currIndex] != -1) return dp[currIndex];//no need for this statement
You should just remove this(the above) statement and the program produces expected output 4
as can be seen here. Basically you have not thought this(LISdp's working) through. You can use the debugger to see where you're going wrong.
There might be other problems in your code but so far i am able to spot these 2.