詳解最長公共子序列問題(三種方法)

這里,為了更方便地解釋,我以洛谷上的一道典型題目為例,為大家講解處理最長公共子序列問題的幾種常見方法。這道題目中規定了兩個子序列的長度相等,如果遇到不等的情況,也只需要對長度稍作修改即可,算法思想不變。

題目描述

給出 1,2,……?,n 的兩個排列 A?和 B?,求它們的最長公共子序列。

輸入格式

第一行是一個數 n。

接下來兩行,每行為 n?個數,為自然數 1,2,……?,n 的一個排列。

輸出格式

一個數,即最長公共子序列的長度。

樣例輸入?
5?
3 2 1 4 5
1 2 3 4 5

樣例輸出?
3

提示

- 對于 50%?的數據, n <=?10^3;
- 對于 100%?的數據, n <=?10^5。

方法1:常規動態規劃

要解決這道題目,必然要使用動態規劃。既然要用到動態規劃,就要知道狀態轉移方程。我們令L[i][j] 表示序列 A 和序列 B 的最長公共子序列的長度,則狀態轉移方程如下:

若a[i]=b[j], 則 L[i][j]=L[i-1][j-1] +1

若a[i]\neqb[j], 則 L[i][j]=max (L[i][j-1],L[i-1][j])

以表格的形式表示整個過程如下:(這里以?3 2 1 4 5 和1 2 3 4 5為例)

i\j032145
0000000
1000111
2001111
3011111
4011122
5011123

填表的過程就相當于解題的過程(第0行、第0列初始值都為0),我們以第0行為參照,先從左到右填滿第1行;再以第1行為參照,從左到右填滿第2行;以此類推,當表格填完后,答案就出來了(即為L[n][n])

代碼如下:

