问:
解:
- 解法一: 反转字符串,求反转字符串和原字符串的最长公共子序列;解法二:范围上的尝试模型,找str在i~j范围上的最长回文子序列长度
function longestPalindromeSubseq {
const str2 = [...str].reverse().join('')
return getMaxLength(str, str2)
function getMaxLength(str1, str2) {
const dp = []
for (let i = 0; i < str1.length; i++) {
dp[i] = []
for (let j = 0; j < str2.length; j++) {
if (i === 0) {
dp[i][j] = str1[i] === str2[j] ? 1 : dp[i][j - 1] ?? 0
continue
}
if (j === 0) {
dp[i][j] = str1[i] === str2[j] ? 1 : dp[i - 1][j] ?? 0
continue
}
let p1 = 0
let p2 = 0
// 回文串可能性分析
// p1.以i结尾 以j结尾
// p2.不以i结尾 不以j结尾
// p3.不以i结尾 以j结尾
// p4.以i结尾 不以j结尾
if (str1[i] === str2[j]) {
p1 = dp[i - 1][j - 1] + 1
}
p2 = dp[i - 1][j - 1]
const p3 = dp[i - 1][j]
const p4 = dp[i][j - 1]
dp[i][j] = Math.max(p1, p2, p3, p4)
}
}
return dp[str1.length - 1][str2.length - 1]
}
};
function longestPalindromeSubseq(str) {
const dp = []
for (let i = 0; i < str.length; i++) {
dp[i] = []
for (let j = 0; j < str.length; j++) {
if (i > j) dp[i][j] = 0
if (i === j) dp[i][j] = 1
if (j === i + 1) dp[i][j] = str[i] === str[j] ? 2 : 1
}
}
for (let i = str.length - 3; i >= 0; i--) {
for (let j = i + 2; j < str.length; j++) {
if (str[i] === str[j]) {
dp[i][j] = (dp[i + 1][j - 1] ?? 0) + 2
continue
}
dp[i][j] = Math.max(dp[i + 1][j] ?? 0, dp[i][j - 1] ?? 0, dp[i + 1][j - 1] ?? 0)
}
}
return dp[0][str.length - 1]
}
// 与上一题思路一样,范围上的尝试模型。 求str在i~j范围上的需要增加几个字符
// 暴力递归
function minInsertions(str) {
function getRes(left, right) {
if (left > right) return Infinity
if (right === left) return 0
if (right === left + 1) return str[left] === str[right] ? 0 : 1
// left和right位置字符相等,不用管这两个位置
if (str[left] === str[right]) return getRes(left + 1, right - 1)
// 剩下两种可能:把left + 1 ~ right范围变成回文,再把left字符添加一个。 把left到right-1范围变成回文,再把right字符添加一个。
return Math.min(getRes(left + 1, right) + 1, getRes(left, right - 1) + 1)
}
return getRes(0, str.length - 1)
};
// 递归改dp
function minInsertions(str) {
const dp = []
for (let i = 0; i < str.length; i++) {
dp[i] = []
for (let j = 0; j < str.length; j++) {
if (i > j) {
dp[i][j] = Infinity
continue
}
if (i === j) {
dp[i][j] = 0
continue
}
if (i === j - 1) {
dp[i][j] = str[i] === str[j] ? 0 : 1
}
}
}
for (let i = str.length - 3; i >= 0; i--) {
for (let j = i + 2; j < str.length; j++) {
if (str[i] === str[j]) {
dp[i][j] = dp[i + 1][j - 1]
continue
}
dp[i][j] = Math.min(dp[i + 1][j] + 1 , dp[i][j - 1] + 1)
}
}
return dp[0][str.length - 1]
}
// 暴力递归
function minCut(str) {
if (str.length <= 1) return 0
function getRes(curIdx) {
if (curIdx === str.length) return 0
let res = Infinity
for (let i = curIdx; i < str.length; i++) {
// 判断 当前是否是回文
const flag = isPalindrome(str.slice(curIdx, i + 1))
if (flag) {
res = Math.min(getRes(i + 1) + 1, res)
}
}
return res
}
return getRes(0) - 1
function isPalindrome(s) {
// 去除异常的字符,先全部转成小写
const str = s.toLocaleLowerCase().replace(/[\W_]/ig, '')
// 提前获取处理后字符串的长度,省的在下边重复获取,用空间换时间
const l = str.length
// 循环次数,如果处理后字符串长度为偶数,/2是整数就不用处理,但是考虑会有基数(12321)的情况,所以加个Math.ceil 取整,当然你开心的话l/2%1 === 0 判断处理也行
for (let i = 0;i < Math.ceil(l / 2);i ++) {
// 判断首位对应的索引不一致直接返回 fase
if (str[i] !== str[l - i - 1]) {
return false
}
}
// 循环走完了证明没问题,返回true
return true
}
}
// 递归改dp。 按理来说判断是否回文这个环节要预处理生成一个dp表优化判断回文的遍历过程。但是coding大同小异就不改了。
function minCut(str) {
if (str.length <= 1) return 0
const dp = []
dp[str.length] = 0
for (let i = str.length - 1; i >= 0; i--) {
dp[i] = Infinity
for (let j = i; j < str.length; j++) {
// 判断 当前是否是回文
const flag = isPalindrome(str.slice(i, j + 1))
if (flag) {
dp[i] = Math.min(dp[j + 1] + 1, dp[i])
}
}
}
return dp[0] - 1
// return getRes(0) - 1
function isPalindrome(s) {
const str = s.toLocaleLowerCase().replace(/[\W_]/ig, '')
const l = str.length
for (let i = 0;i < Math.ceil(l / 2);i ++) {
if (str[i] !== str[l - i - 1]) {
return false
}
}
return true
}
}