1. 问题背景
假设你在开发一款智能的词典软件,例如用户想查询单词fish,但不小心输入了fosh。编辑器是否能智能提示用户fish单词呢?
假设用户输入了fosh,那用户原本想输入的是fish还是fort呢?我们先在网易有道词典上做一下实验,接下来再讲一下原理和代码实现
输入fosh提示fish
网易有道词典:输入fosh提示fish
2. 最长公共子序列问题解决原理
2.1 流程
伪代码如下
if word_a[i]==word_b[j]:
cell[i][j] = cell[i-1][j-1]+1
else:
cell[i][j]=max(cell[i-1][j],cell[i][j-1])
以下为表格法计算最长公共子序列可以看出,
FOSH与FORT为2
FOSH与FISH为3
所以用户更可能是想输入FISH这个单词
FOSH与FORT的对比
2.2 代码实现
相比学术届的MatLab,Java、JavaScript等语言在工业界更流行, 笔者经常使用JavaScript语言进行开发,所以给出JavaScript计算最长公共子序列的JavaScript实现
/**
* Create a matrix
* @param {number} rows Number of rows
* @param {number} columns ANumber of columns
* @returns {Array} Matrix
*/
const createMatrix = (rows = 0, columns = 0) => {
const matrix = [];
for (let i = 0; i < rows; i++) {
matrix[i] = Array(columns).fill(0);
}
return matrix;
};
/**
* Find the longest substring
* @param {string} firstWord First word
* @param {string} secondWord Second word
* @returns {string} The longest substring
*/
const longestSubstring = (firstWord = "", secondWord = "") => {
const matrix = JSON.parse(
JSON.stringify(createMatrix(firstWord.length, secondWord.length))
);
let sizeSequence = 0;
let indexSequence = 0;
for (let i = 0; i < firstWord.length; i++) {
for (let j = 0; j < secondWord.length; j++) {
if (firstWord[i] === secondWord[j]) {
matrix[i][j] = (i && j) > 0 ? matrix[i - 1][j - 1] + 1 : 1;
if (matrix[i][j] >= sizeSequence) {
sizeSequence = matrix[i][j];
indexSequence = i + 1;
}
} else {
matrix[i][j] = 0;
}
}
}
return firstWord.slice(indexSequence - sizeSequence, indexSequence);
};
longestSubstring("vista", "hish"); // "is"
longestSubstring("fish", "hish"); // "ish"
/**
* Find the longest common subsequence
* @param {string} firstWord First word
* @param {string} secondWord Second word
* @returns {number} The longest common subsequence
*/
const longestCommonSubsequence = (firstWord = "", secondWord = "") => {
const matrix = JSON.parse(
JSON.stringify(createMatrix(firstWord.length, secondWord.length))
);
if (matrix.length === 0 || matrix[0].length === 0) return 0;
for (let i = 0; i < firstWord.length; i++) {
for (let j = 0; j < secondWord.length; j++) {
if (firstWord[i] === secondWord[j]) {
matrix[i][j] = (i && j) > 0 ? matrix[i - 1][j - 1] + 1 : 1;
} else {
matrix[i][j] = Math.max(
i > 0 ? matrix[i - 1][j] : 0,
j > 0 ? matrix[i][j - 1] : 0
);
}
}
}
return matrix[firstWord.length - 1][secondWord.length - 1];
};
longestCommonSubsequence("fish", "fosh"); // 3
longestCommonSubsequence("fort", "fosh"); // 2
对比CTTTGACT与ATGA的公共子序列
3. 总结
在调研了过程总发现了由谷歌出品优化工具箱,并选择了一个经典的求最长公共子序列的经典问题。解决动态规划问题的思考路径如下:
- 需要在给定约束条件下优化某种指标时,动态规划很有用
- 问题可以分解为离散子问题时,可以使用动态规划解决
- 每种动态规划解决方案都涉及网格
- 单元格中的值通常就是需要优化的值
- 每个单元格都是一个子问题,因此需要考虑如何将问题分解为子问题
- 没有放之四海皆准的计算动态规划解决方案的公式