最长上升子序列(LIS) nlogn解法
文章目錄
- 經典DP解法O(n^2)
- dp+二分法(O(nlogn))
最長上升子序列LIS:Longest increasing subsequence
題目鏈接:Leetcode300. 最長遞增子序列
經典DP解法O(n^2)
數據范圍:1000.
時間復雜度O(n2^22)
空間復雜度O(n)
分析
轉移方程
j= 0,…,i-1
if(nums[j]<nums[i])
dp[i]=max(dp[i],dp[j]+1);
ac代碼
class Solution { public:int lengthOfLIS(vector<int>& nums) {int n=nums.size();vector<int> dp(n,1);//賦值為1int ans=0;for(int i=0;i<n;i++){for(int j=0;j<i;j++)if(nums[j]<nums[i])dp[i]=max(dp[i],dp[j]+1);ans=max(dp[i],ans);}return ans;} };下面是進行優化
dp+二分法(O(nlogn))
題目Acwing896. 最長上升子序列 II
數據范圍10510^5105,需要nlogn的算法。
給定一個長度為N的數列,求數值嚴格單調遞增的子序列的長度最長是多少。
輸入格式
第一行包含整數N。
第二行包含N個整數,表示完整序列。
輸出格式
輸出一個整數,表示最大長度。
數據范圍
1≤N≤100000,
?109≤數列中的數≤109
輸入樣例:
7
3 1 2 1 8 5 6
輸出樣例:
4
時間復雜度O(nlogn)
空間復雜度O(n)
分析
維護一個數組vec,vec[i]表示 長度為 i+1的LIS的 最小的終點
長度相同的LIS只存終點的最小值。
隨著LIS長度的增加,其結尾的值(最小的那個)一定是單調遞增的,比如:長度為4的LIS的終點值< 長度為5的LIS的終點值。
該策略是讓小元素盡可能更新在前面,替換掉前面的大元素,這是因為 前面的數越小,LIS的長度才可能越長。
舉例(1,5,2,3,4,6)
更新vec如下
第一個1來,vec[0]=1 此時 vec={1}
第二個5來,因為5> 1,vec 拓展,vec[1]=5,此時 vec[ ] ={1,5}
第三個2 來,而2<5, 替換之,vec[1]=2;此時 vec[ ]={1,2}
第四個3來,因為3>2,vec拓展,vec[2]=3;此時 vec[ ]={1,2,3}
第五個4來,因為4>3,vec拓展,vec[3]=4;此時 vec[ ]={1,2,3,4}
第六個6來,因為6>4,vec拓展,vec[4]=6;此時 vec[ ] ={1,2,3,4,6}
最后返回vec數組的長度 LIS=5
lower_bound 函數,查找nums[i] ,返回大于等于nums[i]的第一個位置
lower_bound()函數返回的是一個迭代器(可以簡單地理解成地址),我們需要減去初始迭代器vec.begin()(初始地址),得到的才是下標。
即公式:地址-地址=下標
int p=lower_bound(vec.begin(),vec.end(),nums[i])-vec.begin();//二分查找,
比如vec[ ]= {1,5} 現在新來元素為2,上述lower_bound代碼返回下標p=1,這里就是≥2的第一個位置,即5所在的位置,這時候我們怎么處理呢?沒錯,就是替換掉5,使用語句vec[p]= 2, 我們維護的偽LIS數組就變成了vec[ ]= {1,2} ,LIS長度沒變,只是在保證單調性條件下,使該長度的上升子序列結尾的元素盡可能的小。
class Solution { public:int lengthOfLIS(vector<int>& nums) {int n=nums.size();vector<int> vec;for(int i=0;i<n;i++){int p=lower_bound(vec.begin(),vec.end(),nums[i])-vec.begin();//二分查找,返回大于等于nums[i]的第一個位置if(p==vec.size())//添加進來vec.push_back(nums[i]);elsevec[p]=nums[i];//替換掉}return vec.size();//返回所維護數組的大小} };如果是最長不降子序列
則采用upper_bound()函數,返回第一個大于x的位置這樣的話,遇到相等的元素,則一直會添加到數組中來
比如 (000011111)這樣的二進制序列
對應下面的題目
題目如下
Leetcode 926 將字符串翻轉到單調遞增
如果一個由 ‘0’ 和 ‘1’ 組成的字符串,是以一些 ‘0’(可能沒有 ‘0’)后面跟著一些 ‘1’(也可能沒有 ‘1’)的形式組成的,那么該字符串是單調遞增的。
我們給出一個由字符 ‘0’ 和 ‘1’ 組成的字符串 S,我們可以將任何 ‘0’ 翻轉為 ‘1’ 或者將 ‘1’ 翻轉為 ‘0’。
返回使 S 單調遞增的最小翻轉次數。
示例 1:
輸入:“00110”
輸出:1
解釋:我們翻轉最后一位得到 00111.
示例 2:
輸入:“010110”
輸出:2
解釋:我們翻轉得到 011111,或者是 000111。
示例 3:
輸入:“00011000”
輸出:2
解釋:我們翻轉得到 00000000。
提示:
1 <= S.length <= 20000
S 中只包含字符 ‘0’ 和 ‘1’
時間復雜度O(nlogn)
空間復雜度O(n)
當然這題最快的是O(n)的算法
ac代碼
class Solution { public:int minFlipsMonoIncr(string S) {int len=S.size();vector<char> vec;for(int i=0;i<len;i++){int p = upper_bound(vec.begin(),vec.end(),S[i])-vec.begin();if(vec.size()==p) vec.push_back(S[i]);elsevec[p]=S[i];}return len-vec.size();}};后面再補充另一種思維
題目Acwing896. 最長上升子序列 II該題目下的ac代碼1
/*長度相同的LIS只存終點的最小值。隨著LIS長度的增加,其結尾的值一定是單調遞增的比如:長度為4的LIS的終點值< 長度為5的LIS的終點值*//*二分出來小于某個數的最大的數 */#include<bits/stdc++.h> using namespace std;const int N=100010;int a[N]; //原序列 int q[N]; //存不同長度的上升子序列結尾的最小值。 int n; int main(){cin>>n;for(int i=0;i<n;i++) cin>>a[i];int len=0;//存的上升子序列的最大長度q[0]=-2e9;//哨兵//二分:[0,len]中小于某個數的最大的數for(int i=0;i<n;i++){int l=0,r = len;while(l<r){int mid = l+r+1 >>1;if(q[mid]<a[i]) l = mid ;else r = mid -1;}len =max(len , r+1);q[r+1]=a[i]; //后面添上一個} cout<<len<<endl;}acwing這道題ac代碼2
#include<bits/stdc++.h> using namespace std;const int N=100010; int a[N];int LIS(int a[],int n){vector<int> vec;for(int i=0;i<n;i++){int p= lower_bound(vec.begin(),vec.end(),a[i])-vec.begin(); //找到第一個大于等于a[i]的位置if(p==vec.size()) vec.push_back(a[i]); //如果長度相等,并且新來的元素大于末尾,就替換掉else vec[p]=a[i]; }return vec.size();}int main(){int n;cin>>n;for(int i=0;i<n;i++) cin>>a[i];cout<<LIS(a,n)<<endl;}總結
以上是生活随笔為你收集整理的最长上升子序列(LIS) nlogn解法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: past怎么读,与to有什么区别?
- 下一篇: 并查集简单使用