آرین سلیمان‌زاده
  • خانه
  • وبلاگ
  • پادکست‌ها
  • ویدیوها
  • تماس با من
العربيةArabic
DeutschGerman
EnglishEnglish
فارسیPersian
한국어Korean
中文Chinese
پنل•تماس سریع

Languages

Choose your interface locale

ar

العربية

Arabic

de

Deutsch

German

en

English

English

fa

فارسی

Persian

ko

한국어

Korean

zh

中文

Chinese

درخواست همکاری یا مشاوره

پیام خود را درباره همکاری، مشاوره، توسعه محصول یا پروژه نرم‌افزاری ارسال کنید.

LinkedInپاسخ سریع
خانه/مقاله‌ها/Longest Common Substring چیست؟ پیدا کردن طولانی‌ترین بخش مشترک دو رشته با Dynamic Programming
Algorithmsمقاله

Longest Common Substring چیست؟ پیدا کردن طولانی‌ترین بخش مشترک دو رشته با Dynamic Programming

Longest Common Substring مسئله‌ای کلاسیک برای پیدا کردن طولانی‌ترین بخش پیوسته مشترک بین دو رشته است. در این مقاله مفهوم، جدول Dynamic Programming، پیاده‌سازی TypeScript، پیچیدگی و کاربردهای واقعی آن را بررسی می‌کنیم.

۲۸ مرداد ۱۴۰۵9 دقیقه مطالعه3 بازدید
#Algorithms#Longest Common Substring#String Algorithms#Dynamic Programming#TypeScript#Computer Science

Arian Soleimanzadeh

Software Engineer & Researcher

نمایش الگوریتم Longest Common Substring با جدول Dynamic Programming و بخش مشترک دو رشته

Arian Soleimanzadeh

هوش مصنوعی · کد · محصول

پژوهش + مهندسی
در این صفحه
Substring چیست؟یک مثال سادهروش Brute Forceاستفاده از Dynamic Programmingرابطه اصلی DPمثال مرحله‌به‌مرحلهچرا فقط بزرگ‌ترین مقدار جدول را پیدا می‌کنیم؟پیاده‌سازی ساده با TypeScriptبرگرداندن خود Substringپیچیدگی زمانی و فضاییبهینه‌سازی حافظهLongest Common Substring و Longest Common Subsequence چه تفاوتی دارند؟Longest Common Subsequenceتفاوت در رابطه DPکاربرد در تشخیص شباهت متنکاربرد در Data Deduplicationکاربرد در Bioinformaticsکاربرد در Version Comparisonکاربرد در Plagiarism Detectionکاربرد در Log و Security Analysisآیا می‌توان چند جواب صحیح داشت؟Edge Caseهایکی از رشته‌ها خالی باشدهیچ کاراکتر مشترکی وجود نداشته باشددو رشته کاملاً برابر باشندتفاوت حروف بزرگ و کوچکیک سؤال رایج مصاحبهاشتباه رایج: استفاده از منطق LCSآیا الگوریتم سریع‌تری وجود دارد؟جمع‌بندی

Longest Common Substring یکی از مسائل کلاسیک String Algorithms است که هدف آن پیدا کردن طولانی‌ترین بخش پیوسته مشترک میان دو رشته است.

کلمه مهم در این تعریف، پیوسته یا Contiguous بودن است.

برای مثال دو رشته زیر را در نظر بگیرید:

Code
12
ABABC
BABCA

بخش مشترک:

Code
1
BABC

در هر دو رشته به صورت پیوسته وجود دارد.

بنابراین:

Code
12
Longest Common Substring = "BABC"
Length = 4

این مسئله در نگاه اول ساده به نظر می‌رسد، اما تفاوت مهمی با مسئله معروف Longest Common Subsequence یا LCS دارد.


Substring چیست؟

Substring بخشی از یک رشته است که کاراکترهای آن باید پشت سر هم باشند.

برای رشته:

Code
1
ABCDE

موارد زیر Substring هستند:

Code
1234
ABC
BCD
DE
C

اما:

Code
1
ACE

Substring نیست، زیرا کاراکترها در رشته اصلی پشت سر هم نیستند.


یک مثال ساده

دو رشته زیر را داریم:

Code
12
String 1: programming
String 2: gaming

