【算法】Letter Tile Possibilities 活字印刷

news/2024/10/18 5:39:18/

文章目录

  • Letter Tile Possibilities 活字印刷
    • 问题描述:
    • 分析
    • 代码
      • 回溯
      • 动态规划

Letter Tile Possibilities 活字印刷

问题描述:

你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。

每个活字字模只能使用一次。

tiles.length范围[1,7]

都是大写字母

分析

这个问题规模不大最多就7个字符。
要求在给出的字模中拼出所有的非空字母序列的数目。
字模可以去百度一下,传统的印刷中用的是铅字模。
要求一个字模只能用一次,但是相同的字可能有多个字模可用。
然后就是拼,方向就是递归,字模数量为N,那么结果的字符串的长度就是1~N.
即从N个字模中,选出C个字符的排列。在此需要注意的是排列中重复的问题。
即字符A中2个字模A1,A2,最终只会有1个排列AA.
如果使用map统计配合递归,就不会有这个问题。
将每个字符的频次进行统计,然后定义dfs(map),表示剩余map中的字符可以得到的所有非空字母序列。
因为每一层dfs中,取的字符都不一样,所以剩余的map状态也不一样。
关键点在于dfs的意义,不同的返回值,内部处理也不一样。
每次从字符中选一个,剩余的字符继续dfs,dfs返回的是i-1个字符可以得到排列的数量。
这个比较容易理解,问题是什么时候结束的边界,同时还要解决不同长度的排列。
官解中的边界,为i=0,返回1,也就是说空字符串,也算一个排列。
这样对于单个字符B来说,就是返回2,表示字符串"B",“”,那么再加上之前的"A",可以得到"AB",“A”,为了保留1个"",所以官解上是以res=1进行初始化。

当然也可以用解决排列来处理
但是递归回溯的时间复杂度就非常高。

代码

回溯

class Solution {public int numTilePossibilities(String tiles) {Map<Character, Integer> count = new HashMap<>();for (char t : tiles.toCharArray()) {count.put(t, count.getOrDefault(t, 0) + 1);}Set<Character> tile = new HashSet<>(count.keySet());return dfs(tiles.length(), count, tile) - 1;}private int dfs(int i, Map<Character, Integer> count, Set<Character> tile) {if (i == 0) {return 1;}int res = 1;for (char t : tile) {if (count.get(t) > 0) {count.put(t, count.get(t) - 1);res += dfs(i - 1, count, tile);count.put(t, count.get(t) + 1);}}return res;}
}

时间复杂度 O(N*N!) 空间复杂度: O(N)

class Solution {public int numTilePossibilities(String tiles) {int[] cnt = new int[26];for (char c : tiles.toCharArray()) {++cnt[c - 'A'];}return dfs(cnt);}private int dfs(int[] cnt) {int res = 0;for (int i = 0; i < cnt.length; ++i) {if (cnt[i] > 0) {++res;--cnt[i];res += dfs(cnt);++cnt[i];}}return res;}
}

时间复杂度 O(N*N!) 空间复杂度: O(N)

2种DFS看着很相似,但是其意义却完全不同,所以内部的处理方式,结束边界,也完全不一样。第一个是官解的的dfs,可以理解为字符串长度为i时,可以得到的所有排列数,空字符也算一个。而第二个,可以理解为 【以剩余cnt的字符可以得到所有的字符串排列数】。

其中包含了所有的排列,字符串长度从1~N,而官解的dfs,还多了一个空字符串。

动态规划

以DP的角度思考排列,实际上就是将M个字符选出N个可以得到的排列。

直接定义f[i] [j] 表示 使用前i种字符,可以得到字符串长度=j的数量。

f [ i ] [ j ] = ∑ k = 0 m i n ( j , c n t ) f [ i − 1 ] [ j − k ] × ( j k ) f[i][j] = \sum_{k=0}^{min(j,cnt)}{f[i-1][j-k]\times{j \choose k }} f[i][j]=k=0min(j,cnt)f[i1][jk]×(kj)
对于第i个字符,数量有cnt个,那么k==0,说明没使用第i个字符 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i] [j]=f[i-1] [j] f[i][j]=f[i1][j].

如果k=1,说明使用了1个, f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] ∗ j f[i] [j]=f[i-1] [j-1]*j f[i][j]=f[i1][j1]j.

如果k=2,说明使用了2个, f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] ∗ j ( j − 1 ) f[i] [j]=f[i-1] [j-1]*j(j-1) f[i][j]=f[i1][j1]j(j1).

所以 f [ i ] [ j ] f[i] [j] f[i][j] 是一个累加的数据.

M 是字符的种类,j表示字符串的长度
∑ j = 1 n f [ M ] [ j ] 即所求 \sum_{j=1}^{n}{f[M][j]}即所求 j=1nf[M][j]即所求

