返回
用序列 DP 征服 LeetCode 472:连接词的巧妙应用
后端
2023-12-17 12:41:25
前言
在算法和数据结构的浩瀚世界中,动态规划(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。否则,我们需要考虑以下情况:
- words[i] 与 words[j] 的哈希值差值为 0,表示 words[i] 和 words[j] 是回文串,则 dp[i][j] = true。
- 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