文章目录
*344. 反转字符串 - 12.2
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
(独)解法一:将首尾元素交换一下即可。
class Solution {
public void reverseString(char[] s) {
int end = s.length;
char temp;
//将首尾元素交换
for(int i=0; i<s.length/2; i++){
temp = s[i];
s[i] = s[end-1];
s[end-1] = temp;
end--;
}
}
}
解法二:双指针
class Solution {
public void reverseString(char[] s) {
int l = 0;
int r = s.length - 1;
while(l < r){
s[l] ^= s[r]; //构造 a ^ b 的结果,并放在 a 中
s[r] ^= s[l]; //将 a ^ b 这一结果再 ^ b ,存入b中,此时 b = a, a = a ^ b
s[l] ^= s[r]; //a ^ b 的结果再 ^ a ,存入 a 中,此时 b = a, a = b 完成交换
l++;
r--;
}
}
}
541. 反转字符串 II - 12.3
解析:先将字符串转化成字符数组,然后遍历,i每次移动2k个单位,计算出开始和末尾,然后交换,再将字符数组转为字符串返回即可。
class Solution {
public String reverseStr(String s, int k) {
//将字符串转化成字符串数组
char[] ch = s.toCharArray();
//遍历,i每次移动2k
for(int i=0; i<ch.length; i+=2*k){
int start = i;
//这里是判断尾数够不够k个来取决end指针的位置
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);
}
}
剑指 Offer 05. 替换空格 - 12.3
解法一:字符串拷贝,使用 StringBuilder 逐个复制 字符,碰到空格则替换,否则直接复制。
class Solution {
public String replaceSpace(String s) {
//非空判断
if(s == null) return null;
//选用 StringBuilder 单线程使用,比较快
StringBuilder sb = new StringBuilder();
//使用 sb 逐个复制 str ,碰到空格则替换,否则直接复制
for(int i=0; i<s.length(); i++){
//str.charAt(i) 为 char 类型,为了比较需要将其转为和 " " 相同的字符串类型
if(" ".equals(String.valueOf(s.charAt(i)))){
sb.append("%20");
}else{
sb.append(s.charAt(i));
}
}
//转化为String字符串返回
return sb.toString();
}
}
解法二:双指针法,先对字符串进行扩容,然后从后往前遍历复制。
class Solution {
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. 翻转字符串里的单词 - 12.3
解析:先将多余的空格去掉,然后将整个字符串反转,最后将各个单词反转即可。
class Solution {
public String reverseWords(String s) {
//第一步:去掉多余的空格
StringBuilder sb = removeSpace(s);
//第二步:将字符串反转
reverseString(sb,0,sb.length()-1);
//第三步:将各个单词反转
reverseEachWords(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, int end){
while(start < end){
char temp = sb.charAt(start);
sb.setCharAt(start,sb.charAt(end));
sb.setCharAt(end,temp);
start++;
end--;
}
}
//将各个单词反转
private void reverseEachWords(StringBuilder sb){
int start = 0;
int end = 1;
int len = sb.length();
while(start < len){
while(end < len && sb.charAt(end) != ' '){
end++;
}
//反转单词
reverseString(sb,start,end-1);
//移动到下一个单词
start = end + 1;
end = start + 1;
}
}
}
*剑指 Offer 58 - II. 左旋转字符串 - 12.7
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
解法一:创建一个StringBuilder,先添加后半部分字符串,再添加前半部分字符串。
class Solution {
public String reverseLeftWords(String s, int n) {
if(s == null) return null;
StringBuilder sb = new StringBuilder();
//字符串转字符数组
char[] ch = s.toCharArray();
//先添加后半部分
for(int i=n; i<s.length(); i++){
sb.append(ch[i]);
}
//再添加前半部分
for(int i=0; i<n; i++){
sb.append(ch[i]);
}
return new String(sb);
}
}
28. 实现 strStr() - 12.7
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
解法一:KMP算法,KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
先构建出next数组,然后再使用next数组来做匹配操作。
class Solution {
public int strStr(String haystack, String needle) {
int haystack_len = haystack.length();
int needle_len = needle.length();
if(needle_len == 0) return 0;
//构建next数组
int[] next = new int[needle_len];
int j = 0;
next[0] = 0;
for(int i=1; i<needle_len; i++){ // 注意i从1开始
// 前后缀不相同了
while(j > 0 && needle.charAt(i) != needle.charAt(j)){
j = next[j-1]; // 向前回退
}
// 找到相同的前后缀
if(needle.charAt(i) == needle.charAt(j)){
j++;
}
// 将j(前缀的长度)赋给next[i]
next[i] = j;
}
//使用next数组来做匹配
j = 0;
for(int i=0; i<haystack_len; i++){
//字符不匹配
while(j>0 && haystack.charAt(i) != needle.charAt(j)){
j = next[j-1]; //根据next数组来回退
}
//字符匹配
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
//如果 j等于模式串的长度,就可以返回模式串的开始位置
if(j == needle_len){
return (i - needle_len + 1);
}
}
return -1;
}
}
解法二: 窗口滑动,先找到首字母相同的,然后再看后面的字符是否相同。
class Solution {
public int strStr(String haystack, String needle) {
int haystack_len = haystack.length();
int needle_len = needle.length();
if(needle_len == 0) return 0;
if(haystack_len < needle_len) return -1;
int i=0; //haystack的下标
int j=0; //needle的下标
while(i < (haystack_len - needle_len + 1)){
//先找到首字母
while(i < haystack_len && haystack.charAt(i) != needle.charAt(j)){
i++;
}
//如果 i等于 haystack 的长度,说明没有首字符相等,返回-1
if(i == haystack_len){
return -1;
}
//遍历后续字符,判断是否相等
i++;
j++;
while(i<haystack_len && j<needle_len && haystack.charAt(i) == needle.charAt(j)){
i++;
j++;
}
//找到
if(j == needle_len){
return i - j;
}else{ //未找到
i = i - j - 1;
j = 0;
}
}
return -1;
}
}
459. 重复的子字符串 - 12.8
- 重复的子字符串
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
// 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
s = " " + s;
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
// 构造 next 数组过程,j从0开始(空格),i从2开始
for (int i = 2, j = 0; i <= len; i++) {
// 匹配不成功,j回到前一位置 next 数组所对应的值
while (j > 0 && chars[i] != chars[j + 1]) j = next[j];
// 匹配成功,j往后移
if (chars[i] == chars[j + 1]) j++;
// 更新 next 数组的值
next[i] = j;
}
// 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
if (next[len] > 0 && len % (len - next[len]) == 0) {
return true;
}
return false;
}
}
剑指 Offer 20. 表示数值的字符串
解析:
//小数表示可省去0,-0.4 = -.4,0.4 = .4;2.、3. = 2、3,小数点前有数,后面可以不跟数代表原数
//注意e8即10的8次幂(8次方),也可以是e-7,但题目要求必须跟整数
//题目规定是数值前后可有空格,中间不能有,这个情况要考虑清楚。s:符号、d:数字
class Solution {
public boolean isNumber(String s) {
Map[] states = {
//0:规定0是初值,字符串表示数值,有4种起始状态,开头空格、符号、数字、前面没有数的小数点
//其中 开头空格 还是指向states[0],上一位是 开头空格,下一位可以是 空格、符号、数字、前面没有数的小数点
new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }},
//1:上一位是符号,符号位后面可以是 数字、前面没有数的小数点
new HashMap<>() {{ put('d', 2); put('.', 4); }},
//2:上一位是数字,数字的下一位可以是 数字、前面有数的小数点、e、结尾空格
new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }},
//3:上一位是前面有数的小数点,下一位可以是 数字、e(8.e2 = 8e2,和2的情况一样)、结尾空格
new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }},
//4:上一位是前面没有数的小数点,下一位只能是 数字(符号肯定不行,e得前面有数才行)
new HashMap<>() {{ put('d', 3); }},
//5:上一位是e,下一位可以是 符号、数字
new HashMap<>() {{ put('s', 6); put('d', 7); }},
//6::上一位是e后面的符号,下一位只能是 数字
new HashMap<>() {{ put('d', 7); }},
//7:上一位是e后面的数字,下一位可以是 数字、结尾空格
new HashMap<>() {{ put('d', 7); put(' ', 8); }},
//8:上一位是结尾空格,下一位只能是 结尾空格
new HashMap<>() {{ put(' ', 8); }}
};
int p = 0;
char t;
//遍历字符串,每个字符匹配对应属性并用t标记,非法字符标记?
for(char c : s.toCharArray()) {
if(c >= '0' && c <= '9') t = 'd';
else if(c == '+' || c == '-') t = 's';
else if(c == 'e' || c == 'E') t = 'e';
else if(c == '.' || c == ' ') t = c;
else t = '?';
//当前字符标记和任何一种当前规定格式都不匹配,直接返回false
if(!states[p].containsKey(t)) return false;
//更新当前字符的规定格式,进入下一个规定的Map数组
p = (int)states[p].get(t);
}
//2(正、负整数)、3(正、负小数)、7(科学计数法)、8(前三种形式的结尾加上空格)
//只有这四种才是正确的结尾
return p == 2 || p == 3 || p == 7 || p == 8;
}
}
剑指 Offer 67. 把字符串转换成整数
解析:
class Solution {
public int strToInt(String str) {
//去前后空格
char[] chars = str.trim().toCharArray();
if (chars.length == 0) return 0;
//记录第一个符合是否为负数
int sign = 1;
//开始遍历的位置
int i = 1;
//如果首个非空格字符为负号,那么从位置1开始遍历字符串,并且结果需要变成负数
if (chars[0] == '-') {
sign = -1;
} else if (chars[0] != '+') { //如果首个非空格字符不是负号也不是加号,那么从第一个元素开始遍历
i = 0;
}
int number = Integer.MAX_VALUE / 10;
//结果
int res = 0;
for (int j = i; j < chars.length; j++) {
//遇到非数字直接退出
if (chars[j] > '9' || chars[j] < '0') break;
/*
这里这个条件的意思为,因为题目要求不能超过int范围,所以需要判断结果是否越界
因为res每次都会 * 10 ,所以外面定义了一个int最大值除以10的数字
此时只需要保证本次循环的res * 10 + chars[j] 不超过 int 即可保证不越界
res > number 意思是,此时res已经大于number了,他 * 10 一定越界
res == number && chars[j] > '7' 的意思是,当res == number时,即:214748364
此时res * 10 变成 2147483640 此时没越界,但是还需要 + chars[j],
而int最大值为 2147483647,所以当chars[j] > 7 时会越界
*/
if (res > number || (res == number && chars[j] > '7')) {
//根据字符串首负号判断返回最大值还是最小值
return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
//字符获取数字需要 - '0' 的位移
res = res * 10 + (chars[j] - '0');
}
//返回结果,需要判断正负
return res * sign;
}
}