什么是回文字符串
回文字符串就是一个字符串,从头读到尾和从尾读到头,字符出现的顺序是一样的。
如:
1 2 3 4 5 6
| a aba abba abcba ... abcdefgfedcba
|
问题1:如何判断一个字符串是否回文字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
function isPlalindrome(str) { const len = str.length let i = 0 while(i < len / 2) { if (str[i] !== str[len - i - 1]) { return false } i++ } return true }
|
问题2:让任意字符串成为回文串的需要插入的最小字符数
如:
1 2
| 插入2次 abc => abcba || cbabc
|
分析
如果它最后要变成一个回文字符串,那么它最终的最左侧和最右侧的字符一定要是相同的。
如果当前最左侧和最右侧的字符一样,便可继续遍历;如果不一样我们就进行填补。
填补的方式有两种:
1.在左侧填补一个最右侧的字符,左侧继续向前遍历。
2.在右侧填补一个最左侧的字符,右侧继续向前遍历。
对于这两种填补方式,它们的填补消耗都是 1 个字符,我们也无法确定哪一种是最优解。
所以只有继续推导,直到最终遍历完成后便可得到全局最优解。
基于上述思路,这里可以利用动态规划的方式来实现,或者说动态规划是对于这种思路方式的一种比较不错的实现。
如上述思路中提到的内容,如果我们想知道区间 [left, right] 范围里的最优解,那么可能存在两种情况
1 2 3
| s[left] === s[right] (计数 +0) 或者 s[left] !== s[right] (计数 +1)
|
针对这两种情况,我们可以得到两种对应的结果
1 2
| +0 => [left + 1, right - 1] (加 0 的时候,说明相等,则指针向中间移动) +1 => min([left + 1, right], [left, right - 1]) (加 1 的时候,说明不相等,比较左移指针和右移指针哪个更优)
|
如果写成一个递推公式的话可以是
1 2 3
| f(left, right) = (s[left] === s[right]) ? f(left + 1, right - 1) : 1 + min(f(left + 1, right), f(left, right - 1))
|
对应的递归方法实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const minInsertions = str => { const LEN = str.length const f = (left = 0, right = LEN - 1) => { if (left >= right) { return 0 } if (str[left] === str[right]) { return f(left + 1, right - 1) } return 1 + Math.min(f(left + 1, right), f(left, right - 1)) } return f() }
|
另一种实现方式是按照 动态规划(附录有简介) 方法。
我们使用一个数组来记录递推的过程和中间值,具体流程如下:
1)申明一个二维数组。
2)初始化长度为 1 时候的每个字符串所需要的开销为 0,因为一个字符自身就是回文字符串。
3)根据上面的递推公式,逐层的推出并保存每一层的值。
4)最终取出 [0, s.length - 1] 对应的值就是我们的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const minInsertions = str => { const LEN = str.length const dp = [] for (let i = 0; i < LEN; i++) { dp[i] = new Array(LEN).fill(0) dp[i][i + 1] = str[i] === str[i + 1] ? 0 : 1 } for (let i = 2; i < LEN; i++) { for (j = 0; j < LEN - i; j++) { dp[j][j + i] = str[j] === str[j + i] ? dp[j + 1][j + i - 1] : 1 + Math.min(dp[j + 1][j + i], dp[j][j + i - 1]) } } return dp[0][LEN - 1] }
|
上面的代码时间复杂度 O(n^2),空间复杂度也是 O(n^2)。
把空间复杂度压缩到 O(n),不用二维数组,只用一维数组来记录递推的中间值。
优化代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const minInsertions = str => { let num = 0 const LEN = str.length const dp = new Array(LEN).fill(0) for (let i = LEN - 2; i >= 0; i--) { let prev = 0 for (let j = i + 1; j < LEN; j++) { const tmp = dp[j] if (str[i] === str[j]) { dp[j] = prev } else { dp[j] = 1 + Math.min(dp[j], dp[j - 1]) } prev = tmp } } return dp[LEN - 1] } minInsertions('abcdefg')
|
问题3:找出让任意字符串成为回文串,所需要插入的最少数,并打印出最终的回文字符串
问题1是计算出插入的最少字符数,并没有保存插入的字符和相应的插入位置
所以,在原来的基础上需要打印出最终的回文字符串。
分析:
插入最少字符数只有一个最优解,打印出来的回文字符串可能有多个。
所以需要把 dp[0][1]– dp[i][j]最优的的所有字符串保存起来,得出结果之后再倒推回去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
function getPlalindrome(str) {
}
getPlalindrome('abcdefg')
|
相关链接
知乎-让字符串成为回文串的最少插入次数