leetcode
leetcode 601 ~ 650
贴纸拼词

贴纸拼词

难度:

标签:

题目描述

代码结果

运行时间: 112 ms, 内存: 0.0 MB


/*
 * 问题描述:给定若干个贴纸,每个贴纸上都有一个单词,目标是使用最少的贴纸拼出给定的目标字符串。
 * 题解思路:
 * 1. 统计目标字符串中每个字符的出现次数。
 * 2. 使用动态规划来记录每个状态所需的最小贴纸数量。
 * 3. 状态用一个整数表示,二进制中的每一位表示目标字符串中对应字符的是否已被满足。
 * 4. 使用Java Stream简化遍历过程。
 */
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
 
public class StickersToSpellWordStream {
    public int minStickers(String[] stickers, String target) {
        int n = target.length();
        int[] dp = new int[1 << n];
        IntStream.range(0, dp.length).forEach(i -> dp[i] = Integer.MAX_VALUE);
        dp[0] = 0;
 
        IntStream.range(0, dp.length).forEach(state -> {
            if (dp[state] == Integer.MAX_VALUE) return;
            for (String sticker : stickers) {
                int nextState = state;
                int[] count = new int[26];
                sticker.chars().forEach(c -> count[c - 'a']++);
                IntStream.range(0, target.length()).forEach(i -> {
                    char c = target.charAt(i);
                    if ((state >> i & 1) == 1) return;  // already satisfied
                    if (count[c - 'a'] > 0) {
                        count[c - 'a']--;
                        nextState |= (1 << i);
                    }
                });
                dp[nextState] = Math.min(dp[nextState], dp[state] + 1);
            }
        });
 
        return dp[(1 << n) - 1] == Integer.MAX_VALUE ? -1 : dp[(1 << n) - 1];
    }
}

解释

方法:

这个题解采用了记忆化搜索的方法。首先对贴纸数组按照长度排序,然后用 Counter 统计每个贴纸中字母的频次。在 dfs 函数中,如果当前单词已经被记忆,直接返回记忆的结果。否则,遍历每个贴纸,如果贴纸中包含目标单词的第一个字母,则将目标单词中贴纸所包含的字母全部删去,递归处理剩余的单词。如果剩余单词为空,说明找到了一种拼接方案;如果剩余单词不为空但与原单词不同,则更新最小贴纸数量。最后将目标单词和最小贴纸数记录在备忘录中并返回结果。

时间复杂度:

O(n * m * 2^m)

空间复杂度:

O(m * 2^m)

代码细节讲解

🦆
为什么在这个题解中选择使用记忆化搜索而不是动态规划或其他方法?
记忆化搜索是一种有效的方法来优化递归算法,尤其是在解决组合问题时,它可以防止重复计算已经解决的子问题。在这个题解中,由于目标词可能会通过不同的贴纸组合以不同的方式被构建,使用记忆化搜索可以灵活地处理这种组合性质,同时通过备忘录存储已经计算过的结果来避免不必要的重复计算。动态规划通常适用于问题具有明确的递推关系和较小的状态空间,而本题的状态空间较大且不容易直接定义状态转移方程,因此采用记忆化搜索更为合适。
🦆
在对贴纸数组按长度排序的基础上,这种排序是否对最终解决方案的效率有明显的提升?
按长度排序贴纸数组可能在某些情况下提高效率,因为较长的贴纸可能包含更多的字符,从而在早期尝试时可能更快地减少目标单词的长度。这种策略可能帮助算法快速找到需要更少贴纸的解决方案,尤其是在目标单词较长且贴纸可提供大量字符时。然而,这种排序并不总是保证效率提升,因为实际效果还取决于贴纸中字符的种类和分布。
🦆
如何理解题解中提到的`每个状态最多被访问一次`,这里的状态是指什么?
在这个题解中,`状态`指的是在递归过程中当前处理的目标单词的剩余部分。通过使用备忘录(memoization),每个特定的单词配置(即状态)只计算一次并存储其结果。当这个状态再次出现在递归调用中时,可以直接从备忘录中获取结果,而不需要重新计算。因此,每个状态最多被访问一次指的是每种单词配置在整个计算过程中至多被解决一次,避免了不必要的计算重复。
🦆
题解中提到,如果贴纸中不包含目标单词的第一个字母则跳过,这种做法是否可能忽略掉某些潜在的有效组合?
这种方法可能会忽略掉某些潜在的有效组合。因为一个有效的贴纸组合可能不需要从目标单词的第一个字母开始匹配。例如,如果目标单词的后半部分包含某个贴纸可以完全覆盖的字母,那么从这个贴纸开始可能是一个更优的选择。跳过不包含目标单词第一个字母的贴纸是一种启发式方法,它可以减少搜索空间和计算量,但可能牺牲了解的全面性。在实际应用中,这种方法需要根据具体问题和数据特性进行权衡。

相关问题

赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

 

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

 

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成