一、字符串常用api
string类
函数 | 说明 |
---|---|
char charAt(int index) | 返回 char指定索引处的值。 |
boolean contains(CharSequence s) | 当且仅当此字符串包含指定的char值序列时才返回true。 |
boolean equals(Object anObject) | 将此字符串与指定对象进行比较 |
boolean isEmpty() | 返回 true如果,且仅当 length()为 0 |
int length() | 返回此字符串的长度 |
String replace(char oldChar, char newChar) | 返回从替换所有出现的导致一个字符串 oldChar在此字符串 newChar |
char[] toCharArray() | 将此字符串转换为新的字符数组。 |
static String valueOf(char c) | 返回 char参数的字符串 char形式。 |
String[] split(String regex) | 将此字符串分割为给定的 regular expression的匹配。 |
static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements) | 返回一个新 String的副本组成 CharSequence elements与指定的副本一起加入 delimiter 。 |
StringBuffer类
1、StringBuffer类主要增加了字符串的增删改查等操作
2、StringBuilder类为StringBuffer的子类,为单线程字符串缓存区,优点速度更快
函数 | 说明 |
---|---|
StringBuffer append(char c) | 将 char参数的字符串表示附加到此序列。 |
StringBuffer insert(int offset, char c) | 在此序列中插入 char参数的字符串表示形式。 |
StringBuffer reverse() | 导致该字符序列被序列的相反代替。 |
StringBuffer delete(int start, int end) | 删除此序列的子字符串中的字符。 |
int indexOf(String str) | 返回指定子字符串第一次出现的字符串内的索引。 |
二、翻转字符串里的单词
思路1:
很多语言对字符串提供了 split(拆分),reverse(翻转)和 join(连接)等方法,因此我们可以简单的调用内置的 API 完成操作:
使用 split 将字符串按空格分割成字符串数组;
使用 reverse 将字符串数组进行反转;
使用 join 方法将字符串数组拼成一个字符串。
代码:
class Solution {
public String reverseWords(String s) {
// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);
}
}
思路2:
1.去除首尾以及中间多余空格
2.反转整个字符串
3.反转各个单词
class Solution {
/**
* 1.去除首尾以及中间多余空格
* 2.反转整个字符串
* 3.反转各个单词
*/
public String reverseWords(String s) {
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 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);
//如果字符不为空,或者sb中存储的最后的一个字符不是空字符
if (c != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}
start++;
}
return sb;
}
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
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;
int n = sb.length();
while (start < n) {
while (end < n && sb.charAt(end) != ' ') {
end++;
}
reverseString(sb, start, end - 1);
start = end + 1;
end = start + 1;
}
}
}
三、字符串匹配-KMP
KMP的经典思想是: 当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。
next数组就是一个前缀表(prefix table)。
前缀表有什么作用呢?
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
前缀: 是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀: 是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
kmp 最长公共前后缀: 最长相等前后缀
其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
构造next数组其实就是计算模式串s,前缀表的过程。
主要有如下三步:
1、初始化:
定义两个指针i和j,j指向前缀起始位置(同时也表示前后缀最长相等长度),i指向后缀起始位置。
然后还要对next数组进行初始化赋值,如下:
int j = -1;
next[0] = j;
2、处理前后缀不相同的情况
因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。
所以遍历模式串s的循环下标i 要从 1开始,代码如下:
for(int i = 1; i < s.size(); i++) {
如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
怎么回退呢?
next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。
那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
所以,处理前后缀不相同的情况代码如下:
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
//因为这里其实使用的j+1去做运算的,这里用了-1运算
//j+1 = next[j];
}
3、处理前后缀相同的情况
如果s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
代码如下:
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j;
整合代码:
void getNext(int* next, const string& s){
int j = -1;
next[0] = j;
for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j]; // 向前回退
}
if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;
}
next[i] = j; // 将j(前缀的长度)赋给next[i]
}
}
完整代码(建议直接使用原本的前缀数组):
class Solution {
public int strStr(String haystack, String needle) {
int n = haystack.length(), m = needle.length();
if (m == 0) {
return 0;
}
//求next前缀数组,起始是0,没有-1也没有后移动
int[] pi = new int[m];
for (int i = 1, j = 0; i < m; i++) {//初始化 i = 1, j=0
while (j > 0 && needle.charAt(i) != needle.charAt(j)) {//前后缀末尾不匹配,j前缀末尾根据记录前移
j = pi[j - 1];
}
if (needle.charAt(i) == needle.charAt(j)) {//前后缀匹配,ij同时向后+1
j++;
}
pi[i] = j; //前缀数组赋值
}
//进行两个字符串的匹配
for (int i = 0, j = 0; i < n; i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
j = pi[j - 1];//如果不匹配,则调整j的位置
}
if (haystack.charAt(i) == needle.charAt(j)) {
j++; //匹配,j+1
}
if (j == m) {//判断j是否已经匹配成功
return i - m + 1;
}
}
return -1;
}
}
五、重复的子字符串-没有理解
https://leetcode-cn.com/problems/repeated-substring-pattern/
https://mp.weixin.qq.com/s?__biz=MzUxNjY5NTYxNA==&mid=2247490351&idx=2&sn=5049d24aa9dab51651e7772b67955006&scene=21#wechat_redirect
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
示例 1:
输入: “abab”
输出: True
解释: 可由子字符串 “ab” 重复两次构成。
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
int[] next = new int[len + 1];
// 构造 next 数组过程,
for (int i = 1, j = 0; i < len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && s.charAt(i) != s.charAt(j)) j = next[j-1];
// 匹配成功,j往后移
if (s.charAt(i)==s.charAt(j)) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len-1] > 0 && len % (len - next[len-1]) == 0) {
return true;
}
return false;
}
}