题目描述: 给定一个无序数组,找出最大的单峰序列 (就是先升高后降低的序列) 的值,再输出原数组长度除去单峰序列的长度所剩下的元素数量。
输入格式: n num1 num2…numn。
输出格式: m (原数组长度除去单峰序列的长度所剩下的元素数量)
输入样例1: 8 1 2 3 4 5 3 2 1
输出样例1: 0
输入样例2: 8 5 2 3 8 3 5 1 2
输出样例2: 3
**思路:**首先从前往后找出最大的上升序列,然后再从后往前找出最大的上升序列,再将两者对应相加,用原数组长度减去最大值即可。
具体算法:求上身序列很好解呀,用动态规划嘛,这里会用到两次动态规划。
dp[i]数组的含义:如果选定i位置的数字那么它构成的最大上升序列的长度是多少.
dp_l[i]数组的含义:整体与dp类似,不过是从后往前(相当于从前往后,逆序最大下降序列)。
转移方程: 假设为i位置为数字所在原位置和j位置为dp数组位置。
nums[i]<=nums[j]:dp[i] = dp[i];
nums[i]>nums[j]:dp[i] = max(dp[i],dp[j]+1);
(dp_l与dp类似)
初始化: dp数组由于每个位置至少长度为其自身所以设为1,dp_l按理说也是一样的,可是这里求的是最大单峰序列,是上升序列和下降序列的和,避免将峰 重复相加,所以设为0.
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] hh = Arrays.stream(sc.nextLine()
.split(" ")).mapToInt(Integer::parseInt)
.toArray();
int[] dp = new int[hh[0]+1];
Arrays.fill(dp,1);
for(int i = 2;i<=hh[0];i++){
for (int j = i - 1; j >= 1; j--) {
if(hh[j]<hh[i]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
int[] dp_l = new int[hh[0]+1];
for(int i = hh[0]-1;i>=1;i--){
for (int j = i + 1; j <= hh[0]; j++) {
if(hh[j]<hh[i]){
dp_l[i] = Math.max(dp_l[i],dp_l[j]+1);
}
}
}
int max = -1;
for(int i =1;i<hh[0]+1;i++){
max = Math.max(max,dp_l[i]+dp[i]);
}
System.out.println(hh[0]-max);
}
}
这个时间复杂度为O(n2)(我答题的时候写的这个,过了),还可以优化成O(nlogn)
看到logn首先就想到二分法嘛。
首先将这道题当成是求上升序列的题。
实际上这里用了贪心+二分,设想同样是n的长度,末尾的数字越小是不是后面能接上的数字越多的可能性越多 (贪心) ,那么如果在我们判断第i个位置的数组能接上的最长长度,是不是可以直接在前面1~i-1长度的情况下,找出小于自己的最长长度即可,由于这个数组必然是有序的所以可以二分查找。
然后迁移到这道题上,只需要再此基础上再维护个功能与O(n2) 类似的dp数组即可。
tail[i]数组: 长度为i时末尾最小的值
转移方程: 设tail已经更新过的长度为end,i为数字所在
if(num[i]>tail[end]) tail[end] = min(num[i],tail[end])
else 找到小于num[i]的最长长度(假设为k), tail[k] = min(num[i],tail[k])
初始化,tail[end] = num[0];end=0;
算下降序列时类似。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] hh = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
if(hh[0]<=1){
System.out.println(0);
return ;
}
int[] dp = new int[hh[0]+1];
int[] tail = new int[hh[0]];
int end = 0;
tail[0] = hh[1];
for(int i = 2;i<=hh[0];i++){
if(hh[i]>tail[end]){
tail[++end] = hh[i];
dp[i] = end+1;
}else {
int left = 0,right = end;
while(left<right){
int mid = (left+right)>>1;
if(hh[i]>tail[mid]){
left = mid+1;
}else {
right = mid;
}
}
tail[left]=Math.min(hh[i],tail[left]);
dp[i] = left+1;
}
}
int[] dp_l = new int[hh[0]+1];
end = 0;
Arrays.fill(tail,0);
tail[0] = hh[hh[0]];
for(int i = hh[0]-1;i>=1;i--){
if(hh[i]>tail[end]){
tail[++end] = hh[i];
dp_l[i] = end+1;
}else {
int left = 0,right = end;
while(left<right){
int mid = (left+right)>>1;
if(hh[i]>tail[mid]){
left = mid+1;
}else {
right = mid;
}
}
tail[left]=Math.min(hh[i],tail[left]);
dp_l[i] = left+1;
}
}
int max = -1;
for(int i =1;i<hh[0]+1;i++){
max = Math.max(max,dp_l[i]+dp[i]);
}
System.out.println(hh[0]-max+1);
//注意这里由于初始化逻辑不一样,多加了一个峰,需要减去
}
}