動態規划-最長不下降子序列(LIS)


最長不下降子序列(LIS)

最長不下降子序列(Longest Increasing Subsequence)是動態規划中的一個非常經典的問題:

在一個數字序列中,找到一個最長的子序列(可以不連續),使得這個子序列是不下降(非遞減)的

例如 A = {1, 2, 3, -1, -2, 7, 9} ,它的最長不下降子序列是 {1, 2, 3, 7, 9},長度為 5。

對於這個問題,最簡單的辦法就是暴力枚舉每種情況,即對於每個元素有取和不取兩種選擇,然后判斷這個序列是否為不下降序列。如果是不下降序列,就更新最大長度,但是很嚴峻的一個問題這個時候時間復雜度就變成了O(2n)了。

我們介紹一下動態規划的解法,時間復雜度為O(n2):

首先設置一個數組 dp[] ,令 dp[i] 代表以 A[i] 結尾的最長遞增子序列的長度,通過設置這個數組,最長遞增子序列的長度就為 dp 中的最大值。

由於 dp[i] 是以 A[i] 作為結尾的最長不下降子序列的長度,因此只有兩種情況:

  • A[i] 之前所有的元素都比 A[j] 大,那么 A[i] 只好自己形成一條 LIS,但是長度為 1,dp[i] = 1
  • A[i] 之前存在 A[j] <= A[i] ,此時只需要把 A[j] 添加到末尾就能形成一條新的 LIS,如果形成的 LIS 能夠比當前以 A[i] 結尾的 LIS 長度更長,那么就把 A[i] 跟在以 A[j] 結尾的 LIS 后面,形成一條更長的 LIS,dp[i] = dp[j] + 1

綜上,此時的狀態方程的遞推公式為

dp[i] = max(1, dp[j] + 1)

(j = 1, 2, ..., i -1 && A[j] <= A[i])

上面的狀態方程隱含了邊界:dp[i] = 1。顯然 dp[i] 只與小於 i 的 j 有關,因此只要讓 i 從小到大遍歷即可求出整個 dp 數組。

此時,時間復雜度即為O(n2)。

代碼實現如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 100;
int A[N], dp[N], n;	// 序列和, dp數組, 數字總數

int main() {
  scanf("%d", &n);
  for(int i=1; i <= n; i++) {
    scanf("%d", &A[i]);
  }	// 從 1 ~ N 編號
  int ans = -1;	// 記錄最大的 dp[i]
  for(int i = 1; i < n; i++) {
    dp[i] = 1;	// 至少能夠自身成為一條 LIS
    for(int j = 1; j < i; j++){	// 只需要遞歸到小於 i
      // 如果不比之前的小,而且新形成的子序列更大,則接到這個人后面來
      if(A[i] >= A[j] && (dp[j] + 1 > dp[i])) {
        dp[i] = dp[j] + 1;
      }
    }
    ans = max(ans, dp[i]);	// 確定當前結束的最長子序列的長度之后,比較
  }
  printf("%d", ans);
  return 0;
}

希望對大家有幫助。# 最長不下降子序列(LIS)

最長不下降子序列(Longest Increasing Subsequence)是動態規划中的一個非常經典的問題:

在一個數字序列中,找到一個最長的子序列(可以不連續),使得這個子序列是不下降(非遞減)的

例如 A = {1, 2, 3, -1, -2, 7, 9} ,它的最長不下降子序列是 {1, 2, 3, 7, 9},長度為 5。

對於這個問題,最簡單的辦法就是暴力枚舉每種情況,即對於每個元素有取和不取兩種選擇,然后判斷這個序列是否為不下降序列。如果是不下降序列,就更新最大長度,但是很嚴峻的一個問題這個時候時間復雜度就變成了O(2n)了。

我們介紹一下動態規划的解法,時間復雜度為O(n2):

首先設置一個數組 dp[] ,令 dp[i] 代表以 A[i] 結尾的最長遞增子序列的長度,通過設置這個數組,最長遞增子序列的長度就為 dp 中的最大值。

由於 dp[i] 是以 A[i] 作為結尾的最長不下降子序列的長度,因此只有兩種情況:

  • A[i] 之前所有的元素都比 A[j] 大,那么 A[i] 只好自己形成一條 LIS,但是長度為 1,dp[i] = 1
  • A[i] 之前存在 A[j] <= A[i] ,此時只需要把 A[j] 添加到末尾就能形成一條新的 LIS,如果形成的 LIS 能夠比當前以 A[i] 結尾的 LIS 長度更長,那么就把 A[i] 跟在以 A[j] 結尾的 LIS 后面,形成一條更長的 LIS,dp[i] = dp[j] + 1

綜上,此時的狀態方程的遞推公式為

dp[i] = max(1, dp[j] + 1)

(j = 1, 2, ..., i -1 && A[j] <= A[i])

上面的狀態方程隱含了邊界:dp[i] = 1。顯然 dp[i] 只與小於 i 的 j 有關,因此只要讓 i 從小到大遍歷即可求出整個 dp 數組。

此時,時間復雜度即為O(n2)。

代碼實現如下:

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 100;
int A[N], dp[N], n;	// 序列和, dp數組, 數字總數

int main() {
  scanf("%d", &n);
  for(int i=1; i <= n; i++) {
    scanf("%d", &A[i]);
  }	// 從 1 ~ N 編號
  int ans = -1;	// 記錄最大的 dp[i]
  for(int i = 1; i < n; i++) {
    dp[i] = 1;	// 至少能夠自身成為一條 LIS
    for(int j = 1; j < i; j++){	// 只需要遞歸到小於 i
      // 如果不比之前的小,而且新形成的子序列更大,則接到這個人后面來
      if(A[i] >= A[j] && (dp[j] + 1 > dp[i])) {
        dp[i] = dp[j] + 1;
      }
    }
    ans = max(ans, dp[i]);	// 確定當前結束的最長子序列的長度之后,比較
  }
  printf("%d", ans);
  return 0;
}

希望對大家有幫助。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM