题目描述

这是 LeetCode 上的 467. 环绕字符串中唯一的子字符串 ,难度为 中等

Tag : 「线性 DP」、「树状数组」

把字符串 s 看作是 “abcdefghijklmnopqrstuvwxyz” 的无限环绕字符串,所以 s 看起来是这样的:

"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...." .

现在给定另一个字符串 p 。返回 s 中 唯一 的 p 的 非空子串 的数量 。

示例 1:

输入: p = "a"

输出: 1

解释: 字符串 s 中只有一个"a"子字符。

示例 2:

输入: p = "cac"

输出: 2

解释: 字符串 s 中的字符串“cac”只有两个子串“a”、“c”。.

示例 3:

输入: p = "zab"

输出: 6

解释: 在字符串 s 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。

提示:

  • p 由小写英文字母构成

线性 DP + 树状数组 + 同字符最大长度计数

早上起来没睡醒 老了,脑袋不行了,第一反应是用「线性 DP + 树状数组」来做,估了一下时间复杂度没问题就写了。 该做法有一点点思维难度,因此可能不是这道中等题的标准解法。

定义 为以 为结尾的最大有效子串的长度。

从该状态定义容易得到如下的状态转移方程:

  • 存在 并且 能够接在 后面(除了 为 的下一字母以外,还特别包括 同时 的情况),则我们有 ;
  • 不存在 或者 不能接在 后面,则有 ,含义为 只能自身组成子串。

与此同时,我们知道当结尾元素固定,子串长度固定,对应子串唯一确定。

当不考虑子串重复问题时,若 ,则以 为结尾的有效子串数量为 个(对应以 为结尾,长度范围为 的子数组)。

但实际上,我们不能统计相同的子串,因此我们需要考虑该如何去重。

不失一般性地,假设我们当前处理到字符串 p 中的第 位,以 为结尾的最大子串长度为 :

  • 此前如果 出现过 以 为结尾,长度「大于等于」 的子串的话,那么以 为结尾长度为 的子串必然已被统计,需要跳过。因此我们可以使用一个长度为 的数组 max,记录以每个字符 结尾的,出现过的最大子串长度为多少(当 max[s[i]] >= f[i] 时,跳过计数);
  • 此前如果 出现过 以 为结尾,长度「小于」 的子串的话,我们也不能直接统计累加 到答案上,这会导致那些以 为结尾,长度小于 的子串被重复计数,此时我们 需要知道在以 为结尾,长度为 范围内还有多少个子串尚未被统计,这可以使用「树状数组」解决:在 中总个数为 ,使用树状数组维护在 中已被统计的数的个数 ,那么 即是本次可增加的计数,计数完成后我们还需要在树状数组中的 位置增加 ,确保下次查询相同字符结尾长度不小于 的已覆盖子串数量时不会出错。

至此,我们通过「树状数组」+「记录同字符最大长度」的方式来分别解决「长度比 小」和「长度比 大」的重复子串统计问题。

代码:

class Solution {    int N = 100010;    int[][] trs = new int[26][N];    int[] f = new int[N], max = new int[26];    int n, ans;    int lowbit(int x) {        return x & -x;    }    void add(int[] tr, int x, int v) {        for (int i = x; i <= n + 1; i += lowbit(i)) tr[i] += v;    }    int query(int[] tr, int x) {        int ans = 0;        for (int i = x; i > 0; i -= lowbit(i)) ans += tr[i];        return ans;    }    public int findSubstringInWraproundString(String _p) {        char[] cs = _p.toCharArray();        n = cs.length;         for (int i = 0; i < n; i++) {            int c = cs[i] - 'a';            if (i == 0) {                f[i] = 1;            } else {                int p = cs[i - 1] - 'a';                if ((c == 0 && p == 25) || p + 1 == c) f[i] = f[i - 1] + 1;                else f[i] = 1;            }            if (max[c] >= f[i]) continue;            int cnt = f[i] - query(trs[c], f[i]);            if (cnt == 0) continue;            ans += cnt;            add(trs[c], f[i], cnt);            max[c] = f[i];        }        return ans;    }}
  • 时间复杂度:
  • 空间复杂度: ,其中 为字符串 p 的字符集大小

线性 DP

对于相同的结尾字符 而言,如果在整个动规过程中的最大长度为 ,那么以 为结尾字符对答案的贡献为 。

基于此,我们只需保留解法一中的 max 数组即可,同时利用 只依赖于 进行更新,因此动规数组也可以使用一个变量来代替。

代码:

class Solution {    public int findSubstringInWraproundString(String _p) {        char[] cs = _p.toCharArray();        int n = cs.length, ans = 0;        int[] max = new int[26];        max[cs[0] - 'a']++;        for (int i = 1, j = 1; i < n; i++) {            int c = cs[i] - 'a', p = cs[i - 1] - 'a';            if ((p == 25 && c == 0) || p + 1 == c) j++;            else j = 1;            max[c] = Math.max(max[c], j);        }        for (int i = 0; i < 26; i++) ans += max[i];        return ans;    }}
  • 时间复杂度:
  • 空间复杂度: ,其中 为字符串 p 的字符集大小

最后

这是我们「刷穿 LeetCode」系列文章的第 No.467 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。


本文由 mdnice 多平台发布

