-
字符串反转
-
344.字符串反转
-
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
// 两个指针 一个letf指最前面字符 一个rigth值最后面字符 交换两个指针的字符 完成后letf++,rigth-- 继续交换两个指针字符 直至letf > rigth 代表字符串反转完成
class Solution {
public void reverseString(char[] s) {
int letf = 0; // 前指针
int rigth = s.length - 1; // 后指针
while (letf < rigth) { // 直至letf > rigth 代表字符串反转完成
char temp = s[letf]; // 交换两个指针位置的字符
s[letf] = s[rigth];
s[rigth] = temp;
letf++;
rigth--;
}
}
}
-
字符串反转2
-
541.字符串反转2
-
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
// 定义两个指针 一个前一个后 letf前指针在需要反转字符串的开始处 rigth后指针移动至需要反转字符串的结束处 再将其这一段字符串反转
// 再次寻找下一个需要反转的字符串 letf += 2 * k 至 rifth = Math.min(ch.length - 1, letf + k - 1) (如果剩余字符少于 k 个,则将剩余字符全部反转-取ch.length - 1 : 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符-取letf + k - 1)
class Solution {
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray(); // 转化字符数组 方便移动
for (int i = 0; i < ch.length; i += 2 * k) { // 判断是否越界 i += 2 * k (如果剩余字符小于 2k)
int letf = i; // 前指针 定位至需要反转的字符串开始处
int rigth = Math.min(ch.length - 1, letf + k - 1); // 后指针 定位至需要反转的字符串结束处 (如果剩余字符少于 k 个,则将剩余字符全部反转-取ch.length - 1 : 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符-取letf + k - 1)
while (letf < rigth) { // 反转字符串
char temp = ch[letf];
ch[letf] = ch[rigth];
ch[rigth] = temp;
letf++;
rigth--;
}
}
return new String(ch);
}
}
-
替换空格
-
剑指Offer05.替换空格
-
请实现一个函数,把字符串
s
中的每个空格替换成"%20"。
输入:s = "We are happy."
输出:"We%20are%20happy."
// 因为%20比空格多出两个字符位 先计算字符串中包含多少空格 再往字符串s后面追加空格 满足将空格替换成%20后不再需要扩容
// 两个指针 一个前指针从后往前遍历寻找空格/字符 一个后指针表示当前插入的位置 当检测到字符时将其移至后指针位置上 当检测到空格时在后指针位置直接追加 %20 字符串 直至前指针遍历完字符串
// 首先扩充数组到每个空格替换成"%20"之后的大小。 然后从后向前替换空格,也就是双指针法
class Solution {
public String replaceSpace(String s) {
if (s == null || s.length() == 0) return s; // 判空
StringBuilder sb = new StringBuilder(); // 定义需要追加空格大小的字符串
for (int i = 0; i < s.length(); ++i) { // 遍历字符串
if (s.charAt(i) == ' ') { // 每找到一位空格将追加两位字符大小
sb.append(" ");
}
}
if (sb.length() == 0) return s; // 无空格判断
int letf = s.length() - 1; // 前指针
s += sb.toString(); // 追加空格到字符串s后面
int rigth = s.length() - 1; // 后指针 字符将要被移至的位置
char[] chars = s.toCharArray(); // 转化为字符数组
while (letf >= 0) { // 遍历数组
if (chars[letf] == ' ') { // 当检测到空格时在后指针位置直接追加 %20 字符串
chars[rigth--] = '0';
chars[rigth--] = '2';
chars[rigth] = '%';
} else { // 当检测到字符时将其移至后指针位置上
chars[rigth] = chars[letf];
}
letf--; // 移动指针
rigth--;
}
return new String(chars); // 输出结果
}
}
-
颠倒字符串中的单词
-
151.颠倒字符串中的单词
-
给你一个字符串 s ,颠倒字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
输入:s = "the sky is blue"
输出:"blue is sky the"
输入:s = " hello world "
输出:"world hello"
解释:颠倒后的字符串中不能存在前导空格和尾随空格。
// 三个指针 一个前指针遍历字符串 当遇到字符时即单词时 将rigth指针移动至单词最后 前指针再继续遍历 当遇到空格时即单词段结束将k指针指向单词开头 现k指向单词开头 rigth指向单词末尾 k向后移动将一个一个字符拼接至字符串sb中,直至遍历完这一段单词 还有一个标志位b 用来记录是否为第一次找到单词 如是第一次找到单词则前面不需要追加空格字符 如果不是第一次找到单词则在追加单词时前面需要加一个空格字符
// 以此遍历循环 直至前指针遍历完字符串 如果字符串s前没有空格 则字符串第一个单词的结束条件结束 letf == -1 不再是空格字符 s.charAt(letf) == ' '
class Solution {
public String reverseWords(String s) {
if (s == null || s.length() == 0) return s; // 空字符串
StringBuilder sb = new StringBuilder(); // 将单词追加至此字符串中
boolean b = false; // 标志位 是否为第一次找到单词
int letf = s.length() - 1, rigth = s.length() - 1, k = s.length() - 1; // 初始化三个指针
while (letf >= 0) { // letf前指针遍历字符串
if (s.charAt(letf) != ' ') { // 当遇到字符时即单词时
rigth = letf; // 将rigth指针移动至单词最后
while (letf >= -1) { // 前指针再继续遍历
if (letf == -1 || s.charAt(letf) == ' ') { // 当遇到空格时即单词段结束将k指针指向单词开头
if (b) sb.append(' '); // 如是第一次找到单词则前面不需要追加空格字符
k = letf + 1; // 现k指向单词开头 rigth指向单词末尾
while (k <= rigth) {
sb.append(s.charAt(k++)); // k向后移动将一个一个字符拼接至字符串sb中,直至遍历完这一段单词
}
b = true; // 第一个单词移动完
break; // 单词追加完成 跳出循环 letf前指针继续遍历字符串 当遇到字符时.........以此循环
}
letf--;
}
}
letf--;
}
return sb.toString(); // 返回结果
}
}
-
左旋转字符串
-
剑指Offer58.左旋转字符串
-
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
输入: s = "abcdefg", k = 2
输出: "cdefgab"
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"
// 例如: abcdefg n = 2
// ba cdefg ba gfedc cdefgab
// 1. 反转区间为前n的子串 2. 反转区间为n到末尾的子串 3. 反转整个字符串
class Solution {
public String reverseLeftWords(String s, int n) {
if (s == null || s.length() == 0) return s; // 判空
StringBuilder sb = new StringBuilder(s); // 可变字符串sb
reverseString(sb, 0, n - 1); // 反转区间为前n的子串
reverseString(sb, n, sb.length() - 1); // 反转区间为n到末尾的子串
reverseString(sb, 0, sb.length() - 1); // 反转整个字符串
return sb.toString(); // 返回结果
}
void reverseString(StringBuilder s, int start, int end) { // 反转字符串
while (start < end) {
char temp = s.charAt(start);
s.setCharAt(start, s.charAt(end)); // 将s字符串中start位置上的字符 替换成 s字符串中end位置上的字符
s.setCharAt(end, temp);
start++;
end--;
}
}
}
-
实现strStr()
-
28.实现strStr()
-
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
输入:haystack = "hello", needle = "ll"
输出:2
输入:haystack = "aaaaa", needle = "bba"
输出:-1
// 利用KMP的经典思想:当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配 将needle模式串成前缀表 再根据前缀表跟原串进行匹配
// KMP算法:如果已经匹配的字符串包含相同的前缀和后缀,遇到下一个不匹配的位置时,指向needle的指针跳转到前缀的后一个位置,还是不匹配的话,再往前跳转后继续比较;先构造一个next数组来记录needle指针跳转的位置
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) { // 利用next数组进行跳转匹配,不再需要回退haystack的指针i
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) { // 匹配不成功,needle指针j回退并继续比较
j = next[j - 1];
}
if (haystack.charAt(i) == needle.charAt(j)) { // 当匹配成功时 j++
j++;
}
if (j == needle.length()) { // 当匹配成功的字符个数等于模式串的个数时 即说明haystack 中存在 needle
return i - needle.length() + 1; // 返回出现的第一个位置
}
}
return -1;
}
// 先构造next数组,next数组中的元素表示当前两个元素不匹配时,needle指针要跳转的位置
// haystack: [a, b, e, a, b, a, b, e, a, b, f]
// needle: [a, b, e, a, b, f]
// next: [0, 0, 0, 1, 2, 0]
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]; // 一直和前一位置的值比较,直到遇到相等的字符或者j=0;j通过next[j-1]来回退
}
if (s.charAt(j) == s.charAt(i)) { // 当匹配成功时,j++
j++;
}
next[i] = j; // 更新next前缀表
}
}
}
-
重复的子字符串
-
459.重复的子字符串
-
给定一个非空的字符串
s
,检查是否可以通过由它的一个子串重复多次构成。
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
输入: s = "aba"
输出: false
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
// 利用KMP算法,将字符串生成前缀表 再根据前缀表next数组判断是否是重复的子字符串
// 如果len % (len - next[len]) == 0 ,则说明 (数组长度-最长相等前后缀的长度) 正好可以被 数组的长度整除,说明有该字符串有重复的子字符串。数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。
class Solution {
public boolean repeatedSubstringPattern(String s) {
if (s.equals("")) return false;
int len = s.length();
s = " " + s; // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了
char[] chars = s.toCharArray();
int[] next = new int[len + 1];
for (int i = 2, j = 0; i <= len; i++) { // 构造 next 数组过程,j从0开始(空格),i从2开始
while (j > 0 && chars[i] != chars[j + 1]) j = next[j]; // 匹配不成功,j回到前一位置 next 数组所对应的值
if (chars[i] == chars[j + 1]) j++; // 匹配成功,j往后移
next[i] = j; // 更新 next 数组的值
}
if (next[len] > 0 && len % (len - next[len]) == 0) { // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值
return true;
}
return false;
}
}