华为OJ-合唱队-最长递增子序列-动态规划
2017-07-27 16:54 mengmz 阅读(598) 评论(0) 编辑 收藏 举报题目描述
计算最少出列多少位同学,使得剩下的同学排成合唱队形
说明
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1<=i<=K)使得T1<T2<......<Ti-1
输入描述:
整数N
输出描述:
最少需要几位同学出列
示例1
输入
8
186 186 150 200 160 130 197 200
输出
4
求解思路
- 属于最长递增子序列,可采用动态规划方法;
- 第一次从左至右计算
dp_pos
,第二次从右至左计算dp_neg
,将下标i
处的dp
值相加得到num
,num-1
即为以i
为中心的合唱团队伍人数,故而需要减去的人数为N - num + 1
;
原始数组 | 186 | 186 | 150 | 200 | 160 | 130 | 197 | 200 |
---|---|---|---|---|---|---|---|---|
递增序列 | 1 | 1 | 1 | 2 | 2 | 1 | 3 | 4 |
递减序列 | 3 | 3 | 2 | 3 | 2 | 1 | 1 | 1 |
本人测试的代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// O(N^2)
void get_dp1(vector<int> &array, vector<int> &dp) {
for (int i = 0; i < dp.size(); ++i) {
dp[i] = 1;
for (int j = 0; j < i; ++j) {
if (array[i] > array[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
}
int main() {
ios::sync_with_stdio(false);
int N;
cin >> N;
vector<int> height(N, 0);
for (int i = 0; i < N; ++i) {
cin >> height[i];
}
// 正向数组的最长递增子序列
vector<int> dp_pos(N);
get_dp1(height, dp_pos);
// 逆向数组的最长递增子序列--正向数组的最长递减子序列
reverse(height.begin(), height.end());
vector<int> dp_neg(N);
get_dp1(height, dp_neg);
for (int i = 0; i < N; ++i) {
dp_pos[i] += dp_neg[N - 1 - i];
}
auto pos = max_element(dp_pos.begin(), dp_pos.end());
cout << N - *pos + 1 << endl;
return 0;
}
最长递增子序列的\(O(logN)\)解法
- 利用二分查找来加快
dp
的计算 - 代码如下:
//O(NlogN)
void get_dp2(vector<int> &array, vector<int> &dp) {
vector<int> ends(dp.size());
ends[0] = array[0];
dp[0] = 1;
int right = 0;
int l = 0;
int r = 0;
int m = 0;
for(int i = 1; i< array.size(); ++ i){
l = 0;
r = right;
// binary search
while( l <= r){
m = (l+r)/2;
if(array[i] > ends[m])
l = m + 1;
else
r = m - 1;
}
right = max(right, l);
ends[l] = array[i];
dp[i] = l + 1;
}
}
解析
end
数组用来储存递增子序列LIS的末尾元素的最小值,如果有ends[b]==c
,则表示遍历到目前为止,在所有长度为b+1
的递增序列中(数组下标起始为0),最小的结尾数是c
。
right + 1
记录了当前LIS的长度。在while
循环中,采用二分查找方法找到元素array[i]
在LIS中的位置。有三种情况:
array[i]
比ends
(LIS)中的任意元素都要小,此时l = 0, r = -1
,此时LIS的长度不变,该字符处的dp[i] = 1
;array[i]
比ends
(LIS)中的任意元素都要大,此时l = right + 1, r = right
,此时LIS的长度增加1,即right = l
,更新ends,该字符处的dp[i] = l + 1
;- 普通情况,
r < m == l
, 此时LIS的长度不发生变化,但要更新right
位置的值,因为此时array[i] <= ends[m]
, 按照ends
的定义,它该记录LIS的最小尾数值;
这个算法来自于《程序员代码面试指南》左程云著,理解起来存在难度,算法设计 - LCS 最长公共子序列&&最长公共子串 &&LIS 最长递增子序列一文中的解释更加的通俗易懂,可作为参照。