题目速览
附有leetcode的链接
分题讲解
344反转字符串 如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数
如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
public void reverseString(char[] s) {
int head = 0;
int end = s.length - 1;
char tmp;
while (head < end){
tmp = s[end];
s[end] = s[head];
s[head] = tmp;
head++;
end--;
}
}
复制代码
541反转字符串ii
class Solution {
public String reverseStr(String s, int k) {
char[] arr = s.toCharArray();
int len = s.length();
int head = 0;
int last = 2 * k - 1;
while (last <= len - 1){
reverse(head, head + k - 1, arr);
last += 2 * k;
head += 2 * k;
}
int end = (len - 1 - head < k) ? len - 1 : head + k - 1;
reverse(head, end, arr);
return String.valueOf(arr);
}
public void reverse(int first, int end, char[] arr){
while (first < end){
char tmp = arr[first];
arr[first] = arr[end];
arr[end] = tmp;
first++;
end--;
}
}
}
复制代码
谋而后定 先分块
- 够2k
交换函数
- 不够2k
小于k---交换函数 大于k,小于2k---交换函数
剑指Offer5替换空格
class Solution {
public String replaceSpace(String s) {
//用StringBuffer单线程更快
StringBuffer stb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char point = s.charAt(i);
if (point == ' '){
stb.append("%20");
}else {
stb.append(point);
}
}
return stb.toString();
}
}
复制代码
这里提供另一种思路: 1.可以扩充原始字符串空间变成可以装下新变化的字符。 2.定位原始字符串的最后一个字符 3.定位新字符串最后一个字符 4.对比复制
public String replaceSpace(String s) {
if(s == null || s.length() == 0){
return s;
}
//扩充空间,空格数量2倍
StringBuilder str = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
if(s.charAt(i) == ' '){
str.append(" ");
}
}
//若是没有空格直接返回
if(str.length() == 0){
return s;
}
//有空格情况 定义两个指针
int left = s.length() - 1;//左指针:指向原始字符串最后一个位置
s += str.toString();
int right = s.length()-1;//右指针:指向扩展字符串的最后一个位置
char[] chars = s.toCharArray();
while(left>=0){
if(chars[left] == ' '){
chars[right--] = '0';
chars[right--] = '2';
chars[right] = '%';
}else{
chars[right] = chars[left];
}
left--;
right--;
}
return new String(chars);
}
复制代码
151. 翻转字符串里的单词 法一:比较水,利用字符串中split函数,额外O(N)空间,最后串起来,要注意,去掉最后的空格,以及存在连续的分割符的时候例如两个空格,分割之后就是“”,用.equals()判断 法二:反转全部的字符,最后在反转其中的单词,这个最后的时间复杂度是O(1)。分步骤执行,最开始要除去多余的空格。反转全部字符串。反转其中的单词。
//法一
public static String reverseWords(String s) {
String[] s1 = s.split(" ");
StringBuffer stb = new StringBuffer();
for (int i = s1.length - 1; i >= 0; i--){
if (!s1[i].equals("")) {
stb.append(s1[i] + " ");
}
}
//移除最后一个空格
return stb.toString().substring(0,stb.length() - 1);
}
public static void main(String[] args) {
System.out.println(reverseWords("We are happy"));//happy are We
}
复制代码
//法二
class Solution {
/**
* 不使用Java内置方法实现
* <p>
* 1.去除首尾以及中间多余空格
* 2.反转整个字符串
* 3.反转各个单词
*/
public String reverseWords(String s) {
// System.out.println("ReverseWords.reverseWords2() called with: s = [" + s + "]");
// 1.去除首尾以及中间多余空格
StringBuilder sb = removeSpace(s);
// 2.反转整个字符串
reverseString(sb, 0, sb.length() - 1);
// 3.反转各个单词
reverseEachWord(sb);
return sb.toString();
}
private StringBuilder removeSpace(String s) {
// System.out.println("ReverseWords.removeSpace() called with: s = [" + 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++;
}
// System.out.println("ReverseWords.removeSpace returned: sb = [" + sb + "]");
return sb;
}
/**
* 反转字符串指定区间[start, end]的字符
*/
public void reverseString(StringBuilder sb, int start, int end) {
// System.out.println("ReverseWords.reverseString() called with: sb = [" + sb + "], start = [" + start + "], end = [" + end + "]");
while (start < end) {
char temp = sb.charAt(start);
sb.setCharAt(start, sb.charAt(end));
sb.setCharAt(end, temp);
start++;
end--;
}
// System.out.println("ReverseWords.reverseString returned: sb = [" + sb + "]");
}
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;
}
}
}
复制代码
剑指Offer58-II.左旋转字符串 法一:比较水,就是利用substring 其实使用substr 和 反转 时间复杂度是一样的 ,都是O(n),但是使用substr申请了额外空间,所以空间复杂度是O(n),而反转方法的空间复杂度是O(1)。 法二:不使用substring可以直接利用反转来,一个比较有意思的思路就是:
//法一:
public String reverseLeftWords(String s, int n) {
StringBuilder sb = new StringBuilder();
sb.append(s.substring(n)+s.substring(0,n));
return sb.toString();
}
//法二:
class Solution {
public String reverseLeftWords(String s, int n) {
int len=s.length();
StringBuilder sb=new StringBuilder(s);
reverseString(sb,0,n-1);
reverseString(sb,n,len-1);
return sb.reverse().toString();
}
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--;
}
}
}
复制代码
28. 实现 strStr() 暴力解法必定是不行的,这里有一个好的api,就是KMP算法,很重要,建议背住 KMP算法
public static int strStr(String haystack, String needle) {
if (needle.equals("")) return 0;//每个题目要求不一样,伺机而动
if (haystack.length() == 0 || needle.length() == 0 || haystack.length() < needle.length()) return -1;
char[] str1 = haystack.toCharArray();
char[] str2 = needle.toCharArray();
//建立next[i]数组,存储在str2第i个字符前,最长的(前缀和后缀相等)的长度。注意字符串本身不会包括在里面
int[] next = new int[str2.length];
next[0] = -1;
if (str2.length > 1){//防止str2只有一个字符
next[1] = 0;
}
int i = 2;
//初始cn记录的是next[i- 1]的值,因为这里i的初始是2,所以next[i-1] = 0,所以cn = 0;
int cn = 0;
while (i < str2.length){
if (str2[i - 1] == str2[cn]){
next[i++] = ++cn;
}else if (cn == 0){
next[i++] = 0;
}else {
cn = next[cn];
}
}
//利用next[] 数组求解字符串匹配问题
int t1 = 0; int t2 = 0;
while (t1 < str1.length && t2 < str2.length){
if (str1[t1] == str2[t2]){
t1++;
t2++;
}else if (t2 == 0){
t1++;
}else {
t2 = next[t2];
}
}
return t2 == str2.length ? t1 - t2 : -1;
}
public static void main(String[] args) {
String haystack = "abcdeabcfee";
String needle = "d";
System.out.println(strStr(haystack,needle));
}
复制代码
29. 重复的子字符串 自己一上来还真的是不知道要用KMP算法,但是因为是要看是不是重复的组成字符串,那么next[]的数组绝对是有规律的。可以运行几个next[]数组看看可以得到如下的规律:
public static boolean repeatedSubstringPattern(String s) {
if (s.length() < 2) return false;
//可以利用KMP算法,以为next[]数组中一定会有相应的规律
char[] arr = s.toCharArray();
int n = s.length();
int[] next = new int[n + 1];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
while (i <= n){
if (arr[i - 1] == arr[cn]){
next[i++] = ++cn;
}else if (cn == 0){
next[i++] = 0;
}else {
cn = next[cn];
}
}
for (int h : next){
System.out.println(h);
}
if (next[n] != 0 && n % (n - (next[n])) == 0){
return true;
}
return false;
}
public static void main(String[] args) {
String haystack = "abcabcabc";
System.out.println(repeatedSubstringPattern(haystack));
}
复制代码
总结
- 建议如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。
- 双指针法在数组,链表和字符串中很常用。很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作
- 当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章,有一个思路:先整体反转再局部反转、先局部反转再整体反转
- KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。----字符串中最重要的算法