一、问题描述:
Category | Difficulty | Likes | Dislikes |
---|---|---|---|
algorithms | Easy (78.10%) | 551 | - |
Unknown
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
提示:
1 <= s.length <= 105
s[i]
都是 ASCII 码表中的可打印字符
二、思路:
xin麒的题解1仅beat60%~70%,但是题解2可以beat100%,而两者思路完全一致,只是代码结构略有差异,题解2的是因为交换元素时,调用成员方法,所以才beat100%,这两者的结构为什么差异如此大,麒麒后面会细说。思路是根据题解1来写的,相信明白题解1的思路就可以明白题解2了。
前提引入:
- 字符数组chars用来保存字符串s的所有元素;
- 变量len保存chars的数组长度了;
- 从左往右将chars分段,每2k个元素为一段,用part变量表示有多少2k个元素的
段的数目
; - 不满足元素数目为2k的最后一段的起始元素索引使用变量left表示;
- 最后一段的末尾元素索引使用end变量表示;
分析过程:
- 看到这个题目时,xin麒有一种折叠的感觉,而且需要找规律,对边界的确定尤为重要!
- xin麒打算分两部分走,首先处理连续元素个数满足2k的那些段落,
part = len / (2 * k);
,最后一段剩下的元素个数要么是在k到2k之间,要么是不足k个; - 当处理完part段后,再处理最后一段的元素,处理的时候,对于交换元素的细节一模一样;
首先将s转换为字符数组chars,再定义好数组长度的len以及计算出满足连续元素个数为2k的段落的数目part;
使用普通for循环,外层是使用变量i来遍历part;
对于每一段数目为2k的连续的元素,因为需要反转的是前k个元素,所以先确定这2k个元素里要反转的
最后一个元素
的索引end 以及 需要 反转的元素中的第一个元素
的索引j;
- 如何确认才是重点:
- ①当逻辑很清晰时,可以分析:
- 每一段的首元素的索引应是
(i * 2 * k)
,而再次基础上加上(k - 1)
便得到该数目为2k的元素里的第k个元素的索引,因此end = i * 2 * k + k - 1; j = (i * 2 * k);
;- ②可以先别填end和j的值,先把整体的架构写好,后面再用一些特定的字符串带进去,一一确定;
接着就是交换了,使用一个while循环即可,这里的判断条件的选取为重点,end大于j时,便需要交换两索引指向的两个元素,每次交换完,都需要j++和end–;
当这part个2k的元素都已经处理完毕后,就需要处理最后一段元素了;
最后一段元素的第一个元素索引left如何选取呢?根据part的计算方式,left应为
(part * 2 * k)
;
对于最后一个需要被反转的元素的索引end,我们不能直接使用len来赋值,因为这里有2种情况:1、剩下的元素个数小于k;2、剩下的元素个数在
[k,2k)
之间;
- 对于情况1,end直接赋值为len即可;
- 对于情况2,因为长度超过k,那么找到最后一段第k个元素的索引即可,那么end值为
(left + k - 1)
;
剩下的便是对需要反转的元素进行反转即可,实现细节同上述for循环里的while循环一样。
三、题解:
题解1(无法beat100%,但是题解2可以,并且两题解思路一致):
class Solution {
public String reverseStr(String s, int k) {
char[] chars = s.toCharArray();
int len = chars.length;
int part = len / (2 * k);
for (int i = 0; i < part; i++) {
int end = i * 2 * k + k - 1;
int j = i * 2 * k;
while(j < end){
char c = chars[j];
chars[j++] = chars[end];
chars[end--] = c;
}
}
int left = part * 2 * k;
int end = len;
if (len - left >= k){
end = left + k - 1;
}else{
end = len - 1;
}
while (end > left){
char c = chars[left];
chars[left++] = chars[end];
chars[end--] = c;
}
return String.valueOf(chars);
}
}
运行:
疑惑与思考:
这样子分析,时间复杂度应该是O(1)啊,都是为什么题解1运行效果却不好呢?
xin麒百思不得其解,晚上躺在床上也在思考,睡不着很难受脑海里挂着这个问题,xin麒看到其他评论的都是调用了成员方法,难道他们会有更好的方法吗?难道自己实现的题解的时间复杂度比别人的多很多吗?xin麒看了很久都没有看出来自己的题解哪里存在走多了几步的嫌疑,xin麒始终觉得自己的题解时间复杂度是最优的。突然xin麒灵光一闪,xin麒来一个大胆的假设,于是xin麒在床上躺了近半小时偷偷打开电脑,实在按捺不住想论证自己的猜想:
xin麒按照题解1的思路写出了可以beat100%的题解2,应该是论证了xin麒的假设。
题解2和题解1的区别:将反转的实现过程封装到类Solution的成员方法rever里面,private void rever(int end,int left)
;其中end表示需要传入的末尾索引;left表示需要传入的首元素的索引;保存s字符串的字符数组使用成员变量chars表示。
这个思考的内容很多,为什么调用成员方法就效率提高很多,xin麒想到了很多底层的内容,不过xin麒最近没有足够时间来写文章了,xin麒后面会继续完善的!
题解2:
class Solution {
char[] chars;
public String reverseStr(String s, int k) {
chars = s.toCharArray();
int len = chars.length;
int part = len / (2 * k);
for (int i = 0; i < part; i++) {
int end = i * 2 * k + k - 1;
int j = i * 2 * k;
rever(end,j);
}
int left = part * 2 * k;
int end = len;
if (len - left >= k){
end = left + k - 1;
}else{
end = len - 1;
}
rever(end,left);
return String.valueOf(chars);
}
private void rever(int end,int left){
while (end > left){
char c = chars[left];
chars[left++] = chars[end];
chars[end--] = c;
}
}
}
运行: