ลำดับต่อมาที่ยาวที่สุด: Python, C++ ตัวอย่าง

⚡ สรุปอย่างชาญฉลาด

ลำดับย่อยร่วมที่ยาวที่สุด (Longest Common Subsequence) ระบุรูปแบบองค์ประกอบเรียงลำดับที่ยาวที่สุดที่สตริงสองสตริงมีร่วมกันโดยไม่จำเป็นต้องมีอักขระที่อยู่ติดกัน วิธีการเขียนโปรแกรมเชิงพลวัตแบบคลาสสิกนี้เป็นพื้นฐานของยูทิลิตี้การเปรียบเทียบความแตกต่าง การจัดเรียงลำดับดีเอ็นเอ และการควบคุมเวอร์ชัน โดยการเปรียบเทียบลำดับอย่างมีประสิทธิภาพในเวลาพหุนาม

  • 📘 แนวคิดหลัก: ฟังก์ชัน Longest Common Subsequence จะส่งคืนชุดอักขระที่เรียงลำดับแล้วที่ยาวที่สุดซึ่งปรากฏในสตริงอินพุตทั้งสอง โดยคงลำดับสัมพัทธ์เดิมไว้
  • 🐢 แนวทางที่เรียบง่าย: การใช้กำลังทั้งหมดจะไล่เรียงลำดับย่อยทั้งหมดของสตริงแรกและตรวจสอบกับสตริงที่สอง ซึ่งใช้เวลาในการประมวลผลแบบเลขชี้กำลัง O(n·2^m)
  • 🔁 วิธีการเรียกซ้ำ: กฎแบบเรียกซ้ำจะจับคู่กับอักขระตัวสุดท้ายหรือเรียกซ้ำกับสตริงย่อยที่เล็กกว่า แต่จะคำนวณส่วนที่ทับซ้อนกันใหม่ping ปัญหาย่อยซ้ำๆ
  • 🧮 การเขียนโปรแกรมแบบไดนามิก: ตาราง DP สองมิติจะแคชผลลัพธ์ของปัญหาย่อย ทำให้ได้โซลูชันที่สะอาดตาด้วยประสิทธิภาพ O(m·n) และพื้นที่เสริม O(m·n)
  • 🐍 ความครอบคลุมด้านภาษา: สมบูรณ์ Python และ C++ ตัวอย่างการใช้งานแสดงให้เห็นถึงทั้งวิธีการเรียกซ้ำแบบพื้นฐานและตาราง DP ที่มีการจดจำผลลัพธ์เพื่อการใช้งานจริง
  • 🌐 การใช้งานจริง: Longest Common Subsequence (ลำดับย่อยร่วมที่ยาวที่สุด) เป็นหัวใจสำคัญของเครื่องมือเปรียบเทียบความแตกต่าง เครื่องมือตรวจสอบการลอกเลียนแบบ เครื่องมือแก้ไขการสะกดคำ และการจัดเรียงลำดับทางชีวสารสนเทศศาสตร์ทั้งในดีเอ็นเอและโปรตีน

ลำดับต่อมาที่ยาวที่สุด

ลำดับต่อมาที่ยาวที่สุดคืออะไร?

การหาลำดับย่อยร่วมที่ยาวที่สุด (LCS) หมายความว่า คุณจะได้รับสตริง รูปแบบ หรือลำดับของวัตถุสองชุด คุณต้องหาลำดับย่อยที่ยาวที่สุดขององค์ประกอบที่มีลำดับเดียวกันซึ่งปรากฏอยู่ในทั้งสองสตริงหรือรูปแบบนั้น

ตัวอย่าง

ตัวอย่างเช่น มีข้อความสองข้อความให้มา สมมติว่า:

Pattern_1 = “RGBGARGA”
Pattern_2 = “BGRARG”

  • จาก pattern_1 สามารถสร้างลำดับได้ เช่น “RGB”, “RGGA”, “RGAR” ในการสร้างลำดับนั้น คุณต้องรักษาระตำแหน่งสัมพัทธ์ของแต่ละตัวอักษรในสตริงไว้ด้วย
  • จากรูปแบบที่ 2 เราสามารถสร้างลำดับต่างๆ เช่น “BGR”, “BRAG”, “RARG” ได้ โดยลำดับเหล่านี้สามารถสร้างขึ้นได้ตราบใดที่ยังคงรักษาตำแหน่งสัมพัทธ์ของสตริงเดิมไว้

