[LeetCode:Remove Palindromic Subsequences] | 刷题打卡

504 阅读2分钟

真心求大家关注和点赞,原创不易,你们的支持是我写作的最大动力!

题目描述

有一个只包括字符 'a' 和 'b' 的字符串 s,你每次可以从 s 中删除一个回文子序列(subsequence),返回将字符串 s 清空(s==="")的最小次数。

子序列:如果字符串是通过删除给定字符串的某些字符而不更改其顺序而生成的,则它是给定字符串的子序列。注意,子序列不一定必须是连续的。

回文:正着读和反着读一样的字符串就是回文串

例一:

输入: s = "ababa"
输出: 1
解释: 如果 s 就是回文,那么一次就可以把它删完,所以结果为 1

例二:

输入: s = "abb"
输出: 2
解释: "abb" -> "bb" -> "". 先删 "a" 再删除 "bb",需要两步可以把它删完,所以结果为 2

例二:

输入: s = "baabb"
输出: 2
解释: "baabb" -> "b" -> "". 先删 "baab" 在删除 "b",需要两步可以把它删完,所以结果为 2

思路分析

解题前需要搞清楚的几个问题:

  1. 如何判断一个字符串是回文
  2. 每次删除子序列的策略

问题一:如何判断一个字符串是回文

应该也不难写出来:定义两个变量 i 和 j,i 从左往右走,j 从右往左走,i 和 j 同步向中部靠拢,只要 s[i]s[j] 不相等,字符串肯定不是回文,函数直接返回 false,循环正常结束则说明是回文。

回文还有另外一种写法,把字符串 reverse 后(split/reverse/join)判断和原始字符串是否相等。

第一种思路的代码如下:

const isPalindrome = (s) => {
  if (s.length === 0) {
    return false;
  }
  for (let i = 0, j = s.length - 1; i < j; i++, j--) {
    if (s[i] !== s[j]) {
      return false;
    }
  }
  return true;
};

问题二:每次删除子序列的策略

说实话,例子和解释给的有点误导人,看了例子后,我直观的感觉是从左往右删除连续回文字串。

拿字符串 "aabbaaa" 来说,从左往右连续匹配回文字串,对于每个字符串:

  1. 第一次取到的回文子串是 "a",剩下 "abbaaa"
  2. 第二次是 "aa",剩下 "bbaaa"
  3. 第三次是 "aabbaa",剩下 "a"

子串的删除次数我们定义为 dp[str],拿上例来说,最终结果就应该是:

dp("aabbaaa") = 1 + Min(dp("abbaaa"), dp("bbaaa"), dp("a"))

认为这是一个动态规划问题!

写出了如下自顶向下非动态规划的代码(动态规划是自底向上推出最终结果,一般可以把自顶向下的非动态规划代码用 for 循环改写成动态规划的写法):(考虑到重复计算,用 cache 缓存一些结果)

const cache = new Map();
var removePalindromeSub = function (s) {
  let min = Number.MAX_SAFE_INTEGER;
  if (s === "") {
    return 0;
  }
  if (isPalindrome(s)) {
    return 1;
  }
  if (cache.has(s)) {
    return cache.get(s);
  }
  for (let j = 1; j < s.length; j++) {
    if (isPalindrome(s.slice(0, j))) {
      let current = removePalindromeSub(s.slice(j));
      if (!cache.has(s)) {
        cache.set(s, current);
      }
      min = Math.min(min, 1 + current);
    }
  }
  return min;
};

isPalindrome 函数上面已经定义过了

提交后代码后,结果自然是 wrong answer!并且失败的 case 也很恶心,这谁顶得住,没法调试啊!

"ababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababbaababba"

看到 expect 结果为 2 后后我又仔细看了一遍题目,发现注意事项写的很清楚,子序列(subsequence)不一定必须是连续的,原来是可以跳着选字符去删除啊!

结果就很明显了,如果整个字符串是回文,那么一次就可以删完,否则一定是两次,因为可以先把所有的 a 选出来组成一个 subsequence 删掉,再把剩下的 b 选出来删掉!

AC 代码

var removePalindromeSub = function (s) {
  if (s === "") {
    return 0;
  }
  if (isPalindrome(s)) {
    return 1;
  }
  return 2;
};

总结

substring 和 subsequence 还是有区别的,subsequence一般指不连续的字符序列,substring 一般指连续的字符序列。另外,做题时看题一定要仔细,尤其是注意事项,不然路就走偏了!

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情