题目链接
给定一个字符串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相关推荐

  1. 383. Ransom Note/691. Stickers to Spell Word-- String, Map, back tracking-- 未完待续

    383  easy 题,就是建立字母的hash 表 看第一个String 是否能被第二个String 所构建 canConstruct("aa", "aab") ...

  2. leetcode算法练习 JavaScript实现

    leetcode 表格内容由spider.js从leetcode-cn.com爬取. 已做题目答案也从leetcode-cn.com中爬取并生成文件. 解题进度:已解决 140/637 - 简单 94 ...

  3. leetcode 题解 (500-1000题,持续更新,part 2)

    part1(1-500), part3(1000-*) 502. IPO 题意:给定k,w,profits数组和capital数组.k表示最多可完成的任务数.w是初始资本.profits是各个任务的收 ...

  4. LeetCode All in One 题目讲解汇总(持续更新中...)

    原文地址:https://www.cnblogs.com/grandyang/p/4606334.html 终于将LeetCode的大部分题刷完了,真是漫长的第一遍啊,估计很多题都忘的差不多了,这次开 ...

  5. ORACLE TEXT LEXER PREFERENCE(一)

    介绍完Oracle全文索引的FILTER属性,继续介绍Oracle的LEXER属性. Oracle全文索引的LEXER属性用于处理各种不同的语言.最基本的英文使用BASE_FILTER,而如果需要使用 ...

  6. 英语语法回顾7——状语从句特殊用法

    一,补充as专题 as意思总结: as+名词--作为 v+n+as--此时as的意思取决于前面的意思 as+句子--当,,,时候    因为      虽然    好像    和,,,一样 二,补充t ...

  7. 亚马逊echo中国使用_如何设置和配置您的Amazon Echo

    亚马逊echo中国使用 So you just got an Amazon Echo, either from the recent sales or the holidays. Let's take ...

  8. POJ 1035, Spell checker

    brutal force 1. 生成字典, 并按长度排序. 2. 对于给定单词, 取得区间 [单词长度 - 1, 单词长度 + 1], 并匹配. 3. 如果存在完全匹配的单词, 打印输出, 否则把可能 ...

  9. 解决 The word is not correctly spelled问题

    The word is not correctly spelled 此问题是eclipse校验单词拼写造成,如果出在配置文件中,一般会影响到程序的正常执行 解决方法:Window--Preferenc ...

最新文章

  1. lldb调试使用python脚本问题总结
  2. [性能] SAP销售订单取数逻辑优化---索引表
  3. VTK:PolyData之CellEdgeNeighbors
  4. HTML--- 创建一个登录页面(HTML,CSS)
  5. where does default 20 come from SAP UI5 growingThreshold
  6. iOS小白之路...iOS中基础控件的使用
  7. 洛谷1056 排座椅 解题报告
  8. 20191001:String,StringBuffer,StringBuilder类异同辨析
  9. macOS big Sur 无限进入恢复模式,如何修复
  10. coolfire黑客入门教程系列之(八)最后部分!
  11. 各国国家语言缩写与中文对应表 JAVA工具类
  12. 计算机音量程序是哪个键,电脑如何设置音量快捷键
  13. (转)802.1Q标准中TAG字段简单说明
  14. 《Linux操作系统-基础笔记》第6章 编译调试工具(GCC、GDB)
  15. 安卓广告机带4G一体开发
  16. 宽带连接错误的处理办法
  17. Vue.js 响应式原理
  18. Windows如何使文件显示扩展名
  19. 首次跌至发行价的Snap,要如何抵抗社交巨头Facebook的抄袭?
  20. Android活动+《恐怖游轮》带你了解跳转传递数据如何随时结束程序小技巧

热门文章

  1. springboot2源码2-SpringApplication运行
  2. JavaScript将数字转换为大写金额
  3. fastDFS 命令笔记
  4. 转:Mysql explain
  5. SQL Server创建视图
  6. linux中vi编辑器(转载)
  7. 【网络安全工程师面试合集】—CSRF跨站请求伪造 攻击及防御
  8. 如何用ssh工具连接自己的“小米手机”——雷总看了直呼内行!!!
  9. python爬虫【2021.02.01】
  10. AS3.0常用第三方类库:TweenMax