{{day09}}
KMP的理解方法就是把这一个串自己当作文本串(i)和模式串(j) 发现两者不匹配 那就回退到前缀相同的位置 即数组中前一个下标。
next数组存放的元素是某个阶段上重复的元素个数,由于能组成重复元素的长串next数组一定是单调递增的,取next最后一个元素就是最长相同前后缀(即重复的元素个数,记为max),len - max就是最小重复子串长度,如果len能被最小重复字串长度整除,说明长串均可由其构成。
题目信息151. 反转字符串中的单词
- 题目链接: https://leetcode.cn/problems/reverse-words-in-a-string/description/
- 题目描述:给你一个字符串
s
,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s
中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
**注意:**输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
解题思路
System.arraycopy(源数组, 源数组起始位置, 目标数组, 目标数组起始位置, 要复制的元素个数)
System.arraycopy(chars, 0, newChars, 0, slow);
将 chars
数组中的前 slow
个元素复制到 newChars
数组中
我们将整个字符串都反转过来,那么单词的顺序指定是倒序了,只不过单词本身也倒序了,那么再把单词反转一下,单词不就正过来了。
所以解题思路如下:
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
举个例子,源字符串为:"the sky is blue "
- 移除多余空格 : “the sky is blue”
- 字符串反转:“eulb si yks eht”
- 单词反转:“blue is sky the”
这样我们就完成了翻转字符串里的单词。
代码实现
import org.junit.Test;
/**
* ClassName: Day09 * Package: PACKAGE_NAME * Description: * * @Author dk
* @Create 2024/8/8 6:37
* @Version 1.0
*/public class Day09 {
@Test
public void test1(){
String s = "the sky is blue";
System.out.println(reverseWords(s));
}
//用 char[] 来实现 String 的 removeExtraSpaces,reverse 操作
public String reverseWords(String s) {
char [] chars = s.toCharArray();
//1.去除首尾以及中间多余空格
chars = removeExtraSpace(chars);
//2.整个字符串反转
reverse(chars,0,chars.length - 1);
//3.单词反转
reverseEachWord(chars);
return new String(chars);
}
//1.用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
public char[] removeExtraSpace (char[] chars){
int slow = 0;
for (int fast = 0;fast < chars.length;fast++){
//先用 fast 移除所有空格
if (chars[fast] != ' '){
//在用 slow 加空格。 除第一个单词外,单词末尾要加空格
if (slow != 0) {
chars[slow] = ' ';
slow++;
}
//fast 遇到空格或遍历到字符串末尾,就证明遍历完一个单词了
while (fast < chars.length && chars[fast] != ' '){
chars[slow] = chars[fast];
slow++;
fast++;
}
}
}
char[] newChars = new char[slow];
//将 `chars` 数组中的前 `slow` 个元素复制到 `newChars` 数组中
System.arraycopy(chars,0,newChars,0,slow);
return newChars;
}
//2.双指针实现指定范围内字符串反转,可参考字符串反转题解
public void reverse(char[] chars,int left,int right){
if (right > chars.length){
System.out.println("set a wrong right");
return; }
while (left < right){
chars[left] ^= chars[right];
chars[right] ^= chars[left];
chars[left] ^= chars[right];
left++;
right--;
}
}
//3.单词反转
public void reverseEachWord(char[] chars){
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= chars.length;end++){
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == chars.length || chars[end] == ' '){
reverse(chars,start,end - 1);
start = end + 1;
}
}
}
}
题目信息 55. 右旋字符串(第八期模拟笔试
- 题目链接: https://kamacoder.com/problempage.php?pid=1065
- 题目描述:
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
解题思路
我们需要将字符串右移n位,字符串相当于分成了两个部分,如果n为2,符串相当于分成了两个部分,如图: (length为字符串长度)
右移n位, 就是将第二段放在前面,第一段放在后面,先不考虑里面字符的顺序,是不是整体倒叙不就行了。如图:
此时第一段和第二段的顺序是我们想要的,但里面的字符位置被我们倒叙,那么此时我们在把 第一段和第二段里面的字符再倒叙一把,这样字符顺序不就正确了。 如果:
其实,思路就是 通过 整体倒叙,把两段子串顺序颠倒,两个段子串里的的字符在倒叙一把,负负得正,这样就不影响子串里面字符的顺序了。
代码实现
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = Integer.parseInt(in.nextLine());
String s = in.nextLine();
int len = s.length();
char[] chars = s.toCharArray();
reverseString(chars,0,len - 1);
reverseString(chars,0,n-1);
reverseString(chars,n,len - 1);
System.out.println(chars);
}
public static void reverseString(char[] ch,int start,int end){
while (start < end){
ch[start] ^= ch[end];
ch[end] ^= ch[start];
ch[start] ^= ch[end];
start++;
end--;
}
}
题目信息 459. 重复的子字符串
- 题目链接: https://leetcode.cn/problems/repeated-substring-pattern/description/
- 题目描述:给定一个非空的字符串
s
,检查是否可以通过由它的一个子串重复多次构成。
解法一: {{解法名称}}
解题思路
暴力的解法, 就是一个for循环获取 子串的终止位置, 然后判断子串是否能重复构成字符串,又嵌套一个for循环,所以是O(n^2)的时间复杂度。
有的同学可以想,怎么一个for循环就可以获取子串吗? 至少得一个for获取子串起始位置,一个for获取子串结束位置吧。
其实我们只需要判断,以第一个字母为开始的子串就可以,所以一个for循环获取子串的终止位置就行了。 而且遍历的时候 都不用遍历结束,只需要遍历到中间位置,因为子串结束位置大于中间位置的话,一定不能重复组成字符串。
暴力的解法,这里就不详细讲解了。
主要讲一讲移动匹配 和 KMP两种方法。
移动匹配
当一个字符串s:abcabc,内部由重复的子串组成,那么这个字符串的结构一定是这样的:
也就是由前后相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s,这样组成的字符串中,后面的子串做前串,前面的子串做后串,就一定还能组成一个s,如图:
所以判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候,要刨除 s + s 的首字符和尾字符,这样避免在s+s中搜索出原来的s,我们要搜索的是中间拼接出来的s。
代码实现
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
// 构造 next 数组过程,j从0开始(空格),i从2开始
for (int i = 2, j = 0; i <= len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
// 匹配成功,j往后移
if (chars[i] == chars[j + 1]) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len] > 0 && len % (len - next[len]) == 0) {
return true;
}
return false;
}
}