【宫水三叶的刷题日记】467. 环绕字符串中唯一的子字符串(中等)相关推荐

  1. 【宫水三叶的刷题日记】209. 长度最小的子数组(中等)

    题目描述 这是 LeetCode 上的 209. 长度最小的子数组 ,难度为 中等. Tag : 「前缀和」.「二分」 给定一个含有 n 个正整数的数组和一个正整数 target. 找出该数组中满足其 ...

  2. 【宫水三叶的刷题日记】961. 在长度 2N 的数组中找出重复 N 次的元素

    题目描述 这是 LeetCode 上的 961. 在长度 2N 的数组中找出重复 N 次的元素 ,难度为 简单. Tag : 「模拟」.「计数」.「构造」.「哈希表」 给你一个整数数组 nums ,该 ...

  3. 【宫水三叶的刷题日记】1022. 从根到叶的二进制数之和

    题目描述 这是 LeetCode 上的 1022. 从根到叶的二进制数之和 ,难度为 简单. Tag : 「DFS」.「BFS」.「二叉树」.「树的遍历」 给出一棵二叉树,其上每个结点的值都是   或 ...

  4. 【宫水三叶的刷题日记】1037. 有效的回旋镖(简单)

    题目描述 这是 LeetCode 上的 1037. 有效的回旋镖 ,难度为 简单. Tag : 「计算几何」.「数学」 给定一个数组 points,其中   表示 X-Y 平面上的一个点,如果这些点构 ...

  5. 【宫水三叶的刷题日记】497. 非重叠矩形中的随机点(中等)

    题目描述 这是 LeetCode 上的 497. 非重叠矩形中的随机点 ,难度为 中等. Tag : 「前缀和」.「二分」.「随机化」 给定一个由非重叠的轴对齐矩形的数组 rects,其中 表示 是第 ...

  6. 【宫水三叶的刷题日记】732. 我的日程安排表 III

    题目描述 这是 LeetCode 上的 「732. 我的日程安排表 III」 ,难度为 「困难」. Tag : 「线段树(动态开点)」.「分块」.「线段树」 当 个日程安排有一些时间上的交叉时(例如 ...

  7. 【宫水三叶的刷题日记】715. Range 模块

    题目描述 这是 LeetCode 上的 715. Range 模块 ,难度为 困难. Tag : 「线段树」.「线段树(动态开点)」 Range 模块是跟踪数字范围的模块.设计一个数据结构来跟踪表示为 ...

  8. 【宫水三叶的刷题日记】730. 统计不同回文子序列(困难)

    题目描述 这是 LeetCode 上的 730. 统计不同回文子序列 ,难度为 困难. Tag : 「区间 DP」.「动态规划」 给定一个字符串 s,返回 s 中不同的非空「回文子序列」个数 . 通过 ...

  9. 【宫水三叶的刷题日记】508. 出现次数最多的子树元素和

    题目描述 这是 LeetCode 上的 508. 出现次数最多的子树元素和 ,难度为 中等. Tag : 「树的遍历」.「DFS」.「哈希表」 给你一个二叉树的根结点 root,请返回出现次数最多的子 ...

最新文章

  1. SpringSecurity使用 配置文件 和wen.xml 文件配置
  2. Android JNI编程(六)——C语言函数指针、Unition联合体、枚举、Typedef别名、结构体、结构体指针...
  3. 远程办公从学习开始,潜伏在家,技术如何逆袭?
  4. CCF NOI1115 找数
  5. html5表单与Jquery Ajax结合使用
  6. linux中unzip命令无法使用解决方法
  7. 代码检查、评审、单元测试工具 大搜集
  8. sklearn 决策树例子_sklearn CART决策树分类
  9. Python量化投资——这个均线择时投资策略,12年只交易24次,比沪深300收益率高700倍
  10. iOS前后台切换和监听
  11. 制作u盘winpe启动盘_如何下载优启通U盘启动盘制作工具并制作启动盘?
  12. 2022.8.22 小W的玻璃弹珠 题解
  13. 关于PIN只能更改不能删除的解决方法
  14. vue图片时间轴滑动_vue 写的时间区间拖动控件
  15. 什么是formData
  16. Autosar Configuration(五) Security之Csm配置
  17. 极路由 刷linux,极路由极壹HC6361刷OpenWrt固件教程
  18. 计算机一级考试试题excel,计算机一级考试模拟题(word、excel、ppt以及基础知识);...
  19. 硬盘的种类、区别、运行原理
  20. linux查看已经连接的wifi的密码

热门文章

  1. LeetCode 笔记系列16.3 Minimum Window Substring [从O(N*M), O(NlogM)到O(N),人生就是一场不停的战斗]...
  2. 怒斩获了30家互联网公司offer,赶紧收藏备战金三银四!
  3. JSE5-异常和断言
  4. Unicode编码表大全
  5. 虚拟机安装redhat 9.0后,解决屏幕不能全屏以及避免鼠标来回切换的方法
  6. 【分布式】Rabbitmq死信队列模型、实战场景---订单延迟30min支付处理
  7. uni-app小程序分享自定义全局设置,分享朋友,分享朋友圈(亲测有效!!!)
  8. 微信公众号开发之验证服务器的有效性
  9. OAF的Table中,LOV列依赖一个messageStyledText列
  10. 高校非计算机专业学生计算机基础,浅析高校非计算机专业计算机基础教学改革...