یکی از بخش‌های مشترک مهم:

Code
1
ming

است.

این چهار کاراکتر در انتهای هر دو رشته به صورت پیوسته ظاهر شده‌اند.

در نتیجه:

Code
12
Longest Common Substring = "ming"
Length = 4

روش Brute Force

ساده‌ترین راه این است که تمام Substringهای رشته اول را تولید کنیم و بررسی کنیم کدام‌یک در رشته دوم وجود دارند.

یک رشته با طول n تقریباً:

Code
1
n × (n + 1) / 2

Substring دارد.

اگر هرکدام را نیز در رشته دوم جستجو کنیم، هزینه محاسباتی می‌تواند به سرعت زیاد شود.

این روش برای ورودی‌های کوچک قابل استفاده است، اما برای داده‌های بزرگ انتخاب مناسبی نیست.


استفاده از Dynamic Programming

روش کلاسیک برای حل این مسئله استفاده از Dynamic Programming است.

فرض کنید دو رشته داریم:

Code
12
a
b

تعریف می‌کنیم:

Code
1
dp[i][j]

برابر باشد با طول Longest Common Substring که دقیقاً در موقعیت i - 1 از رشته اول و j - 1 از رشته دوم پایان پیدا می‌کند.

این قسمت از تعریف بسیار مهم است.


رابطه اصلی DP

اگر دو کاراکتر برابر باشند:

Code
1
a[i - 1] === b[j - 1]

می‌توانیم Substring قبلی را یک کاراکتر ادامه دهیم:

Code
1
dp[i][j] = dp[i - 1][j - 1] + 1

اما اگر متفاوت باشند:

Code
1
dp[i][j] = 0

چرا صفر؟

چون Substring باید پیوسته باشد. یک mismatch زنجیره پیوستگی را قطع می‌کند.

این مهم‌ترین تفاوت با Longest Common Subsequence است.


مثال مرحله‌به‌مرحله

فرض کنید:

Code
12
a = "ABABC"
b = "BABCA"

جدول DP به صورت مفهومی چنین خواهد بود:

Code
123456
      B  A  B  C  A
A     0  1  0  0  1
B     1  0  2  0  0
A     0  2  0  0  1
B     1  0  3  0  0
C     0  0  0  4  0

بزرگ‌ترین عدد موجود در جدول:

Code
1
4

است.

این مقدار در کاراکتر C پایان یافته و Substring متناظر:

Code
1
BABC

است.


چرا فقط بزرگ‌ترین مقدار جدول را پیدا می‌کنیم؟

برخلاف بعضی مسائل Dynamic Programming، پاسخ الزاماً در آخرین Cell جدول قرار ندارد.

برای مثال ممکن است Longest Common Substring در وسط دو رشته تمام شود.

بنابراین هنگام پر کردن جدول باید یک متغیر مانند:

Code
1
maxLength

نگه داریم و بزرگ‌ترین مقدار دیده‌شده را ذخیره کنیم.


پیاده‌سازی ساده با TypeScript