คำว่าตำแหน่งสัมพัทธ์หมายถึงความเป็นระเบียบ

ตัวอย่างเช่น “BRG” เป็นลำดับที่ถูกต้อง เพราะ “B” ปรากฏเป็นตัวแรก ตามด้วย “R” และ “G” ตามลำดับในรูปแบบสตริงดั้งเดิม pattern_2 อย่างไรก็ตาม หากลำดับเป็น “RBRG” จะไม่ถูกต้อง เพราะในสตริงดั้งเดิม (pattern_2) “B” มาเป็นตัวแรก

ตัวอย่างสตริงลำดับย่อยร่วมที่ยาวที่สุด

เรามีสองทางเลือกในการค้นหาลำดับย่อยร่วมที่ยาวที่สุดจากสองลำดับหรืออาร์เรย์ที่กำหนด

  • วิธีการไร้เดียงสา
  • โซลูชันการเขียนโปรแกรมแบบไดนามิก: ลำดับย่อยทั่วไปที่ยาวที่สุดเรียกอีกอย่างว่า LCS

วิธีแก้ปัญหาแบบง่ายๆ มีความซับซ้อนด้านเวลามากกว่าและไม่ใช่วิธีแก้ปัญหาที่ดีที่สุด การใช้การเขียนโปรแกรมเชิงพลวัต (Dynamic Programming: DP) ช่วยแก้ปัญหาความซับซ้อนนี้ได้

วิธีการไร้เดียงสา

วิธีการแบบง่าย (Naive method) เป็นวิธีการแก้ปัญหาที่เรียบง่าย โดยไม่คำนึงถึงความซับซ้อนของเวลาและปัจจัยการปรับให้เหมาะสมอื่นๆ วิธีการนี้ประกอบด้วย "การใช้กำลังทั้งหมด" (brute force) การวนลูปหลายครั้ง และการเรียกซ้ำในกรณีส่วนใหญ่ คำว่า "การใช้กำลังทั้งหมด" หมายถึงการลองทุกรูปแบบที่เป็นไปได้สำหรับปัญหาที่กำหนด

ตัวอย่าง

จากตัวอย่างข้างต้นของ pattern1 และ pattern2 ให้เราถือว่า pattern1 มีความยาว m และ pattern2 มีความยาว n เพื่อตรวจสอบทุกกรณีที่เป็นไปได้ เราจำเป็นต้องประเมินทุกลำดับย่อยที่เป็นไปได้ของ pattern1 ด้วย pattern2

นี่คือสตริงง่ายๆ 4 ตัวอักษร “ABCD” ตัวอย่างเช่น เราต้องการสร้างลำดับจาก “ABCD” เราสามารถเลือกตัวอักษรได้หรือไม่ก็ได้ นั่นหมายความว่า สำหรับแต่ละตัวอักษร เรามีสองทางเลือก:

  • ตัวละครจะถูกเพิ่มเข้าไปในลำดับถัดไป
  • อักขระจะไม่ถูกเพิ่มลงในลำดับถัดไป

ในที่นี้ รูปภาพจะแสดงลำดับทั้งหมดที่เราสามารถสร้างได้จากสตริง “ABCD”

ลำดับวิธีแบบง่ายของ ABCD

ลำดับที่มี 1 ตัวอักษร:

ลำดับอักขระเดี่ยวแบบวิธีพื้นฐาน

ลำดับที่มี 2 ตัวอักษร:

วิธีแบบง่าย ลำดับอักขระสองตัว

ลำดับที่มี 3 ตัวอักษร:

วิธีการแบบง่าย ลำดับอักขระสามตัว

จากแผนภาพข้างต้น มีลำดับทั้งหมด 14 ลำดับ หากเราไม่นำตัวอักษรใดๆ มาใช้ ซึ่งก็คือสตริงว่าง จำนวนลำดับทั้งหมดจะเป็น 15 ลำดับ นอกจากนี้ สตริง “ABCD” เองก็เป็นลำดับหนึ่ง ดังนั้น จำนวนลำดับทั้งหมดจึงเป็น 16 ลำดับ

ดังนั้น จึงเป็นไปได้ที่จะสร้างลำดับย่อยได้ 2^4 หรือ 16 ลำดับจาก “ABCD” จากนั้น สตริงที่มีความยาวเท่ากับ m จะมีลำดับย่อยทั้งหมดเท่ากับ 2^m

