【Leetcode】691. Stickers to Spell Word

题目地址:

https://leetcode.com/problems/stickers-to-spell-word/

给定 n n n个不同的单词,以字符串数组 A A A给出,再给定一个目标单词 s s s,问至少要从 A A A中挑出多少个单词,可以使得这些单词的总共的字母构成完全覆盖 s s s的字母构成。可以重复挑选。如果无解则返回 − 1 -1 1

思路是状态压缩 + 记忆化搜索。用二进制位表示 s s s的右起第 k k k位是否覆盖,那么每个二进制数就能表示 s s s中已经被覆盖了哪些字母。那么状态 11...1 11...1 11...1一共 l s l_s ls 1 1 1就表示整个 s s s都被覆盖了这种状态,即我们需要达到的状态。设 f [ x ] f[x] f[x]是从 x x x这个状态到达终点状态还需要多少个单词,那么可以枚举每个单词,如果该单词不能使得 x x x这个状态发生改变,则略过(比如这个单词不能覆盖还未覆盖的字母,那这个单词是没用的);否则加入该单词,然后继续DFS进入下一层,继续枚举加入每个单词,直到到达终止状态为止。如果到不了则说明无解,返回 − 1 -1 1。这里有可能某个状态被重复搜索多次,可以使用记忆化,每次在 f f f里查询记忆,算出答案之前要保存记忆。此外,某个状态加入某个字母会到达哪个状态,这个信息也经常要被重复调用,也可以做记忆化存储。代码如下:

import java.util.Arrays;

public class Solution {
    public int minStickers(String[] stickers, String target) {
        int n = target.length();
        // f[x]表示从x这个状态走到全是1的状态要多少步,即需要多少个单词
        int[] f = new int[1 << n];
        // g[x][ch]表示将ch加入x这个状态能得到的新的状态是什么
        int[][] g = new int[1 << n][26];
        Arrays.fill(f, -1);
        for (int[] row : g) {
            Arrays.fill(row, -1);
        }
        
        // 初始状态是0
        int res = dfs(0, f, g, stickers, target);
        return res == 0x3f3f3f3f ? -1 : res;
    }
    
    private int dfs(int state, int[] f, int[][] g, String[] strs, String target) {
    	// 有记忆则调取记忆
        if (f[state] != -1) {
            return f[state];
        }
        
        // 走到终点了,则不需要新的单词了,做记忆并返回
        if (state == (1 << target.length()) - 1) {
            return f[state] = 0;
        }
        
        int res = 0x3f3f3f3f;
        // 开始枚举本层DFS添加的单词
        for (String s : strs) {
            int cur = state;
            // 将该单词加入state得到新的状态cur
            for (int i = 0; i < s.length(); i++) {
                cur = fill(cur, s.charAt(i), g, target);
            }
            
            // 如果cur和原来的state相等,则说明添加当前单词没有效果,略过该单词
            if (cur == state) {
                continue;
            }
            
            // 否则枚举添加该单词的情形,继续DFS,并更新答案
            res = Math.min(res, dfs(cur, f, g, strs, target) + 1);
        }
        
        // 返回前做记忆
        return f[state] = res;
    }
    
    // 返回state添加字母ch后到达的状态
    private int fill(int state, char ch, int[][] g, String target) {
        if (g[state][ch - 'a'] != -1) {
            return g[state][ch - 'a'];
        }
        
        int cur = state;
        for (int i = 0; i < target.length(); i++) {
        	// 这里只需要找到第一个可以填充的位置就可以了,
        	// 因为本质上同一个字母填充的顺序是不影响答案的
            if ((state >> i & 1) == 0 && ch == target.charAt(i)) {
                cur += 1 << i;
                break;
            }
        }
        
        // 做记忆化存储并返回
        return g[state][ch - 'a'] = cur;
    }
}

时间复杂度 O ( n 2 l s ) O(n2^{l_s}) O(n2ls),空间 O ( 2 l s ) O(2^{l_s}) O(2ls)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值