class Solution {private static final int MX = 8;private static final int[][] c = new int[MX][MX];static {for (int i = 0; i < MX; i++) {c[i][0] = c[i][i] = 1;for (int j = 1; j < i; j++)c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; // 预处理组合数}}public int numTilePossibilities(String tiles) {var counts = new HashMap<Character, Integer>(); // 统计每个字母的出现次数for (var c : tiles.toCharArray())counts.merge(c, 1, Integer::sum); // counts[c]++int m = counts.size(), n = tiles.length();var f = new int[m + 1][n + 1];f[0][0] = 1; // 构造空序列的方案数int i = 1;for (var cnt : counts.values()) { // 枚举第 i 种字母for (int j = 0; j <= n; j++) // 枚举序列长度 jfor (int k = 0; k <= j && k <= cnt; k++) // 枚举第 i 种字母选了 k 个f[i][j] += f[i - 1][j - k] * c[j][k];i++;}int ans = 0;for (int j = 1; j <= n; j++)ans += f[m][j];return ans;}
}

时间复杂度 O(N*N) 空间复杂度: O(N)

DP代码来源 是灵神大佬的,可以借鉴一下。

因为组合数的规模不大,所以可以使用该方法计算,对于大规模的组合数,这个就不好使了。毕竟组合数的预处理以及数值范围,也是需要考虑的。

还有更高效的,以后再看吧,

Tag

Hash BackTracking Dynamic Programming


http://www.ppmy.cn/news/86578.html

相关文章

python爬取网页代码-python爬虫爬取网页所有数据详细教程

Python爬虫可通过查找一个或多个域的所有 URL 从 Web 收集数据。Python 有几个流行的网络爬虫库和框架。大家熟知的就是python爬取网页数据&#xff0c;对于没有编程技术的普通人来说&#xff0c;怎么才能快速的爬取网站数据呢&#xff1f;今天给大家分享的这款免费爬虫软件让您…

java面试题(MyBatis)

目录 1.什么是MyBatis&#xff1f; 2.MyBatis存在哪些优缺点&#xff1f; 3.MyBatis中#{}和${}的区别 4.MyBatis 有哪几种 SQL 编写形式 5.MyBatis 怎么实现分页 6.MyBatis 如何防止 SQL 注入 7.MyBatis 延迟加载 8.MyBatis 中的缓存机制有啥用&#xff1f; 9.MyBatis…

【数据结构】--单链表力扣面试题①移除链表元素

题述&#xff1a; 给你一个链表的头结点head和一个整数val,请你删除链表中所有满足Node.val val的节点&#xff0c;并返回新的头结点。 思考&#xff1a; 为什么说要返回新的头结点&#xff0c;因为你删除的可能存在把原来的头结点删除的情况&#xff0c;这时就需要有新的头结…

Spring-IOC是什么

Spring-IOC是什么 Spring-IOC是什么IOC是什么 Spring-IOC是什么 Spring-IOC是Spring框架的核心&#xff0c;是一个容器&#xff0c;它负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。 IOC是什么 控制反转&#xff0c;指的是将对象的控制权交给Spring容器&a…

自动构建之Makefile

链接: 自动构建之CMake Makefile Makefile是用于自动化构建软件项目的工具&#xff0c;Makefile的优点是简单、直接&#xff0c;可以直接使用make工具进行构建。但是&#xff0c;Makefile通常需要手动编写和维护&#xff0c;可能会导致跨平台和跨编译器的兼容性问题。 Makef…

adb 命令速查(下)

ADB 关于APP安装、调试和monkey压力测试 作者&#xff1a;炭烤毛蛋 &#xff0c;查看博主了解更多。 提示&#xff1a;承接上篇《adb 命令速查(中)》&#xff0c;本文将 文章目录 ADB 关于APP安装、调试和monkey压力测试7 adb 关于 apk 的相关操作7.1 安装 apk普通安装带有命…

虹科HiveMQ与MQTT:构建互联汽车的新架构

前言 随着汽车的互联程度越来越高&#xff0c;汽车制造商和互联汽车平台提供商通过使用物联网技术&#xff0c;提供新服务并从车辆收集有价值的遥测数据&#xff0c;以此来增加营收。从高效的车队管理和汽车共享到预测性维护和高级驾驶员辅助系统&#xff0c;未来移动出行的可…

uniapp内使用 mescroll

前言 在使用uniapp开发项目的过程中&#xff0c;在很多场景里都需要下拉刷新和上拉加载&#xff0c;而 mescroll.js 则是一个非常精致的下拉刷新和上拉加载 js 框架。 官网地址&#xff1a;mescroll 介绍 mescroll.js 是在 H5端 运行的下拉刷新和上拉加载插件&#xff0c;时…