สำหรับแต่ละลำดับย่อย เราจำเป็นต้องตรวจสอบรูปแบบทั้งหมด 2 รูปแบบ ซึ่งจะใช้เวลา O(n) โดย O(n) คือฟังก์ชันความซับซ้อนที่ใช้คำนวณเวลาที่ใช้ในการดำเนินการ

ดังนั้นความซับซ้อนของเวลาทั้งหมดจึงกลายเป็น O(n*2^m) จากตัวอย่างที่เราได้เห็นข้างต้น ค่าของ m=8 และ n=5

ต่อไปนี้เป็นขั้นตอนของวิธีไร้เดียงสา:

ขั้นตอน 1) นำลำดับจากรูปแบบที่ 1 มาใช้
ขั้นตอน 2) จับคู่ลำดับจากขั้นตอนที่ 1 กับแบบแผนที่ 2
ขั้นตอน 3) หากตรงกัน ให้บันทึกลำดับถัดไป
ขั้นตอน 4) ถ้ายังมีลำดับเหลืออยู่ในรูปแบบที่ 1 ให้กลับไปที่ขั้นตอนที่ 1 อีกครั้ง
ขั้นตอน 5) พิมพ์ลำดับที่ยาวที่สุด

โครงสร้างพื้นฐานที่เหมาะสมที่สุด

คำว่าโครงสร้างย่อยที่เหมาะสมที่สุด หมายความว่าสามารถหาคำตอบที่เหมาะสมที่สุดได้โดยการแก้ปัญหาย่อย ตัวอย่างเช่น ในตัวอย่างข้างต้น เรามีรูปแบบที่ 1 และรูปแบบที่ 2

ขั้นตอน 1) นำอักขระสองตัวแรกจากแต่ละรูปแบบมาใช้

ขั้นตอน 2) ใช้อักขระตัวที่สามถึงห้าจากแต่ละรูปแบบ

ขั้นตอน 3) ดำเนินการต่อในทำนองเดียวกันกับอักขระที่เหลือ

โครงสร้างแบบเรียกซ้ำของปัญหา LCS

โครงสร้างแบบเรียกซ้ำของปัญหา LCS

เราหาค่า LCS ของสตริงย่อย (สตริงที่สร้างขึ้นจากสตริงต้นฉบับ) จากนั้นเราบันทึกความยาวของค่า LCS ของสตริงย่อยเหล่านั้นไว้

นี่ก็เป็นอีกหนึ่งทรัพย์สินที่น่าสนใจนั่นก็คือ คาบเกี่ยวกันping ปัญหาย่อยกล่าวกันว่าปัญหามีการทับซ้อนกันping ปัญหาย่อย หากโจทย์ปัญหาหลักสามารถแบ่งออกเป็นปัญหาย่อยเล็กๆ และนำมาใช้ซ้ำหลายครั้งในโปรแกรมได้

แผนภาพด้านล่างแสดงให้เห็นว่าอัลกอริธึมแบบเรียกซ้ำเรียกใช้ฟังก์ชันที่มีพารามิเตอร์เดียวกันหลายครั้ง

การทับซ้อนโครงสร้างย่อยที่เหมาะสมที่สุดping ปัญหาย่อย

ตัวอย่างเช่น ลองดูแผนผังการเรียกซ้ำ ในกรอบสีเข้ม คุณจะสังเกตเห็นส่วนที่ทับซ้อนกันping ปัญหาย่อย (“RG”, “RA”), (“RG”, “R”) และอื่นๆ ถูกเรียกหลายครั้ง

เพื่อให้ได้ผลลัพธ์ที่ดีที่สุด เราจึงมีแนวทางดังนี้ การเขียนโปรแกรมแบบไดนามิก (ดีพี).

วิธีการเรียกซ้ำของลำดับย่อยร่วมที่ยาวที่สุด

กราฟที่แสดงด้านบนคือวิธีการเรียกซ้ำ ฟังก์ชันเรียกซ้ำแต่ละฟังก์ชันจะมีกรณีพื้นฐานเพื่อหยุดการเรียกซ้ำหรือเริ่มส่งค่ากลับจากสแต็ก

สำหรับการใช้งานนี้ เราจะใช้กรณีพื้นฐาน ดังนั้น ขั้นตอนวิธี เป็นเหมือนกับต่อไปนี้:

  • ถ้าองค์ประกอบทั้งหมดก่อนองค์ประกอบสุดท้ายตรงกัน ให้เพิ่มความยาวขึ้นหนึ่งแล้วส่งกลับ
  • ส่งรูปแบบสองรูปแบบไปยังฟังก์ชัน และเลือกค่าสูงสุดจากค่าที่ส่งกลับมา
  • หากรูปแบบหนึ่งมีความยาวเป็นศูนย์ เราก็ไม่มีลำดับต่อไปให้เปรียบเทียบ ส่งคืนค่า 0 ในกรณีนี้ นี่เป็นกรณีพื้นฐานของการเรียกซ้ำ

