[LeetCode] 828. Unique Letter String 独特字符串
Let's define a function `countUniqueChars(s)` that returns the number of unique characters on `s`, for example if `s = "LEETCODE"` then `"L"`, `"T"`,`"C"`,`"O"`,`"D"` are the unique characters since they appear only once in `s`, therefore `countUniqueChars(s) = 5`.
On this problem given a string s we need to return the sum of countUniqueChars(t) where t is a substring of s. Notice that some substrings can be repeated so on this case you have to count the repeated ones too.
Since the answer can be very large, return the answer modulo 10 ^ 9 + 7.
Example 1:
Input: s = "ABC"
Output: 10
Explanation: All possible substrings are: "A","B","C","AB","BC" and "ABC".
Evey substring is composed with only unique letters.
Sum of lengths of all substring is 1 + 1 + 1 + 2 + 2 + 3 = 10
Example 2:
Input: s = "ABA"
Output: 8
Explanation: The same as example 1, except `countUniqueChars`("ABA") = 1.
Example 3:
Input: s = "LEETCODE"
Output: 92
Constraints:
0 <= s.length <= 10^4scontain upper-case English letters only.
这道题给了我们一个字符串S,要统计其所有的子串中不同字符的个数之和,这里的子串是允许重复的,而且说结果需要对一个超大数取余,这暗示了返回值可能会很大,这样的话对于纯暴力的解法,比如遍历所有可能的子串并统计不同字符的个数的这种解法肯定是不行的。这道题还真是一点没有辱没其 Hard 标签,确实是一道很有难度的题,不太容易想出正确解法。还好有 [李哥 lee215 的帖子](https://leetcode.com/problems/unique-letter-string/discuss/128952/One-pass-O(N)-Straight-Forward),一个帖子的点赞数超过了整个第一页所有其他帖子的点赞数之和,简直是刷题界的 Faker,你李哥永远是你李哥。这里就按照李哥的帖子来讲解吧,首先来看一个字符串 CACACCAC,若想让第二个A成为子串中的唯一,那么必须要知道其前后两个相邻的A的位置,比如 CA(CACC)AC,括号中的子串 CACC 中A就是唯一的存在,同样,对于 CAC(AC)CAC,括号中的子串 AC 中A也是唯一的存在。这样就可以观察出来,只要左括号的位置在第一个A和第二个A之间(共有2个位置),右括号在第二个A和第三个A之间(共有3个位置),这样第二个A在6个子串中成为那个唯一的存在。换个角度来说,只有6个子串可以让第二个A作为单独的存在从而在结果中贡献。这是个很关键的转换思路,与其关注每个子串中的单独字符个数,不如换个角度,对于每个字符,统计其可以在多少个子串中成为单独的存在,同样可以得到正确的结果。这样的话,每个字母出现的位置就很重要了,由于上面的分析说了,只要知道三个位置,就可以求出中间的字母的贡献值,为了节省空间,只保留每个字母最近两次的出现位置,这样加上当前位置i,就可以知道前一个字母的贡献值了。这里使用一个长度为 26x2 的二维数组 idx,因为题目中限定了只有26个大写字母。这里只保留每个字母的前两个出现位置,均初始化为 -1。然后遍历S中每个字母,对于每个字符减去A,就是其对应位置,此时将前一个字母的贡献值累加到结果 res 中,假如当前字母是首次出现,也不用担心,前两个字母的出现位置都是 -1,相减后为0,所以累加值还是0。然后再更新 idx 数组的值。由于每次都是计算该字母前一个位置的贡献值,所以最后还需要一个 for 循环去计算每个字母最后出现位置的贡献值,此时由于身后没有该字母了,就用位置N来代替即可,参见代码如下:
解法一:
class Solution {
public:
int uniqueLetterString(string S) {
int res = 0, n = S.size(), M = 1e9 + 7;
vector<vector<int>> idx(26, vector<int>(2, -1));
for (int i = 0; i < n; ++i) {
int c = S[i] - 'A';
res = (res + (i - idx[c][1]) * (idx[c][1] - idx[c][0]) % M) % M;
idx[c][0] = idx[c][1];
idx[c][1] = i;
}
for (int c = 0; c < 26; ++c) {
res = (res + (n - idx[c][1]) * (idx[c][1] - idx[c][0]) % M) % M;
}
return res;
}
};
我们也可以换一种解法,使得其更加简洁一些,思路稍微有些不同,这里参考了 [大神 meng789987 的帖子](https://leetcode.com/problems/unique-letter-string/discuss/158378/Concise-DP-O(n)-solution)。使用的是动态规划 Dynmaic Programming 的思想,用一个一维数组 dp,其中 dp[i] 表示以 S[i] 为结尾的所有子串中的单独字母个数之和,这样只要把 [0, n-1] 范围内所有的 dp[i] 累加起来就是最终的结果了。更新 dp[i] 的方法关键也是要看重复的位置,比如当前是 AB 的话,此时 dp[1]=3,因为以B结尾的子串是 B 和 AB,共有3个单独字母。若此时再后面加上个C的话,由于没有重复出现,则以C结尾的子串 C,BC,ABC 共有6个单独字母,即 dp[2]=6,怎么由 dp[1] 得到呢?首先新加的字母本身就是子串,所以一定是可以贡献1的,然后由于之前都没有C出现,则之前的每个子串中C都可以贡献1,而原本的A和B的贡献值也将保留,所以总共就是 dp[2] = 1+dp[1]+2 = 6。但若新加的字母是A的话,就比较 tricky 了,首先A本身也是子串,有稳定的贡献1,由于之前已经有A的出现了,所以只要知道了之前A的位置,那么中间部分是没有A的,即子串 B 中没有A,A可以贡献1,但是对于之前的有A的子串,比如 AB,此时新加的A不但不能贡献,反而还会伤害之前A的贡献值,即变成 ABA 了后,不但第二个A不能贡献,连第一个A之前的贡献值也要减去,此时 dp[2] = 1+dp[1]+(2-1)-(1-0) = 4。其中2是当前A的位置,1是前一个A的位置加1,0是再前一个A的位置加1。讲到这里应该就比较清楚了吧,这里还是要知道每个字符的前两次出现的位置,这里用两个数组 first 和 second,不过需要注意的是,这里保存的是位置加1。又因为每个 dp 值只跟其前一个 dp 值有关,所以为了节省空间,并不需要一个 dp 数组,而是只用一个变量 cur 进行累加即可,记得每次循环都要把 cur 存入结果 res 中。那么每次 cur 的更新方法就是前一个 cur 值加上1,再加上当前字母产生的贡献值,减去当前字母抵消的贡献值,参见代码如下:
解法二:
class Solution {
public:
int uniqueLetterString(string S) {
int res = 0, n = S.size(), cur = 0, M = 1e9 + 7;
vector<int> first(26), second(26);
for (int i = 0; i < n; ++i) {
int c = S[i] - 'A';
cur = cur + 1 + i - first[c] * 2 + second[c];
res = (res + cur) % M;
second[c] = first[c];
first[c] = i + 1;
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/828
参考资料:
https://leetcode.com/problems/count-unique-characters-of-all-substrings-of-a-given-string/
[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)
[LeetCode] 828. Unique Letter String 独特字符串的更多相关文章
- 【leetcode】828. Unique Letter String
题目如下: A character is unique in string S if it occurs exactly once in it. For example, in string S = ...
- Leetcode 344:Reverse String 反转字符串(python、java)
Leetcode 344:Reverse String 反转字符串 公众号:爱写bug Write a function that reverses a string. The input strin ...
- [Swift]LeetCode828. 独特字符串 | Unique Letter String
A character is unique in string S if it occurs exactly once in it. For example, in string S = " ...
- LeetCode828. Unique Letter String
https://leetcode.com/problems/unique-letter-string/description/ A character is unique in string S if ...
- Unique Letter String LT828
A character is unique in string S if it occurs exactly once in it. For example, in string S = " ...
- [LeetCode] 288.Unique Word Abbreviation 独特的单词缩写
An abbreviation of a word follows the form <first letter><number><last letter>. Be ...
- 【LeetCode】Unique Email Addresses(独特的电子邮件地址)
这道题是LeetCode里的第929道题. 题目要求: 每封电子邮件都由一个本地名称和一个域名组成,以 @ 符号分隔. 例如,在 alice@leetcode.com中, alice 是本地名称,而 ...
- LeetCode 929. Unique Email Addresses (独特的电子邮件地址)
题目标签:String 题目说明 有两个规则针对于 local name. 所以先把local name 和 domain name 分开. 两个规则是: rule 1:'.' 会被去除. (利用re ...
- [LeetCode] 929. Unique Email Addresses 独特的邮件地址
Every email consists of a local name and a domain name, separated by the @ sign. For example, in ali ...
随机推荐
- LeetCode28——实现strStr()
6月中下旬辞职在家,7 月份无聊的度过了一个月.8 月份开始和朋友两个人写项目,一个后台和一个 APP ,APP 需要对接蓝牙打印机.APP 和蓝牙打印机都没有搞过,开始打算使用 MUI 开发 APP ...
- 明解C语言 入门篇 第十三章答案
练习13-1 /* 打开与关闭文件 */ #include <stdio.h> int main(void) { ]; FILE* fp; printf("请输入你要打开的文件& ...
- 明解C语言 入门篇 第十一章答案
练习11-1 /* 用指针实现的字符串的改写 */ #include <stdio.h> int main(void) { "; printf("p = \" ...
- nodejs块级作用域
现在让我们了解3个关键字var.let.const,的特性和使用方法. var JavaScript中,我们通常说的作用域是函数作用域,使用var声明的变量,无论是在代码的哪个地方声明的,都会提升到当 ...
- jQuery-ready与load
// ready 在DOM加载完成时运行的代码 $(document).ready(function(){ // 在这里写代码... }) // 可以简写为 $(function(){ // 在这 ...
- MES系统如何帮助烟草行业管理生产流程
与很多其他行业一样,烟草MES系统可以帮助卷烟企业实现智能生产.精益制造.快速实现烟草企业数字化车间的创建,助力企业实现改造升级,从而提升企业生产效率,降低生产成产.烟草行业得MES者得天下. 烟草行 ...
- Java同步和异步,阻塞和非阻塞
同步和异步.阻塞和非阻塞 同步和异步关注的是消息通信机制. 同步是指: 发送方发出数据后, 等待接收方发回响应后才发下一个数据包的通讯方式. 就是在发出一个调用时, 在没有得到结果之前, 该调用就不返 ...
- 第17节-BLE安全管理概述
安全管理是BLE中最复杂的内容,涉及LL层.SM层.GAP层 一.妈妈的担心 1. 白名单: 妈妈说,你只能跟A.B.C这3个好孩子玩:他们打电话给你,你才可以出去玩. A.B.C三人,就在妈妈的“白 ...
- RegxUtils正则表达式工具类
public class RegxUtils { //------------------常量定义 /** * Email正则表达式="^([a-z0-9A-Z]+[-|\\.]?)+[a- ...
- 吴丽丽-201871010123 《面向对象程序设计(java)》第六、七周学习总结
吴丽丽-201871010123 <面向对象程序设计(java)>第六.七周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh ...