最長遞增子序列(LIS)問題詳解

最長遞增子序列LIS問題詳解

    • 一、問題定義與核心特征
      • 1.1 問題描述
      • 1.2 核心特征
    • 二、基礎解法:動態規劃(DP)
      • 2.1 解法思路
      • 2.2 Java代碼實現
      • 2.3 復雜度分析
    • 三、優化解法:二分查找+貪心
      • 3.1 核心思路
      • 3.2 二分查找的作用
      • 3.3 Java代碼實現
        • 代碼說明:
      • 3.4 復雜度分析
    • 四、變種問題與拓展
      • 4.1 最長非遞減子序列
      • 4.2 輸出具體的最長遞增子序列
      • 4.3 二維LIS問題(信封嵌套)
    • 五、LIS的實際應用場景
    • 六、兩種解法的對比與選擇
    • 總結

最長遞增子序列(Longest Increasing Subsequence,簡稱LIS)是動態規劃領域的經典問題,它看似簡單——在一個無序數組中找到最長的嚴格遞增子序列(子序列無需連續),但背后隱藏著從暴力到高效的多種解法思路。本文我將系統解析LIS問題的核心邏輯,從基礎的動態規劃到優化的“二分查找+貪心”解法,并結合代碼實現、復雜度分析及變種拓展全面解讀。

一、問題定義與核心特征

1.1 問題描述

給定一個整數數組nums,找到其中最長的嚴格遞增子序列的長度。

  • 子序列:由數組中部分元素組成,元素順序與原數組一致(無需連續)。
  • 嚴格遞增:子序列中每個元素都大于前一個元素(如[2,5,7]是遞增子序列,[2,2,3]不是)。

示例:

  • 輸入:nums = [10,9,2,5,3,7,101,18]
  • 輸出:4(最長遞增子序列為[2,3,7,18][2,5,7,101]

1.2 核心特征

  • 無連續性要求:子序列元素在原數組中可間隔存在(區別于“最長遞增子數組”)。
  • 嚴格遞增:需滿足nums[i] < nums[j]i < j),若允許相等則為“最長非遞減子序列”。
  • 多解性:可能存在多個長度相同的最長子序列,但問題通常只要求返回長度。

二、基礎解法:動態規劃(DP)

動態規劃是解決LIS問題的基礎方法,核心思路是通過“以每個元素為結尾的最長子序列長度”推導全局最優。

2.1 解法思路

  1. 定義狀態
    dp[i]表示“以nums[i]為結尾的最長遞增子序列的長度”。
    (核心:子序列必須包含nums[i],且nums[i]是子序列的最后一個元素)

  2. 遞推關系
    對于每個i(當前元素),遍歷所有j < i(前驅元素):

    • nums[j] < nums[i](滿足遞增),則dp[i]可由dp[j] + 1更新(在前驅子序列后添加nums[i])。
    • 取所有符合條件的dp[j] + 1的最大值,即:
      dp[i] = max(dp[j] + 1) (j < i 且 nums[j] < nums[i])
    • 若沒有符合條件的j(即nums[i]比所有前驅都小),則dp[i] = 1(自身為長度1的子序列)。
  3. 邊界條件
    所有dp[i]初始化為1(每個元素自身都是長度為1的子序列)。

  4. 結果計算
    全局最長遞增子序列長度為dp數組中的最大值。

2.2 Java代碼實現

