版权声明:本文为 DouMiaoO_Oo 原创文章,未经允许不得转载!转载请注明出处 https://blog.youkuaiyun.com/DouMiaoO_Oo/article/details/102461343
这一章讨论了3个问题,问题1为了引出二分搜索,而我在这里只总结一下问题2,旋转数组。
问题2
把一个
n
n
n元一维数组向左旋转(即循环移位)
k
k
k个位置。例如对于
n
n
n=8的数组abcdefg, 当
k
=
3
k=3
k=3时,通过旋转可以得到
d
e
f
g
a
b
c
defgabc
defgabc。
注:类似题目可以参考 LeetCode 189. Rotate Array, 这题是把右边的k个元素交换到左边的位置,也就是左移
n
−
k
n-k
n−k个元素。
这个问题,书中给了多个方案。
方案1
我们可以借助 O ( k ) O(k) O(k)的辅助空间,保存需要旋转的元素。
void rotate_array(vector<int>& str, int k){
/*
空间复杂度O(k), 时间复杂度O(n)
*/
int n = str.size();
if(n == 0) return;
k %= n;
if(k == 0) return;
vector<int> tmp(k, 0); // 辅助空间
for(int i = 0; i < k; ++i)
tmp[i] = str[i];
for(int i = k; i < n; ++i)
str[i-k] = str[i];
for(int i = n-1, j = 0; j < k; ++j){
str[i-j] = tmp[k-1-j];
}
}
方案2
每次移动一个元素,然后移动 k k k次。空间复杂度 O ( 1 ) O(1) O(1), 时间复杂度 O ( k n ) O(kn) O(kn)
void rotate_one_by_one(string& str, int k){
/*
空间复杂度O(1), 时间复杂度O(kn)
*/
int n = str.size();
if(n == 0) return;
k %= n;
for(int i = 0; i < k; ++i){
char tmp = str[0];
for(int j = 1; j < n; ++j){
str[j-1] = str[j];
} str[n-1] = tmp;
}
}
这个方法时间复杂度太高了,进一步想,我们怎么利用常数大小的空间,在 O ( n ) O(n) O(n)的时间复杂度中完成这个任务呢?
方案3
我们把数组的左边
k
k
k个元素部分记为
a
a
a,把剩下的
n
−
k
n-k
n−k个元素记为
b
b
b。所以这个任务的目标就是把
a
b
ab
ab交换为
b
a
ba
ba。
当
a
a
a 的长度小于
b
b
b 时,我们把
b
b
b拆分成
b
l
b
r
b_lb_r
blbr,其中
b
r
b_r
br的长度与
a
a
a相同。然后通过交换将
a
b
l
b
r
ab_lb_r
ablbr变为
b
r
b
l
a
b_rb_la
brbla。此时
a
a
a已经在正确的位置上了。我们要做的是交换
b
r
b_r
br与
b
l
b_l
bl,此时变成一个规模更小的相同问题。
同理当
a
a
a的长度大于
b
b
b时,可以拆分为
a
l
a
r
b
a_la_rb
alarb,然后交换得到
b
a
r
a
l
ba_ra_l
baral,此时
b
b
b到了正确的位置,而我们下一步要交换
a
r
a_r
ar与
a
l
a_l
al。
void helper(vector<int>& arr, int l, int r, int k){
if(k <= 0 || (r-l+1) <= k)
return; // 这里的判断条件易错
int n = r-l+1;
int kl = k, kr = n-k; // length of a and b
if(kl < 0 || kr < 0){
cout << "fuck!!!" << endl;
}
if(kl <= kr){
for(int i = 0; i < kl; ++i){
swap(arr[l+i], arr[r-kl+1+i]);
} helper(arr, l, r-kl, kl);
}
else{
for(int i = 0; i < kr; ++i){
swap(arr[l+i], arr[r-kr+1+i]);
} helper(arr, l+kr, r, kl-kr);
} return;
}
void rotate_recursion(vector<int>& arr, int k) {
/*target: ab -> ba, where len(a) = k */
int n = arr.size();
if(n == 0) return;
k %= n;
if(k == 0) return;
// k = n-k;
helper(arr, 0, n-1, k);
}
测试样例:
[1,2,3,4,5,6,7]
3
[1,2]
3
[1,2]
0
[1,2]
2
[1,2,3,4,5,6]
2
[1,2,3,4,5,6]
4
方案4
这个是我最喜欢的方法。 我们把数组的左边
k
k
k个元素部分记为
a
a
a,把剩下的
n
−
k
n-k
n−k个元素记为
b
b
b。所以这个任务的目标就是把
a
b
ab
ab交换为
b
a
ba
ba。我们用符号
a
ˉ
\bar a
aˉ 代表逆序的
a
a
a。 举例来说,对于
a
a
a = Hello,那么
a
ˉ
\bar a
aˉ = olleH。
此时这个任务可以下面的流程来解决:对于数组
a
b
ab
ab,我们先分别逆序
a
a
a和
b
b
b得到
a
ˉ
b
ˉ
\bar a \bar b
aˉbˉ,然后再逆序整个数组即可得到
b
a
ba
ba.
该方法空间复杂度为O(1),空间复杂度为O(n),事实上该方法在连续读取的条件下(此时可以用到缓存)把每个元素交换了2遍,效率很高。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void rotate_between(string& str, int s, int e){ // 逆序的方法
while(s < e){
swap(str[s++], str[e--]);
}
}
void rotate_k(string& str, int k){
k %= str.size();
rotate_between(str, 0, k-1);
rotate_between(str, k, str.size()-1);
rotate_between(str, 0, str.size()-1);
}
int main(){
string str = "Hello World!";
rotate_k(str, 3);
cout << str << endl;
return 0;
}
方案5
该方法是每个元素交换次数最少的,时间复杂度为O(n),空间复杂度为O(1)。但是因为操作中并不是处理连续的元素,寻址还有随机操作数组都带来了时间的开销。
void rotate_magic(vector<int>& arr, int k){
/*
杂技法,每个元素交换一次
空间复杂度O(1), 时间复杂度O(n)
*/
int n = arr.size();
if(n == 0) return;
k %= n;
if(k == 0) return;
int cnt = 0, idx = 0;
while(cnt < n){
int tmp = arr[idx];
for(int i = (idx+k)%n; i != idx; i = (i+k)%n){
arr[(i+n-k)%n] = arr[i];
++cnt;
} arr[(idx+n-k)%n] = tmp;
++cnt;
++idx;
}
}
版权声明:本文为 DouMiaoO_Oo 原创文章,未经允许不得转载!转载请注明出处 https://blog.youkuaiyun.com/DouMiaoO_Oo/article/details/102461343