双指针系列
344 反转字符串
如果题目的关键部分可以直接使用库函数解决,则尽量不要使用库函数。
如果库函数仅是解题过程中的一小部分,需要首先先了解库函数的内部原理,再考虑是否需要使用
class Solution {//双指针法翻转字符串
public void reverseString(char[] s) {
int l = 0,r = s.length-1;
while(l < r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
}
class Solution {//双指针法翻转字符串--- 异或
public void reverseString(char[] s) {
int l = 0,r = s.length-1;
while(l < r){
s[l] ^= s[r];
s[r] ^= s[l];
s[l] ^= s[r];
l++;
r--;
}
}
}
剑指offer 05 替换空格
本题需要预先给数组扩容,然后再从后往前操作字符串。
class Solution {
public String replaceSpace(String s) {
//首先为原数组扩容
StringBuilder sb = new StringBuilder();
for(int i = 0;i < s.length();i++){
if(s.charAt(i) == ' '){
sb.append(" ");
}
}
//如果填充长度为0,则直接返回原字符串
if(sb.length() == 0){
return s;
}
//双指针
//左指针指向原数组的末尾
int left = s.length() - 1;
s += sb;
//右指针指向新数组的末尾
int right = s.length() - 1;
char[] str = s.toCharArray();
while(left >= 0){
if(str[left] != ' '){
str[right] = str[left];
}else{
str[right--] = '0';
str[right--] = '2';
str[right] = '%';
}
right--;
left--;
}
return new String(str);
}
}
151 反转字符串中的单词
在移除完空格之后,一定要重新设置一下字符串的大小。
class Solution {
public String reverseWords(String s) {
char[] ch = s.toCharArray();
ch = removeSpace(ch);
reverse(ch,0,ch.length-1);
reverseEachWord(ch);
return new String(ch);
}
//移除空格
public char[] removeSpace(char[] ch){
int slow = 0;
for(int fast = 0;fast < ch.length;fast++){
if(ch[fast] != ' '){
//手动添加空格
if(slow != 0) ch[slow++] = ' ';
}
while(fast < ch.length && ch[fast] != ' '){
ch[slow++] = ch[fast++];
}
}
//重设大小
char[] newch = new char[slow];
System.arraycopy(ch,0,newch,0,slow);
return newch;
}
//实现指定范围内的字符串翻转
public void reverse(char[] ch,int left,int right){
if(right >= ch.length){
return;
}
while(left < right){
ch[left] ^= ch[right];
ch[right] ^= ch[left];
ch[left] ^= ch[right];
left++;
right--;
}
}
//实现每个单词的翻转
public void reverseEachWord(char[] ch){
int start = 0;
for(int end = 0;end <= ch.length;end++){
if(end == ch.length || ch[end] == ' '){
reverse(ch,start,end-1);
start = end + 1;
}
}
}
}
反转系列
541 反转字符串ii
当需要固定规律一段一段去处理字符串,考虑对for循环做一些修改
class Solution {
public String reverseStr(String s, int k) {
char[] str = s.toCharArray();
//寻找需要进行反转的起始位置
for(int i = 0;i < str.length;i += 2*k){
int start = i;
//如果字符少于k个,则将剩余字符全部反转
//字符数只要是大于k个,无论是小于2k,还是大于2k,都是反转前k个字符。
int end = Math.min(str.length-1,i+k-1);
reverse(str,start,end);
}
return new String(str);
}
public void reverse(char[] s,int start,int end){
while(start < end){
s[start] ^= s[end];
s[end] ^= s[start];
s[start] ^= s[end];
start++;
end--;
}
}
}
151 反转字符串里的单词
本题的思想是先整体反转然后再局部反转,达到反转字符串里单词的效果。
class Solution {
public String reverseWords(String s) {
char[] str = s.toCharArray();
str = removeSpace(str);
// //反转整个字符串------整体反转
reverseAll(str,0,str.length-1);
//反转每个单词 ----- 局部反转
reverseEach(str);
return new String(str);
}
//去除多余的空格
public char[] removeSpace(char[] s){
int slow = 0;
for(int fast = 0;fast < s.length;fast++){
//手动添加空格
if(s[fast] != ' '){
if(slow != 0){
s[slow++] = ' ';
}
}
while(fast < s.length && s[fast] != ' '){
s[slow++] = s[fast++];
}
}
//重设字符串的大小
char[] newch = new char[slow];
System.arraycopy(s,0,newch,0,slow);
return newch;
}
//整体反转
public void reverseAll(char[] s,int start,int end){
while(start < end){
s[start] ^= s[end];
s[end] ^= s[start];
s[start] ^= s[end];
start++;
end--;
}
}
//局部反转
public void reverseEach(char[] s){
int start = 0;
//寻找每个单词末尾的位置
for(int end = 0;end <= s.length;end++){
if(end == s.length || s[end] == ' '){
reverseAll(s,start,end-1);
//下一个单词的起始位置
start = end + 1;
}
}
}
}
剑指 Offer 58 - II. 左旋转字符串
本题通过先局部反转,再整体反转的思想从而达到左旋的效果。
class Solution {
public String reverseLeftWords(String s, int n) {
char[] str = s.toCharArray();
//先局部反转
reverse(str,0,n-1);
reverse(str,n,str.length-1);
//再整体反转
reverse(str,0,str.length-1);
return new String(str);
}
public void reverse(char[] s,int start,int end){
while(start < end){
s[start] ^= s[end];
s[end] ^= s[start];
s[start] ^= s[end];
start++;
end--;
}
}
}
KMP
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
要明确前缀、后缀、最长相等前后缀的概念,就可以实现next数组的代码了。
28. 找出字符串中第一个匹配项的下标
class Solution {
public int strStr(String haystack, String needle) {
if(needle.length() == 0){
return -1;
}
//next数组
int[] next = new int[needle.length()];
getNext(needle,next);
int j = -1;
for(int i = 0;i < haystack.length();i++){
//注意这里需要使用while
while(j >= 0 && haystack.charAt(i) != needle.charAt(j+1)){
j = next[j];
}
if(haystack.charAt(i) == needle.charAt(j+1)){
j++;
}
//如果匹配成功
if(j == needle.length() - 1){
return i - needle.length() + 1;
}
}
return -1;
}
//构造next数组
public void getNext(String t,int[] next){
int j = -1;
next[0] = j;
for(int i = 1;i < t.length();i++){
//注意这里需要使用while
while(j >= 0 && t.charAt(i) != t.charAt(j+1)){
j = next[j];
}
if( t.charAt(i) == t.charAt(j+1)){
j++;
}
next[i] = j;
}
}
}
- 假设字符串s的长度为len,字符串中出现的最小重复子串的长度为x
len = nx
;- 有重复子串时,最长相等前后子串的长度为
mx
且n-m = 1
- 因此当判断
nx % (n-m)x == 0
时就可以判定有重复出现的子字符串 - 因为next 数组保存的就是最长相等前后缀的长度,所以
mx = next[len-1] + 1
概括来说就是数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。
class Solution {
public boolean repeatedSubstringPattern(String s) {
int nlen = s.length();
int[] next = new int[nlen];
getNext(s,next);
int mlen = next[nlen - 1] + 1;
if(next[nlen-1] != -1 && nlen % (nlen - mlen) == 0){
return true;
}
return false;
}
public void getNext(String s,int[] next){
int j = -1;
next[0] = j;
for(int i = 1;i < s.length();i++){
while(j >= 0 && s.charAt(i) != s.charAt(j+1)){
j = next[j];
}
if(s.charAt(i) == s.charAt(j+1)){
j++;
}
next[i] = j;
}
}
}