Skip to main content
  1. Posts/

LeetCode-691 贴纸拼词

·1 min·

LeetCode-691 贴纸拼词 #

Solution 1 #

这道题要用给定的字符串中的字符来拼出目标字符串, 其中给定字符串中每一个都可以使用无数次. 由题意, 对于每个字符串, 我们可以使用一个数组记录其可以提供的字符种类及个数. 首先思考贪心做法是否可以, 但没能找到解决办法. 再考虑动态规划, 对于一个给定的字符串, 其所需步骤为 $min(f(sticker) + 1), for\ each\ sticker\ in\ stickers[]$ .那么怎样记录状态呢? 一个办法是用 $1$ 表示当前字符已经处理完, $0$ 表示当前字符需要被处理, 这样我们就可以用 $0,1,…,2^n-1$ 来表示字符串拼接过程中所有可能的状态. 由于拼接过程的特性, 我们倾向于使用自顶向下递归的做法来处理状态转移, 通过记忆化搜索避免重复运算, 这一递归过程本质上是广度优先搜索. 代码如下:

class Solution {
public:
    int minStickers(vector<string>& stickers, string target) {
        int n = target.size();
        vector<int>dp(1 << n, -1); // 赋值, 未被处理的为-1
        dp[0] = 0;
        int ans = recursive(dp, stickers, (1 << n) - 1, target);
        return ans > n ? -1: ans; // 如果ans>n说明没有可行方案, 返回-1
    }

    // 记忆化搜索, 注意数组dp要引用, 否则值不会传回去
    int recursive(vector<int>& dp, vector<string>& stickers, int mask, string target) {
        // 记忆化搜索, 如果之前搜索到这一步了, 直接返回答案即可
        // 这一情况也包含了边界情况dp[0], 我们在调用递归函数之前已经给dp[0]赋值为0了
        if (dp[mask] != -1) {
            return dp[mask];
        }
        dp[mask] = target.size() + 1; // mask如果可以处理完, 那么至多target.size()步可以处理完
        for (string sticker: stickers) {
            int left = mask; // left记录当前sticker处理完后剩余的状态
            vector<int> cnt(26, 0);
            // 记录可用字符
            for (char c: sticker) {
                cnt[c - 'a']++;
            }
            for (int i = 0; i < target.size(); i++) {
                // mask对应的字符串如果未被处理, 且我们有剩余字符可以处理它, 就进行处理.
                if ((mask >> i & 1) && cnt[target[i] - 'a'] > 0) {
                    left = left ^ (1 << i); // left二进制下对应位归0
                    cnt[target[i] - 'a']--; // 可用字符减少
                }
            }
            // 处理后的left必须比mask小
            if (left < mask) {
                dp[mask] = min(dp[mask], recursive(dp, stickers, left, target) + 1); // 状态转移方程
            }
        }
        return dp[mask];
    }
};