leetcode691:Stickers to Spell Word
题目链接
给定一个字符串target和一个字符串数组stickers[],要求从stickers中选取尽量少的字符串,以这些字符串所包含的字母为原材料,拼出target。
其中:
对于stickers中的每个字符串,可以使用多次。
target和stichers中的字符串字符集全部为小写字母集。
分析
初看这道题,这是一个整数规划问题。
target为aabbb,stickers为[abb,aab]。
设需要x1个abb,x2个aab,则:
min z=x1+x2
1*x1+2*x2>=2
(提供的a的个数应该大于target中包含的a的个数)
2*x1+x2>=3
(提供的b的个数应该大于target中包含的b的个数)
x1和x2为非负整数
原题目等价于整数规划问题,这是一个NP问题。
但是题目给的数据范围有提示:target的长度小于15。
方法一:暴力搜索
import collectionsclass Solution(object):def minStickers(self, stickers, target):# 统计target中各个字符的个数t_count = collections.Counter(target)# 统计stickers中每个字符串中各个字符的个数,去掉target中不包含的字符A = [collections.Counter(sticker) & t_countfor sticker in stickers]# 如果sticker1包含sticker2,那么sticker2是无论如何不会选择的# 这个循环必须倒着写,因为要删除元素for i in range(len(A) - 1, -1, -1):if any(A[i] == A[i] & A[j] for j in range(len(A)) if i != j):A.pop(i)self.best = len(target) + 1def search(ans=0):if ans >= self.best: returnif not A:if all(t_count[letter] <= 0 for letter in t_count):self.best = ansreturn# A一直进行pop操作,直到A为空的时候,得到答案sticker = A.pop()# 在当前的target中至多需要多少个sticker,当前的target用t_count来表示used = max((t_count[letter] - 1) // sticker[letter] + 1for letter in sticker)used = max(used, 0)for c in sticker:t_count[c] -= used * sticker[c]search(ans + used)# 实际使用该sticker的个数可以是0~used之间的任意一个数字,都要尝试一遍for i in range(used - 1, -1, -1):for letter in sticker:t_count[letter] += sticker[letter]search(ans + i)A.append(sticker)search()return self.best if self.best <= len(target) else -1
方法二:动态规划
使用Python语言编写动态规划代码时,很容易超时。必须加上一个优化,对stickers进行“瘦身”,如果sticker A完爆sticker B,那么无论如何都不能选择B,这样就可以把B从stickers中删掉。使用C++是不需要这个优化就能通过的。
import collectionsclass Solution:def minStickers(self, stickers, target):""":type stickers: List[str]:type target: str:rtype: int"""# 首先,进行一些优化,对stickers进行“瘦身”操作,没有这一步,会超时的t_count = collections.Counter(target)A = [collections.Counter(sticker) & t_countfor sticker in stickers]for i in range(len(A) - 1, -1, -1):if any(A[i] == A[i] & A[j] for j in range(len(A)) if i != j):A.pop(i)stickers = ["".join(s_count.elements()) for s_count in A]# stickers瘦身完毕,开始执行动规N = 1 << len(target)big = 0xffffffdp = [big] * Ndp[0] = 0for i in range(N):if dp[i] == big:continue# 当前状态为i,让当前状态加上每一个sticker,为将来的状态进行赋值for j in stickers:now = i# 怎么让当前状态now+当前sticker j呢?# 要让sticker j中的每一个字符都发挥作用for k in j:for r in range(len(target)):if now & (1 << r): # 如果now中已经有了第r个字符,那就不需要了continue# now中没有,并且target中有,sticker j中也有,那就可以应用字符k了if k == target[r]:now |= (1 << r)breakdp[now] = min(dp[i] + 1, dp[now])return -1 if dp[-1] > len(target) else dp[-1]
这是一种很常见的、动态规划解决NP问题的模式!每个状态用一个数字表示,然后从0开始循环,把每个状态的答案保存起来。用当前状态+操作产生未来状态,并给未来状态进行赋值。
dp=[1<<N]
dp[0]=0
for cur in range(1<<N):#当前状态for op in operations:#所有操作nextState,value=apply(cur,op)#对当前状态应用操作dp[nextState]=min(value,dp[nextState])#更新将来的状态
从下面往上面赋值要比从上面往下面赋值简单清晰的多!
更关键的是,有时候只能自下而上的更新状态,因为上面根本不知道自己前面有哪些状态。
这个数组dp虽然是一维数组,实际上它是一个有向无环图。如果状态A可以推出状态B,那么就有一条从A指向B的有向边。将此一维数组一展开,就是一个有向无环图。在这个有向无环图中,寻找某个节点的后继结点可能很容易,而求解某个结点的前向结点就很复杂(关键是要找到全部的前向结点)。这种问题就像二极管一样,从一个方向到另一个方向可以很容易通过,从另一方向回来就难了。就像中国象棋中的马一样,跳过去容易,跳回来难。但是,不排除有时候寻找前向结点的可能性。也许,有些问题就是对状态减去某个操作比较简单。
dp=[1<<N]
dp[0]=0
for cur in range(1,1<<N):for op in operations:lastState=sub(cur,op)dp[cur]=min(dp[lastState]+1,dp[cur])
方法三:一种超时算法
统计字符个数之后进行动规。
动规的关键在于如何定义每一个状态,定义完状态之后,状态之间通过状态转移来联系起来,就会产生一个有向无环图。
方法二中,没有对输入的各个字符串进行处理,导致循环中需要循环O(字符串长度)次。如果预先统计出每个字符串中各个字符的个数就可以循环O(字符种类数)次。
import collections
import functools
from queue import Queueclass Solution:def minStickers(self, stickers, target):""":type stickers: List[str]:type target: str:rtype: int"""def solve(A, b):ks = list(b.keys())vs = [b[i] for i in ks]START = tuple([0] * len(b)) # 当前状态ma = dict()ma[START] = 0states = Queue() # 状态队列states.put(START)while states.qsize():nowTuple = states.get()nowStep = ma[nowTuple]# print(nowTuple, nowStep)for sticker in A: # 当前状态加上每一个sticker产生未来的状态state = list(nowTuple)for j, c in enumerate(ks):if c in sticker:state[j] = min(state[j] + sticker[c], vs[j])t = tuple(state)if t not in ma or nowStep + 1 < ma[t]:if t not in ma:states.put(t)ma[t] = nowStep + 1return ma[tuple(vs)]A = [collections.Counter(i) for i in stickers]b = collections.Counter(target)# 对stickers进行瘦身for i in range(len(A) - 1, -1, -1):if any(A[i] == A[i] & A[j] for j in range(len(A)) if i != j):A.pop(i)# stickers包含的字母集合s = functools.reduce(lambda s, i: s | i, A, collections.Counter())return -1 if not all(i in s for i in b)else solve(A, b)
这种方法虽然超时了,但是我认为当字符串长度足够长的时候,这种算法效率更高些。
转载于:https://www.cnblogs.com/weiyinfu/p/7639064.html
leetcode691:Stickers to Spell Word相关推荐
- 383. Ransom Note/691. Stickers to Spell Word-- String, Map, back tracking-- 未完待续
383 easy 题,就是建立字母的hash 表 看第一个String 是否能被第二个String 所构建 canConstruct("aa", "aab") ...
- leetcode算法练习 JavaScript实现
leetcode 表格内容由spider.js从leetcode-cn.com爬取. 已做题目答案也从leetcode-cn.com中爬取并生成文件. 解题进度:已解决 140/637 - 简单 94 ...
- leetcode 题解 (500-1000题,持续更新,part 2)
part1(1-500), part3(1000-*) 502. IPO 题意:给定k,w,profits数组和capital数组.k表示最多可完成的任务数.w是初始资本.profits是各个任务的收 ...
- LeetCode All in One 题目讲解汇总(持续更新中...)
原文地址:https://www.cnblogs.com/grandyang/p/4606334.html 终于将LeetCode的大部分题刷完了,真是漫长的第一遍啊,估计很多题都忘的差不多了,这次开 ...
- ORACLE TEXT LEXER PREFERENCE(一)
介绍完Oracle全文索引的FILTER属性,继续介绍Oracle的LEXER属性. Oracle全文索引的LEXER属性用于处理各种不同的语言.最基本的英文使用BASE_FILTER,而如果需要使用 ...
- 英语语法回顾7——状语从句特殊用法
一,补充as专题 as意思总结: as+名词--作为 v+n+as--此时as的意思取决于前面的意思 as+句子--当,,,时候 因为 虽然 好像 和,,,一样 二,补充t ...
- 亚马逊echo中国使用_如何设置和配置您的Amazon Echo
亚马逊echo中国使用 So you just got an Amazon Echo, either from the recent sales or the holidays. Let's take ...
- POJ 1035, Spell checker
brutal force 1. 生成字典, 并按长度排序. 2. 对于给定单词, 取得区间 [单词长度 - 1, 单词长度 + 1], 并匹配. 3. 如果存在完全匹配的单词, 打印输出, 否则把可能 ...
- 解决 The word is not correctly spelled问题
The word is not correctly spelled 此问题是eclipse校验单词拼写造成,如果出在配置文件中,一般会影响到程序的正常执行 解决方法:Window--Preferenc ...
最新文章
- lldb调试使用python脚本问题总结
- [性能] SAP销售订单取数逻辑优化---索引表
- VTK:PolyData之CellEdgeNeighbors
- HTML--- 创建一个登录页面(HTML,CSS)
- where does default 20 come from SAP UI5 growingThreshold
- iOS小白之路...iOS中基础控件的使用
- 洛谷1056 排座椅 解题报告
- 20191001:String,StringBuffer,StringBuilder类异同辨析
- macOS big Sur 无限进入恢复模式,如何修复
- coolfire黑客入门教程系列之(八)最后部分!
- 各国国家语言缩写与中文对应表 JAVA工具类
- 计算机音量程序是哪个键,电脑如何设置音量快捷键
- (转)802.1Q标准中TAG字段简单说明
- 《Linux操作系统-基础笔记》第6章 编译调试工具(GCC、GDB)
- 安卓广告机带4G一体开发
- 宽带连接错误的处理办法
- Vue.js 响应式原理
- Windows如何使文件显示扩展名
- 首次跌至发行价的Snap,要如何抵抗社交巨头Facebook的抄袭?
- Android活动+《恐怖游轮》带你了解跳转传递数据如何随时结束程序小技巧