反转字符串,这是一个非常经典的题,算法很多种
我的算法是借鉴《编程之法》一书中的 三步反转法(题中的移动操作其实就是变向的反转)
三步反转法非常好理解,简单来说就是
数据(数组,字符串等),由 A,B两个部分组成:
<—A— | <—B—
变化后的目标为:
<—B— | <—A—
那我们可以用三步反转来组成:
第一步, 反转A:—A—> | <—B—
第二步,反转B:—A—> | —B—>
第三步,反转整个数据,也即是AB: <—B— | <—A—
数学公式来表示就是:(X^TY^T)^T=YX
public class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
// 首先,右移位数需要化为n以内数,移动等于n则相当于没有移动
k %= n;
// 交换点为 n-k
int swap = n-k;
reverse(nums,0,swap-1);
reverse(nums,swap,n-1);
reverse(nums,0,n-1);
}
private void reverse(int[] nums,int begin,int end){
int i = begin;
int j = end;
int temp;
while(j>i){
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
i++;
j--;
}
}
}
下面来介绍一些其他的算法:
暴力转移法:最直接的方法,直接一位一位的移动,但是这样的时间代价很高 O(n*k),leetcode也不会通过,超时
额外空间法:
新创建一个数组,从 n-k 的位置开始读入,到尾部就从头读入。最终 再使得 nums = newNums 即可
时间和空间的复杂度都是 O(n)
public class Solution {
public void rotate(int[] nums, int k) {
int[] a = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
a[(i + k) % nums.length] = nums[i]; // 这一步是最关键的
}
for (int i = 0; i < nums.length; i++) {
nums[i] = a[i];
}
}
}
循环置换法,这个方法空间复杂度为 O(1),事件复杂度为 O(n)
真正的思路其实异常的简答清晰,细节部分可以慢慢理解
首先来讨论,为什么暴力移动法是非常耗时的?
1,2,3,4,5,6
如果我要移动2位 那么如果用暴力移动法 第一次移动的结果;
6,1,2,3,4,5
现在,每一个数字都在错误的位置上,但是有没有可能避免这种错误置位,直接将数字移动到正确的位置呢?
直接移动到 数字移位的正确的位置,就是循环移位的核心了。
如上面的举例,
1,2,3,4,5,6
1移动两位应该在3的位置
X , 2,1, 4,5,6 (X表示目前未知)
而3应该在 5的位置
X , 2,1, 4,3,6
5应该在1的位置
5 , 2,1, 4,3,6
这样就完成了一次循环,这次循环过后,所有交换过的位置,都是最终移位后的位置,对比可见
5 , 2,1, 4,3,6 (一次循环后的)
5, 6,1, 2,3,4 (最终的)
可以看到我们交换的 1,3,5都是正确的。
这样的循环,执行k次,就可以完成了
代码如下,细节可以自己理解了:
public class Solution {
public void rotate(int[] nums, int k) {
k = k % nums.length;
int count = 0;
for (int start = 0; count < nums.length; start++) {
int current = start;
int prev = nums[start];
do {
int next = (current + k) % nums.length;
int temp = nums[next];
nums[next] = prev;
prev = temp;
current = next;
count++;
} while (start != current);
}
}
}