TypeScript
12345678910111213141516171819202122232425262728
function longestCommonSubstringLength(
  a: string,
  b: string
): number {
  const dp: number[][] = Array.from(
    { length: a.length + 1 },
    () => new Array(b.length + 1).fill(0)
  );

  let maxLength = 0;

  for (let i = 1; i <= a.length; i++) {
    for (let j = 1; j <= b.length; j++) {
      if (a[i - 1] === b[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
        maxLength = Math.max(maxLength, dp[i][j]);
      } else {
        dp[i][j] = 0;
      }
    }
  }

  return maxLength;
}

console.log(
  longestCommonSubstringLength("ABABC", "BABCA")
); // 4

برگرداندن خود Substring

در بسیاری از مسائل فقط طول کافی نیست و می‌خواهیم خود رشته مشترک را نیز پیدا کنیم.

برای این کار علاوه بر maxLength، موقعیت پایان بهترین Match را نیز ذخیره می‌کنیم.

TypeScript
12345678910111213141516171819202122232425262728293031
function longestCommonSubstring(
  a: string,
  b: string
): string {
  const dp: number[][] = Array.from(
    { length: a.length + 1 },
    () => new Array(b.length + 1).fill(0)
  );

  let maxLength = 0;
  let endIndex = 0;

  for (let i = 1; i <= a.length; i++) {
    for (let j = 1; j <= b.length; j++) {
      if (a[i - 1] === b[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1] + 1;

        if (dp[i][j] > maxLength) {
          maxLength = dp[i][j];
          endIndex = i;
        }
      }
    }
  }

  return a.slice(endIndex - maxLength, endIndex);
}

console.log(
  longestCommonSubstring("ABABC", "BABCA")
); // BABC

پیچیدگی زمانی و فضایی

اگر طول رشته اول n و طول رشته دوم m باشد:

Code
12
Time Complexity: O(n × m)
Space Complexity: O(n × m)

زیرا برای هر ترکیب از موقعیت‌های دو رشته یک Cell محاسبه می‌کنیم.


بهینه‌سازی حافظه

برای محاسبه Row فعلی فقط به Row قبلی نیاز داریم.

بنابراین می‌توانیم فضای حافظه را از:

Code
1
O(n × m)

به:

Code
1
O(m)

یا دقیق‌تر:

Code
1
O(min(n, m))

کاهش دهیم.

نمونه:

TypeScript
1234567891011121314151617181920212223242526
function longestCommonSubstringOptimized(
  a: string,
  b: string
): number {
  if (b.length > a.length) {
    [a, b] = [b, a];
  }

  let previous = new Array(b.length + 1).fill(0);
  let maxLength = 0;

  for (let i = 1; i <= a.length; i++) {
    const current = new Array(b.length + 1).fill(0);

    for (let j = 1; j <= b.length; j++) {
      if (a[i - 1] === b[j - 1]) {
        current[j] = previous[j - 1] + 1;
        maxLength = Math.max(maxLength, current[j]);
      }
    }

    previous = current;
  }

  return maxLength;
}

Longest Common Substring و Longest Common Subsequence چه تفاوتی دارند؟

این دو نام بسیار شبیه‌اند و یکی از رایج‌ترین اشتباهات در مسائل الگوریتمی همین است.

فرض کنید:

Code
12
a = "ABCDEF"
b = "ACEF"

Longest Common Subsequence

می‌تواند باشد:

Code
1
ACEF

زیرا لازم نیست کاراکترها کنار هم باشند؛ فقط ترتیب باید حفظ شود.

اما Longest Common Substring باید پیوسته باشد.

در این مثال یکی از Substringهای مشترک:

Code
1
EF

است.

بنابراین:

Code
1
Longest Common Subsequence ≠ Longest Common Substring

تفاوت در رابطه DP

برای Longest Common Substring، هنگام mismatch داریم:

Code
1
dp[i][j] = 0

اما در LCS معمولاً داریم:

Code
1234
dp[i][j] = max(
  dp[i - 1][j],
  dp[i][j - 1]
)

همین تفاوت کوچک نشان‌دهنده تفاوت بنیادی دو مسئله است.


کاربرد در تشخیص شباهت متن

فرض کنید دو رشته داریم:

Code
12
customer_relationship_management
relationship_manager

وجود بخش مشترک بزرگی مانند:

Code
1
relationship_manag

می‌تواند نشان دهد دو رشته دارای بخش محتوایی مشترک قابل توجهی هستند.

البته Longest Common Substring به تنهایی معیار کامل Similarity نیست، اما می‌تواند یکی از Featureها باشد.


کاربرد در Data Deduplication

در سیستم‌های CRM، Data Cleaning یا Import ممکن است داده‌هایی داشته باشیم که بخشی از آن‌ها مشترک است.

مثلاً:

Code
12
Arian Soleimanzadeh Software Engineer
Arian Soleimanzadeh

یک Substring مشترک طولانی می‌تواند یکی از Signalهای تشخیص ارتباط دو Record باشد.

در سیستم واقعی باید این معیار را با مواردی مانند:

  • Levenshtein Distance
  • Exact Email Match
  • Phone Match
  • Token Similarity
  • Normalization

ترکیب کنیم.


کاربرد در Bioinformatics

Sequenceهای DNA یا Protein را می‌توان مانند رشته‌ها در نظر گرفت.

برای مثال:

Code
12
ACGTACGT
TTGTACGG

یک بخش مشترک پیوسته مانند:

Code
1
GTACG

می‌تواند نشان‌دهنده یک ناحیه مشابه میان Sequenceها باشد.

البته Bioinformatics واقعی معمولاً از الگوریتم‌های تخصصی‌تر Sequence Alignment نیز استفاده می‌کند.


کاربرد در Version Comparison

فرض کنید دو نسخه از یک متن یا Config داریم.

Code
12345
version 1:
/api/v1/customer/profile

version 2:
/api/v2/customer/profile

بخش:

Code
1
/customer/profile

مشترک است.

Longest Common Substring می‌تواند در بعضی مسائل مقایسه Versionها یا Sequenceها به عنوان یک Building Block استفاده شود.


کاربرد در Plagiarism Detection

یکی از ایده‌های ابتدایی برای پیدا کردن بخش‌های کپی‌شده میان دو متن، بررسی Segmentهای مشترک طولانی است.

اگر دو Document دارای چندین Substring بلند و یکسان باشند، می‌تواند یک Signal برای Similarity باشد.

البته سیستم‌های واقعی Plagiarism Detection بسیار پیچیده‌تر هستند و معمولاً از Tokenization، Fingerprinting، N-grams و Semantic Similarity نیز استفاده می‌کنند.


کاربرد در Log و Security Analysis

گاهی لازم است قسمت‌های مشترک میان چند Log Entry یا Payload بررسی شوند.

مثلاً:

Code
12
ERROR_AUTH_TOKEN_EXPIRED_USER_101
ERROR_AUTH_TOKEN_EXPIRED_USER_205

بخش مشترک بزرگی مانند:

Code
1
ERROR_AUTH_TOKEN_EXPIRED_USER_

می‌تواند به تشخیص Patternهای مشابه کمک کند.


آیا می‌توان چند جواب صحیح داشت؟

بله.

ممکن است دو یا چند Substring با طول برابر بیشینه وجود داشته باشند.

برای مثال:

Code
12
a = "abcXYZ123"
b = "abcABC123"

دو Substring مشترک داریم:

Code
12
abc
123

که هر دو طول 3 دارند.

اگر فقط یک جواب لازم باشد، معمولاً اولین یا آخرین Match را برمی‌گردانیم.

اگر همه جواب‌ها لازم باشند باید End Index تمام Cellهایی را که مقدارشان برابر maxLength است نگه داریم.


Edge Caseها

یکی از رشته‌ها خالی باشد

Code
12
"hello"
""

پاسخ:

Code
1
Length = 0

هیچ کاراکتر مشترکی وجود نداشته باشد

Python
12
abc
def

پاسخ:

Code
1
0

دو رشته کاملاً برابر باشند

Code
12
algorithm
algorithm

کل رشته پاسخ است.

تفاوت حروف بزرگ و کوچک

Code
12
Hello
hello

در مقایسه استاندارد H و h متفاوت هستند.

اگر Case-insensitive Search نیاز باشد، قبل از الگوریتم باید Normalization انجام شود.


یک سؤال رایج مصاحبه

صورت مسئله معمولاً به این شکل است:

دو String داده شده است. طول یا خود Longest Common Substring آن‌ها را پیدا کنید.

یک جواب خوب باید شامل این نکات باشد:

  1. تعریف صحیح Substring و پیوستگی.
  2. تعریف dp[i][j].
  3. Reset کردن مقدار به صفر هنگام mismatch.
  4. نگه داشتن maxLength.
  5. تحلیل پیچیدگی O(n × m).
  6. در صورت نیاز، توضیح Space Optimization.

اشتباه رایج: استفاده از منطق LCS

رایج‌ترین اشتباه این است که هنگام mismatch بنویسیم:

Code
1
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

این رابطه مربوط به Longest Common Subsequence است.

برای Substring باید زنجیره قطع شود:

Code
1
dp[i][j] = 0

زیرا دیگر Match پیوسته نیست.


آیا الگوریتم سریع‌تری وجود دارد؟

بله. برای ورودی‌های بسیار بزرگ می‌توان از ساختارها و الگوریتم‌های پیشرفته‌تری مانند:

  • Suffix Tree
  • Suffix Array
  • Suffix Automaton

استفاده کرد.

برخی از این روش‌ها می‌توانند مسئله را با پیچیدگی زمانی بسیار بهتر حل کنند، اما پیاده‌سازی و درک آن‌ها پیچیده‌تر است.

برای بسیاری از مسائل آموزشی، مصاحبه‌ای و ورودی‌های متوسط، روش Dynamic Programming انتخابی واضح و قابل فهم است.


جمع‌بندی

Longest Common Substring طولانی‌ترین بخش پیوسته مشترک میان دو رشته را پیدا می‌کند.

رابطه اصلی آن بسیار ساده است:

Code
1234
if a[i - 1] === b[j - 1]
    dp[i][j] = dp[i - 1][j - 1] + 1
else
    dp[i][j] = 0

پیچیدگی نسخه کلاسیک:

Code
12
Time Complexity:  O(n × m)
Space Complexity: O(n × m)

و با بهینه‌سازی حافظه:

Code
1
Space Complexity: O(min(n, m))

مهم‌ترین نکته برای به خاطر سپردن این است:

در Longest Common Substring، Match باید بدون وقفه ادامه پیدا کند؛ یک mismatch طول زنجیره فعلی را به صفر برمی‌گرداند.

همین ویژگی آن را از Longest Common Subsequence متمایز می‌کند.

در این صفحه
Substring چیست؟یک مثال سادهروش Brute Forceاستفاده از Dynamic Programmingرابطه اصلی DPمثال مرحله‌به‌مرحلهچرا فقط بزرگ‌ترین مقدار جدول را پیدا می‌کنیم؟پیاده‌سازی ساده با TypeScriptبرگرداندن خود Substringپیچیدگی زمانی و فضاییبهینه‌سازی حافظهLongest Common Substring و Longest Common Subsequence چه تفاوتی دارند؟Longest Common Subsequenceتفاوت در رابطه DPکاربرد در تشخیص شباهت متنکاربرد در Data Deduplicationکاربرد در Bioinformaticsکاربرد در Version Comparisonکاربرد در Plagiarism Detectionکاربرد در Log و Security Analysisآیا می‌توان چند جواب صحیح داشت؟Edge Caseهایکی از رشته‌ها خالی باشدهیچ کاراکتر مشترکی وجود نداشته باشددو رشته کاملاً برابر باشندتفاوت حروف بزرگ و کوچکیک سؤال رایج مصاحبهاشتباه رایج: استفاده از منطق LCSآیا الگوریتم سریع‌تری وجود دارد؟جمع‌بندی

جزئیات مقاله

اطلاعات انتشار، زمان مطالعه و تعداد بازدید این محتوا.

انتشار

۲۸ مرداد ۱۴۰۵

آخرین ویرایش

۲۹ مرداد ۱۴۰۵

زمان مطالعه

9 دقیقه مطالعه

بازدید

3

نویسنده

Arian Soleimanzadeh

مقاله قبلی

الگوریتم Kosaraju چیست و اصلاً چه مشکلی را حل می‌کند؟

مقاله بعدی

Palindrome چیست؟ آموزش تشخیص رشته‌های قرینه با Two Pointers و TypeScript

بیایید محصولی هوشمند، دقیق و مقیاس‌پذیر بسازیم.

ارتباط سریع برای همکاری، مشاوره، توسعه محصول یا طراحی سامانه‌های هوشمند کسب‌وکار.

تماس سریعایمیل به من
آرین سلیمان‌زاده

پورتفولیوی شخصی با تمرکز بر Agentic CRM، سامانه‌های هوشمند کسب‌وکار، مهندسی مدرن وب، طراحی سیستم‌های رابط کاربری و توسعه محصولات نرم‌افزاری کاربردی.

لینک‌های سریع

  • درباره من
  • وبلاگ
  • پروژه‌ها
  • تماس

ارتباط

  • info@ariansoleimanzadeh.site
  • soleimanzadeh.a.work@gmail.com

در دسترس: روزهای کاری

معمولاً پاسخ در ۲۴ ساعت

خبرنامه

به‌روزرسانی‌های مربوط به نوشته‌ها، پروژه‌ها و انتشارهای جدید را دریافت کنید.

© 2026 ariansoleimanzadeh.site — تمامی حقوق محفوظ است.

لینکدین