Count the string(kmp+dp)

KMP算法与DP求解
本文介绍了一种使用KMP算法结合动态规划方法解决字符串匹配问题的技术。该方法能够高效计算一个字符串的所有非空前缀在自身出现的次数总和,并通过模运算处理可能的大数值结果。

Count the string

It is well known that AekdyCoin is good at string problems as well as number theory problems. When given a string s, we can write down all the non-empty prefixes of this string. For example:
s: "abab"
The prefixes are: "a", "ab", "aba", "abab"
For each prefix, we can count the times it matches in s. So we can see that prefix "a" matches twice, "ab" matches twice too, "aba" matches once, and "abab" matches once. Now you are asked to calculate the sum of the match times for all the prefixes. For "abab", it is 2 + 2 + 1 + 1 = 6.
The answer may be very large, so output the answer mod 10007.

Input
The first line is a single integer T, indicating the number of test cases.
For each case, the first line is an integer n (1 <= n <= 200000), which is the length of string s. A line follows giving the string s. The characters in the strings are all lower-case letters.
Output
For each case, output only one number: the sum of the match times for all the prefixes of s mod 10007.
Sample Input
1
4
abab
Sample Output
6

如果求出next数组以每个子串再用kmp分别求个数肯定会超时。

这个题利用next数组的性质,利用dp,可以得到递推式dp[i] = dp[Next[i]] + 1求,下面具体解释这个递推式的含义和成立原因

dp[i]表示的是以下标为i-1的字母为结尾的任意前缀子串的个数,为什么是i-1,因为字符串从0开始记录,next数组next[i]记录的字符串长度是i实际是下标从0~i-1,同理dp[i]也是存在长度为i的前缀中也就是下标从0~i-1,以i-1号字符为结尾的任意前缀个数

这个公式为什么是对的呢

首先+1就是加上目前这个前缀字符串本身,因为我们又增加了一个字符嘛,比如a b c d  dp[3]存abc中以c结尾任意前缀长度

                                                                                                                        0 1 2 3

+1的目的就是先把abc这个本身加上

然后再来看dp[Next[i]],Next[i]是0~i-1子串s中公共前后缀串的最大长度,同时也是这个子串公共前缀的下一个下标p,

所以dp[p]就代表了我们之前已经求完的,s中最长公共前缀(也是后缀)中以前缀最后一个字符为结尾的任意前缀个数(有点绕),同理他也是后缀的,那么后缀是不是和我们新加的i-1号字符相连,那么之前有多少个,再加上这个新字符是不是就有多少个新的字符了,然后再加1本身,就是以i-1号字符为结尾所有了前缀了

例如   0  1   2  3  4

          a   b  a  b

next  -1   0  0  1  2

dp      0   1  1  2  2

dp[1]=1->"a"   dp[2]=1->"ab"  dp[3]=2->"aba","a"  dp[3]=2->"abab","ab"

code:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int MAXN = 200100;
const int MOD = 10007;
char s[MAXN];
int n;
int Next[MAXN];
int dp[MAXN];
int sum;
void getNext(){
    int i = -1,j = 0;
    Next[0] = -1;
    int len = strlen(s);
    while(j < len){
        if(i == -1 || s[i] == s[j]){
            i++,j++;
            Next[j] = i;
        }
        else
            i = Next[i];
    }
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
      scanf("%d",&n);
      scanf("%s",s);
      getNext();
      int i;
      memset(dp,0,sizeof(dp));
      sum = 0;
      for(i = 1; i <= n; i++){
         dp[i] = dp[Next[i]] + 1;
         sum = (sum + dp[i])%MOD;
      }
      printf("%d\n",sum);
    }
    return 0;
}


