• 华为OJ-合唱队-最长递增子序列-动态规划


    题目描述

    计算最少出列多少位同学,使得剩下的同学排成合唱队形

    说明

    N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1<=i<=K)使得T1<T2<......<Ti-1Ti+1>......>TK。你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

    输入描述:

    整数N

    输出描述:

    最少需要几位同学出列

    示例1

    输入

    8
    186 186 150 200 160 130 197 200

    输出

    4


    求解思路

    • 属于最长递增子序列,可采用动态规划方法;
    • 第一次从左至右计算dp_pos ,第二次从右至左计算dp_neg,将下标i处的dp值相加得到numnum-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 最长递增子序列一文中的解释更加的通俗易懂,可作为参照。

  • 相关阅读:
    CPT104-labs
    Java-数据结构-ArrayList
    INT104-lab13[Parzen Window Method][此方法无数据集划分]
    INT104-lab12 [KNN Algorithm][lambda表达式]
    INT104-lab11 [聚类] [iris数据集] [K-means Algorithm]
    Nginx配置https兼容http
    JS获取整个网页html代码
    nginx重启生效conf文件的修改
    WampServer
    在win10系统中,开启hyper-v要满足下列条件
  • 原文地址:https://www.cnblogs.com/mengmz/p/7245797.html
Copyright © 2020-2023  润新知