返回

用序列 DP 征服 LeetCode 472:连接词的巧妙应用

后端

前言

在算法和数据结构的浩瀚世界中,动态规划(DP)算法无疑是解决复杂问题的利器。而序列 DP 是 DP 算法的一种特例,主要用于解决序列相关的问题。本文将以 LeetCode 上的 472. 连接词 题目为例,详解序列 DP 的应用,带领你领略其优雅与强大。

问题

给定一个不含重复单词的字符串数组 words,找出数组中可以串联形成一个回文串的所有子序列。返回所有可能形成的回文串的集合。

解题思路

连接词问题的关键在于如何判断一个子序列能否形成回文串。这里我们采用字符串哈希的方法进行优化。字符串哈希是一种将字符串映射为一个固定长度数字的方法,具有时间复杂度为 O(n) 的优点。

对于每个字符串,我们计算其哈希值,并记录其与之前所有字符串哈希值之间的差值。这样,当我们考虑一个子序列时,只需计算子序列第一个字符串与最后一个字符串哈希值的差值,就能判断子序列是否能形成回文串。

接下来,我们使用序列 DP 的思想,定义状态 dp[i][j] 表示以 words[i] 为开头,words[j] 为结尾的子序列是否能形成回文串。初始状态为 dp[i][i] = true,表示单字符字符串肯定能形成回文串。

对于子问题 dp[i][j] 的转移,我们考虑 words[i] 和 words[j] 是否相等。如果相等,则 dp[i][j] = true。否则,我们需要考虑以下情况:

  1. words[i] 与 words[j] 的哈希值差值为 0,表示 words[i] 和 words[j] 是回文串,则 dp[i][j] = true。
  2. words[i] 与 words[j] 的哈希值差值不为 0,且存在 k (i < k < j) 使得 dp[i][k] = dp[k+1][j] = true,则 dp[i][j] = true。

代码实现

from typing import List

def find_palindrome_subsequences(words: List[str]) -> set:
    """
    :param words: 给定的字符串数组
    :return: 所有可以形成回文串的子序列集合
    """

    # 计算字符串哈希值
    hash_map = {}
    for word in words:
        hash_map[word] = hash(word)

    # 构建哈希值差值表
    diff_map = {}
    for i in range(len(words)):
        for j in range(i+1, len(words)):
            diff = hash_map[words[i]] - hash_map[words[j]]
            if diff not in diff_map:
                diff_map[diff] = []
            diff_map[diff].append((i, j))

    # 序列 DP
    dp = [[False] * len(words) for _ in range(len(words))]
    for i in range(len(words)):
        dp[i][i] = True

    for diff in diff_map:
        for i, j in diff_map[diff]:
            if diff == 0 or (i+1 < j and dp[i+1][j-1]):
                dp[i][j] = True

    # 提取回文串集合
    palindrome_subsequences = set()
    for i in range(len(words)):
        for j in range(i, len(words)):
            if dp[i][j]:
                palindrome_subsequences.add(''.join(words[i:j+1]))

    return palindrome_subsequences