笔试遇到的一道题 求最大单峰序列(动态规划)

题目描述: 给定一个无序数组,找出最大的单峰序列 (就是先升高后降低的序列) 的值,再输出原数组长度除去单峰序列的长度所剩下的元素数量。
输入格式: 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);
        //注意这里由于初始化逻辑不一样,多加了一个峰,需要减去
    }
}

你可能感兴趣的:(算法/数据结构)