字符串
字符串可能在算法处理上面和数组是类似的,但是String和数组的数据结构还是有一些不一样的
1、反转字符串
双指针的经典应用,两个指针同时向中间移动
public void reverseString(char[] s) {
for(int i = 0,j = s.length - 1; i < s.length/2; i++,j--){
char tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
2、反转字符串2
- 这个题就是要求复杂一些,需要注意的就是不要上来用for循环就想着 i++。应该是i += 2k
- 每次只是反转i 到 i+k,可以单独写一个方法,注意一般编程语言中都是左闭右开的,所以是不包括 i+k的
- 但是需要有一个条件,if ( i+k < s.length ),不能超过数组长度了,这个if里判断的是最后剩余的部分是大于k的
- 如果不大于k的也需要被考虑到,所以在上面 if 的语句内需要加上一个continue
public String reverseStr(String s, int k) {
char[] arr = s.toCharArray(); // 0.需要学会怎么将字符串转成数组
for(int i = 0; i < arr.length; i += (2*k)){ // 1. 每隔 2k 个字符的前 k 个字符进行反转
if(i + k <= arr.length){
reverse(arr,i,i+k-1); // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
continue;
}
reverse(arr,i,arr.length-1); // 3. 剩余字符少于 k 个,则将剩余字符全部反转
}
return new String(arr);
}
//对第i到第i+k进行反转
public void reverse(char[] s,int i,int j){
for(int m = i,n = j; m < n; m++,n--){
char tmp = s[m];
s[m] = s[n];
s[n] = tmp;
}
}
3、翻转字符串⾥的单词(较为综合)
这个题复杂在,空格的位置是不确定的,单词的前面中间后面都可能有。但是我们最后的结果中不能包含额外空格
-
可以首先对整个字符串进行一次反转,这样单词的对应位置就是正确的了
-
之后再对每一个单词再进行单独的反转
-
需要注意的就是对空格的处理
而对空格的处理,想到了之前总结数组部分中的,移动元素,这一个题,用双指针解决的问题
但是在用双指针去移动元素的过程中,需要注意最后需要重新获取我们需要的部分,只要0-slow部分
class Solution {
public String reverseWords(String s) {
// 1.去除首尾以及中间多余空格
char[] arr = s.toCharArray();
arr = removeSpace(arr);
// 2.反转整个字符串
reverseString(arr, 0, arr.length - 1);
// 3.反转各个单词
reverseEachWord(arr);
return new String(arr);
}
//用 快慢指针 去除首尾以及中间多余空格,可参考数组元素移除的题解
private char[] removeSpace(char[] arr) {
int slow = 0;
for(int fast = 0; fast < arr.length; fast++){
if(arr[fast] != ' '){ //遇到非空格就处理,即删除所有空格。
if(slow != 0) //手动控制空格,给单词之间添加空格 slow != 0说明不是第一个单词,需要单词前添加空格
arr[slow++] = ' ';
while(fast < arr.length && arr[fast] != ' ')
arr[slow++] = arr[fast++];
}
}
//这里需要注意的是,最后需要重新获取我们需要的部分,只要0-slow部分
char[] newChars = new char[slow];
System.arraycopy(arr, 0, newChars, 0, slow);
return newChars;
}
//双指针实现指定范围内字符串反转
public void reverseString(char[] arr, int start, int end) {
while (start < end) {
char tmp = arr[start];
arr[start] = arr[end];
arr[end] = tmp;
start++;
end--;
}
}
//单词反转
private void reverseEachWord(char[] arr) {
int start = 0;
//end <= s.length() 这里的 = ,是为了让 end 永远指向单词末尾后一个位置,这样 reverse 的实参更好设置
for (int end = 0; end <= arr.length; end++) {
// end 每次到单词末尾后的空格或串尾,开始反转单词
if (end == arr.length || arr[end] == ' ') {
reverseString(arr, start, end - 1);
start = end + 1;
}
}
}
}
4、KMP
目标是对目标文本串进行模式匹配,给定一个文本串和一个模式串,去寻找文本串中有无模式串出现
这里的关键是找到模式串的最长相等前后缀
模式串:aabaaf
那么a:0、aa:1、aab:0、aaba:1、aabaa:2、aabaaf:0
所以如果模式串aabaaf 的next数组,就是[0,1,0,1,2,0]
这就说明比如在匹配到模式串5位置,如果不匹配,就需要按照前面一个字符的next数组元素值,去跳到模式串的序号2的位置接替f位置
next数组构造过程
-
需要两个指针:i 指向后缀末尾位置;j 指向前缀末尾位置,同时代表最长相等的前后缀长度
-
首先初始化:
- 让 j = 0,此时next[0] = 0。j直接初始化为0,是因为它是前缀的一个末尾
- 而 i 的初始化是放在for循环中,for(int i = 1;i<长度;i++)
-
如果i 和 j 不相等,那就需要让j 进行回退,是看它前一位的next数组元组,其实也就是类似在实际匹配过程中使用next数组一样
但是不能一直回退,前提是j > 0
回退是一个连续的过程,所以“前提是j > 0”,这个是while中,不是if中
-
如果i 和 j 相等,让j++,同时更新next数组值,next[i] = j
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
整体代码:
class Solution {
//前缀表(不减一)Java实现
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
}
5、重复的子字符串
有两种思路:
-
比较巧的思路是,如果两个相同字符串s拼接起来,并移除第一个和最后一个字符。如果 s 是该字符串的子串,那么 s 就满足题目要求。[这个是可以证明的,不过比较难理解]
下面代码的含义是:检查字符串
s
在自身重复连接后(即s + s
)中,从第二个s
开始的位置是否不等于s
字符串的长度。如果不相等,则返回true
,否则返回false
。class Solution { public boolean repeatedSubstringPattern(String s) { return (s + s).indexOf(s, 1) != s.length(); //从索引位置 1 开始搜索。搜索第一个 s 出现的位置。这意味着它将忽略新字符串的第一个字符,从第二个字符开始搜索。如果在去除掉首元素后,去查找有无s这个子串,查找到的的起始位置是s.length(),那就说明这个是只能在后面拼接上的那个s匹配上,并不能靠中间部分匹配上 } }
-
用kmp【这个方法暂时还未具体实践】