【算法】动态规划 - 最长不下降子序列(LIS)

最长不下降子序列(Longest Increasing Sequence):在一个数字序列中,找到一个最长的子序列(可以不连续),使得这样的子序列是不下降(即非递减)的。

例如序列 a = {1, 2, 3, -1, -2, 7, 9},它的最长不下降子序列是 {1, 2, 3, 7, 9} 长度为5,{1, 2, 3} 和 {-2, 7, 9} 也是非递减序列但不是最长的。

动态规划求解:dp[i] 表示以 a[i] 结尾的最长不下降子序列的长度。于是对 a[i] 来说就有两种可能:

  1. 在 a[i] 前存在一个数 a[j](即 j < i ),使得 a[j] <= a[i],那说明 a[i] 可以跟在 a[j] 的后面形成一个新的不降序列,此时,dp[i] 是否要更新则取决于 dp[j] + 1 是否大于 dp[i]。(dp[j] + 1 表示以 a[j] 结尾的LIS加上 a[i] 这个元素后的长度)
  2. 在 a[i] 前的所有元素都比它大,那么以 a[i] 只能自己形成一个LIS,所以 dp[i] = 1

所以,以 a[i] 结尾的最长不下降子序列的长度就是上面两种结果中较大的那个。状态转移方式:
d p [ i ] = m a x ( 1 ,   d p [ j ] + 1 ) ( j = 1 , 2 , ⋯   , i − 1   a n d   a [ j ] < a [ i ] ) dp[i] = max \left ( 1, \ dp[j] + 1 \right ) \left ( j = 1,2,\cdots,i-1\ and\ a[j]< a[i] \right ) dp[i]=max(1, dp[j]+1)(j=1,2,,i1 and a[j]<a[i])

写出代码:

#include 
#include 
using namespace std;
const int N = 110;
int main() {
    int n, a[N], dp[N], ans = -1;
    cin >> n;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    for (int i = 0; i < n; i++) {
        dp[i] = 1;	// 初始值为1,即以自己为一个序列
        for (int j = 0; j < i; j++) {
            if (a[i] >= a[j] && dp[j] + 1 > dp[i]) {
                dp[i] = dp[j] + 1;
            }
        }
        ans = max(ans, dp[i]);
    }
    cout << ans;
    return 0;
}

输入:

8   
1 2 3 -9 3 9 0 11
1 2 3 3 9 11

输出:

6	// 1 2 3 3 9 11

这里主要问题是得到LIS的长度,但是要做一个好的程序员如果都不想把这个序列输出来,那是真是叫人悲伤。

解决方案:因为可能不止一个序列满足最长,因此用邻接表的方式,声明vector pre[N]把所有符合条件的序列用前驱指针的方式存起来。vector dest存储可以形成LIS的终点结点的索引。

之后只需要用 DFS,遍历一下 pre 数组就行了:

void dfs(int u) {
    path.push_back(u);
    if (pre[u].size() == 0) {
        for (int i = path.size() - 1; i >= 0; i--) {
            printf("%d", a[path[i]]);
            if (i != 0) printf(" ");
        }
        printf("\n");
        path.pop_back();    // 1
        return;             // 2 这两句不加也可, 为了体现其已走到尽头, 则做return
    }
    for (auto it : pre[u]) dfs(it);
    path.pop_back();  // u的所有孩子都访问完了, 把u从路径数组中移除
}

完整实现:

#include 
#include 
using namespace std;
const int N = 110;
int n, a[N], dp[N], maxlen = -1, ansIndex = 0;
vector<int> pre[N], path, ans;
void dfs(int u) {
    path.push_back(u);
    if (pre[u].size() == 0) {
        for (int i = path.size() - 1; i >= 0; i--) {
            printf("%d", a[path[i]]);
            if (i != 0) printf(" ");
        }
        printf("\n");
        path.pop_back();    // 1
        return;             // 2 这两句不加也可, 为了体现其已走到尽头, 则做return
    }
    for (auto it : pre[u]) dfs(it);
    path.pop_back();  // u的所有孩子都访问完了, 把u从路径数组中移除
}
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (a[i] >= a[j]) {
                if (dp[j] + 1 > dp[i]) {  // 比原来的LIS更长, 则i的前驱清空, 将j添加进去
                    dp[i] = dp[j] + 1;
                    pre[i].clear();
                    pre[i].push_back(j);
                } else if (dp[j] + 1 == dp[i]) {  // 一样长,则j也可作为一种选择, 把j添加进去
                    pre[i].push_back(j);
                }
            }
        }
        if (dp[i] > maxlen) {
            maxlen = dp[i];
            ans.clear();
            ans.push_back(i);
        } else if (dp[i] == maxlen) {
            ans.push_back(i);
        }
    }
    for (auto it : ans) dfs(it);
    return 0;
}

输入:

8   
1 2 3 -9 3 9 0 11

输出:(这里只有一个子串满足LIS)

1 2 3 3 9 11

你可能感兴趣的:(笔记,动态规划,算法,数据结构,c语言)