数组arr中的字符串是贴纸,每种贴纸任选无数张,想要将target串拼出来,至少需要多少张贴纸?

提示:逻辑上比较明了,但是实现上很难的动态规划题目

这个题,特别像一道动态规划的题目,简直就是一模一样
(1)数组arr里面是面币值,每种面币任选无数张,组成target的办法有多少种
这个题目,是非常非常经典的从左往右的尝试模型
本题读来跟(1)一模一样啊!!!但是上面求的组合方法数,本题要的不是方法数,而是最少的使用贴纸的张数。
但是因为本题arr中放的贴纸,贴纸还能剪开,所以就比较繁琐了!
逻辑上实现很容易,但是代码实现,需要理解哈希表用来记录词频的技巧——这个技巧,之前无数次用了


文章目录

  • 数组arr中的字符串是贴纸,每种贴纸任选无数张,想要将target串拼出来,至少需要多少张贴纸?
    • @[TOC](文章目录)
  • 题目
  • 一、审题
  • 关键在暴力递归函数的定义
  • 字符串词频的统计处理
  • 将暴力递归与傻缓存dp表同时用,适用于字符串的动态规划
  • 总结

题目

数组arr中的字符串是贴纸,每种贴纸任选无数张,想要将target串拼出来,至少需要多少张贴纸?
你可以剪开arr[i]串,拿去拼target。


一、审题

示例:
arr= ba c abcd
target=babac
你可以用2张ba,1张c,共3张完成任务
你可以用2张abcd,这样也能拼target,共2张完成任务
最少张数为2


关键在暴力递归函数的定义

其实,你也看见了,示例中,完全不需要关心字符串的顺序,只需要知道target,需要多少字符来拼就行
arr[i]也是可以随意切开的,arr[i]可以重复用(这个重复使用,可以通过不断调用同一个递归函数来模拟,每次进递归函数都是用arr所有的串枚举一遍排头)

不妨设**f(arr,rest)**是咱们要的递归函数,含义是:余下rest=target这个串还没有拼完的情况下,用arr去拼,最少能用多少张搞定?
不妨设最开始ans=无穷张

那么宏观调度上,
(1)我们只需要枚举arr的每一个串,做一次排头,用1张这个排头,就需要把target中相应的字符消除,然后拿剩下的restNext串去继续递归cur=f(arr,restNext),看看这种情况下,我使用的最少张数cur是多少?
(2)每次一个arr[i]做排头,得到的结果cur+1(为啥+1,因为我排头这张也用了哦,你cur是restNext的最少张数呢),都需要跟ans对比,把最小值给ans。最后返回ans最小张数。

这个逻辑自己好好思考一遍!【算法的关键就在捋清逻辑,然后才能下手写代码】


字符串词频的统计处理

f(arr,restNext)这个逻辑也不难吧?

但是图中那个rest-arr[i]=restNext串啊,可不是数字这么简单的
rest-arr[i]=restNext是:rest用arr[i]串消除了arr[i]中的串之后余下没有拼的字符串

咱们在代码中模拟这个过程,需要知道[i]串中的每个字符a–z有多少个?
比如abcda

而arr整个数组,咱们都要统计,方面用
所以啊,咱直接用哈希表map来替换数组arr,

咋做呢?

哈希表map的
key:放arr[i]串,
value:代表是一个1位26长度的数组,每一位是a–z,0–25索引,是arr[i]串的词频。
这个技巧我们用了多次了哦!
比如:
arr= ba c abcd
value是一个定长26的数组,0–25代表a–z字符

索引任意字符串中的一个字符:如果arr[i]中的1个字符串用str来表示,要索引str中的其中一个字符时,用str[i] - ‘a’=0–25来索引(因为字符串存的就是ASCII码值)

同理,target要统计它的词频,也类似,只不过因为target是1个字符串,咱用1个定长26数组来模拟哈希表
比如令tmp = target = 26长度的数组,0–25代表a–z

这个数据处理好了以后,咱们拿1张map(arr)中的贴纸来拼target怎么搞呢?
显然,tmp(target)中的26字符都检查一遍,这仍然是当o(1)的速度,因为26就是常规长度,很短的。
将tmp(target)中每个字符的词频 - 减去 map中所用贴纸的各个对应位的词频,剩下的就是没拼的restNext串

