四、字符串
一、反转字符串(344 简单)
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while(l < r){
char temp = s[l];
s[l] = s[r];
s[r] = temp;
l++;
r--;
}
}
}
二、反转字符串Ⅱ(541 简单)
题目描述:给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于 k 个,则将剩余字符全部反转。
- 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
class Solution {
public String reverseStr(String s, int k) {
//字符串——>字符数组
char[] ch=s.toCharArray();//用toCharArray()方法把字符串转换成数组
for(int i=0;i<ch.length;i+=2*k){
//下面把前k个字符反转,两个指针,前一个后一个,交换
int start=i;
int end=Math.min(ch.length-1,start+k-1);
while(start<end){
char temp=ch[start];
ch[start]=ch[end];
ch[end]=temp;
start++;
end--;
}
}
//字符数组——>字符串
return new String(ch);
}
}
三、替换数字
题目描述:给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串"a1b2c3",函数应该将其转换为"anumberbnumbercnumber"。
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.nextLine();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
sb.append("number");
}else sb.append(s.charAt(i));
}
System.out.println(sb);
}
}
对于上述代码,有以下方法:
Character.isDigit(s.charAt(i))
s.charAt(i)
sb.append(“number”)
四、翻转字符串里的单词(151 中等)
题目描述:给定一个字符串,逐个翻转字符串中的每个单词。单词是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的单词分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入: “the sky is blue”
输出: “blue is sky the”
示例 2:
输入: " hello world! "
输出: “world! hello”
解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
示例 3:
输入: “a good example”
输出: “example good a”
解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。
- 算法思路:主要有三个步骤
- 移除多余空格
类似于数组中的移除元素(27题),因为数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。所以数组移除元素的方法是用双指针遇到目标元素用数组后面的元素将其覆盖。 - 将整个字符串反转
- 将每个单词反转
- 代码
class Solution {
/*
1.去除首尾以及中间多余空格
2.反转整个字符串
3.反转各个单词
*/
public String reverseWords(String s) {
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
private StringBuilder removeSpace(String s){
int start = 0;
int end = s.length() - 1;
//去掉首尾的空格
while (s.charAt(start) == ' ') start++;
while (s.charAt(end) == ' ') end--;
StringBuilder sb = new StringBuilder();
while (start <= end) {
char c = s.charAt(start);
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
return sb;
}
private void reverseString(StringBuilder sb){
int start=0;
int end=sb.length()-1;
while(start<end){
char temp=sb.charAt(start);
sb.setCharAt(start,sb.charAt(end));
sb.setCharAt(end,temp);
start++;
end--;
}
}
private void reverseEachWord(StringBuilder sb){
int start=0;
int end=1;
while(start<sb.length()){
//end<sb.length()判断最后一个单词,此时已经找不到空格了
//sb.charAt(end)!=' '判断中间的单词
while(end<sb.length() && sb.charAt(end)!=' '){
end++;
}
int i=start;
int j=end-1;
while(i<j){
char temp=sb.charAt(i);
sb.setCharAt(i,sb.charAt(j));
sb.setCharAt(j,temp);
i++;
j--;
}
start=end+1;
end=start+1;
}
}
}
五、右旋字符串(卡码网)
题目描述:字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出:输出共一行,为进行了右旋转操作后的字符串。
代码
import java.util.Scanner;
public class Main {
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(); //获取字符串长度
//把String转换成StringBuffer
StringBuffer sb1 = new StringBuffer();
for(int i=len-n;i<len;i++){
sb1.append(s.charAt(i));
}
for(int i=0;i<len-n;i++){
sb1.append(s.charAt(i));
}
System.out.println(sb1);
}
}
六、实现 strStr()(28 简单)
题目描述:给你两个字符串 haystack 和 needle,请你在haystack字符串中找出 needle字符串的第一个匹配项的下标(下标从0开始)。如果needle不是haystack的一部分,则返回-1。
6.1 滑动窗口
class Solution {
public int strStr(String haystack, String needle) {
int n=haystack.length();
int m=needle.length();
if (m == 0) {
return 0;
}
if (n < m) {
return -1;
}
int i=0;
int j=0;
while(i<n-m+1){
while (i < n && haystack.charAt(i) != needle.charAt(j)) {
i++;
}
if (i == n) {// 没有首字母相等的
return -1;
}
//找到首字母相等,判断后面的字母是否相等
i++;
j++;
while (i < n && j < m && haystack.charAt(i) == needle.charAt(j)) {
i++;
j++;
}
if (j == m) {// 找到
return i - j;
} else{
i-=j-1;
j=0;
}
}
return -1;
}
}
6.2 KMP算法
- KMP算法:解决字符串匹配的问题。
- KMP算法的思路:不像暴力方法那样把每一个字符都循环作比较,例如,needle字符串当匹配到某一个字符a和haystack不相等时,不再从头开始匹配,而是观察a之前的子串,找到某一个字符b,b有最长相等前后缀,从b接着匹配(用前缀表来解决)。
- 前缀:包含首字母,不包含尾字母的所有子串
后缀:包含尾字母,不包含首字母的所有子串
最长相等前后缀:例如:aabaaf 子串及其最长相等前后缀的长度① a 0 ② aa 1 ③ aab 0 ④ aaba 1 ⑤ aabaa 2 ⑥aabaaf 0 ,所有模式串aabaaf的前缀表010120 - 得到模式串aabaaf的前缀表010120,每一个字符对应一个数字,文本串aabaabaafa,当匹配不上时,找到它的前一个的字符的前缀表的值,把指针移到那个位置(因为对称,前缀表的值是前后相等的字符串的长度,所以前缀表的值就是指针移动到此处继续匹配)。
- 前缀表除了最长相等前后缀的长度的值,有的还将其全部右移第一项赋为-1,那么此时匹配不上时,该字符的前缀表的值就是指针应该移动的位置;有的前缀表将其全部-1,则匹配不上时,它的前一个字符的前缀表的值加1就是指针应该移动的位置。用next数字存储上述这些前缀表的情况。
- 思路:先得到next数组,即前缀表,然后做匹配。
- 得到next数组的思路:例如:abcabd它的前缀表为:0,0,0,1,2,0
设i指向后缀末尾位置,j指向前缀末尾位置,这里的前缀后缀指的是子串的长度相等的前后缀(我们要找的是最长相等前后缀),通过条件控制语句对j进行循环,得到最长相等前后缀,因为j指向前缀末尾位置,所以next[i]=j,j的值其实就是前缀表的值。这里需要注意的一点是,例如最长相等前后缀的长度为1,next[i]=1,j=1,j应该指向前缀的下一个字符。然后才能比较s.charAt(i)和s.charAt(j),如果相等j加一,如果不等j=next[j-1](递归)。思路:将前缀末尾和后缀末尾对齐,从后往前比。
举例一:ababa,① 它的子串abab,前缀有:a、ab、aba,后缀有:b、ab、bab,所以最长相等前后缀为2,next[3]=2,指向a;② ababa,前缀有:a、ab、aba、abab,后缀有:a、ba、aba、baba,所以最长相等前后缀为3。从①到②的过程:j=2指向a,比较s.charAt(4)和s.charAt(2),相等,所以j++=3,next[4]=3。
举例二:因为比较s.charAt(i)和s.charAt(j),把前后缀的末尾对齐,比如前缀:ab,后缀:c,因为b不等于c,所以再比较a处的前缀表的值和c(这里体现了递归的思想)。例如
举例三:abxabcabxabx考虑匹配到最后x的时候
public 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++;
//前面的while和if语句影响的都是j
//最后next[i]即前缀表等于j,只和j有关
next[i] = j;
}
}
- KMP算法的思路:匹配不上时返回到这个字符的前缀表的值继续匹配
class Solution {
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;
}
}
}
七、重复的子字符串(459 简单)
题目描述:给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
7.1 暴力解法
- 思路:
- 两个for循环,因为这个字符串是由它的一个子字符串重复多次构成的,所以子字符串的第一个字符一定是该字符串的第一个字符,所以用外面的for循环定位子字符串的末尾位置。因为子字符串重复的话至少有两个,所以i超过字符串的一半时再往下循环就没有意义了,所以当2*i大于n时循环终止。
- 字符串长度n必须是i的倍数
- 周期性:以i为周期
- 代码
class Solution {
public boolean repeatedSubstringPattern(String s) {
int n=s.length();
for(int i=1;2*i<=n;i++){
if(n%i==0){
boolean match=true;
for(int j=i;j<n;j++){
if(s.charAt(j)!=s.charAt(j-i)){
match=false;
break;
}
}
if(match){
return true;
}
}
}
return false;
}
}
7.2 移动匹配
- 思路:判断字符串s是否由重复子串组成,只要两个s拼接在一起,里面还出现一个s的话,就说明是由重复子串组成。
- 代码
class Solution {
public boolean repeatedSubstringPattern(String s) {
return (s + s).indexOf(s, 1) != s.length();
}
}
- 注:public int indexOf(int ch, int fromIndex): 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1。
7.3 KMP
- 思路:由重复子字符串组成的字符串分别减掉它的最长相等前后缀,得到的子字符串就是重复子字符串(可以证明)。所以找字符串的最长相等前后缀的长度(即前缀表的值),用字符串的长度减去最长相等前后缀的长度,判断能否被字符串的长度整除。
- 代码
class Solution {
public boolean repeatedSubstringPattern(String s) {
int len=s.length();
int[] next = new int[len];
int j=0;
next[0]=0;
for(int i=1;i<len;i++){
while(j>0 && s.charAt(i)!=s.charAt(j)){
j=next[j-1];
}
if(s.charAt(i)==s.charAt(j)){
j++;
}
next[i]=j;
}
if(next[len-1]==0){
return false;
}
if(len%(len-next[len-1])==0){
return true;
}
return false;
}
}