经典的动态规划问题

动态规划

定义:

       是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。
       动态规划常常适用于有重叠子问题[1]和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。
       动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。
       通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。
       动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

适用条件

  1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
  2. 无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。
  3. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

关于本题

当你看到最X的什么的时候,很容易想到动态规划。(虽然不是什么都能用)恰好这道题可以使用动态规划解决。

  • 0 <= i < s.size()0 <= j < s.size()
  • table[i][j] == true 表示从 i + 1j + 1 的字符串是一个回文字符串
  • table[i + 1][j - 1] == trues[i] == s[j] 表明了从 i + 2js 子串是一个回文字符串并且第 i + 1j + 1 位置的字符是相同的
  • 自然从 i + 1j + 1 的字符串是一个回文字符串

开始

       实际上,根据上面的解释和题面我们知道,我们需要找出 s 的子串,可以归结为找出子串的 start 和 end 然后使用 subString 直接取出子串。即得到 table[i][j] = true 且 这时候 j - i 为所有满足该条件的 i、j 中最大的。
       只要我们求出了 table 中所有位置的值,那么要找到结果是轻而易举的。
       我们把子问题归结为:table[i][j] 的值应该为什么。
       而上面那个步骤,就是求解这个问题的子问题的方法。

起始
i == j 的时候,子串 [i...j] 一定是回文的(一个字符肯定回文),美好的时光开始了。

不说废话,上完整代码

class Solution {
public:
    string longestPalindrome(string str) {
        if (str.size() == 0) return "";
        if (str.size() == 1) return str;
        int s = 0, f = 1;
        for (int i = 0; i < str.size();) {
          if (str.size() - i <= f / 2) break;
          int j = i, k = i;
          while (k < str.size()-1 && str[k+1] == str[k]) {
              ++k;
          }
          i = k+1;
          while (k < str.size()-1 && j > 0 && str[k + 1] == str[j - 1]) {
              ++k;
              --j;
          }
          int len = k - j + 1;
          if (len > f) {
              s = j;
              f = len;
          }
        }
        return str.substr(s, f);
    }
};

接下来我会一步一步解释的

if (str.size() == 0) return "";
if (str.size() == 1) return str;

s 为空,没有子串。
s 为一个字符,子串就是本身。

int s = 0, f = 1;

这里 s 表示子串开始位置,f 表示子串结束位置。

for (int i = 0; i < str.size();)

理论上是要遍历一遍 s 然后对每个位置进行回文子串计算。

if (str.size() - i <= f / 2) break;

如果已经知道就算当前计算出的回文子串的长度肯定小于已经计算出来的,那么不必计算了,直接返回结果。

int j = i, k = i;
while (k < str.size() - 1 && str[k + 1] == str[k]) {
  ++k;
}
i = k+1;

这里是一个优化,遇到了 aaaaab 这种字符串的话就不必计算回文字串,直接把 k 移动到最后一位然后继续进行计算,并且把 i 也移动到最后一位。

while (k < str.size()-1 && j > 0 && str[k + 1] == str[j - 1]) {
  ++k;
  --j;
}

核心代码,这里就是刚才列出的计算过程。

int len = k - j + 1;
if (len > f) {
  s = j;
  f = len;
}

如果计算出的回文子串与之前计算出的长度相比较大就将 s 和 f 重新定位到新的回文子串

return str.substr(s, f);

返回结果

总结

这只是一个较为简单的动态规划问题,鄙人不才无法想到 O(n) 的解法,如果有某些大神可以使用 O(n) 复杂度的算法解决这个问题,请务必告诉我。


sugerPocket
1 声望0 粉丝