今日任务:
- 反转字符串
- 反转字符串 II
- 替换空格
- 翻转字符串里的单词
- 左旋转字符串
反转字符串
基础操作,实现 O(n) 的时间复杂度,类似二分,但是虽然是左闭右闭,这里不用比较交换中间的那个元素,所以 while 循环中的条件是 < 而不是 <=。
public void reverseString(char[] s) {
int left = 0;
int right = s.length - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
反转字符串 II
这个题目我一开始是自己做的,带画图找边界,看了好久,代码虽然可以运行但是也不是太好,思路是这样的,因为题目要求的是反转前 k 个元素,所以我就先判断这个传入的字符串,有没有这个前 k 个元素,初始化 left = 0, right = 0, 然后判断如果 left + k - 1 < len 满足条件,就代表我们可以对这前 k 个元素进行交换,如果 len - left < k 也就是说它的剩余的长度要小于这个 k 的的话,就将它们都进行交换,然后交换之后,把它返回即可。 还有一点就是在第一个 if 判断之后,需要移动 left 和 right 将他们移动到 2k 的位置,也就是如果 right + 1 + k < len 就进行移动,如果不好理解的话,就举个例子 [1, 2, 3, 4] 然后 k = 2,第一次就是 0 + 2 - 1 < 4 然后交换 1, 2 变成了 [2, 1, 3, 4] 然后判断,1 + 2 + 1 < 4 就说明 right 不可以移动到索引 4 这个位置,这时候直接返回,因为已经到达了边界。
public String reverseStr(String s, int k) {
//首先将s转为字符数组
char[] chars = s.toCharArray();
int count = k;
int left = 0;
int right = 0;
int len = chars.length;
while (true) {
//判断是否有前k个的范围,如果有的话就执行反转
if (left + count - 1 < len) {
right += count - 1;
//将这个范围的值进行交换
swap(chars, left, right);
//交换之后查看之后是否还有元素可以移动
if (right + k + 1 < len) {
//将left和right的位置进行更新
right += k + 1;
left = right;
} else {//如果不能移动,说明这个后面不到k个元素就结束了,然后我们就可以直接返回,因为后面的并不需要进行反转
return convert(chars);
}
} else if (len - left < count) {
//如果是长度要小于count的话直接全部反转即可!
right = len - 1;
swap(chars, left, right);
left = right;
return convert(chars);
}
}
}
private static String convert(char[] chars) {
String ret = "";
for (int i = 0; i < chars.length; i++) {
ret += chars[i];
}
return ret;
}
private static void swap(char[] chars, int left, int right) {
//将这个传入范围内的数组值进行交换
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
}
然后其实这个解法并不好,有一种更好的解法: 这里使用 for 循环每次跟新 i 往后移动 2 * k 就不需要关心它是否越界,只用在里面判断,如果是剩余元素小于 k 的情况,将 ch.length - 1 全部反转,否则就反转 k 个,然后移动索引交给 for 循环来处理,reverse 里面也可以指定反转的范围
(我觉得我可能还需要再消化消化🤡)
public String reverseStr(String s, int k) {
char[] ch = s.toCharArray();
// 1. 每隔 2k 个字符的前 k 个字符进行反转
for (int i = 0; i< ch.length; i += 2 * k) {
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= ch.length) {
reverse(ch, i, i + k -1);
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转
reverse(ch, i, ch.length - 1);
}
return new String(ch);
}
// 定义翻转函数
public void reverse(char[] ch, int i, int j) {
for (; i < j; i++, j--) {
char temp = ch[i];
ch[i] = ch[j];
ch[j] = temp;
}
}
替换空格
没啥好写的,基础操作
遍历这个 s 然后如果找到 ” “ 就在 sb 中添加 %20 即可
public String replaceSpace(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == ' ') {
sb.append("%20");
} else {
sb.append(c);
}
}
return sb.toString();
}
反转字符串里的单词
这道题害惨我了,我以为自己可以用 StringBuilder 来做的,后来发现始终无法处理中间的空格(留一个空格),也没想那么复杂,看了题解才发现对中间空格的处理原来这么巧妙,这还没结束,还需要对整个处理之后的字符进行反转,然后对里面的单词进行反转
public String reverseWords(String s) {
//首先将str转为一个字符数组
StringBuilder sb = replaceSpace(s);
reverseAll(sb, 0, sb.length() - 1);
return reverseAllWords(sb);
}
private String reverseAllWords(StringBuilder sb) {
int left = 0, right, len = sb.length();
for (right = 0; right < len; right++) {
while (right < len && sb.charAt(right) != ' ') {
right++;
}
//找到第一个空格之后我们需要将它进行反转,同时需要对索引的边界进行控制
reverseAll(sb, left, right - 1);
left = right + 1;
right = left;
}
return sb.toString();
}
private void reverseAll(StringBuilder s, int start, int end) {
int left = start;
int right = end;
while (left < right) {
char temp = s.charAt(left);
s.setCharAt(left, s.charAt(right));
s.setCharAt(right, temp);
left++;
right--;
}
}
public StringBuilder replaceSpace(String str) {
char[] chars = str.toCharArray();
int left = 0;
int right = chars.length - 1;
StringBuilder sb = new StringBuilder();
while (left < right && chars[left] == ' ') left++;
while (right > left && chars[right] == ' ') right--;
//此时的left和right都是在字符上的,分别代表左右没有空格的开始
//然后我们开始对中间进行去重
for (int i = left; i <= right; i++) {
char temp = chars[i];
if (temp != ' ' || sb.charAt(sb.length() - 1) != ' ') {
sb.append(temp);
}
}
return sb;
}
左旋转字符串
这道题我做过类似的,我可以把那个讲解复制过来,我觉得讲的挺清晰的(其实就是旋转数组类型题)
Problem: Rotate an array of n elements to the right by k steps. For example, with n
= 7 and k = 3, the array [1,2,3,4,5,6,7] is rotated to [5,6,7,1,2,3,4].
How many different ways do you know to solve this problem?
Can we do this in O(1) space and in O(n) time? The following solution does.
Assuming we are given 1,2,3,4,5,6 and order 2. The basic idea is:
-
Divide the array two parts: 1,2,3,4 and 5, 6
-
Rotate first part: 4,3,2,1,5,6
-
Rotate second part: 4,3,2,1,6,5
-
Rotate the whole array: 5,6,1,2,3,4
public static void rotate(int[] arr, int order) {
order = order % arr.length;
if (arr == null || order < 0) {
throw new IllegalArgumentException("Illegal argument!");
}
//length of first part
int a = arr.length - order;
reverse(arr, 0, a-1);
reverse(arr, a, arr.length-1);
reverse(arr, 0, arr.length-1);
}
public static void reverse(int[] arr, int left, int right){
if(arr == null || arr.length == 1)
return;
while(left < right){
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
}
我这道题的代码(和上面还是有有些不一样的,你可以看看题目的条件):
public String reverseLeftWords(String s, int n) {
char[] chars = s.toCharArray();
if (chars == null || n < 0) throw new IllegalArgumentException("Illegal Argument!");
//对n进行取余
n = n % chars.length;
n = chars.length - n;
int a = chars.length - n - 1;
//进行翻转操作
reverseArr(chars, 0, a);
reverseArr(chars, a + 1, chars.length - 1);
reverseArr(chars, 0, chars.length - 1);
return new String(chars);
}
private void reverseArr(char[] nums, int left, int right) {
while (left < right) {
char temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
}
总结
字符串的反转和翻转是一个难点,重点在于对索引边界的处理和局部反转之类的,旋转数组感觉是一个模板,以后可以套用在同类题上。