Skip to content

Commit 673b187

Browse files
committed
update DP Longest Bitonic Subsequence
1 parent a61c476 commit 673b187

File tree

1 file changed

+167
-4
lines changed

1 file changed

+167
-4
lines changed

✅ Pattern 15: 0-1 Knapsack (Dynamic Programming).md

Lines changed: 167 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ This shows that `Banana + Melon` is the best combination as it gives us the `max
163163
### Basic Brute Force Soultion
164164

165165
A basic <b>brute-force solution</b> could be to try all combinations of the given items (as we did above), allowing us to choose the one with `maximum profit` and a weight that doesn’t exceed `C`. Take the example of four items `A, B, C, and D`, as shown in the diagram below. To try all the combinations, our algorithm will look like:
166-
![](./images/./images/./images/knapsack.png)
166+
![](./images/knapsack.png)
167167

168168
All <b>green boxes</b> have a total weight that is less than or equal to the capacity `7`, and all the <b>red ones</b> have a weight that is more than `7`. The best solution we have is with items `[B, D]` having a total profit of `22` and a total weight of `7`.
169169

@@ -230,7 +230,7 @@ console.log(
230230
### Overlapping Sub-problems
231231

232232
Let’s visually draw the recursive calls to see if there are any overlapping sub-problems. As we can see, in each recursive call, `profits` and `weights` arrays remain constant, and only `capacity` and `currIndex` change. For simplicity, let’s denote capacity with `c` and `currIndex` with `i`:
233-
![](./images/./images/./images/subproblems.png)
233+
![](./images/subproblems.png)
234234
We can clearly see that `c:4, i=3` has been called twice. Hence we have an <b>overlapping sub-problems pattern</b>. We can use <b>[Memoization](https://en.wikipedia.org/wiki/Memoization)</b> to solve <b>overlapping sub-problems</b> efficiently.
235235

236236
### Top-down Dynamic Programming with Memoization
@@ -394,7 +394,7 @@ As we know, the final profit is at the bottom-right corner. Therefore, we will s
394394
As you remember, at every step, we had two options: include an item or skip it. If we skip an item, we take the profit from the remaining items (i.e., from the cell right above it); if we include the item, then we jump to the remaining profit to find more items.
395395

396396
Let’s understand this from the above example:
397-
![](./images/./images/./images/dpselected.png)
397+
![](./images/dpselected.png)
398398

399399
1. `22` did not come from the top cell (which is `17`); hence we must include the item at index `3` (which is item `D`).
400400
2. Subtract the profit of item `D` from `22` to get the remaining profit `6`. We then jump to profit `6` on the same row.
@@ -5994,7 +5994,7 @@ if str[strIndex] == pat[patIndex] {
59945994
dp[strIndex][patIndex] += dp[strIndex-1][patIndex]
59955995
```
59965996
5997-
Here is the code for our </b>bottom-up dynamic programming</b> approach:
5997+
Here is the code for our <b>bottom-up dynamic programming</b> approach:
59985998
```js
59995999
function findSPMCount(str, pattern) {
60006000
//every empty pattern has one match
@@ -6130,6 +6130,169 @@ console.log(
61306130
- The <b>time complexity</b> of the above algorithm is exponential `O(2ⁿ)`, where `n` is the lengths of the input array.
61316131
- The <b>space complexity</b> is `O(n)` which is used to store the <i>recursion stack</i>.
61326132
6133+
### Top-down Dynamic Programming with Memoization
6134+
To overcome the <b>overlapping subproblems</b>, we can use an array to store the already solved <b>subproblems</b>.
6135+
6136+
We need to <b>memoize</b> the <i>recursive functions</i> that calculate the <b>longest decreasing subsequence<b>. The two changing values for our <i>recursive function</i> are the `current` and the `previous` index. Therefore, we can store the results of all <b>subproblems</b> in a two-dimensional array. (Another alternative could be to use a <i>hash-table</i> whose key would be a string (`currIndex` + `|` + `prevIndex`)).
6137+
6138+
Here is the code:
6139+
6140+
````js
6141+
function findLBSLength(nums) {
6142+
const lds = [];
6143+
const ldsReversed = [];
6144+
6145+
function findLDSLength(nums, currIndex, prevIndex) {
6146+
//find the longest decreasing subsequence from
6147+
//currIndex to the end of the array
6148+
if (currIndex === nums.length) return 0;
6149+
6150+
lds[currIndex] = lds[currIndex] || [];
6151+
6152+
if (typeof lds[currIndex][prevIndex + 1] === 'undefined') {
6153+
//include nums[currIndex] if it is smaller than the previous Number
6154+
6155+
let checkWithCurrIndex = 0;
6156+
6157+
if (prevIndex === -1 || nums[currIndex] < nums[prevIndex]) {
6158+
checkWithCurrIndex = 1 + findLDSLength(nums, currIndex + 1, currIndex);
6159+
}
6160+
//excluding the number at currIndex
6161+
let checkExcludingCurrIndex = findLDSLength(
6162+
nums,
6163+
currIndex + 1,
6164+
prevIndex
6165+
);
6166+
6167+
lds[currIndex][prevIndex + 1] = Math.max(
6168+
checkWithCurrIndex,
6169+
checkExcludingCurrIndex
6170+
);
6171+
}
6172+
return lds[currIndex][prevIndex + 1];
6173+
}
6174+
6175+
function findLDSLengthReverse(nums, currIndex, prevIndex) {
6176+
//find the longest decreasing subsequence from
6177+
//currIndex to the beginning of the array
6178+
if (currIndex < 0) return 0;
6179+
6180+
ldsReversed[currIndex] = ldsReversed[currIndex] || [];
6181+
6182+
if (ldsReversed[currIndex][prevIndex + 1] == null) {
6183+
// include nums[currIndex] if it is smaller than the prev number
6184+
let checkWithCurrIndex = 0;
6185+
6186+
if (prevIndex == -1 || nums[currIndex] < nums[prevIndex]) {
6187+
checkWithCurrIndex =
6188+
1 + findLDSLengthReverse(nums, currIndex - 1, currIndex);
6189+
}
6190+
6191+
//excluding the number at currIndex
6192+
let checkExcludingCurrIndex = findLDSLengthReverse(
6193+
nums,
6194+
currIndex - 1,
6195+
prevIndex
6196+
);
6197+
6198+
ldsReversed[currIndex][prevIndex + 1] = Math.max(
6199+
checkWithCurrIndex,
6200+
checkExcludingCurrIndex
6201+
);
6202+
}
6203+
6204+
return ldsReversed[currIndex][prevIndex + 1];
6205+
}
6206+
let maxLength = 0;
6207+
6208+
for (let i = 0; i < nums.length; i++) {
6209+
const checkIndexToEnd = findLDSLength(nums, i, -1);
6210+
const checkIndexToStart = findLDSLengthReverse(nums, i, -1);
6211+
maxLength = Math.max(maxLength, checkIndexToEnd + checkIndexToStart - 1);
6212+
}
6213+
6214+
return maxLength;
6215+
}
6216+
6217+
console.log(
6218+
`Length of Longest Bitonic Subsequence: ---> ${findLBSLength([
6219+
4, 2, 3, 6, 10, 1, 12,
6220+
])}`
6221+
);
6222+
// Output: 5
6223+
// Explanation: The LBS is {2,3,6,10,1}.
6224+
6225+
console.log(
6226+
`Length of Longest Bitonic Subsequence: ---> ${findLBSLength([
6227+
4, 2, 5, 9, 7, 6, 10, 3, 1,
6228+
])}`
6229+
);
6230+
// Output: 7
6231+
// Explanation: The LBS is {4,5,9,7,6,3,1}.
6232+
````
6233+
6234+
### Bottom-up Dynamic Programming
6235+
The above algorithm shows us a clear <b>bottom-up approach</b>. We can separately calculate <b>LDS</b> for every index i.e., from the beginning to the end of the array and vice versa. The required length of <b>LBS</b> would be the one that has the maximum sum of <b>LDS</b> for a given index (from both ends).
6236+
6237+
Here is the code for our <b>bottom-up dynamic programming approach</b>:
6238+
````js
6239+
function findLBSLength(nums) {
6240+
const lds = Array(nums.length).fill(0);
6241+
const ldsReversed = Array(nums.length).fill(0);
6242+
6243+
//find the longest decreasing subsequence from
6244+
//currIndex to the end of the array
6245+
for (let i = 0; i < nums.length; i++) {
6246+
//initially set every element of LDS to a length of 1
6247+
lds[i] = 1;
6248+
6249+
for (let j = i - 1; j >= 0; j--) {
6250+
if (nums[j] < nums[i]) {
6251+
lds[i] = Math.max(lds[i], lds[j] + 1);
6252+
}
6253+
}
6254+
}
6255+
6256+
//find the longest decreasing subsequence from
6257+
//currIndex to the beginning of the array
6258+
for (let i = nums.length - 1; i >= 0; i--) {
6259+
//initially set every element of LDS to a length of 1
6260+
ldsReversed[i] = 1;
6261+
6262+
for (let j = i + 1; j < nums.length; j++) {
6263+
if (nums[j] < nums[i]) {
6264+
ldsReversed[i] = Math.max(ldsReversed[i], ldsReversed[j] + 1);
6265+
}
6266+
}
6267+
}
6268+
6269+
let maxLength = 0;
6270+
6271+
for (let i = 0; i < nums.length; i++) {
6272+
maxLength = Math.max(maxLength, ldsReversed[i] + ldsReversed[i] - 1);
6273+
}
6274+
6275+
return maxLength;
6276+
}
6277+
6278+
console.log(
6279+
`Length of Longest Bitonic Subsequence: ---> ${findLBSLength([
6280+
4, 2, 3, 6, 10, 1, 12,
6281+
])}`
6282+
);
6283+
// Output: 5
6284+
// Explanation: The LBS is {2,3,6,10,1}.
6285+
6286+
console.log(
6287+
`Length of Longest Bitonic Subsequence: ---> ${findLBSLength([
6288+
4, 2, 5, 9, 7, 6, 10, 3, 1,
6289+
])}`
6290+
);
6291+
// Output: 7
6292+
// Explanation: The LBS is {4,5,9,7,6,3,1}.
6293+
````
6294+
- The <b>time omplexity</b> of the above algorithm is `O(n²)` and the <b>space complexity</b> is `O(n)`.
6295+
61336296
## Longest Alternating Subsequence
61346297
61356298
https://www.geeksforgeeks.org/longest-alternating-subsequence/

0 commit comments

Comments
 (0)