算法刷题5-字符串

5 字符串

5.0 字符串解题思路

  1. 整体上和数组的解法差不多,都是使用多指针。
  2. KMP算法:
    • 原理部分(这一部分是根据b站代码随想录总结的)
      • 前缀与后缀
        • 前缀:包含首字符,不包括尾字符的所有子串。
        • 后缀:包含尾字符,不包括首字符的所有子串。
      • 最长相等前后缀
        • 对于一个字符串来说,它的相同长度的前缀和后缀一样,则可以认为是前后缀相等,能够相等的最长长度就是最长相等前后缀的长度。(不是对称!
          • 比如:a,其没有前后缀,最长相等前后缀的长度为0
          • 比如:aaba,其最长相等前后缀的长度为1,是a|ab|a
          • 比如:aabaa,其最长相等前后缀的长度为2,是aa|b|aa
          • 比如:abba,其最长相等前后缀的长度为1,是a|bb|a(当ab||bd划分的前后缀分别为ab和ba,不相等)(不是对称!
      • 前缀表:
        • 对于一个字符串所有前缀子串来说的最长相等前后缀的长度的表
          • 比如:aabaaf
          • 前缀子串长度为1是:a,最长相等前后缀的长度为0
          • 前缀子串长度为2是:aa,最长相等前后缀的长度为1,划分为:a||a
          • 前缀子串长度为3是:aab,最长相等前后缀的长度为0,划分为:|aab|
          • 前缀子串长度为4是:aaba,最长相等前后缀的长度为0,划分为:a|aa
          • 前缀子串长度为5是:aabaa,最长相等前后缀的长度为2,划分为:aa|b|aa
          • 前缀子串长度为6是:aabaaf,最长相等前后缀的长度为0,划分为:|aabaaf|
        • 前缀表prefix的物理意义:对于i位置的字符,如果没匹配上,那么则对prefix[i-1]指向的字符匹配。
          • 也就是说,在字符匹配时,prefix[i]代指的是,后面一个字符(即i+1)没匹配上时,应该往回去匹配那个位置。
    • 求前缀表(伪代码)
      • (next为前缀表,s为字符串)
      • 整体思路:
        • 初始化prefix数组为0,prefix[0]=0;
        • 用一个遍历指针cur从1开始遍历到数组结尾,表示以当前为结尾的后缀能匹配到的最长前缀。
        • 这个长度只取决于cur-1位置的值:
          • 不断匹配:设before = prefix[cur-1](before意为上一个位置的匹配值,假如s[cur-1]是’a’,那么before-1就是上一个’a’对应的位置,所以s[before]是要和当前进行匹配的位置),判断s[before]的位置的元素是否等于s[cur],
            • 等于则说明匹配上了,prefix[cur]=before+1,continue,继续匹配下一个。
            • 不等于则说明,没匹配上,则设before=prefix[prefix[before]],不断重复上述过程,直至匹配上或者before为0了。(before为0说明都没有匹配上)
            • 如果上述循环没有匹配上说明该位置应当为0,prefix[cur]=0
      • void getNext(next,s)
        • prefix = new int[s.length()];
        • for:cur=1,cur小于s.length(),cur++
          • //初始化前置匹配指针
          • before = prefix[cur-1];
          • //尝试匹配
          • while:s.charAt(before)!=s.charAt(cur) 且 before!=0
            • before = prefix[before-1];
          • //判断匹配结果:
          • if:s.charAt(before)==s.charAt(cur)(说明匹配上了合适的位置)
            • prefix[cur] = before+1;
          • else(说明没匹配上合适的位置,后续要从0开始匹配)
            • prefix[cur] = 0;

5.1 反转字符串2 (LC-541)

  1. 问题描述:给定一个字符串s和一个整数k,从字符串开头算起,每2k个字符,反转2k字符中的前k个字符,如果剩余字符小于k个,直接将剩余字符反转。
  2. 解题思路:2k个一组调用反转(可以理解为固定滑动窗口)
    • 将字符串s变成字符数组ch
    • for:i从0开始,i+=2k
      • if:i+k小于s.length()
        • reverse(ch,i,i+k)
      • else:
        • reverse(ch,i,s.length())
    • return new String(ch);
  3. 伪代码:
    • char[] ch = s.toCharArray();
    • for:i从0开始,i+=2k
      • if:i+k小于s.length()
        • reverse(ch,i,i+k)
      • else:
        • reverse(ch,i,s.length())
    • return new String(ch);
  4. 时间复杂度O(time),空间复杂度O(space)

5.2 翻转字符串里的单词 (LC-151)

  1. 问题描述:给定一个字符串s,将字符串中的词翻转,并保证翻转后的字符串的单词之间只有一个空格。(原字符串的单词直接可能有多个空格)
  2. 解题思路:区域指针移除多余空格+翻转2次
    • 初始化,区域指针end=0,遍历指针cur=0
    • 当cur小于s.length()时
      • 如果cur指向的是空格
        • while:cur小于s.length()且cur指向的是空格(找到当前的一个空格)
          • cur++;
        • 如果cur大于等于s.length()
          • break
        • 如果end不等于0(不是第一个单词)
          • s[end] = 空格
          • end++
      • s[end] = s[cur];
      • end++;
      • cur++;
    • reverse(s,0,end)
    • start = 0;
    • 但cur小于end时:
      • 当cur指向的是空格时
        • reverse(s,start,cur)
        • start = cur+1;
      • cur+
  3. 伪代码:
    • char[] ch = s.toCharArray();
    • int end = 0;
    • int cur = 0;
    • //去除空格
    • while:cur小于ch.length
      • if:ch[cur]==’ ’
        //找到第一个不为空格的位置
        • while2:cur小于ch.length且ch[cur]==’ ’
          • cur++
        • //判断是否已经遍历完毕,遍历完毕直接退出
        • if2:cur大于等于ch.length
          • break
        • //非第一个单词,需要先加空格
        • if3:end!=0
          • ch[end] = ’ ’
          • end++
        • //将非空格加入
        • ch[end] = ch[cur]
        • end++
        • cur++
    • //第一次翻转(翻转整体)
    • reverse(ch,0,end)
    • //第二次翻转(翻转单词)
    • start = 0;
    • cur = 0;
    • while3:cur小于等于end
      • if:ch等于end或ch[cur]等于’ ’
        • reverse(ch,start,cur-1)
      • cur++
    • return new String(Arrays.copyOfRange(ch,0,end))
  4. 时间复杂度O(time),空间复杂度O(space)
  5. 补充说明:
    • 这里的reverse是左闭右闭区间,也就是包含start和cur

5.3 重复的子字符串 (LC-459)

  1. 问题描述:判断给定的一个字符串s能否由一个子串p重复组成。
  2. 解题思路:移动匹配 或 KMP算法
    1. 移动匹配(只能判断是否由子串周期构成)
      • 对于一个字符串s,如果能从字符串s2=(s+s)[1,2n-2](也就是两个s拼接在一起,然后去掉首尾字符)中找到一个新的s,那么就说明这个字符是周期字符串。
      • 关于如何从s2中找s,那么就是一个KMP算法了。
    2. KMP算法(能够找到最小子串p)
      • 对于一个字符串s,如果其最长相等前后缀为len,当且仅当s.length()%(s.length-len)时,s为周期串。
      • 对应的最小子串长度为s.length-len。
      • 其实这个问题就变成了求解字符串s的前缀表。
  3. 伪代码:(以KMP算法为例)
    • char[] ch = s.toCharArray();
    • int[] prefix = new int[ch.length];
    • int before = 0;
    • for:i从1到ch.length-1,i++
      • //初始化before
      • before = prefix[i-1]
      • //不断匹配
      • while:ch[before]!=ch[i] && before!=0
        • before = prefix[before-1];
      • //判断是否匹配上
      • if:ch[before]==ch[i]
        • prefix[i] = before+1;
      • else:
        • prefix[i] = 0;
    • int subLength = ch.length - prefix[ch.length];
    • if2:subLength == ch.length 或 ch.length%subLength!=0
      • return false;
    • else:
      • return true;
  4. 时间复杂度O(n),空间复杂度O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值