Patience Sorting is a powerful technique that can transfrom your solutions to Longest Increasing Subsequence type Dynamic Programming problems from O(n^2) to O(nlogn) complexity. It will allow you to solve hard DP problems.
The agenda of this post

The goal of this game is to form as few piles as possible following the below rules,
In the above example diagram we have cards valued [3, 7, 5, 6, 4, 2, 10, 9, 8],
Steps
3 begins a new pile, lets call it Pile-1.7 is greater than the topmost card of the Pile-1, so 7 begins a new pile. Lets call it Pile-2.5, can be placed on top of the Pile-2 as 5<7.6, cannot be placed on top any of the previous piles, so it begins a new pile Pile-3.4, can be placed in top of Pile-2 as 4<5.2 can be placed on top of Pile-1 as 2<3. ( Note that 2 could have been placed on top of Pile-1, Pile-2 or Pile-3, but we should choose the left most pile that fits. Which is Pile-1 in this case )10 forms a new pile Pile-4.9 can be placed on top of Pile-4.8 can be placed on top of Pile-4.In this case, the length of the longest increasing subsequence is 4.
In this case, card 8 has a pointer to the top of previous pile, card 6. Card 6 has a pointer to the top of previous pile, card 5 ( The top of Pile-2 when card 6 was inserted ). Card 5 has a poinnter to the top of previous pile, card 3 ( The top of Pile-1 when card 5 was inserted )
So [3, 5, 6, 8] is one of the longest increasing subsequences.
This is a greedy algorithm and uses binary search. You will use binary search to find the left-most pile that can accomodate a card.
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
// seq stores the piles
vector<int> seq;
// create the first pile
seq.push_back(nums[0]);
// Go through each card
for(int j=1; j<n; j++){
// Find the left-most pile that can accomodate this card
int idx = binSearch(seq, nums[j]);
if(idx == -1){
// If no such pile exists, then create a new pile
seq.push_back(nums[j]);
}else{
// If a pile is found that can accomodate this card,
// then place this card on top of that pile
seq[idx] = nums[j];
}
}
// The number of piles is the length of the longest increasing subsequence
return seq.size();
}
// Binary search to find the left-most pile that can accomodate a card
// If a pile is found, then it returns the index to that pile
// If a pile is not found, then return -1
int binSearch(vector<int> &seq, int i){
int l=0, h = seq.size()-1, m;
int res = -1;
while(l<=h){
m = l+(h-l)/2;
if(seq[m] >= i){
res = m;
h = m-1;
}else{
l = m+1;
}
}
return res;
}
};LIS steps
Binary Search
1671. Minimum Number of Removals to Make Mountain Array
https://leetcode.com/problems/minimum-number-of-removals-to-make-mountain-array/
Hard Problem
class Solution {
public:
int minimumMountainRemovals(vector<int>& nums) {
int n = nums.size();
// Keeps track of the LIS for each element in nums
vector<int> is(n);
// Stores the piles
vector<int> cont;
// Keeps track of the Longest Decreasing Subsequence from each element in nums
vector<int> ds(n);
// Calculate the increasing sequence
// Create the first pile
cont.push_back(nums[0]);
// The length of LIS for first element is 1
is[0] = 1;
// Go through each card
for(int i=1; i<n; i++){
// Find the leftmost pile that can accomodate this card
auto it = lower_bound(cont.begin(), cont.end(), nums[i]);
if(it == cont.end()){
// If such a pile does not exist
// then create a new pile
cont.push_back(nums[i]);
is[i] = cont.size()-1;
}else{
// If such a pile exists
// Then put is card on top of that pile
*it = nums[i];
is[i] = distance(cont.begin(), it);
}
}
// Calculate the decreasing sequence
// The same steps as above but for the decreasing sequence
cont.clear();
cont.push_back(nums[n-1]);
ds[n-1] = 1;
for(int i=n-2; i>=0; i--){
auto it = lower_bound(cont.begin(), cont.end(), nums[i]);
if(it == cont.end()){
cont.push_back(nums[i]);
ds[i] = cont.size()-1;
}else{
*it = nums[i];
ds[i] = distance(cont.begin(), it);
}
}
// Find the longest mountain array
int ans = INT_MAX;
for(int i=1; i<n-1; i++){
if(is[i] && ds[i])
ans = min(ans, n-(is[i]+ds[i]+1));
}
return ans;
}
};Read though the comments in the code for understanding the implementation details
Russian Doll Envelopes
https://leetcode.com/problems/russian-doll-envelopes/
Hard Problem
class Solution {
public:
int maxEnvelopes(vector<vector<int>>& envelopes) {
// container to store the piles
vector<int> seq;
// sort the envelopes
// If two envelopes have the same width, then the envelope with the largest height is placed before
// This is because we will apply the patience sort algorithm of the heights
sort(envelopes.begin(), envelopes.end(), [](vector<int> a, vector<int> b){
return ((a[0] < b[0]) || (a[0] == b[0] && a[1] > b[1]));
});
// For each envelope
for(vector<int> e: envelopes){
// Find the left most pile that can accomodate the envelope
int idx = binSearch(seq, e[1]);
if(idx == -1){
// If no such pile is found, then create a new pile
seq.push_back(e[1]);
}else{
// If such a pile is found then
// Make this envelope the top of the pile
seq[idx] = e[1];
}
}
// The number of piles is the length of the LIS
return seq.size();
}
// Binary search to find the left most pile that can accomodate a envelope
// If such a pile exists, then return the index of the pile
// Else return -1
int binSearch(vector<int> &seq, int b){
int low = 0, high = seq.size()-1, mid, res = -1;
while(low <= high){
mid = low + (high-low)/2;
if(seq[mid] >= b){
res = mid;
high = mid-1;
}else{
low = mid+1;
}
}
return res;
}
};Read though the comments in the code for understanding the implementation details
If you liked the post, please don't forget to upvote.