跳至主要內容

0691. 贴纸拼词

ITCharge大约 3 分钟

0691. 贴纸拼词open in new window

  • 标签:位运算、数组、字符串、动态规划、回溯、状态压缩
  • 难度:困难

题目链接

题目大意

描述:给定一个字符串数组 stickersstickers 表示不同的贴纸,其中 stickers[i]stickers[i] 表示第 ii 张贴纸上的小写英文单词。再给定一个字符串 targettarget。为了拼出给定字符串 targettarget,我们需要从贴纸中切割单个字母并重新排列它们。贴纸的数量是无限的,可以重复多次使用。

要求:返回需要拼出 targettarget 的最小贴纸数量。如果任务不可能,则返回 1-1

说明

  • 在所有的测试用例中,所有的单词都是从 10001000 个最常见的美国英语单词中随机选择的,并且 targettarget 被选择为两个随机单词的连接。
  • n==stickers.lengthn == stickers.length
  • 1n501 \le n \le 50
  • 1stickers[i].length101 \le stickers[i].length \le 10
  • 1target.length151 \le target.length \le 15
  • stickers[i]stickers[i]targettarget 由小写英文单词组成。

示例

  • 示例 1:
输入:stickers = ["with","example","science"], target = "thehat"
输出:3
解释:
我们可以使用 2"with" 贴纸,和 1"example" 贴纸。
把贴纸上的字母剪下来并重新排列后,就可以形成目标 “thehat“ 了。
此外,这是形成目标字符串所需的最小贴纸数量。
  • 示例 2:
输入:stickers = ["notice","possible"], target = "basicbasic"
输出:-1
解释:我们不能通过剪切给定贴纸的字母来形成目标“basicbasic”。

解题思路

思路 1:状态压缩 DP + 广度优先搜索

根据题意,targettarget 的长度最大为 1515,所以我们可以使用一个长度最多为 1515 位的二进制数 statestate 来表示 targettarget 的某个子序列,如果 statestateii 位二进制值为 11,则说明 targettarget 的第 ii 个字母被选中。

然后我们从初始状态 state=0state = 0(没有选中 targettarget 中的任何字母)开始进行广度优先搜索遍历。

在广度优先搜索过程中,对于当前状态 curstatecur\underline{}state,我们遍历所有贴纸的所有字母,如果当前字母可以拼到 targettarget 中的某个位置上,则更新状态 nextstatenext\underline{}state 为「选中 targettarget 中对应位置上的字母」。

为了得到最小最小贴纸数量,我们可以使用动态规划的方法,定义 dp[state]dp[state] 表示为到达 statestate 状态需要的最小贴纸数量。

那么在广度优先搜索中,在更新状态时,同时进行状态转移,即 dp[nextstate]=dp[curstate]+1dp[next\underline{}state] = dp[cur\underline{}state] + 1

注意:在进行状态转移时,要跳过 dp[nextstate]dp[next\underline{}state] 已经有值的情况。

这样在到达状态 1 << len(target)11 \text{ <}\text{< } len(target) - 1 时,所得到的 dp[1 << len(target)1]dp[1 \text{ <}\text{< } len(target) - 1] 即为答案。

如果最终到达不了 dp[1 << len(target)1]dp[1 \text{ <}\text{< } len(target) - 1],则说明无法完成任务,返回 1-1

思路 1:代码

class Solution:
    def minStickers(self, stickers: List[str], target: str) -> int:
        size = len(target)
        states = 1 << size
        dp = [0 for _ in range(states)]

        queue = collections.deque([0])

        while queue:
            cur_state = queue.popleft()
            for sticker in stickers:
                next_state = cur_state
                cnts = [0 for _ in range(26)]
                for ch in sticker:
                    cnts[ord(ch) - ord('a')] += 1
                for i in range(size):
                    if cnts[ord(target[i]) - ord('a')] and next_state & (1 << i) == 0:
                        next_state |= (1 << i)
                        cnts[ord(target[i]) - ord('a')] -= 1
                
                if dp[next_state] or next_state == 0:
                    continue
                
                queue.append(next_state)
                dp[next_state] = dp[cur_state] + 1
                if next_state == states - 1:
                    return dp[next_state]
        return -1

思路 1:复杂度分析

  • 时间复杂度O(2n×i=0m1len(stickers[i])×nO(2^n \times \sum_{i = 0}^{m - 1} len(stickers[i]) \times n,其中 nntargettarget 的长度,mmstickersstickers 的元素个数。
  • 空间复杂度O(2n)O(2^n)