一、KMP算法 ****
1. 理论
- 主要思想
当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
next数组 - next数组和前缀表
针对模式串而言
next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)。 - 核心概念
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
模式串与前缀表对应位置的数字表示的就是: 下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
- 做题思路 两部曲
对模式串求next数组(难点):双指针,一个指向后缀的最后一个元素,一个指向前缀的最后一个元素
利用数组来优化,再文本串中找模式串 - 如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
宫水三叶解释
T459. 重复的子字符串(用next数组的特点求解)
假设字符串s使用多个重复子串构成(这个子串是最小重复单位),重复出现的子字符串长度是x,所以s是由n * x组成。
因为字符串s的最长相同前后缀的长度一定是不包含s本身,所以 最长相同前后缀长度必然是m * x,而且 n - m = 1,
如果len % (len - (next[len - 1] + 1)) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。
数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。
class Solution {
//kmp算法:利用next数组
public boolean repeatedSubstringPattern(String s) {
int len = s.length();
int[] next = new int[len];
next[0] = 0;
int j = 0;//指向前缀的最后一个元素
for(int i=1;i<len;i++){//i指向后缀的最后一个元素
while(j>0&&s.charAt(i) != s.charAt(j)){//不同,j指针不断往回
j = next[j-1];
}
if(s.charAt(i) == s.charAt(j)){//相同
j++;
}
next[i] = j;
}
if(len%(len-next[len-1])==0 && next[len-1]>0){
return true;
}
return false;
}
}
T28. 找出字符串中第一个匹配项的下标
时间复杂度:o(m+n)
class Solution {
//kmp算法
//称haystack为文本串;needle为模式串
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);//由模式串获得next数组
//遍历文本串,只需要遍历1次,i递增
//遍历模式串,j不是递增的
int j = 0;
for(int i = 0;i<haystack.length();i++){
while(j>0 && needle.charAt(j) != haystack.charAt(i)){//文本串和模式串不匹配
j = next[j-1];//j不需要从头遍历 可以根据next跳转
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j == needle.length()){
return i-needle.length()+1;
}
}
return -1;
}
//找出模式串的next数组
//next表示记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
public int[] getNext(String needle){
//初始化
int j = 0;
int[] next = new int[needle.length()];
next[0] = 0;
//从1开始遍历
for(int i = 1;i<needle.length();i++){
while(j>0 && needle.charAt(i) != needle.charAt(j)){
j = next[j-1];//字符不相等. 难点1
}
if(needle.charAt(i) == needle.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}
二. 反转旋转系列
T541. 反转字符串Ⅱ (分段反转字符,字符数组)
class Solution {
public String reverseStr(String s, int k) {
char[] sc = s.toCharArray();
for(int i=0;i<s.length();i+=2*k){
int start = i;
int end = Math.min(start+k-1,s.length());
while(start<end){
char temp = sc[end];
sc[end] = sc[start];
sc[start] = temp;
start++;
end--;
}
}
return new String(sc);
}
}
剑指Offer 05.替换空格 (StringBuilder)
class Solution {
public String replaceSpace(String s) {
if(s.length()==0) return "";
StringBuilder sb = new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' '){
sb.append("%20");
}else{
sb.append(s.charAt(i));
}
}
return sb.toString();
}
}
T151. 反转字符串中的单词(翻转字符+移除空格) ***
class Solution {
public String reverseWords(String s) {
//1.删除空格
//2.翻转整个字符串
//3.翻转单词
String sb = removeSpace(s);
int start = 0;
int end = sb.length() - 1;
String reverseStr = reverseStr(sb,start,end);
String str = reverseWord(reverseStr);
return str;
}
//1.移除空格
public String removeSpace(String s){
StringBuilder sb = new StringBuilder();
int start = 0;
int end = s.length()-1;
//移除开头和结尾的空格
while(s.charAt(start) == ' ') {start++;}
while(s.charAt(end) == ' '){ end--;}
while(start<=end){
char c = s.charAt(start);
//c不为空格
//c为空格但是sb字符串的最后一个字符不是空格
if(c != ' ' || sb.charAt(sb.length()-1) != ' '){
sb.append(c);
}
start++;
}
return sb.toString();
}
//2.翻转字符串
public String reverseStr(String s, int start, int end){
char[] schar = s.toCharArray();
while (start<end){
char temp = schar[start];
schar[start] = schar[end];
schar[end] = temp;
start++;
end--;
}
return new String(schar);
}
public String reverseWord(String s){
//从前开始遍历,遇到空格和到达结尾就翻转字符串s
int start = 0;
int end = 1;
int len = s.length();
String sb = s;
while(start<len){
while(end<len && s.charAt(end) != ' '){
end++;
}
sb = reverseStr(sb,start,end-1);
start = end+1;
end = start+1;
}
return sb;
}
}
剑指 Offer 58 - II. 左旋转字符串(附加要求:不能申请额外空间,只能在本串上操作)
class Solution {
//不用额外空间
public String reverseLeftWords(String s, int n) {
char[] sc = s.toCharArray();
//翻转前n个
reverseHelp(sc,0,n-1);
//翻转n到 len个
reverseHelp(sc,n,sc.length-1);
//整体翻转
reverseHelp(sc,0,sc.length-1);
return new String(sc);
}
public void reverseHelp(char[] s, int start, int end){
while(start<end){
char temp = s[start];
s[start] = s[end];
s[end] = temp;
start++;
end--;
}
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n) + s.substring(0,n);
}
}
三. 括号问题
T678. 有效的括号字符串(栈模拟 / dp)
两个栈模拟
两个栈存放索引,方便后续左括号和*号进行比较
class Solution {
public boolean checkValidString(String s) {
Deque<Integer> leftStack = new LinkedList<>();//放的是索引,方便后续判断
Deque<Integer> star = new LinkedList<>();
//遇到左括号和*号入栈,遇到右括号先判断左括号,再判断*号
for(int i=0;i<s.length();i++){
char c = s.charAt(i);
if(c == '('){
leftStack.push(i);
}else if(c == '*'){
star.push(i);
}else if(c == ')'){
if(!leftStack.isEmpty()){
leftStack.pop();
}else if(!star.isEmpty()){
star.pop();
}else {
return false;
}
}
}
//处理左括号和*
//要左和*匹配,则左的索引必须小于*的索引
while(!leftStack.isEmpty() && !star.isEmpty()){
int leftIndex = leftStack.pop();
int starIndex = star.pop();
if(leftIndex>starIndex){
return false;
}
}
return leftStack.isEmpty();
}
}
T20. 有效的括号
T22. 括号生成
T32. 最长有效括号
T301. 删除无效的括号
0. 面试真题
T394.字符串解码(栈+字符串) *** 华为
class Solution {
//辅助栈+模拟
public String decodeString(String s) {
StringBuilder res = new StringBuilder();
int num = 0;//用来记录遍历到的数字,因为要乘10处理
Deque<Integer> stackInt = new LinkedList<>();
Deque<String> stackStr = new LinkedList<>();//辅助栈:保存处理好的str
for(Character c:s.toCharArray()){
if(c == '['){//数字和字母都入栈
stackStr.push(res.toString());//把之前处理好的字符入栈
stackInt.push(num);
num = 0;//清空数字
res = new StringBuilder();//清空字符
}else if(c == ']'){
int multi = stackInt.pop();//取出要乘的倍数
StringBuilder temp = new StringBuilder();
for(int i =0;i<multi;i++){
temp.append(res);
}
res = new StringBuilder(stackStr.pop()+temp);
}else if(c >='0' && c<='9'){
num = num*10 + Integer.parseInt(String.valueOf(c));
}else{
res.append(c);
}
}
return res.toString();
}
}
压缩算法:腾讯,与上一题类似 ***
import java.util.Deque;
import java.util.LinkedList;
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
StringBuilder res = new StringBuilder();
int num = 0;
Deque<Integer> stackNum = new LinkedList<>();
Deque<String> stackStr = new LinkedList<>();
for (char c : line.toCharArray()) {
if(c == '['){
stackStr.push(res.toString());
res = new StringBuilder();
}else if(c == '|'){
stackNum.push(num);
num=0;
}else if(c >= '0' && c <= '9'){
num = num*10 + c - '0';
}else if(c == ']'){
int multi = stackNum.pop();
StringBuilder sb = new StringBuilder();
for(int i=0;i<multi;i++){
sb.append(res);
}
res = new StringBuilder(stackStr.pop() + sb);
}else{
res.append(c);
}
}
System.out.println(res.toString());
}
}
华为 HJ27 查找兄弟单词
import java.util.Scanner;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
String[] wordSet = new String[n];
for (int i = 0; i < n; i++) {
wordSet[i] = in.next();
}
String target = in.next();
int m = in.nextInt();
int len = target.length();
int count = 0;
List<String> res = new ArrayList<>();
char[] tempTarget = target.toCharArray();
Arrays.sort(tempTarget);
for (int i = 0; i < n; i++) {
if (wordSet[i].length() != len || target.compareTo(wordSet[i]) == 0) {
continue;
}
char[] temp = wordSet[i].toCharArray();
Arrays.sort(temp);
Arrays.sort(tempTarget);
String str = String.valueOf(temp);
if (str.equals(String.valueOf(tempTarget))) {
count++;
res.add(wordSet[i]);
}
}
System.out.println(count);
if(m<res.size()){
Collections.sort(res);
System.out.println(res.get(m-1));
}
}
}