编程珠玑读书笔记-第2章

版权声明:本文为 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 nk个元素。

这个问题,书中给了多个方案。

方案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 nk个元素记为 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 nk个元素记为 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值