比如:
tmp(target)= 17 7 10【分别代表0 1 2位置的a b c:a还有17个没拼,b还有7个,c还有10个】
实际上target=aaaaaaaaaaaaaaaaabbbbbbbcccccccccc
mapi=10 13 19【分别代表0 1 2位置的a b c:a还有10个可以拿去用,b还有13个,c还有19个】
restNext = rest-arr[i]=tmp(target)- mapi = 17 7 10 - 10 13 19 = 7 0 0 【也就是还有7个a没有拼,还需要去递归找贴纸来拼】

这里:rest-arr[i]=restNext是:rest用arr[i]串消除了arr[i]中的串之后余下没有拼的字符串

如何?这个用纸拼target的过程明白了吧?
我为啥用哈希表替代arr,就是为了统计词频,因为剪,拼,都不需要关心顺序,只需要看词频!

【当然,咱们真的实现代码时,没法往哈希表中放数组,所以呢,用二维数组表示map】


将暴力递归与傻缓存dp表同时用,适用于字符串的动态规划

情况是这样的,别的那些不是字符串的数组,可以用来到arr的i位置,还有rest需要拼,咋操作,得到最小的结果ans,更新给dp[i][rest]
比如(1)数组arr里面是面币值,每种面币任选无数张,组成target的办法有多少种
(1)中可以在i位置决定面币用多少张,要去拼rest值,那很方便整一个二维表来存结果到i rest格子

但是咱们这个题,很特殊!!!
为啥呢,由于我们可以重复用同一张贴纸,还能剪切,而且与顺序无关,所以我们只关心贴纸的字符的词频
而且递归函数中,我们不是像纸币那样,枚举本面币用k张,而去计算结果
我们是不枚举的,枚举行为通过不断调用f(arr,rest),f中每次都枚举arri做一次排头来达到多次使用同一个贴纸的目的
——这里一定要想清楚,这里的枚举,不是赤裸裸的枚举张,而是通过递归调用,枚举每个贴纸做排头的过程来实现

还有,我们关注,target在不断被拼接之后,剩余没拼的next串,
next串其实在咱们拼接的过程中,由于词频减少,next串可能有各种千奇百怪的组合,比如aaac,cbadfg等等等等,
到底有多少next串的可能性,我们不清楚
那么,我们想在暴力递归的过程中,一旦某个next串出现了,咱就统计一下,这个next串,它后续拼完的话,需要的最少张数是多少?
用一个哈希表dp来存,这样每次更新最小值,都放入这个dp表,类似于(1)文章中的dp表

dp表的key:字符串target的各种情况,
value:完成拼接target所需最少的张数。

默认最开始dp已经放了“”空串,只需要0张
这样的话,最后我们要dp.get(target)的结果——作为本题的答案!

这个想法,是非常巧妙的,之前我学的时候也没搞懂为啥这么干,
现在才明白,主要是咱们要枚举next串它的各种可能性,然后保存最小值,这本身也就是动态规划的本质。

okay!
有了这么多关于题目的理解,和相应的处理,咱们开始手撕代码!!
这个玩意一定要自己手撕清楚!!!

