ডায়নামিক প্রোগ্রামিং৭ মিনিট পড়া

এডিট ডিসট্যান্স (Edit Distance)

একটি শব্দকে অন্য শব্দে রূপান্তর করতে কতগুলো টাইপো-ফিক্স বা সংশোধনের প্রয়োজন হয়?
time:\(O(m·n)\)space:\(O(m·n)\) বা \(O(\min(m,n))\)

আপনি হয়তো "kitten" টাইপ করেছেন এবং অটো-কারেক্ট সাজেস্ট করছে "sitting"। এটি কীভাবে বোঝে যে শব্দ দুটি কাছাকাছি? এটি মূলত একটি শব্দকে অন্যটিতে রূপান্তর করতে প্রয়োজনীয় ন্যূনতম একক-অক্ষর পরিবর্তনের সংখ্যা গণনা করে। একটি অক্ষর যোগ করা (insert), একটি অক্ষর মুছে ফেলা (delete), অথবা একটি অক্ষর অন্য অক্ষর দিয়ে পরিবর্তন করা (replace) — প্রতিটির খরচ বা কস্ট ১। এই ন্যূনতম সংখ্যাটিই হলো এডিট ডিসট্যান্স (Edit Distance) (একে লেভেনস্টেইন ডিসট্যান্সও বলা হয়)।

kitten → sitten (k-কে s দিয়ে পরিবর্তন) → sittin (e-কে i দিয়ে পরিবর্তন) → sitting (শেষে g যোগ করা)। এখানে মোট তিনটি অপারেশন লেগেছে। সুতরাং, এডিট ডিসট্যান্স =

তিনটি অপারেশন

ইনসার্ট (Insert): যেকোনো জায়গায় একটি অক্ষর যোগ করা। "cat" → "coat" = ১টি ইনসার্শন।

ডিলিট (Delete): একটি অক্ষর মুছে ফেলা। "cats" → "cat" = ১টি ডিলিশন।

রিপ্লেস (Replace): একটি অক্ষরের বদলে অন্য একটি অক্ষর বসানো। "cat" → "bat" = ১টি রিপ্লেসমেন্ট।

2D ডিপি (DP) টেবিল

ধরি dp[i][j] = s-এর প্রথম \(i\) সংখ্যক অক্ষরকে t-এর প্রথম \(j\) সংখ্যক অক্ষরে রূপান্তর করতে ন্যূনতম কতবার এডিট করতে হবে। টেবিলটি হবে (m+1) × (n+1) সাইজের।

বেস কেস (Base cases): dp[i][0] = i (s-এর সব \(i\) সংখ্যক অক্ষর মুছে ফেলা) এবং dp[0][j] = j (কিছুই না থাকা অবস্থা থেকে t তৈরি করতে \(j\) সংখ্যক অক্ষর ইনসার্ট করা)।

রিকারেন্স বা পুনরাবৃত্তি

যদি s[i-1] == t[j-1] হয়: dp[i][j] = dp[i-1][j-1]। অক্ষরগুলো মিলে গেছে — তাই কোনো অপারেশনের প্রয়োজন নেই, শুধু কোণাকুণি বা ডায়াগোনাল মানটি নিলেই হবে।

যদি s[i-1] != t[j-1] হয়: dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])। এটি একটি অপারেশন, সাথে এই তিনটির মধ্যে ন্যূনতমটি: রিপ্লেসিং (কোণাকুণি), s[i-1] ডিলিট করা (ওপরে), অথবা t[j-1] ইনসার্ট করা (বামে)।

স্পেস অপ্টিমাইজেশন (Space Optimization)

dp[i][j] মানটি শুধুমাত্র কোণাকুণি ওপর-বামে, সরাসরি ওপরে এবং সরাসরি বামে থাকা সেলের ওপর নির্ভর করে — তাই শুধুমাত্র একটি রRow বা সারি এবং একটি অতিরিক্ত ভেরিয়েবলই যথেষ্ট। এর ফলে স্পেস \(O(m·n)\) থেকে কমে \(O(\min(m,n))\) হয়ে যায়। এতে আপনি রূপান্তরের ধাপগুলো (reconstruction) ট্র্যাক করার ক্ষমতা হারাবেন, তবে ডিসট্যান্স ভ্যালুটি সঠিকভাবে পাবেন।

দ্রষ্টব্য: স্পেল চেকাররা সাধারণত এডিট ডিসট্যান্স ১ বা ২-এর মধ্যে থাকা শব্দগুলো সাজেস্ট করে — অর্থাৎ যেসব শব্দ মূলত 'একটি ভুল' বা 'দুটি ভুলের' দূরত্বে আছে। ডিএনএ অ্যালাইনমেন্ট (DNA alignment) টুলগুলোও একইভাবে পরিমাপ করে যে দুটি সিকোয়েন্স মানসিকভাবে বা জেনেটিকভাবে কতটা কাছাকাছি।

এডিট ডিসট্যান্স — ক্লাসিক ডিপি (Classic DP)

def edit_distance(s, t):
m, n = len(s), len(t)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1): dp[i][0] = i
for j in range(n+1): dp[0][j] = j
for i in range(1, m+1):
for j in range(1, n+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
return dp[m][n]
print(edit_distance('kitten', 'sitting')) # 3
print(edit_distance('horse', 'ros')) # 3
Output
3
3

Complexity

⏱️ সময় (Time)
\(m\) = s-এর দৈর্ঘ্য, \(n\) = t-এর দৈর্ঘ্য; প্রতিটি সেলের জন্য \(O(1)\) সময় লাগে
m×n সাইজের টেবিল পূরণ করুন \(O(m·n)\)
📊 স্পেস (সম্পূর্ণ টেবিল)
এডিটিং ধাপগুলো সঠিকভাবে পেছনের দিকে ট্র্যাক করার জন্য পুরো টেবিলটি প্রয়োজন
পুনর্গঠনের জন্য (Reconstruction) \(O(m·n)\)
🪄 স্পেস (শুধুমাত্র দূরত্ব)
শুধুমাত্র আগের সারিটি সংরক্ষণ করা হয় — ফলে রিকনস্ট্রাকশন ক্ষমতা হারিয়ে যায়
একটি সারি + একটি ভেরিয়েবল \(O(\min(m,n))\)
🐌 ব্রুট ফোর্স (Brute force)
সবগুলো ইনসার্ট/ডিলিট/রিপ্লেস সিকোয়েন্স ট্রাই করার চেয়ে ডিপি বহুগুণ দ্রুত কাজ করে
সব অপারেশন সিকোয়েন্স ট্রাই করা \(O(3^{m+n})\)

ছোট কুইজ

'kitten' এবং 'sitting'-এর মধ্যে এডিট ডিসট্যান্স কত?

পড়া চালিয়ে যান

লংগেস্ট কমন সাবসিকোয়েন্স বা দীর্ঘতম সাধারণ উপক্রম (Longest Common Subsequence)
দুটি সিকোয়েন্সের বা অনুক্রমের মধ্যে থাকা দীর্ঘতম একই অংশ বা গল্পটি খুঁজে বের করুন — যেখানে মাঝখানের কিছু অংশ এড়িয়ে যাওয়া বা স্কিপ (skip) করা সম্ভব
ডিপি প্যাটার্ন (DP Pattern)
একই পাজলের (puzzle) টুকরো বা সমস্যার সমাধান কখনোই দু'বার করবেন না — বরং সেটিকে লিখে রাখুন এবং দরকার হলে পরের বার সেখান থেকেই দেখে নিন