ชื่อเล่น Code:

def lcs:
    input: pattern_1, pattern_2, len_1, len_2
    if len_1 or len_2 is zero:
        return 0
    if pattern_1[len_1 - 1] equals pattern_2[len_2 - 1]:
        return 1 + lcs(pattern_1, pattern_2, len_1 - 1, len_2 - 1)
    else:
        return max(lcs(pattern_1, pattern_2, len_1 - 1, len_2),
                   lcs(pattern_1, pattern_2, len_1, len_2 - 1))

การนำไปปฏิบัติใน C++

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int lcs(string pattern_1, string pattern_2, int len_1, int len_2) {
  if (len_1 == 0 || len_2 == 0)
    return 0;
  if (pattern_1[len_1 - 1] == pattern_2[len_2 - 1]) {
    return 1 + lcs(pattern_1, pattern_2, len_1 - 1, len_2 - 1);
  } else {
    return max(lcs(pattern_1, pattern_2, len_1 - 1, len_2), lcs(pattern_1, pattern_2, len_1, len_2 - 1));
  }
}
int main() {
  string pattern_1, pattern_2;
  pattern_1 = "RGBGARGA";
  pattern_2 = "BGRARG";
  cout<<"Length of LCS is: "<<lcs(pattern_1, pattern_2, pattern_1.size(), pattern_2.size())<<endl;
}

Output:

Length of LCS is: 5

การนำไปปฏิบัติใน Python

def lcs(pattern_1, pattern_2, len_1, len_2):
    if len_1 == 0 or len_2 == 0:
        return 0
    if pattern_1[len_1 - 1] == pattern_2[len_2 - 1]:
        return 1 + lcs(pattern_1, pattern_2, len_1 - 1, len_2 - 1)
    else:
        return max(lcs(pattern_1, pattern_2, len_1 - 1, len_2),
                   lcs(pattern_1, pattern_2, len_1, len_2 - 1))

pattern_1 = "RGBGARGA"
pattern_2 = "BGRARG"
print("Length of LCS is: ", lcs(pattern_1, pattern_2, len(pattern_1), len(pattern_2)))

Output:

Length of LCS is:  5

วิธีการเขียนโปรแกรมเชิงพลวัตของลำดับย่อยร่วมที่ยาวที่สุด (LCS)

การเขียนโปรแกรมแบบไดนามิกหมายถึงการปรับปรุงวิธีการเรียกซ้ำแบบธรรมดาให้เหมาะสมที่สุด ตัวอย่างเช่น หากเราดูที่กราฟของวิธีการเรียกซ้ำแบบพื้นฐาน เราจะเห็นว่ามีการเรียกใช้ฟังก์ชันที่เหมือนกันหลายครั้ง วิธีการเขียนโปรแกรมแบบไดนามิกจะบันทึกการคำนวณทั้งหมดไว้ในอาร์เรย์และนำกลับมาใช้ใหม่เมื่อจำเป็น

เราจะใช้อาร์เรย์ 2 มิติที่มีขนาด m x n โดยที่ m และ n คือความยาวของแพทเทิร์น 1 และแพทเทิร์น 2 ตามลำดับ สำหรับ a อาร์เรย์ 2 มิติเราสามารถใช้โครงสร้างข้อมูลแบบลิสต์ได้ใน Python หรือโครงสร้างข้อมูลเวกเตอร์/อาร์เรย์ใน C++.

ชื่อเล่น Code สำหรับ LCS ที่ใช้ DP:

LCS(pattern_1, pattern_2):
    m = length of pattern_1 + 1
    n = length of pattern_2 + 1
    dp[n][m]
    for i in range 0 to n + 1:
        for j in range 0 to m + 1:
            if i or j equals to 0:
                dp[i][j] = 0
            else if pattern_1[i] == pattern_2[j]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[n][m]

นี่คือตาราง LCS ซึ่งใช้เป็นโครงสร้างข้อมูลอาร์เรย์ 2 มิติสำหรับวิธีการเขียนโปรแกรมแบบไดนามิก

วิธีการเขียนโปรแกรมแบบไดนามิกของตาราง LCS 2 มิติ