# include <iostream>using namespace std;const int maxn = 1e3 + 10;
int n;
int A[maxn];
int B[maxn];
int L[maxn][maxn];int main()
{cin >> n;for (int i = 1; i <= n; i++) {cin >> A[i];}for (int i = 1; i <= n; i++) {cin >> B[i];}for (int i = 1; i <= n; i++) {for (int j = 1; j <= n; j++) {//對應狀態轉移方程if (A[i] == B[j]) {L[i][j] = L[i - 1][j - 1] + 1;}else {L[i][j] = max(L[i - 1][j], L[i][j - 1]);}}}cout << L[n][n] << endl;return 0;
}

這種方法是最基本的方法。容易看出它的時間復雜度是O(n^2);但這種方法有一個缺點,就是對空間的要求非常高,因為我們創建了一個二維數組 L,所以空間復雜度為O(n^2) ,如果 n 的值比較大,那么我們就無法創建 L數組了。因此,下面又給出了一種節省空間的辦法。

方法2:改進常規動態規劃

我們的算法思想還和原來基本一致,只不過,我們要把二維數組 L 變成一個一維數組。實現的思想如下:在填表的過程中,我們可以發現,當我們在填某一行時,我們其實只需要用到上一行的數組作為參照,表格中其他的部分并沒有用。所以,我們想到,可以只創建一個一維數組 L ,保存需要用作參照的上一行數據;用一個變量 ans 保存計算得到的需要填入表格的新值;在填寫當前一行數據的同時,更新數組 L已經遍歷過的部分(后面不再用到)為當前行的數據(相當于把當前行的數據逐步填入 L);這樣,在填寫下一行數據時,L也已經更新為新的參照行。最后得到的 ans 就相當于原表格最右下角的位置,即為最終答案。

改進后的代碼如下:

# include <iostream>using namespace std;const int maxn = 1e5 + 10;
int n;
int A[maxn];
int B[maxn];
int L[maxn];int main()
{cin >> n;for (int i = 1; i <= n; i++) {cin >> A[i];}for (int i = 1; i <= n; i++) {cin >> B[i];}int ans = 0, t;for (int i = 1; i <= n; i++) {ans = 0;for (int j = 1; j <= n; j++) {t = ans;  //提前記錄上一個ans的值if (A[i] == B[j]) {ans = L[j - 1] + 1;}else {ans = max(ans, L[j]);}//對已經遍歷過的地方將L更新為下一行的值L[j - 1] = t;  }L[n] = ans;  }//運行到最后,ans便是原二維數組最右下角的結果cout << ans << endl;return 0;
}

方法2和方法1算法思想基本一致,時間復雜度也都是 O(n^2),但方法2的空間復雜度只有 O(n),顯然是方法2更勝一籌(當然,某一問題所需要的空間不大時,我們還是優先選擇方法1,因為方法1寫起來更簡便)。

但上述兩種做法,時間復雜度都是 O(n^2)。遇到某些對時間限制比較高的情況,就不適用了,所以,我們又提出了下面一種方法。

方法3:巧用另一種動態規劃

上面解決最長公共子序列問題的算法可簡稱為LCS。我們還有另一種巧妙的方法來解決這類問題,就是將LCS轉化為LIS。什么是LIS呢?LIS是解決最長遞增(或不下降)子序列的算法。LIS算法的核心思想也是動態規劃。我們先來講講轉化的過程:

能夠轉化的前提是序列A和序列B的數據范圍必須相同

我們仍以 3 2 1 4 5 和?1 2 3 4 5 為例

A: 3 2 1 4 5

B: 1 2 3 4 5

我們把A中的數據按順序變成1、2、3、4、5(變成遞增順序),即3 -> 1,2?-> 2,1?-> 3,4?-> 4,5?-> 5;然后B按照A的轉化規則進行轉化,于是變成:

A: 1 2 3 4 5
B:?3 2 1 4 5

這樣標號之后,序列的長度顯然不會改變。但是出現了一個性質:兩個序列的子序列,一定是A的子序列。而A本身就是遞增的,因此這個子序列是遞增的。換句話說,只要這個子序列在B中遞增,它就是A的子序列。于是,問題就轉化成了求B中的最長遞增子序列。

你可能覺得這樣的轉化多此一舉,但請注意,解決最長遞增子序列類問題,時間復雜度最低可以達到 O(nlogn);也就是說,用這種方法,我們可以將求解最長公共子序列問題的時間復雜度降為O(nlogn),這樣在處理相關問題時就可以避免時間超限的情況。

但新的問題又來了,怎么在O(nlogn)時間復雜度內求解最長遞增子序列問題?這里,我參考了別人給出的一個解釋:

我們以數列 5?2 3 1 4 為例

首先,把 5 加入答案序列中,然后遍歷到?2,發現 2<5 , 于是,我們用2替換5;然后加3,發現3>2,所以直接把3加到答案序列中,這時候就是 [2,3] ;然后遍歷到1,我們發現1<3,于是我們找到一個最小的但是比1大的數字2,然后把1替換2,為什么這么做不會影響結果呢?你可以這么想,我們當前已經求出了一個當前最優的序列,如果我們用1替換2,然后后面來一個數字替換了3,那么我們就可以得到一個更優的序列,而如果沒有數字替換3,那么這個1替換2也就是沒有貢獻的,不會影響我們結果的最優性。另外,解題時可以直接使用STL的lower_bound函數來找到一個最小的但是大于某個數字的數。

代碼如下:

# include <iostream>
# include <vector>
# include <map>using namespace std;const int maxn = 1e5 + 10;
int n;
map<int, int>m;
int B[maxn];int main()
{cin >> n;int a;for (int i = 1; i <= n; i++) {cin >> a;m[a] = i;}int b;for (int i = 1; i <= n; i++) {cin >> b;//按照A的轉化規則,轉化BB[i] = m[b];}//序列C用于保存當前的最優解vector<int>C;C.push_back(0);int len = 0; //保存最終結果for (int i = 1; i <= n; i++) {if (B[i] > C[len]) {C.push_back(B[i]);len++;}else {C[lower_bound(C.begin(), C.end(), B[i]) - C.begin()] = B[i];}}cout << len << endl;return 0;
}

用這種方法時間復雜度就降為O(nlogn)了。我上面給出的那一道題,也只有采用這種方法才不會時間超限。而前兩種只能得一半的分。

總結:

這里,我給出了解決最長公共子序列的三種方法,大家可以根據實際問題,各取所需。以上便是我的看法,很高興與大家分享。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/165354.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/165354.shtml
英文地址,請注明出處:http://en.pswp.cn/news/165354.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

qs-一個序列化和反序列化的JavaScript庫

起因 一個業務場景中&#xff0c;最終得到一串字符"status[0]value1&status[1]value2" 通過解析&#xff0c;理應得到一個數組&#xff0c;卻得到一個對象 于是展開問題排查 最終發現是qs.parse 這個地方出了問題 排查結果 qs解析這種帶下標的字符串時&#xff…

基于python的NBA球員數據可視化分析的設計與實現

完整下載&#xff1a;基于python的NBA球員數據可視化分析的設計與實現.docx 基于python的NBA球員數據可視化分析的設計與實現 Design and Implementation of NBA Player Data Visualization Analysis based on Python 目錄 目錄 2 摘要 3 關鍵詞 4 第一章 引言 4 1.1 研究背景 …

【Java 進階篇】Redis 命令操作:輕松掌握基本操作

Redis是一款高性能的鍵值對存儲系統&#xff0c;以其快速、靈活的特性而備受開發者推崇。本文將詳細介紹Redis的基本命令操作&#xff0c;包括鍵值操作、數據查詢、事務處理等方面&#xff0c;幫助初學者更好地理解和使用Redis。 基本命令 1. 鍵值操作 1.1 SET&#xff1a;設…

spark shuffle 剖析

ShuffleExchangeExec private lazy val writeMetrics SQLShuffleWriteMetricsReporter.createShuffleWriteMetrics(sparkContext)private[sql] lazy val readMetrics SQLShuffleReadMetricsReporter.createShuffleReadMetrics(sparkContext)用在了兩個地方&#xff0c;承接的是…

目標檢測YOLO系列從入門到精通技術詳解100篇-【目標檢測】SLAM(基礎篇)(三)

目錄 前言 移動機器人視覺SLAM回環檢測 01 回環檢測問題描述 02 主流回環檢測方法 2.1 根據路標點先驗信息

【Flink】Standalone運行模式

獨立模式是獨立運行的&#xff0c;不依賴任何外部的資源管理平臺&#xff1b;當然獨立也是有代價的&#xff1a;如果資源不足&#xff0c;或者出現故障&#xff0c;沒有自動擴展或重分配資源的保證&#xff0c;必須手動處理。所以獨立模式一般只用在開發測試或作業非常少的場景…

Ps:參考線

參考線 Guides用于幫助精確地定位圖像或元素&#xff0c;顯示為浮動在圖像上的非打印線&#xff0c;可以移動或移除&#xff0c;還可以臨時鎖定。 Ps 中的參考線可分為三大類&#xff1a;畫布參考線、畫板參考線和智能參考線。 可在“首選項/參考線、網格和切片”中設置參考線的…

C 標準庫 - <stddef.h>和<stdio.h>詳解

目錄 C 標準庫 - 簡介 庫變量 庫宏 實例 C 標準庫 - 簡介 庫變量 庫宏 庫函數 實例 C 標準庫 - <stddef.h> 簡介 <stdio.h> 是 C 語言中的一個標準庫&#xff0c;它提供了一些常用的函數和類型定義&#xff0c;用于處理與大小相關的操作。 庫變量 …

深信服防火墻路由模式開局部署-手把手教學(小白篇)

PS&#xff1a;深信服的設備只有400能夠通過console連接&#xff0c;一般用戶是無法連接的&#xff0c;所以大家不要妄想著從Console連接設備了&#xff0c;開局就通過MANAGE進入Web就可以 接通電源后&#xff0c;開機拿一根網線&#xff0c;一端連接防火墻的MANAGE口&#xf…

uniapp uni.navigateBack返回后刷新頁面數據

方法1: 父頁面設置鉤子函數(onBackPress): 頁面簡介 | uni-app官網 適用于刷新多處數據 onBackPress(options) {this.refreshData(); }, methods:{refreshData: function() {//加載數據}, }, 方法2: 返回加success回調 uni.navigateBack({delta: 1, //返回層數&#xff0…

【C++】泛型編程 ? ( 類模板示例 - 數組類模板 | 容器思想 | 自定義類可拷貝 - 深拷貝與淺拷貝 | 自定義類可打印 - 左移運算符重載 )

文章目錄 一、容器思想1、自定義類可拷貝 - 深拷貝與淺拷貝2、自定義類可拷貝 - 代碼示例3、自定義類可打印 - 左移運算符重載 二、代碼示例1、Array.h 頭文件2、Array.cpp 代碼文件3、Test.cpp 主函數代碼文件4、執行結果 一、容器思想 1、自定義類可拷貝 - 深拷貝與淺拷貝 上…

百戰python02-語言元素

文章目錄 指令與程序變量與類型變量命名變量的使用運算符賦值運算符比較運算符和邏輯運算符練習1:華氏溫度轉換為攝氏溫度練習2:輸入圓的半徑計算計算周長和面積練習3:輸入年份判斷是不是閏年字符串常用操作注:需要對python有基本了解,可查看本作者python基礎專欄,有任何問…

大模型生態新篇章:以AI Agent為引,助企業創新應用落地

文 | 智能相對論 作者 | 沈浪 以聊天機器人、虛擬助手、智能客服等為代表的對話式人工智能 (Conversational AI Agents ) 在具體服務場景中的應用已經十分普遍。今年以來&#xff0c;隨著大模型技術的爆發與加持&#xff0c;對話式AI被市場賦予了更高的期望。 “所有行業都值…

Spring 事務失效的7種場景, 事務失效后如何進行處理

文章目錄 簡單說說spring事務失效的場景Spring 事務失效的7種場景1.1、未啟用[spring事務管理](https://so.csdn.net/so/search?qspring事務管理&spm1001.2101.3001.7020)功能1.2、方法不是public類型的1.3、數據源未配置事務管理器1.4、自身調用問題1.5、異常類型錯誤1.6…

《golang設計模式》第三部分·行為型模式-07-觀察者模式(Observer)/發布者—訂閱者模式

文章目錄 1. 概念1.1 角色1.2 類圖 2. 代碼示例2.1 代碼2.2 類圖 1. 概念 觀察者&#xff08;Observer&#xff09;指當目標對象狀態發生變化后&#xff0c;對狀態變化事件進行響應或處理的對象。 1.1 角色 Subject&#xff08;抽象主題&#xff09;&#xff1a; 它可以有多…

微服務實戰系列之Feign

前言 不知不覺&#xff0c;“微服務實戰系列”已完成了六篇&#xff0c;每篇都聚焦一個主題&#xff0c;目的是便于各位盆友能夠快速、全面地接收和消化。 博主從服務注冊到服務監控&#xff0c;從服務路由到服務安全&#xff0c;從身份認證到加密技術均有涉獵。凡此均有關微服…

Java核心知識點整理大全10-筆記

往期快速傳送門&#xff1a; Java核心知識點整理大全-筆記_希斯奎的博客-CSDN博客文章瀏覽閱讀9w次&#xff0c;點贊7次&#xff0c;收藏7次。Java核心知識點整理大全https://blog.csdn.net/lzy302810/article/details/132202699?spm1001.2014.3001.5501 Java核心知識點整理…

【LeetCode刷題】--67.二進制求和

67.二進制求和 方法&#xff1a;模擬計算 class Solution {public String addBinary(String a, String b) {StringBuilder ans new StringBuilder();int carry 0;for(int ia.length()-1,jb.length()-1;i>0||j>0;i--,j--){int sum carry;sum i >0 ? a.charAt(i) …

web:[WUSTCTF2020]樸實無華

題目 點開頁面顯示如下 頁面顯示了一行報錯&#xff1a;Cannot modify header information - headers already sent by (output started at /var/www/html/index.php:3) in /var/www/html/index.php on line 4 意思為不能修改報頭信息-報頭已經發送(輸出開始于/var/www/html/i…

vue3 websocket連接 發送數據

先建一個websocket.js放在項目中&#xff0c;內容如下&#xff1a; var websock null; let rec; //斷線重連后&#xff0c;延遲5秒重新創建WebSocket連接 rec用來存儲延遲請求的代碼 let isConnect false; //連接標識 避免重復連接 let checkMsg "heartbeat"; /…