如果你是一名专业的java高级架构师,现在你在面试知识,如下是 数据结构与算法的面试知识体系结构图 请按照这个体系给出每个知识点的学习理解方便记忆和消化,同时给出每个知识点的高频面试的标准面试答案,结合项目经验给出解释和实战 数据结构与算法面试核心知识体系 ├── 一、复杂度分析基础 │ ├── 时间复杂度 │ │ ├── 大O表示法:O(1), O(logn), O(n), O(nlogn), O()等含义? │ │ ├── 最好、最坏、平均时间复杂度分析? │ │ └── 递归算法的时间复杂度分析:主定理? │ ├── 空间复杂度 │ │ ├── 算法运行所需的额外空间?原地操作含义? │ │ ├── 递归调用的空间复杂度(调用栈深度)? │ │ └── 如何权衡时间与空间复杂度? │ └── 实际应用 │ ├── 如何根据数据规模选择合适的算法?(10⁵数据不能用O()) │ ├── 常数项优化在实际工程中的意义? │ └── 摊还分析:某些操作的平均代价? ├── 二、数组、链表与字符串 │ ├── 数组(Array) │ │ ├── 特点:随机访问O(1),插入删除O(n)? │ │ ├── 双指针技巧:快慢指针、左右指针、滑动窗口? │ │ ├── 前缀和数组:快速计算区间和? │ │ └── 差分数组:快速进行区间增减操作? │ ├── 链表(Linked List) │ │ ├── 单链表、双链表、循环链表区别? │ │ ├── 虚拟头节点(dummy node)的作用? │ │ ├── 常见问题:反转链表、检测环、相交链表、合并有序链表? │ │ └── 链表排序:归并排序实现? │ └── 字符串(String) │ ├── 字符串匹配算法:KMP、Rabin-Karp? │ ├── 回文串问题:中心扩展法、动态规划? │ ├── 字符串操作:翻转、替换、分割? │ └── 不可变字符串的优势?(线程安全、缓存哈希值) ├── 三、栈、队列与哈希表 │ ├── 栈(Stack) │ │ ├── LIFO特性,应用场景:函数调用栈、括号匹配? │ │ ├── 单调栈:解决"下一个更大元素"问题? │ │ └── 最小栈:如何O(1)获取栈中最小值? │ ├── 队列(Queue) │ │ ├── FIFO特性,BFS算法基础? │ │ ├── 优先队列(堆):获取最值,Dijkstra算法? │ │ ├── 单调队列:解决滑动窗口最值问题? │ │ └── 双端队列(Deque):实现滑动窗口? │ └── 哈希表(Hash Table) │ ├── 原理:哈希函数、冲突解决(链地址法、开放寻址法)? │ ├── 设计哈希集合、哈希映射? │ ├── 实际应用:缓存(LRU)、快速查找、去重? │ └── 哈希碰撞攻击原理?如何设计好的哈希函数? ├── 四、树形数据结构 │ ├── 二叉树基础 │ │ ├── 遍历方式:前序、中序、后序(递归/迭代)? │ │ ├── 二叉搜索树(BST):性质、查找、插入、删除? │ │ ├── 平衡二叉树:AVL树、红黑树基本概念? │ │ └── 完全二叉树、满二叉树定义? │ ├── 树的变种 │ │ ├── 堆(Heap):大顶堆、小顶堆,优先队列实现? │ │ ├── Trie树(前缀树):字符串前缀匹配? │ │ ├── 线段树:区间查询、区间更新? │ │ └── 树状数组(BIT):单点更新、前缀查询? │ └── 树的应用 │ ├── 最近公共祖先(LCA)问题? │ ├── 二叉树的序列化与反序列化? │ ├── 树的直径、高度、路径和问题? │ └── 哈夫曼编码:数据压缩? ├── 五、图论算法 │ ├── 图的表示 │ │ ├── 邻接矩阵 vs 邻接表?空间时间复杂度? │ │ ├── 有向图、无向图、加权图? │ │ └── 图的遍历:BFS、DFS实现? │ ├── 最短路径算法 │ │ ├── Dijkstra算法:非负权图,贪心策略? │ │ ├── Bellman-Ford算法:处理负权边? │ │ ├── Floyd-Warshall算法:多源最短路径? │ │ └── A算法:启发式搜索? │ ├── 最小生成树 │ │ ├── Prim算法:从点开始扩展? │ │ ├── Kruskal算法:按边排序+并查集? │ │ └:适用场景:网络布线、电路设计? │ └── 其他图算法 │ ├── 拓扑排序:有向无环图(DAG),课程安排? │ ├── 并查集(Union-Find):连通分量,路径压缩优化? │ ├── 欧拉路径/回路:一笔画问题? │ └── 强连通分量:Kosaraju或Tarjan算法? ├── 六、排序与搜索算法 │ ├── 排序算法 │ │ ├── 比较排序:冒泡、选择、插入、归并、快速、堆排序? │ │ ├── 非比较排序:计数排序、桶排序、基数排序? │ │ ├── 稳定性分析:哪些是稳定排序? │ │ └── 各排序算法的时间/空间复杂度总结? │ ├── 搜索算法 │ │ ├── 二分查找:模板、边界处理、旋转数组搜索? │ │ ├── DFS深度优先:回溯法,排列组合问题? │ │ ├── BFS广度优先:最短路径,层级遍历? │ │ └── 启发式搜索:A算法,估价函数设计? │ └── 实际应用 │ ├── 海量数据排序:外部排序,归并思想? │ ├── 第K大/小元素:快速选择算法O(n)? │ ├── 在排序数组中查找目标值的起始和结束位置? │ └── 寻找旋转排序数组中的最小值? ├── 七、动态规划(DP) │ ├── 基本思想 │ │ ├── 重叠子问题、最优子结构? │ │ ├── 自顶向下(记忆化递归) vs 自底向上(迭代)? │ │ └── 状态定义、状态转移方程、边界条件? │ ├── 经典问题 │ │ ├── 背包问题:0-1背包、完全背包、状态压缩? │ │ ├── 最长公共子序列(LCS)、最长递增子序列(LIS)? │ │ ├── 编辑距离:字符串转换的最小操作数? │ │ ├── 股票买卖问题:多种限制条件? │ │ └── 打家劫舍、零钱兑换、路径问题? │ └── 解题技巧 │ ├── 如何识别DP问题?状态设计技巧? │ ├── 空间优化:滚动数组? │ ├── 输出具体方案而不仅仅是数值? │ └── 数位DP、状压DP、区间DP简介? ├── 八、贪心算法 │ ├── 基本概念 │ │ ├── 贪心选择性质?局部最优导致全局最优? │ │ ├── 与动态规划的区别?贪心无法回溯? │ │ └── 如何证明贪心策略的正确性? │ ├── 典型问题 │ │ ├── 区间调度:最多不重叠区间? │ │ ├── 哈夫曼编码:最优前缀码? │ │ ├── 分糖果、跳跃游戏、加油站问题? │ │ ├:贪心+排序:根据某个指标排序后贪心? │ │ └:贪心+优先队列:实时获取最优解? │ └── 应用场景 │ ├── 最小生成树:Prim和Kruskal算法中的贪心思想? │ ├── 最短路径:Dijkstra算法的贪心选择? │ ├── 数据压缩:贪心构造最优编码? │ └── 任务调度:合理安排任务顺序? ├── 九、高级数据结构与算法 │ ├── 高级数据结构 │ │ ├── 跳表(Skip List):Redis有序集合实现? │ │ ├── 布隆过滤器(Bloom Filter):判断存在性,可能误判? │ │ ├── LRU缓存:哈希表+双向链表实现? │ │ ├── 一致性哈希:分布式系统数据分片? │ │ └── 倒排索引:搜索引擎核心? │ ├── 数学与位运算 │ │ ├── 位操作技巧:判断奇偶、交换数值、找出单独数字? │ │ ├── 素数判断、最大公约数(GCD)、最小公倍数(LCM)? │ │ ├── 快速幂算法:计算a^b % mod? │ │ └── 随机数生成:拒绝采样,水塘抽样? │ └── 高级算法思想 │ ├── 分治算法:归并排序、快速排序、最近点对? │ ├── 回溯算法:N皇后、数独、全排列? │ ├── 位运算优化状态表示(状态压缩)? │ ├:扫描线算法:矩形面积并、天际线问题? │ └:摩尔投票法:寻找多数元素? └── 十、实战技巧与系统设计 ├── 解题方法论 │ ├── 解题步骤:理解题意 -> 分析 -> 选择数据结构 -> 编码 -> 测试? │ ├── 边界条件考虑:空输入、极端值、溢出? │ ├── 代码规范:变量命名、注释、模块化? │ └── 测试用例设计:正常情况、边界情况、错误情况? ├── 系统设计中的应用 │ ├── LRU缓存设计:哈希表+双向链表? │ ├── 排行榜设计:跳表、有序集合? │ ├── 短网址系统:发号器、哈希算法? │ ├:朋友圈设计:并查集管理社交关系? │ └:搜索提示:Trie树实现自动补全? └── 海量数据处理 ├── 分治思想:大数据分解为小数据? ├── 哈希分片:数据均匀分布到多台机器? ├── 位图法:判断存在性,节省空间? ├:堆的应用:Top K问题? └:外部排序:内存不足时如何排序?
最新发布
09-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值