เรามาพิจารณาตรรกะที่เราใช้ในที่นี้กัน ขั้นตอนมีดังนี้:

ขั้นตอน 1) ถ้า i หรือ j เป็นศูนย์ เราจะเลือกสตริงว่างจากสตริงสองสตริงที่กำหนด และพยายามหาลำดับย่อยที่เหมือนกัน อย่างไรก็ตาม เนื่องจากสตริงย่อยที่เราเลือกนั้นว่าง ความยาวของลำดับย่อยจึงเป็น 0

ขั้นตอน 2) หากอักขระสองตัวตรงกัน เราจะกำหนดค่าให้กับดัชนี (i,j) โดยการเพิ่มค่า LCS ที่คำนวณไว้ก่อนหน้านี้ ซึ่งอยู่ในดัชนี (i-1,j-1) (จากแถวก่อนหน้า)

ขั้นตอน 3) ถ้าไม่ตรงกัน เราจะเลือกค่า LCS สูงสุดของดัชนีสองตัวที่อยู่ติดกัน และด้วยวิธีนี้ เราจะต้องเติมค่าทั้งหมดลงในอาร์เรย์ 2 มิติ

ขั้นตอน 4) สุดท้ายนี้ เราจะคืนค่าของเซลล์สุดท้ายของอาร์เรย์ 2D

โดยพื้นฐานแล้ว ค่าทั้งหมดในอาร์เรย์ 2 มิติจะประกอบด้วยความยาวของลำดับย่อยที่เหมือนกัน และในจำนวนนี้ เซลล์สุดท้ายจะประกอบด้วยความยาวของลำดับย่อยที่เหมือนกันที่ยาวที่สุด

การนำไปปฏิบัติใน C++

