Type two strings below and watch the dynamic-programming table fill in. Each cell (i, j) holds the edit distance between the first i characters of the top string and the first j characters of the side string. The answer appears in the bottom-right corner.
<div class="hint">Type two strings to see the edit-distance DP table. Each cell (i,j) is the cost to turn the first <em>i</em> characters of <b>A</b> into the first <em>j</em> of <b>B</b>. The answer is in the bottom-right.</div>
<div class="inputs">
<label>A: <input id="strA" type="text" value="kitten" maxlength="14" /></label>
<label>B: <input id="strB" type="text" value="sitting" maxlength="14" /></label>
</div>
<div id="result" class="result"></div>
<div id="tableWrap" class="table-wrap"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin-bottom: .6rem; line-height: 1.45; }
.inputs { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: .7rem; }
.inputs label { font-size: .9rem; font-weight: 600; display: flex; align-items: center; gap: .4rem; }
.inputs input { font: inherit; border: 1.5px solid #adb5bd; border-radius: 6px; padding: .3rem .5rem; width: 130px; }
.result { font-size: 1rem; font-weight: 700; margin-bottom: .6rem; min-height: 1.4em; color: #1d3557; }
.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; font-size: .8rem; }
th, td { width: 30px; height: 30px; text-align: center; vertical-align: middle; border: 1px solid #dee2e6; }
thead th { background: #e8eef3; font-weight: 700; color: #1d3557; }
tbody th { background: #e8eef3; font-weight: 700; color: #1d3557; }
td { background: #f8fafc; color: #333; }
td.diag { background: #d4edda; color: #155724; font-weight: 700; }
td.answer { background: #1d3557; color: #fff; font-weight: 700; border-radius: 4px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Levenshtein edit distance via standard DP.
// dp[i][j] = edit distance between a[0..i-1] and b[0..j-1].
// Recurrence: dp[i][j] = dp[i-1][j-1] if a[i-1]===b[j-1]
// = 1 + min(dp[i-1][j], // delete from a
// dp[i][j-1], // insert into a
// dp[i-1][j-1]) // substitute
function editDistance(a, b) {
const n = a.length, m = b.length;
const dp = Array.from({length: n+1}, (_, i) => Array.from({length: m+1}, (_, j) => i===0 ? j : j===0 ? i : 0));
for (let i = 1; i <= n; i++)
for (let j = 1; j <= m; j++)
dp[i][j] = a[i-1] === b[j-1]
? dp[i-1][j-1]
: 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
return dp;
}
function render() {
const a = document.getElementById('strA').value.slice(0, 14);
const b = document.getElementById('strB').value.slice(0, 14);
const dp = editDistance(a, b);
const dist = dp[a.length][b.length];
document.getElementById('result').textContent =
fmt('Edit distance: %dist% | Table size: %v% Ă %v2% = %v3% cells', { dist: dist, v: a.length+1, v2: b.length+1, v3: (a.length+1)*(b.length+1) });
// Build table
const wrap = document.getElementById('tableWrap');
const tbl = document.createElement('table');
// Header row: '' | '' | b chars
const thead = document.createElement('thead');
const hr = document.createElement('tr');
[' ', ' ', ...b.split('')].forEach((ch, i) => {
const th = document.createElement('th'); th.textContent = ch; hr.appendChild(th);
});
thead.appendChild(hr); tbl.appendChild(thead);
// Body rows
const tbody = document.createElement('tbody');
for (let i = 0; i <= a.length; i++) {
const tr = document.createElement('tr');
// row label
const lbl = document.createElement('th');
lbl.textContent = i === 0 ? ' ' : a[i-1];
tr.appendChild(lbl);
for (let j = 0; j <= b.length; j++) {
const td = document.createElement('td');
td.textContent = dp[i][j];
if (i === a.length && j === b.length) td.className = 'answer';
else if (i > 0 && j > 0 && a[i-1] === b[j-1] && dp[i][j] === dp[i-1][j-1]) td.className = 'diag';
tr.appendChild(td);
}
tbody.appendChild(tr);
}
tbl.appendChild(tbody);
wrap.innerHTML = ''; wrap.appendChild(tbl);
}
document.getElementById('strA').addEventListener('input', render);
document.getElementById('strB').addEventListener('input', render);
render();
// Verification log (for node test): editDistance("kitten","sitting")[6][7] === 3
// editDistance("","abc")[0][3] === 3, editDistance("abc","abc")[3][3] === 0
console.log('kitten->sitting:', editDistance('kitten','sitting')[6][7]);
console.log('empty->abc:', editDistance('','abc')[0][3]);
console.log('abc->abc:', editDistance('abc','abc')[3][3]);
console.log('abc->:', editDistance('abc','')[3][0]);
<div class="hint">Type two strings to see the edit-distance DP table. Each cell (i,j) is the cost to turn the first <em>i</em> characters of <b>A</b> into the first <em>j</em> of <b>B</b>. The answer is in the bottom-right.</div>
<div class="inputs">
<label>A: <input id="strA" type="text" value="kitten" maxlength="14" /></label>
<label>B: <input id="strB" type="text" value="sitting" maxlength="14" /></label>
</div>
<div id="result" class="result"></div>
<div id="tableWrap" class="table-wrap"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.hint { font-size: .88rem; color: #444; margin-bottom: .6rem; line-height: 1.45; }
.inputs { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: .7rem; }
.inputs label { font-size: .9rem; font-weight: 600; display: flex; align-items: center; gap: .4rem; }
.inputs input { font: inherit; border: 1.5px solid #adb5bd; border-radius: 6px; padding: .3rem .5rem; width: 130px; }
.result { font-size: 1rem; font-weight: 700; margin-bottom: .6rem; min-height: 1.4em; color: #1d3557; }
.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; font-size: .8rem; }
th, td { width: 30px; height: 30px; text-align: center; vertical-align: middle; border: 1px solid #dee2e6; }
thead th { background: #e8eef3; font-weight: 700; color: #1d3557; }
tbody th { background: #e8eef3; font-weight: 700; color: #1d3557; }
td { background: #f8fafc; color: #333; }
td.diag { background: #d4edda; color: #155724; font-weight: 700; }
td.answer { background: #1d3557; color: #fff; font-weight: 700; border-radius: 4px; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Levenshtein edit distance via standard DP.
// dp[i][j] = edit distance between a[0..i-1] and b[0..j-1].
// Recurrence: dp[i][j] = dp[i-1][j-1] if a[i-1]===b[j-1]
// = 1 + min(dp[i-1][j], // delete from a
// dp[i][j-1], // insert into a
// dp[i-1][j-1]) // substitute
function editDistance(a, b) {
const n = a.length, m = b.length;
const dp = Array.from({length: n+1}, (_, i) => Array.from({length: m+1}, (_, j) => i===0 ? j : j===0 ? i : 0));
for (let i = 1; i <= n; i++)
for (let j = 1; j <= m; j++)
dp[i][j] = a[i-1] === b[j-1]
? dp[i-1][j-1]
: 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
return dp;
}
function render() {
const a = document.getElementById('strA').value.slice(0, 14);
const b = document.getElementById('strB').value.slice(0, 14);
const dp = editDistance(a, b);
const dist = dp[a.length][b.length];
document.getElementById('result').textContent =
fmt('Edit distance: %dist% | Table size: %v% Ă %v2% = %v3% cells', { dist: dist, v: a.length+1, v2: b.length+1, v3: (a.length+1)*(b.length+1) });
// Build table
const wrap = document.getElementById('tableWrap');
const tbl = document.createElement('table');
// Header row: '' | '' | b chars
const thead = document.createElement('thead');
const hr = document.createElement('tr');
[' ', ' ', ...b.split('')].forEach((ch, i) => {
const th = document.createElement('th'); th.textContent = ch; hr.appendChild(th);
});
thead.appendChild(hr); tbl.appendChild(thead);
// Body rows
const tbody = document.createElement('tbody');
for (let i = 0; i <= a.length; i++) {
const tr = document.createElement('tr');
// row label
const lbl = document.createElement('th');
lbl.textContent = i === 0 ? ' ' : a[i-1];
tr.appendChild(lbl);
for (let j = 0; j <= b.length; j++) {
const td = document.createElement('td');
td.textContent = dp[i][j];
if (i === a.length && j === b.length) td.className = 'answer';
else if (i > 0 && j > 0 && a[i-1] === b[j-1] && dp[i][j] === dp[i-1][j-1]) td.className = 'diag';
tr.appendChild(td);
}
tbody.appendChild(tr);
}
tbl.appendChild(tbody);
wrap.innerHTML = ''; wrap.appendChild(tbl);
}
document.getElementById('strA').addEventListener('input', render);
document.getElementById('strB').addEventListener('input', render);
render();
// Verification log (for node test): editDistance("kitten","sitting")[6][7] === 3
// editDistance("","abc")[0][3] === 3, editDistance("abc","abc")[3][3] === 0
console.log('kitten->sitting:', editDistance('kitten','sitting')[6][7]);
console.log('empty->abc:', editDistance('','abc')[0][3]);
console.log('abc->abc:', editDistance('abc','abc')[3][3]);
console.log('abc->:', editDistance('abc','')[3][0]);
Notice that every cell depends on its three neighbors â left, above, and diagonal. The table has n Ă m cells, so the algorithm does exactly that much work. There is no known shortcut that avoids filling most of the table, and the SETH lower bound suggests none exists.
Comments
Loading comments...