1. 问题背景

假设你在开发一款智能的词典软件,例如用户想查询单词fish,但不小心输入了fosh。编辑器是否能智能提示用户fish单词呢?
假设用户输入了fosh,那用户原本想输入的是fish还是fort呢?我们先在网易有道词典上做一下实验,接下来再讲一下原理和代码实现

image.png
输入fosh提示fish

image.png
网易有道词典:输入fosh提示fish

2. 最长公共子序列问题解决原理

2.1 流程

伪代码如下

  1. if word_a[i]==word_b[j]:
  2. cell[i][j] = cell[i-1][j-1]+1
  3. else:
  4. cell[i][j]=max(cell[i-1][j],cell[i][j-1])

以下为表格法计算最长公共子序列可以看出,
FOSH与FORT为2
FOSH与FISH为3
所以用户更可能是想输入FISH这个单词
image.png
FOSHFORT的对比

image.png
FOSHFISH的对比

2.2 代码实现

相比学术届的MatLab,Java、JavaScript等语言在工业界更流行, 笔者经常使用JavaScript语言进行开发,所以给出JavaScript计算最长公共子序列的JavaScript实现

  1. /**
  2. * Create a matrix
  3. * @param {number} rows Number of rows
  4. * @param {number} columns ANumber of columns
  5. * @returns {Array} Matrix
  6. */
  7. const createMatrix = (rows = 0, columns = 0) => {
  8. const matrix = [];
  9. for (let i = 0; i < rows; i++) {
  10. matrix[i] = Array(columns).fill(0);
  11. }
  12. return matrix;
  13. };
  14. /**
  15. * Find the longest substring
  16. * @param {string} firstWord First word
  17. * @param {string} secondWord Second word
  18. * @returns {string} The longest substring
  19. */
  20. const longestSubstring = (firstWord = "", secondWord = "") => {
  21. const matrix = JSON.parse(
  22. JSON.stringify(createMatrix(firstWord.length, secondWord.length))
  23. );
  24. let sizeSequence = 0;
  25. let indexSequence = 0;
  26. for (let i = 0; i < firstWord.length; i++) {
  27. for (let j = 0; j < secondWord.length; j++) {
  28. if (firstWord[i] === secondWord[j]) {
  29. matrix[i][j] = (i && j) > 0 ? matrix[i - 1][j - 1] + 1 : 1;
  30. if (matrix[i][j] >= sizeSequence) {
  31. sizeSequence = matrix[i][j];
  32. indexSequence = i + 1;
  33. }
  34. } else {
  35. matrix[i][j] = 0;
  36. }
  37. }
  38. }
  39. return firstWord.slice(indexSequence - sizeSequence, indexSequence);
  40. };
  41. longestSubstring("vista", "hish"); // "is"
  42. longestSubstring("fish", "hish"); // "ish"
  43. /**
  44. * Find the longest common subsequence
  45. * @param {string} firstWord First word
  46. * @param {string} secondWord Second word
  47. * @returns {number} The longest common subsequence
  48. */
  49. const longestCommonSubsequence = (firstWord = "", secondWord = "") => {
  50. const matrix = JSON.parse(
  51. JSON.stringify(createMatrix(firstWord.length, secondWord.length))
  52. );
  53. if (matrix.length === 0 || matrix[0].length === 0) return 0;
  54. for (let i = 0; i < firstWord.length; i++) {
  55. for (let j = 0; j < secondWord.length; j++) {
  56. if (firstWord[i] === secondWord[j]) {
  57. matrix[i][j] = (i && j) > 0 ? matrix[i - 1][j - 1] + 1 : 1;
  58. } else {
  59. matrix[i][j] = Math.max(
  60. i > 0 ? matrix[i - 1][j] : 0,
  61. j > 0 ? matrix[i][j - 1] : 0
  62. );
  63. }
  64. }
  65. }
  66. return matrix[firstWord.length - 1][secondWord.length - 1];
  67. };
  68. longestCommonSubsequence("fish", "fosh"); // 3
  69. longestCommonSubsequence("fort", "fosh"); // 2

image.png
对比CTTTGACTATGA的公共子序列

3. 总结

在调研了过程总发现了由谷歌出品优化工具箱,并选择了一个经典的求最长公共子序列的经典问题。解决动态规划问题的思考路径如下:

  • 需要在给定约束条件下优化某种指标时,动态规划很有用
  • 问题可以分解为离散子问题时,可以使用动态规划解决
  • 每种动态规划解决方案都涉及网格
  • 单元格中的值通常就是需要优化的值
  • 每个单元格都是一个子问题,因此需要考虑如何将问题分解为子问题
  • 没有放之四海皆准的计算动态规划解决方案的公式