#include<iostream>
using namespace std;
int lcs(string pattern_1, string pattern_2) {
  int m = pattern_1.size();
  int n = pattern_2.size();
  // dp will store solutions as the iteration goes on
  int dp[n + 1][m + 1];
  for (int i = 0; i < n + 1; i++) {
    for (int j = 0; j < m + 1; j++) {
      if (i == 0 || j == 0) {
        dp[i][j] = 0;
      } else if (pattern_2[i - 1] == pattern_1[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }
  return dp[n][m];
}
int main() {
  string pattern_1 = "RGBGARGA";
  string pattern_2 = "BGRARG";
  cout<<"Length of LCS: "<<lcs(pattern_1, pattern_2)<<endl;
}

Output:

Length of LCS: 5

การนำไปปฏิบัติใน Python

def lcs(pattern_1, pattern_2):
    m = len(pattern_1)
    n = len(pattern_2)
    # dp will store solutions as the iteration goes on
    dp = [[None] * (n + 1) for item in range(m + 1)]
    for i in range(m + 1):
        for j in range(n + 1):
            if i == 0 or j == 0:
                dp[i][j] = 0
            elif pattern_1[i - 1] == pattern_2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return dp[m][n]

pattern_1 = "RGBGARGA"
pattern_2 = "BGRARG"
print("Length of LCS: ", lcs(pattern_1, pattern_2))

Output:

Length of LCS: 5

ดังนั้น สตริงทั้งสองมีลำดับย่อยร่วมที่ยาวที่สุดที่มีความยาว 5

โดยสรุปแล้ว ในวิธีการ DP เราจะคำนวณแต่ละงานเพียงครั้งเดียวเท่านั้น ในขณะที่วิธีการเรียกซ้ำอาจเกิดการทับซ้อนกันได้ping ปัญหาย่อย

ในอัลกอริทึมการเขียนโปรแกรมแบบไดนามิกนี้ เรากำลังใช้เมทริกซ์ 2 มิติ จะมีการกำหนดไว้สองสาย (สมมติว่าทั้งสองมีความยาว n) ดังนั้นพื้นที่ที่ต้องการในอาร์เรย์คือ nx n หากสตริงมีขนาดใหญ่เพียงพอ เราจะต้องมีเวอร์ชันเพิ่มประสิทธิภาพหน่วยความจำของโซลูชัน DP

ตรรกะแบบง่ายที่ใช้ในโค้ดคือ:

  • ประกาศอาร์เรย์ 2 มิติ DP[m][n]
  • เติมแถวแรกและคอลัมน์แรกของอาร์เรย์ DP ด้วย 0
  • เอา i และ j สำหรับการวนซ้ำ
  • ถ้า pattern1[i] เท่ากับ pattern2[j] ให้อัปเดต DP[i][j] = DP[i-1][j-1] + 1
  • ถ้า pattern1[i] ไม่เท่ากับ pattern2[j] แล้ว DP[i][j] จะเป็นค่าสูงสุดระหว่าง DP[i-1][j] และ DP[i][j-1]
  • ทำต่อไปจนกระทั่ง i และ j ถึง m และ n
  • องค์ประกอบสุดท้าย DP[m-1][n-1] จะมีค่าความยาว

ในที่นี้ จะเรียกชื่อว่า DP[m-1][n-1] เนื่องจากดัชนีของอาร์เรย์เริ่มต้นจาก 0

คำถามที่พบบ่อย

กระบวนการเรียนรู้ของเครื่องใช้ LCS เป็นคุณลักษณะความคล้ายคลึงกันในการจำแนกประเภทข้อความ การประเมินลำดับต่อลำดับ และการตรวจจับการลอกเลียนแบบโค้ด นอกจากนี้ยังเป็นพื้นฐานของเมตริกแบบ BLEU และ ROUGE ที่ให้คะแนนข้อความที่สร้างขึ้นเทียบกับผลลัพธ์อ้างอิง

ใช่แล้ว โปรแกรมช่วยเขียนโค้ด AI อย่าง GitHub Copilot และ GPT สามารถสร้างเวอร์ชันการเขียนโปรแกรมแบบเรียกซ้ำและแบบไดนามิกของ LCS ได้ Python, C++หรือ Javaนอกจากนี้ ยังสามารถเพิ่มการจดจำผลลัพธ์ (memoization) พิมพ์ลำดับย่อยที่แท้จริง หรือแปลงโค้ดเป็นรูปแบบวนซ้ำได้ตามคำขอ

สตริงย่อยต้องต่อเนื่องกัน ในขณะที่ลำดับย่อยเพียงแค่ต้องรักษาลำดับไว้ สำหรับ "ABCDE" นั้น "ACD" เป็นลำดับย่อยที่ถูกต้องแต่ไม่ใช่สตริงย่อย ในขณะที่ "BCD" เป็นทั้งสตริงย่อยและลำดับย่อย

เวอร์ชันการเขียนโปรแกรมเชิงพลวัตใช้เวลาและพื้นที่ O(m·n) โดยที่ m และ n คือความยาวของลำดับอินพุตทั้งสอง เวอร์ชันการเรียกซ้ำแบบธรรมดาใช้เวลาแบบเลขชี้กำลัง O(2^(m+n)) ในกรณีที่เลวร้ายที่สุด

LCS เป็นเครื่องมือที่ทรงพลังสำหรับโปรแกรมเปรียบเทียบไฟล์, การรวมข้อมูลด้วย Git, การจัดเรียงลำดับดีเอ็นเอและโปรตีนในชีวสารสนเทศ, การตรวจจับการลอกเลียนแบบ, โปรแกรมตรวจสอบการสะกดคำ และเครื่องมือซิงโครไนซ์ข้อมูลที่ต้องรักษาลำดับของระเบียนที่ใช้ร่วมกันไว้

ตารางมาตรฐานต้องการพื้นที่ O(m·n) การเพิ่มประสิทธิภาพแบบสองแถวแบบหมุนเวียนจะลดพื้นที่ลงเหลือ O(min(m, n)) เมื่อคุณต้องการเพียงความยาวเท่านั้น แต่การสร้างลำดับย่อยที่แท้จริงขึ้นมาใหม่ยังคงต้องใช้ตารางทั้งหมด

ใช่ การเรียกซ้ำแบบบริสุทธิ์ใช้ได้กับสตริงสั้นๆ แต่จะคำนวณปัญหาย่อยเดิมซ้ำหลายครั้งและไม่เหมาะสมเมื่อสตริงยาวเกิน 20-25 ตัวอักษร การเพิ่มการจดจำผลลัพธ์ (memoization) หรือตาราง DP จะช่วยแก้ปัญหานี้ได้ tracประสิทธิภาพของตาราง

ใช่แล้ว แนวคิด DP ขยายไปถึงลำดับ k โดยใช้ตาราง k มิติ ด้วยเวลาและพื้นที่ O(n^k) รูปแบบนี้ปรากฏในเครื่องมือเปรียบเทียบไฟล์หลายไฟล์และการจัดเรียงลำดับหลายลำดับในชีวสารสนเทศ

สรุปโพสต์นี้ด้วย: