题目:

466. 统计重复个数

由 n 个连接的字符串 s 组成字符串 S,记作 S = [s,n]。例如,["abc",3]=“abcabcabc”。
如果我们可以从 s2中删除某些字符使其变为 s1,则称字符串 s1可以从字符串 s2 获得。例如,根据定义,”abc” 可以从 “abdbec” 获得,但不能从 “acbbe” 获得。
现在给你两个非空字符串 s1 和 s2(每个最多 100 个字符长)和两个整数 0 ≤ n1 ≤ 10和 1 ≤ n2 ≤ 10。现在考虑字符串 S1 和 S2,其中 S1=[s1,n1]S2=[s2,n2]
请你找出一个可以满足使[S2,M]S1 获得的最大整数 M 。

示例:
输入:
s1 =”acb”,n1 = 4
s2 =”ab”,n2 = 2

返回:
2

解题思路:

简单的方式是通过遍历解决,但是n1可能会很大,从而造成超时;所以要想办法减少循环;
方式:找循环,记录遍历完一次s1后下一个待找寻的s2节点序号pnext,记录当前完成s2循环的次数psum;如果有当某一次s1遍历完后pnext的值和从前的某一次pnext相同,则说明这之间产生了循环,从而直接结束遍历,只要根据循环计算即可。当无法整除时,最后的几个s2通过psum求得。

解题图解示例:

QQ图片20200422204924.jpg

代码:

  1. class Solution {
  2. public:
  3. int getMaxRepetitions(string s1, int n1, string s2, int n2) {
  4. //清除无用的字符,减短搜索时间
  5. string s3 = "";
  6. for(int i = 0; i < s1.size(); i++){
  7. for(int j = 0; j < s2.size(); j++){
  8. if(s1[i] == s2[j]){
  9. s3 += s1[i];
  10. break;
  11. }
  12. }
  13. }
  14. int i = 0, j = 0; //循环因子
  15. int sum = 0; //s2循环次数
  16. int k = 0; //M=0情况的出循环标志
  17. int y = 0; //s1循环次数
  18. int* pnext = new int [n1 + 1]; //定义记录并初始化开头的值
  19. pnext[0] = 0;
  20. int* psum = new int [n1 + 1];
  21. psum[0] = 0;
  22. bool flagcir = false; //循环节找到标志
  23. int x = 0; //循环节的初始的标志
  24. for(; y < n1; i++){ //y:循环节的结尾标志
  25. if(i >= s2.size()){ //s1循环
  26. i = 0;
  27. sum++;
  28. }
  29. bool flag = false; //已匹配到字符标志
  30. k = 0;
  31. do{
  32. if(j >= s3.size()){ s2循环
  33. j = 0;
  34. y++;
  35. pnext[y] = i;
  36. psum[y] = sum;
  37. for(x = 0; x < y; x++){ //找寻循环节
  38. if(i == pnext[x]){
  39. flag = true;
  40. flagcir = true;
  41. break;
  42. }
  43. }
  44. k++;
  45. if(flagcir){
  46. break;
  47. }
  48. }
  49. if(s3[j] == s2[i]){ //匹配字符
  50. flag = true;
  51. j++;
  52. // cout << "nei:" << j << endl;
  53. break;
  54. }
  55. j++;
  56. }while(k < 2);
  57. // cout << "wai:" << j << endl;
  58. if(!flag){
  59. cout << "error!" << endl;
  60. return 0;
  61. }
  62. if(flagcir){
  63. break;
  64. }
  65. }
  66. if(flagcir){
  67. cout << "cir" << endl;
  68. cout << x << " " << psum[x] << " " << y - 1 << " " << psum[y - 1] << endl;
  69. sum = psum[x] + (psum[y] - psum[x])*((n1 - x) / (y - x))
  70. + psum[(n1 - x) % (y - x) + x] - psum[x];
  71. /*可化简:sum = (psum[y] - psum[x])*((n1 - x) / (y - x))
  72. + psum[(n1 - x) % (y - x) + x];
  73. */
  74. }
  75. return sum/n2;
  76. }
  77. };

思考:

