5 字符串
5.0 字符串解题思路
- 整体上和数组的解法差不多,都是使用多指针。
- 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
- 不断匹配:设before = prefix[cur-1](before意为上一个位置的匹配值,假如s[cur-1]是’a’,那么before-1就是上一个’a’对应的位置,所以s[before]是要和当前进行匹配的位置),判断s[before]的位置的元素是否等于s[cur],
- 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;
- 原理部分(这一部分是根据b站代码随想录总结的)
5.1 反转字符串2 (LC-541)
- 问题描述:给定一个字符串s和一个整数k,从字符串开头算起,每2k个字符,反转2k字符中的前k个字符,如果剩余字符小于k个,直接将剩余字符反转。
- 解题思路:2k个一组调用反转(可以理解为固定滑动窗口)
- 将字符串s变成字符数组ch
- for:i从0开始,i+=2k
- if:i+k小于s.length()
- reverse(ch,i,i+k)
- else:
- reverse(ch,i,s.length())
- if:i+k小于s.length()
- return new String(ch);
- 伪代码:
- 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())
- if:i+k小于s.length()
- return new String(ch);
- 时间复杂度O(time),空间复杂度O(space)
5.2 翻转字符串里的单词 (LC-151)
- 问题描述:给定一个字符串s,将字符串中的词翻转,并保证翻转后的字符串的单词之间只有一个空格。(原字符串的单词直接可能有多个空格)
- 解题思路:区域指针移除多余空格+翻转2次
- 初始化,区域指针end=0,遍历指针cur=0
- 当cur小于s.length()时
- 如果cur指向的是空格
- while:cur小于s.length()且cur指向的是空格(找到当前的一个空格)
- cur++;
- 如果cur大于等于s.length()
- break
- 如果end不等于0(不是第一个单词)
- s[end] = 空格
- end++
- while:cur小于s.length()且cur指向的是空格(找到当前的一个空格)
- s[end] = s[cur];
- end++;
- cur++;
- 如果cur指向的是空格
- reverse(s,0,end)
- start = 0;
- 但cur小于end时:
- 当cur指向的是空格时
- reverse(s,start,cur)
- start = cur+1;
- cur+
- 当cur指向的是空格时
- 伪代码:
- 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++
- while2:cur小于ch.length且ch[cur]==’ ’
- if:ch[cur]==’ ’
- //第一次翻转(翻转整体)
- reverse(ch,0,end)
- //第二次翻转(翻转单词)
- start = 0;
- cur = 0;
- while3:cur小于等于end
- if:ch等于end或ch[cur]等于’ ’
- reverse(ch,start,cur-1)
- cur++
- if:ch等于end或ch[cur]等于’ ’
- return new String(Arrays.copyOfRange(ch,0,end))
- 时间复杂度O(time),空间复杂度O(space)
- 补充说明:
- 这里的reverse是左闭右闭区间,也就是包含start和cur
5.3 重复的子字符串 (LC-459)
- 问题描述:判断给定的一个字符串s能否由一个子串p重复组成。
- 解题思路:移动匹配 或 KMP算法
- 移动匹配(只能判断是否由子串周期构成)
- 对于一个字符串s,如果能从字符串s2=(s+s)[1,2n-2](也就是两个s拼接在一起,然后去掉首尾字符)中找到一个新的s,那么就说明这个字符是周期字符串。
- 关于如何从s2中找s,那么就是一个KMP算法了。
- KMP算法(能够找到最小子串p)
- 对于一个字符串s,如果其最长相等前后缀为len,当且仅当s.length()%(s.length-len)时,s为周期串。
- 对应的最小子串长度为s.length-len。
- 其实这个问题就变成了求解字符串s的前缀表。
- 移动匹配(只能判断是否由子串周期构成)
- 伪代码:(以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;
- 时间复杂度O(n),空间复杂度O(n)