Java&C++题解与拓展——leetcode691.贴纸拼词【么的新知识】

195 阅读1分钟
每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路:DFS+记忆化搜索

  • 用一个二进制串statestate的每一位表示当前字母是否被拼出,11为拼出;
    • 初始statestate00,拼成后statestate(1<<n)1(1<<n)-1,也就是每一位都是一。
  • 每个贴纸都可以多次使用,所以从某一状态到最终结果的最小步数是固定的,所以定义一个ff数组记录每个statestate的结果,避免重复搜索(记忆化搜索);
    • 用枚举stickersstickers的方式更新statestate,下一状态为nsns,则f[state]f[state]的值为最小的dfs(ns)+1dfs(ns)+1,即下一状态到结果的最小步数。

Java

class Solution {
    int N = 20, M = 1 << 20, INF = 50;
    int[] f = new int[M]; // 记录已搜索状态结果
    String[] stickers;
    String target;
    int n;
    int dfs(int state) { // 用二进制数表示每一位状态
        if(state == ((1 << n) - 1)) // 全1即已拼成
            return 0;
        if(f[state] != -1) // 查询当前状态是否计算过
            return f[state];
        int res = INF;
        for(String s : stickers) {
            int ns = state; // 下一状态
            for(char c : s.toCharArray()) { // 逐位比较
                for(int i = 0; i < n; i++) {
                    if(target.charAt(i) == c && ((ns >> i) & 1) == 0) { // 可贴且未遍历
                        // 当前位状态置1
                        ns |= (1 << i);
                        break; // 跳出循环下一位
                    }
                }
            }
            if(ns != state) // 以下一状态继续搜索
                res = Math.min(res, dfs(ns) + 1);
        }
        return f[state] = res;
    }

    public int minStickers(String[] stickers, String target) {
        this.stickers = stickers;
        this.target = target;
        n = target.length();
        Arrays.fill(f, -1);
        int res = dfs(0);
        return res == INF ? -1 : res;
    }
}
  • 时间复杂度:O(2n×i=0m1stickers[i].length×n)O(2^n\times\sum^{m-1}_{i=0}stickers[i].length\times n),其中nn为字符串targettarget的长度,mm为数组stickersstickers的长度,可知共有2n2^n个状态,每个状态都要遍历数组和字符串,计算复杂度为O(i=0m1stickers[i].length×n)O(\sum^{m-1}_{i=0}stickers[i].length\times n)
  • 空间复杂度:O(2n)O(2^n)

C++

const static int N = 20, M = 1 << 20, INF = 50;
class Solution {
    int n;
    int f[M]; // 记录已搜索状态结果
    vector<string> stickers;
    string target;
public:
    int minStickers(vector<string>& stickers, string target) {
        this->stickers = stickers;
        this->target = target;
        n = target.size();
        memset(f, -1, M);
        int res = dfs(0);
        return res == INF ? -1 : res;
    }

    int dfs(int state) { // 用二进制数表示每一位状态
        if(state == ((1 << n) - 1)) // 全1即已拼成
            return 0;
        if(f[state] != -1) // 查询当前状态是否计算过
            return f[state];
        int res = INF;
        for(auto s : stickers) {
            int ns = state; // 下一状态
            for(auto c : s) { // 逐位比较
                for(int i = 0; i < n; i++) {
                    if(target[i] == c && ((ns >> i) & 1) == 0) { // 可贴且未遍历
                        // 当前位状态置1
                        ns |= (1 << i);
                        break; // 跳出循环下一位
                    }
                }
            }
            if(ns != state) // 以下一状态继续搜索
                res = min(res, dfs(ns) + 1);
        }
        return f[state] = res;
        
    }
};
  • 时间复杂度:O(2n×i=0m1stickers[i].length×n)O(2^n\times\sum^{m-1}_{i=0}stickers[i].length\times n),其中nn为字符串targettarget的长度,mm为数组stickersstickers的长度,可知共有2n2^n个状态,每个状态都要遍历数组和字符串,计算复杂度为O(i=0m1stickers[i].length×n)O(\sum^{m-1}_{i=0}stickers[i].length\times n)
  • 空间复杂度:O(2n)O(2^n)

总结

感觉不是很好理解,看了半天也没有完全懂,带着实例推导了几轮才理清楚一点,其实就是排列组合的枚举,然后记录状态。

DFS就是以每个贴纸单词为基础深入到拼成状态,然后再换下一个贴纸……


欢迎指正与讨论!