public static int f(int[][] map,HashMap<String, Integer> dp,String rest){

代码中如何定义呢?
(0)当rest已经在dp中出现了,直接返回dp.get(rest),dp存的就是最小张数,返回【这里dp已经包含了】
那么宏观调度上,
(1)我们只需要枚举arr的每一个串,做一次排头,用1张这个排头,就需要把target中相应的字符消除,然后拿剩下的restNext串去继续递归cur=f(arr,restNext),看看这种情况下,我使用的最少张数cur是多少?
(2)每次一个arr[i]做排头,得到的结果cur+1(为啥+1,因为我排头这张也用了哦,你cur是restNext的最少张数呢),都需要跟ans对比,把最小值给ans。最后返回ans最小张数。

OK:手撕代码:

//不妨设**f(arr,rest)**是咱们要的递归函数,// 含义是:余下rest=target这个串还没有拼完的情况下,用arr去拼,最少能用多少张搞定?//不妨设最开始ans=无穷张//我们想在暴力递归的过程中,一旦某个next串出现了,咱就统计一下,这个next串,它后续拼完的话,需要的最少张数是多少?//用一个哈希表dp来存,这样每次更新最小值,都放入这个dp表public static int f(int[][] map,HashMap<String, Integer> dp,String rest){//当rest已经在dp中出现了,**直接返回dp.get(rest),dp存的就是最小张数**,返回【这里dp已经包含了】if (dp.containsKey(rest)) return dp.get(rest);//没拼完的话,//首先,咱们要准备target为tmp,也转化它的词频int[] tmp = new int[26];char[] target = rest.toCharArray();for(char ch:target) {tmp[ch - 'a']++;}int ans = Integer.MAX_VALUE;//先认为需要无穷张贴纸//那么宏观调度上,////(1)我们只需要枚举arr的每一个串,做一次排头,用1张这个排头,就需要把target中相应的字符消除,// 然后拿剩下的**restNext串**去继续递归**cur=f(arr,restNext)**,看看这种情况下,我使用的最少张数cur是多少?for (int i = 0; i < map.length; i++) {//枚举每个贴纸做排头//咱要拿arri去拼target,如果压根arri中就不存在target的第一个字符,那算了吧if (map[i][target[0] - 'a'] == 0) continue;//枚举别的贴纸吧//tmp(target)中的26字符都检查一遍,// 将tmp(target)中每个字符的词频 - 减去 map中所用贴纸的各个对应位的词频**,剩下的就是没拼的restNext串StringBuffer sb = new StringBuffer();//准备收集没有没拼的字符for (int j = 0; j < 26; j++) {//tmp(target)中的26字符都检查一遍,if (tmp[j] > 0){//确实target还需要拼//拼调arri中这么多,剩余的就是没有拼的呗int need = Math.max(0, tmp[j] - map[i][j]);//map多了剩下0个还需要拼,// map不够,就还有很多要拼//没有拼的j字符,先储备到next中for (int k = 0; k < need; k++) {sb.append((char)(j + 'a'));//j字符,还原ASCII码去字符// 储备到next中}}}//(2)每次一个arr[i]做排头,得到的结果cur+1// (**为啥+1**,因为我排头这张也用了哦,你cur是restNext的最少张数呢),// 都需要跟ans对比,把最小值给ans记录在dp中。最后返回ans最小张数。String restNext = sb.toString();//储备好的没拼的字符串,继续递归,找最小值int cur = f(map, dp, restNext);//去看看后续没有拼好的最少需要多少张if (cur != -1) ans = Math.min(ans, cur + 1);//cur有效,再+1算上我rest已经用了arri这张贴纸哦}//所有帖子都枚举排头了,记录rest这个串,最小结果//当然,得保证ans有效才放,否则就放-1,说明这个拼法压根不行,无效拼接方式dp.put(rest, ans == Integer.MAX_VALUE ? -1 : ans);return dp.get(rest);}//主函数:public static int leastTieZhiNum(String[] arr, String target){if (arr == null || arr.length == 0) return -1;if (target.compareTo("") == 0 || target == null) return 0;int N = arr.length;int[][] map = new int[N][26];//转化arr统计词频for (int i = 0; i < N; i++) {char[] str = arr[i].toCharArray();for (int j = 0; j < str.length; j++) {//对个串,每个位统计词频+1map[i][str[j] - 'a']++;}}//dp用来存拼接rest串所需的最少张数HashMap<String, Integer> dp = new HashMap<>();dp.put("", 0);//空串的话,直接就0张return f(map, dp, target);}

这个逻辑要仔细阅读和实现

测试一下:

public static void test(){String[] arr = {"bc","c","abcd"};String target = "babac";//案例System.out.println(leastTieZhiNum(arr, target));}public static void main(String[] args) {test();}

完美:

2

总结

提示:重要经验:

1)字符串和普通的数字数组处理方式不一样,一定要理解这里的map和dp表,他们分别的功能
2)直接傻缓存搞定暴力递归,加速算法
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。

