动态规划:最长上升子序列

附上Leetcode链接
https://leetcode-cn.com/problems/longest-increasing-subsequence/

\(O(n^2)\)的算法

思路:使用dp[i]表示结尾字符为a[i],首字符为a[0]的字符串的最长子序列长度。使用简单遍历的方法求解,对于

每一个结尾字符为a[i]的序列,寻找所有a[k] (\(k<i\)),如果a[i]大于a[k],则dp[i] = max{dp[k] + 1, dp[i]},不断的

更新dp[i]的值,直到找到一个最大值。

最优子结构特性: 如果dp[i]是结尾字符为a[i]的序列的最长单调递增子序列的长度,则dp[k] (\(k<i\))一定是结尾字

符为a[i]的序列的最长单调递增子序列的长度。

反证法:如果dp[k]不是最长单调递增子序列的长度,那么有dp[k]'>dp[k],则后面未更新的dp[i]也不是结尾字符

为a[i]的序列的最长单调子序列的长度,会更长

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
int dp[2505];
int a[2505];
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    for (int i = 0; i < n; i++)
    {
        dp[i] = 1;
    }
    for (int i = 0; i < n; i++)
    {
        if (i == 0)
            dp[i] = 1;
        else
        {
            for (int j = 0; j < i; j++)
            {
                if (a[j] < a[i])
                    dp[i] = max(dp[j] + 1, dp[i]);
            }
        }
    }
    int maxx = 0;
    for (int i = 0; i < n; i++)
        if (maxx < dp[i])
            maxx = dp[i];
    cout << maxx << endl;
    return 0;
}

\(O(nlogn)\)的算法

分析上述算法可以发现,在搜索\(max(dp[k] + 1, dp[i])\) 时使用的是\(O(n)\)方式的简单遍历查找。可以从这里入手进

行优化。

进一步分析可以发现,对于长度为 \(i\) 前缀子序列的 \(dp[i]\) 要使得 \(dp[j] = dp[i]+1 (i<j)\)\(a[j]\) 一定要大于长度

\(i\) 前缀子序列的最长上升子序列的最小结尾字符 \(a[k]\)

因此我们可以使用 \(b[k]\) 来表示长度为k的最长上升子序列的最小结尾字符,对于每个 \(a[i]\) ,如果 \(a[i]>b[k]\) ,则

\(b[++k] = a[i]\) ,如果\(a[i] < b[k]\) ,就更新相应的 \(b[j]\)的值,其中\(b[j-1]<=a[i]<b[j]\) ,这样就可以保证

\(b[k]\) 的值一定是最小值。

如何更新相应的\(b[j]\)值?

使用二分查找来寻找 \(j\)

代码如下:

#include <iostream>
#include <algorithm>
using namespace std;
int dp[2505];
int a[2505];
int b[2505];
int Bin(int i, int k); //寻找b[1:k]中 b[j-1]<=a[i]<b[j]的值
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    b[0] = a[0];
    int k = 0;
    for (int i = 0; i < n; i++)
    {
        if (a[i] > b[k] || k == 0 && i == 0)
            b[++k] = a[i]; //更新最大长度
        else
        {
            int j = Bin(i, k);
            b[j] = a[i]; //更新最小值
        }
    }
    cout << k << endl;
    return 0;
}

int Bin(int i, int k) //寻找b[1:k]中 b[j-1]<=a[i]<b[j]的值
{
    if (a[i] < b[1])
        return 1; //防止找不到
    int p = 1;
    int q = k;
    while (p < q)
    {
        int mid = (p + q) / 2;
        if (b[mid] >= a[i])
            q = mid;
        else
            p = mid + 1;
    }
    return q;
}

posted @ 2021-03-26 09:20  鲜衣  阅读(64)  评论(0编辑  收藏  举报