LCP 69. Hello LeetCode!
https://leetcode.cn/problems/rMeRt2/
这题没有花费多长时间想出是二重DP
- 内存每个单词可以枚举去匹配 "helloleetcode" 的状态以及代价
- 外层枚举每个单词上可以匹配的状态,然后计算最小代价
但是我实现起来的时候大约遇到了两个坑:
- 没有使用二进制状态表示,比如这个字符串里面有4个e其实只需要3bit, 3个l需要2bit. 不要小看减少2bit,这个可以压缩不少状态空间。
- 第一步预处理的时候我使用了记忆化搜索。记忆化搜索的缺点就是需要枚举所有的状态,反倒是DFS更快。
没有使用二进制状态表示的版本写的非常糟糕,这里就不贴了,感觉学习意义也不大。
这里贴一下两个搜索版本,记忆化和DFS. 在实现记忆化版本的时候,一定行需要做快速检查将某些不合理的状态去掉,否则会出现超时的情况。
class Solution: def Leetcode(self, words: List[str]) -> int: INF = 1 << 30 # HELLO LEETCODE # H E L O T D C # count 1 4 3 2 1 1 1 # bits 1 3 2 2 1 1 1 # off 10 7 5 3 2 1 0 # mask 0x1 0x7 0x3 0x3 0x1 0x1 0x1 RULES = { # (off, count, mask) 'h': (10, 1, 0x1), 'e': (7, 4, 0x7), 'l': (5, 3, 0x3), 'o': (3, 2, 0x3), 't': (2, 1, 0x1), 'd': (1, 1, 0x1), 'c': (0, 1, 0x1), } FINAL = 0b11001110111 # 第一步预处理并且填充cache. cache = [{} for _ in range(len(words))] use_dfs = False # 使用记忆化搜索的版本 if not use_dfs: # 列举所有的状态 def list_states(): states = [] for st in range(FINAL + 1): ok = True for c, (off, count, mask) in RULES.items(): if (st >> off) & mask > count: ok = False break if not ok: continue states.append(st) return states # 快速过滤不可行的状态 def fast_check(st, w): for c in w: if c in RULES: off, count, mask = RULES[c] if (st >> off) & mask == 0: continue st = st - (1 << off) return st == 0 states = list_states() for i in range(len(words)): cc = cache[i] import functools @functools.cache def dfs(stm, w): if stm == 0: return 0 if not w: return INF ans = INF for j in range(len(w)): c = w[j] if c not in RULES: continue off, count, mask = RULES[c] if (stm >> off) & mask == 0: continue cost = j * (len(w) - 1 - j) if cost >= ans: continue res = dfs(stm - (1 << off), w[:j] + w[j + 1:]) ans = min(ans, res + cost) return ans for st in states: if not fast_check(st, words[i]): continue res = dfs(st, words[i]) if res != INF: cc[st] = res # 使用DFS版本. else: for i in range(len(words)): cc = cache[i] def dfs(stm, w, tc): if stm not in cc or tc < cc[stm]: cc[stm] = tc for j in range(len(w)): c = w[j] if c not in RULES: continue off, count, mask = RULES[c] if (stm >> off) & mask == count: continue cost = j * (len(w) - 1 - j) stm2 = stm + (1 << off) w2 = w[:j] + w[j + 1:] dfs(stm2, w2, tc + cost) dfs(0, words[i], 0) def check_state(a, b): for c, (off, count, mask) in RULES.items(): if (a >> off) & mask > (b >> off) & mask: return False return True import functools @functools.cache def find_all(stm, i): if stm == 0: return 0 if i == len(words): return INF ans = INF for st, cost in cache[i].items(): if cost >= ans: continue # 剪枝放在check_state之前 if check_state(st, stm): r = find_all(stm - st, i + 1) ans = min(ans, r + cost) return ans ans = find_all(FINAL, 0) if ans == INF: ans = -1 return ans