数组arr中的字符串是贴纸,每种贴纸任选无数张,想要将target串拼出来,至少需要多少张贴纸相关推荐

  1. 获取键盘录入的5个int数,并存放到int数组arr中,输入前提示输入的是第几个值 * * 2.2 传递数组arr调用getNum(int[] arr)方法,获取返回值,并打印输出

    package Day05;import java.util.Scanner;/*** 2.定义main方法:* * 2.1 获取键盘录入的5个int数,并存放到int数组arr中,输入前提示输入的是 ...

  2. (转)Shell中获取字符串长度的七种方法

    Shell中获取字符串长度的七种方法 原文:http://blog.csdn.net/jerry_1126/article/details/51835119 求字符串操作在shell脚本中很常用,下面 ...

  3. Shell脚本中计算字符串长度的5种方法

    这篇文章主要介绍了Shell脚本中计算字符串长度的5种方法,来自于个人Shell脚本长期的开发经验,需要的朋友可以参考下 有时在Linux操作系统中需要计算某个字符串的长度,通过查询资料整理了下目前S ...

  4. c语言中按照“|”字符串截取,shell中取字符串子串的几种方式 截取substr

    echo "123456789" | awk '{print substr($0,5,2)}' 截取 1)awk中函数substr substr(源字符串,开始索引,长度)   开 ...

  5. js二维数组arr中表示读取第i行第j列的是:_c++ c语言 数组与字符串

    c语法7 - 数组与字符串 概述 定义:把具有相同类型的若干变量按有序形式组织起来称为数组. C语言数组属于构造数据类型.一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型.因 ...

  6. java字符连接字符串数组_Java中连接字符串的最佳方法

    java字符连接字符串数组 最近有人问我这个问题–在Java中使用+运算符连接字符串是否对性能不利? 这让我开始思考Java中连接字符串的不同方法,以及它们如何相互对抗. 这些是我要研究的方法: 使用 ...

  7. 终端输入五个数,存入整形数组arr中,求最值(最大、最小)、求和、平均值。

    #include <stdio.h> int main(int argc, const char *argv[]) {     int arr[5];     int i;     int ...

  8. linux中特殊符号分割,Shell_Linux Shell 中实现字符串切割的几种方法

    我们在shell 脚本编程中,经常需要用到字符串切割,即将字符串切割为一个数组,类似java 中的split 函数,下面对几种常见的方式做一个总结. 参考文章: shell 使用指定的分割符来分割字符 ...

  9. shell中取字符串子串的几种方式

    (1)awk中函数substr substr(源字符串,开始索引,长度)   开始索引以0开始 示例: awk '{$a=substr($0,0,2);print $a;}' filename 假设文 ...

最新文章

  1. 汇编语言系统调用过程
  2. 如何修改hosts文件?
  3. python画折线图代码-Python绘制折线图和散点图的详细方法介绍(代码示例)
  4. 游戏行业案例 | 99.7% 的充值玩家比例提升,从何而来?
  5. Java 1.8 HashMap源码探究
  6. 这一次,终于弄懂了协变和逆变
  7. circle loss代码实现_Python全栈之路-23-使用Python实现Logistic回归算法
  8. 同事:别加班了,今天可是你们1024程序员节啊!
  9. 遍历二叉树的基本思路
  10. 2020年创业公司到底过得怎么样?数据分析来为你揭晓
  11. Android 轮播图从 0 到 1
  12. 2017中国“互联网+”数字经济指数 | 腾讯研究院
  13. sqlite简单笔记
  14. 51Nod-1191-消灭兔子
  15. AspUpload 组件上传 安装方法及其Demo(全)
  16. 常用的评论/帖子/文章排序算法四(牛顿冷却定律)
  17. 基于深度学习的3D pose estimation总结(包括几篇2D pose estimation)
  18. ROC/AUC、精准率、召回率、真正率,假正率等指标含义,学习笔记
  19. ImportError: libopencv_imgcodecs.so.4.3: cannot open shared object file: No such file or directory报错
  20. 赛效:WPS中绘制的表格如何添加边框?

热门文章

  1. supermap webgl倾斜摄影加载效率
  2. idea断点突然变灰色及debugger模式缓慢
  3. 魔兽世界之一:备战(模拟)
  4. 小米一体机使用计算机投屏,小米VR一体机、Oculus Go投屏到PC、TV教程
  5. java虚拟机-java内存模型与线程
  6. unity3d实现第一人称射击游戏之CS反恐精英(三)(人物旋转限制和跳跃功能(消除连跳,碰撞消除,高度稳定))
  7. OpenCV之DNN模块,实现深度学习网络的推理加速
  8. 全系标配L2占比首次突破30%,「数据」赛道争夺战一触即发
  9. 对高斯——克吕格投影的认识
  10. windows安装git和环境变量配置