import java.util.Arrays;public class LIS_DP {public int lengthOfLIS(int[] nums) {if (nums == null || nums.length == 0) {return 0;}int n = nums.length;int[] dp = new int[n];Arrays.fill(dp, 1); // 初始化:每個元素自身是長度1的子序列int maxLen = 1; // 最小長度為1for (int i = 1; i < n; i++) {// 遍歷所有前驅元素jfor (int j = 0; j < i; j++) {// 若j位置元素小于i位置,嘗試更新dp[i]if (nums[j] < nums[i]) {dp[i] = Math.max(dp[i], dp[j] + 1);}}// 更新全局最大值maxLen = Math.max(maxLen, dp[i]);}return maxLen;}public static void main(String[] args) {LIS_DP solution = new LIS_DP();int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};System.out.println(solution.lengthOfLIS(nums)); // 輸出4}
}

2.3 復雜度分析

  • 時間復雜度O(n2)O(n^2)O(n2)。外層循環遍歷n個元素,內層循環對每個元素遍歷其所有前驅(平均n/2次),總操作次數為n×n/2=O(n2)n \times n/2 = O(n^2)n×n/2=O(n2)
  • 空間復雜度O(n)O(n)O(n)。需要dp數組存儲n個狀態。

適用場景:數組長度較小(n ≤ 1000)時,該方法簡單直觀,易于實現。

三、優化解法:二分查找+貪心

當數組長度較大(如n > 10^4)時,O(n2)O(n^2)O(n2)的動態規劃解法會超時。“二分查找+貪心”解法通過優化狀態維護方式,將時間復雜度降至O(nlog?n)O(n \log n)O(nlogn),是LIS問題的最優解法。

3.1 核心思路

貪心思想:維護一個盡可能小的“遞增子序列尾部元素”。尾部元素越小,后續能添加的元素就越多,越容易形成更長的子序列。

例如:

  • 對于nums = [2,5,3,7],若已有的子序列尾部是[2,5],當遇到3時,可將5替換為3(得到[2,3])——雖然當前長度不變,但尾部更小,后續可添加7形成[2,3,7](比[2,5,7]更優)。

具體步驟:

  1. 定義一個列表tailstails[i]表示“長度為i+1的遞增子序列的最小尾部元素”。
  2. 遍歷數組nums,對每個元素x
    • x大于tails的最后一個元素,直接加入tails(子序列長度+1)。
    • 否則,在tails中找到第一個大于等于x的元素,用x替換它(維持尾部最小化)。
  3. 最終tails的長度即為LIS的長度。

3.2 二分查找的作用

tails是嚴格遞增的(因為每次添加的元素都大于尾部,替換的元素也保證了遞增性),因此可通過二分查找快速定位“第一個大于等于x的元素”,時間復雜度從O(n)O(n)O(n)降至O(log?n)O(\log n)O(logn)

3.3 Java代碼實現

import java.util.ArrayList;
import java.util.List;public class LIS_BinarySearch {public int lengthOfLIS(int[] nums) {if (nums == null || nums.length == 0) {return 0;}List<Integer> tails = new ArrayList<>();for (int x : nums) {// 二分查找:找到tails中第一個 >= x的元素索引int left = 0, right = tails.size();while (left < right) {int mid = left + (right - left) / 2;if (tails.get(mid) < x) {left = mid + 1; // x更大,繼續向右找} else {right = mid; // 可能找到,縮小右邊界}}// 若left等于長度,說明x是最大的,直接添加if (left == tails.size()) {tails.add(x);} else {// 否則替換對應位置的元素tails.set(left, x);}}return tails.size();}public static void main(String[] args) {LIS_BinarySearch solution = new LIS_BinarySearch();int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};System.out.println(solution.lengthOfLIS(nums)); // 輸出4}
}
代碼說明:
  • tails始終保持嚴格遞增,例如輸入[10,9,2,5,3,7,101,18]時,tails的變化過程為:
    [10] → [9] → [2] → [2,5] → [2,3] → [2,3,7] → [2,3,7,101] → [2,3,7,18]
    最終長度為4,與預期結果一致。

3.4 復雜度分析

  • 時間復雜度O(nlog?n)O(n \log n)O(nlogn)。遍歷數組n次,每次二分查找操作耗時O(log?k)O(\log k)O(logk)k為當前tails長度,最大為n),總復雜度為n×log?n=O(nlog?n)n \times \log n = O(n \log n)n×logn=O(nlogn)
  • 空間復雜度O(n)O(n)O(n)tails列表最壞情況下存儲n個元素(數組完全遞增時)。

適用場景:數組長度較大(n ≥ 10^4)時,該方法效率顯著優于動態規劃。

四、變種問題與拓展

4.1 最長非遞減子序列

問題:允許子序列元素相等(即nums[i] ≤ nums[j]),求最長子序列長度。
解法:修改二分查找條件——將“找到第一個大于等于x的元素”改為“找到第一個大于x的元素”(允許替換相等元素)。
代碼調整:

// 非遞減子序列的二分查找條件
if (tails.get(mid) <= x) { // 原條件為 < xleft = mid + 1;
} else {right = mid;
}

4.2 輸出具體的最長遞增子序列

基礎解法和優化解法均只能得到長度,若需輸出具體子序列,需結合動態規劃記錄前驅:

  1. dp數組記錄長度,prev數組記錄每個元素的前驅索引。
  2. 找到dp數組最大值對應的索引,通過prev回溯得到子序列。

示例代碼:

public int[] getLIS(int[] nums) {int n = nums.length;int[] dp = new int[n];int[] prev = new int[n];Arrays.fill(dp, 1);Arrays.fill(prev, -1);int maxLen = 1, maxIndex = 0;for (int i = 1; i < n; i++) {for (int j = 0; j < i; j++) {if (nums[j] < nums[i] && dp[j] + 1 > dp[i]) {dp[i] = dp[j] + 1;prev[i] = j; // 記錄前驅}}if (dp[i] > maxLen) {maxLen = dp[i];maxIndex = i; // 記錄最長子序列的結尾索引}}// 回溯構建子序列int[] lis = new int[maxLen];int index = maxLen - 1;while (maxIndex != -1) {lis[index--] = nums[maxIndex];maxIndex = prev[maxIndex];}return lis;
}

4.3 二維LIS問題(信封嵌套)

問題:給定n個信封(w, h),若w1 < w2h1 < h2則可嵌套,求最多嵌套層數(本質是二維LIS)。
解法:

  1. 按寬度w升序排序(寬度相等時按高度h降序,避免同寬信封嵌套)。
  2. 對高度h求LIS,長度即為最大嵌套層數。

五、LIS的實際應用場景

  1. 任務調度:安排依賴關系的任務,找到最長的連續執行序列。
  2. 導彈攔截:攔截導彈的高度需嚴格遞增,求最多攔截數量。
  3. 數據可視化:在時序數據中找到最長的增長趨勢段。
  4. 算法優化:作為其他問題的子步驟(如最長遞增子序列的個數、最大上升子序列和等)。

六、兩種解法的對比與選擇

解法時間復雜度空間復雜度優勢場景核心思想
動態規劃O(n2)O(n^2)O(n2)O(n)O(n)O(n)數組短(n ≤ 1000)、需輸出子序列以每個元素為結尾的子序列長度
二分查找+貪心O(nlog?n)O(n \log n)O(nlogn)O(n)O(n)O(n)數組長(n ≥ 10^4)、僅需長度維護最小尾部元素,貪心優化

總結

LIS問題是動態規劃與貪心思想結合的經典案例,從O(n2)O(n^2)O(n2)的基礎解法到O(nlog?n)O(n \log n)O(nlogn)的優化解法,體現了“狀態優化”的核心思路——通過更高效的狀態維護(如tails列表)減少冗余計算。

掌握LIS問題的關鍵在于:

  1. 理解動態規劃的狀態定義(以每個元素為結尾的子序列長度);
  2. 掌握“二分查找+貪心”的優化邏輯(最小尾部元素的維護);
  3. 能根據問題需求(長度/具體子序列、數組大小)選擇合適解法。

That’s all, thanks for reading~~
覺得有用就點個贊、收進收藏夾吧!關注我,獲取更多干貨~

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

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

相關文章

什么是HTTP長連接、短連接?誰更能抗DoS攻擊?

想象你在快餐店點餐&#xff1a; 你&#xff1a;“一個漢堡”收銀員&#xff1a;“好的&#xff0c;15元”交易結束&#xff0c;你離開隊伍你想加杯可樂&#xff0c;重新排隊你&#xff1a;“一杯可樂”收銀員&#xff1a;“好的&#xff0c;8元”再次離開… 這種每次溝通后立即…

微軟徽標認證是什么?如何快速獲取驅動簽名?

在Windows系統中安裝硬件驅動時&#xff0c;是否遇到過“無法驗證發布者”的警告&#xff1f;這正是驅動數字簽名在背后發揮作用。對于軟件開發者而言&#xff0c;驅動數字簽名不僅是系統兼容性的保障&#xff0c;更是企業品牌信任度的核心。一、驅動數字簽名的核心作用驅動數字…

Apache Ignite緩存基本操作

這段內容主要講解了 Apache Ignite 中緩存&#xff08;IgniteCache&#xff09;的基本操作&#xff0c;包括獲取緩存、創建緩存、銷毀緩存、執行原子操作以及異步操作等。下面我將用中文對這些內容進行詳細解釋&#xff0c;幫助你更好地理解。一、獲取緩存實例&#xff08;Gett…

最新基于R語言結構方程模型分析與實踐技術應用

現代統計學理論和方法的不斷完善&#xff0c;使科研工作對統計方法的要求也越來越高&#xff0c;面對紛繁復雜的數據&#xff0c;如何選擇最為合適的數據分析方法已成為科研工作者&#xff0c;尤其是廣大剛處于科研生涯起步階段的研究生們最為棘手問題。隨著科學的發展&#xf…

物聯網_TDengine_EMQX_性能測試

一、Tdengine接口開發文檔 1、數據庫 1.創建數據庫 URL /dp/createdb/ method post 請求示例 {"db_name":"demo01" // 必填 }響應示例 // 成功 {"code": 1,"data": {"成功創建數據庫": "demo04"},"error…

從分析到優化:Amazon Q CLI 助力 EKS 網絡調用鏈剖析與運維實踐

1. 引言 在 Amazon EKS&#xff08;Elastic Kubernetes Service&#xff09;環境中&#xff0c;理解從 ALB&#xff08;Application Load Balancer&#xff09;到 Pod 的完整網絡調用鏈對運維人員至關重要。本文將展示如何利用 Amazon Q CLI 這一 AI 助手工具&#xff0c;通過…

Class10簡潔實現

Class10簡潔實現 import torch from torch import nn from d2l import torch as d2l# 輸入為28*28&#xff0c;輸出為10類&#xff0c;第1、2隱藏層256神經元 num_inputs, num_outputs, num_hiddens1, num_hiddens2 784, 10, 256, 256 # 第1個隱藏層丟棄率為0.2&#xff0c;第…

【多線程篇22】:ConcurrentHashMap的并發安全原理剖析

文章目錄一、HashMap 的“不安全”&#xff1a;問題的根源1. 數據結構回顧 (JDK 1.8)2. 并發下的致命缺陷&#xff1a;put 操作二、ConcurrentHashMap 的安全之道 (JDK 1.8)1. 核心數據結構2. 安全的 put 操作&#xff1a;分場景精細化加鎖3. 安全的 size() 計算&#xff1a;并…

【Java + Vue 實現圖片上傳后 導出圖片及Excel 并壓縮為zip壓縮包】

系統環境&#xff1a; Java JDK&#xff1a;1.8.0_202 Node.js&#xff1a;v12.2.0 Npm&#xff1a;6.9.0 Java后端實現 Controller /*** xxxx-導出* param response 返回信息體* param files 上傳的圖片文件* param param1 參數1* param param2 參數2*/PostMapping("/ex…

安科瑞:能源微電網助力工業園區“綠色”發展

朱以真近日&#xff0c;廈門市工業和信息化局印發工業園區綠色智慧微電網建設&#xff0c;擬開展全市工業園區綠色智慧微電網試點通知&#xff0c;那么對于如何實現綠色園區的建設是今天的話題。對工業園區綠色智慧微電網建設需求&#xff0c;其核心價值體現在“源-網-荷-儲-充…

VUE2 學習筆記3 v-on、事件修飾符、鍵盤事件

事件處理v-on用于事件交互。語法&#xff1a;v-on:要綁定的事件“事件觸發時執行的函數” &#xff08;函數這里可以寫括號&#xff0c;也可以不寫&#xff0c;沒有影響&#xff09;簡寫&#xff1a;:事件觸發時要執行的函數&#xff0c;在Vue配置參數中&#xff0c;通過method…

變換域通訊系統CCSK的matlab仿真

CCSK&#xff08;Cyclic Code Shift Keying&#xff09;通信系統的MATLAB仿真。實現完整的CCSK調制、AWGN信道傳輸和解調過程&#xff0c;并計算了誤碼率&#xff08;BER&#xff09;。 % CCSK通信系統仿真 clear; clc; close all;% 參數設置 L 31; % m序列…

技術演進中的開發沉思-40 MFC系列:多線程協作

今天說說MFC的線程&#xff0c;當年用它實現中間件消息得心應手之時&#xff0c;可以實現一邊實時接收數據&#xff0c;一邊更新界面圖表圖文信息&#xff0c;順滑得讓人想吹聲口哨。 MFC 多線程它像給程序裝上了分身術&#xff0c;讓原本只能 “單任務跑腿” 的代碼&#xff0…

高速公路自動化安全監測主要內容

近年來&#xff0c;隨著社會經濟的快速發展&#xff0c;高速公路的通車里程不斷增加&#xff0c;交通流量日益增大。與此同時&#xff0c;高速公路交通事故數量也呈現出一定的增長趨勢。這些事故不僅造成了大量的人員傷亡和財產損失&#xff0c;還嚴重影響了社會的穩定和經濟的…

完美解決 Ubuntu 中自定義啟動器圖標重復的問題(以 MATLAB 為例)

如果你在 Ubuntu 上為 MATLAB、PyCharm、Android Studio 或其他第三方應用創建了自定義啟動器&#xff0c;并把它固定到了左側的 Dock 欄&#xff0c;你很可能遇到過這個令人抓狂的場景&#xff1a; 當你滿心歡喜地點擊固定的圖標啟動程序后&#xff0c;Dock 欄上竟然“憑空”冒…

一文讀懂深度模型優化器,掌握煉丹工具

深度模型優化器是訓練神經網絡的核心工具&#xff0c;其目標是高效地找到損失函數的最小值。從基礎的隨機梯度下降&#xff08;SGD&#xff09;到結合一階動量修正與兩階段更新的Ranger&#xff0c;優化器的發展始終圍繞著加速收斂、提升穩定性、適應參數差異和增強泛化能力四大…

關于虛擬機部署的k8s集群calico組件在開機恢復快照時的問題

在恢復虛擬機開機快照的時候總會出現各種的網絡問題&#xff0c;連接不到自己的443端口&#xff0c;集群狀態異常等等&#xff0c;繞一圈都發現不了問題&#xff0c;其實根本就沒問題。虛擬機環境再現&#xff0c;恢復快照后的k8s環境&#xff0c;nfs莫名宕掉提示此報錯&#x…

從感知到決策:虛擬仿真系統與視覺算法融合下的多路RTSP視頻接入技術探究

1?? 背景概述 隨著國防信息化和智能化進程的不斷加快&#xff0c;虛擬仿真系統 與 智能視覺算法平臺 正逐步成為現代化裝備研發、測試驗證與戰術訓練的重要技術支撐。相比傳統的靜態建模或離線推演&#xff0c;新一代作戰仿真與智能系統更強調 實時性、感知能力與動態交互&a…

Android app如果不適配Android 15會怎么樣

深度思考完成 用時49秒 首先&#xff0c;用戶的問題是&#xff1a;“Android app如果不適配Android 15會怎么樣&#xff1f;”我需要基于搜索結果來回答。 讓我回顧搜索結果&#xff1a; ?1&#xff1a;標題是“Android 15應用適配指南”&#xff0c;內容強調了不適配Androi…

Vue-21-利用Vue3的axios+Python的flask實現前后端交互功能

文章目錄 1 任務需求 2 前端 2.1 模板template 2.2 JS代碼script 2.3 Calculate.vue(子組件) 2.4 App.vue(根組件) 3 后端 3.1 導入模塊 3.2 創建應用實例 3.3 配置CORS 3.4 定義路由 3.5 處理請求 3.6 main.py 4 附錄 4.1 CORS 4.1.1 全局啟用CORS 4.1.2 限制允許的域名(更安…