1.这一题做完花费了很长时间,且写题出现了中断,自我反省;
2.错误历史:(1)直接计算一个s1内的s2出现次数,未考虑跨s2的情况;
(2)直接使用遍历的方式,结果超时;
(3)开始考虑s1在s2的循环查找,但是仅仅考虑相邻s1间出现循环的可能;
(4)未考虑为遍历前的pnext[0] = 0,结果也可以对,但是会延迟循环节寻到的时间;
3.考虑2.4错误后,出现错误heap-buffer-overflow,内存非法访问,pnext[n1],psum[n1]在添加未遍历前起点标志后,空间可能不足,所以定义时int *pnext = new int [n1+ 1];
4.在时间上仍然有改进的空间,附上0ms代码,来自leetcode官网:

  1. class Solution {
  2. public:
  3. int getMaxRepetitions(string s1, int n1, string s2, int n2) {
  4. if (n1 == 0) {
  5. return 0;
  6. }
  7. int s1cnt = 0, index = 0, s2cnt = 0;
  8. // recall 是我们用来找循环节的变量,它是一个哈希映射
  9. // 我们如何找循环节?假设我们遍历了 s1cnt 个 s1,此时匹配到了第 s2cnt 个 s2 中的第 index 个字符
  10. // 如果我们之前遍历了 s1cnt' 个 s1 时,匹配到的是第 s2cnt' 个 s2 中同样的第 index 个字符,那么就有循环节了
  11. // 我们用 (s1cnt', s2cnt', index) 和 (s1cnt, s2cnt, index) 表示两次包含相同 index 的匹配结果
  12. // 那么哈希映射中的键就是 index,值就是 (s1cnt', s2cnt') 这个二元组
  13. // 循环节就是;
  14. // - 前 s1cnt' 个 s1 包含了 s2cnt' 个 s2
  15. // - 以后的每 (s1cnt - s1cnt') 个 s1 包含了 (s2cnt - s2cnt') 个 s2
  16. // 那么还会剩下 (n1 - s1cnt') % (s1cnt - s1cnt') 个 s1, 我们对这些与 s2 进行暴力匹配
  17. // 注意 s2 要从第 index 个字符开始匹配
  18. unordered_map<int, pair<int, int>> recall;
  19. pair<int, int> pre_loop, in_loop;
  20. while (true) {
  21. // 我们多遍历一个 s1,看看能不能找到循环节
  22. ++s1cnt;
  23. for (char ch: s1) {
  24. if (ch == s2[index]) {
  25. index += 1;
  26. if (index == s2.size()) {
  27. ++s2cnt;
  28. index = 0;
  29. }
  30. }
  31. }
  32. // 还没有找到循环节,所有的 s1 就用完了
  33. if (s1cnt == n1) {
  34. return s2cnt / n2;
  35. }
  36. // 出现了之前的 index,表示找到了循环节
  37. if (recall.count(index)) {
  38. auto [s1cnt_prime, s2cnt_prime] = recall[index];
  39. // 前 s1cnt' 个 s1 包含了 s2cnt' 个 s2
  40. pre_loop = {s1cnt_prime, s2cnt_prime};
  41. // 以后的每 (s1cnt - s1cnt') 个 s1 包含了 (s2cnt - s2cnt') 个 s2
  42. in_loop = {s1cnt - s1cnt_prime, s2cnt - s2cnt_prime};
  43. break;
  44. }
  45. else {
  46. recall[index] = {s1cnt, s2cnt};
  47. }
  48. }
  49. // ans 存储的是 S1 包含的 s2 的数量,考虑的之前的 pre_loop 和 in_loop
  50. int ans = pre_loop.second + (n1 - pre_loop.first) / in_loop.first * in_loop.second;
  51. // S1 的末尾还剩下一些 s1,我们暴力进行匹配
  52. int rest = (n1 - pre_loop.first) % in_loop.first;
  53. for (int i = 0; i < rest; ++i) {
  54. for (char ch: s1) {
  55. if (ch == s2[index]) {
  56. ++index;
  57. if (index == s2.size()) {
  58. ++ans;
  59. index = 0;
  60. }
  61. }
  62. }
  63. }
  64. // S1 包含 ans 个 s2,那么就包含 ans / n2 个 S2
  65. return ans / n2;
  66. }
  67. };