返回

力扣刷题 | 680:验证回文字符串 II:高级攻略和深入解析

前端

导言

回文,即从左到右读和从右到左读都相同的字符串,在计算机科学和自然语言处理中都有着广泛的应用。在力扣平台上,680 题「验证回文字符串 II」要求我们在原有的「验证回文字符串 I」基础上更进一步,允许删除一个字符来判断字符串是否可以成为回文。这道题考察了算法、字符串操作和动态规划等多方面的知识,需要我们深入理解回文的本质和解决问题的策略。

问题

给定一个字符串 s,判断在至多删除一个字符的情况下,它是否可以成为回文。

示例

示例 1:

输入:s = "aba"
输出:true

示例 2:

输入:s = "abca"
输出:true

示例 3:

输入:s = "abc"
输出:false

算法解析

我们知道,回文的核心特征是左右对称。因此,我们可以从字符串的两端开始比较,如果遇到不匹配的字符,则尝试删除左指针或右指针指向的字符,并继续比较。需要注意的是,如果删除了字符,则需要更新左右指针的位置。

算法步骤如下:

  1. 初始化左指针 left 为 0,右指针 rights.length - 1
  2. 循环比较 s[left]s[right]
    • 如果相等,则 left++right--
    • 如果不相等,则尝试删除左或右指针指向的字符:
      • 删除 s[left]left++
      • 删除 s[right]right--
    • 如果删除字符后仍然不相等,则返回 false
  3. 如果循环结束时 left 指针位于 right 指针的右边或相等,则返回 true

动态规划解决方案

除了暴力枚举删除字符的所有可能情况外,我们还可以使用动态规划来优化算法。动态规划的思想是将问题分解成更小的子问题,然后逐步求解子问题,最后得到整个问题的解。

对于本题,我们可以定义一个二位数组 dp[i][j],其中 i 表示字符串的左端点,j 表示字符串的右端点。dp[i][j] 表示在删除了至多一个字符的情况下,字符串 s[i:j+1] 是否可以成为回文。

动态规划的状态转移方程如下:

dp[i][j] = true  # 如果 s[i:j+1] 本身就是回文
dp[i][j] = dp[i+1][j-1]  # 如果 s[i] 和 s[j] 相等,且 s[i+1:j] 可以成为回文
dp[i][j] = dp[i+1][j] 或 dp[i][j-1]  # 如果 s[i] 和 s[j] 不相等,尝试删除 s[i] 或 s[j]

最终,我们只需要判断 dp[0][s.length - 1] 是否为 true 即可。

示例代码

暴力枚举法:

def valid_palindrome_ii(s):
    """
    暴力枚举法

    :param s: str
    :return: bool
    """
    n = len(s)

    for i in range(n):
        # 尝试删除第 i 个字符
        t = s[:i] + s[i+1:]
        if t == t[::-1]:
            return True

    return False

动态规划法:

def valid_palindrome_ii(s):
    """
    动态规划法

    :param s: str
    :return: bool
    """
    n = len(s)
    dp = [[False] * n for _ in range(n)]

    # 初始化回文长度为 1 的子串
    for i in range(n):
        dp[i][i] = True

    # 填补动态规划表
    for l in range(2, n+1):
        for i in range(n-l+1):
            j = i + l - 1
            if s[i] == s[j]:
                dp[i][j] = True
            else:
                dp[i][j] = dp[i+1][j] or dp[i][j-1]

    return dp[0][n-1]