189. Rotate Array leetcode(array)

Rotate Array
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].
Note:
Try to come up as many solutions as you can, there are at least 3 different ways to solve this problem.
Related problem: Reverse Words in a String II

方法一、类似于左移右移字符串多少位的问题

void rotate(vector<int>& nums, int k) {
        int len = nums.size();
        if(k>len)
        {
            k = k % len; //允许出现k大于len的情况
        }

        reverse(nums.begin(),nums.begin()+len-k);//注意reverse(int start, int end)函数的起始范围,是对start和end之间的数字进行倒置,包括start,不包括end
        reverse(nums.begin()+len-k,nums.end());
        reverse(nums.begin(),nums.end());
    }

方法二、采用空间换取时间的方法,利用新的一个vector,先保存vector的后K个元素,在保存vector前面部分的元素,从而实现

void rotate(vector<int>& nums, int k) {
        int len = nums.size();

        if(len <= 1)
        {
            return ;
        }
        if(k>len)
        {
            k = k % len; //允许出现k大于len的情况
        }

        //采用空间换时间的方法
        vector<int> result;
        vector<int>::iterator iter;
        for(iter = nums.begin()+len - k; iter != nums.end(); iter++)
        {
            //cout<<*iter<<endl;
            result.push_back(*iter);
        }

        for(iter = nums.begin(); iter != nums.begin() + len - k; iter++)
        {
            result.push_back(*iter);
        }

        nums = result;
    }

方法三、超时的方法,移动

void rotate(vector<int>& nums, int k) {
        int len = nums.size();

        if(len <= 1)
        {
            return ;
        }
        if(k>len)
        {
            k = k % len; //允许出现k大于len的情况
        }

        //最普通最普通的移动策略,时间复杂度高~~
        while(k)
        {
            int temp = nums[len-1];
            for(int i = len - 1; i > 0; i--)
            {
                nums[i] = nums[i-1];
            }

            nums[0] = temp;
            k--;
        }
    }

方法四:使用系统自带的函数来解决

void rotate(vector<int>& nums, int k) {
        int len = nums.size();

        if(len <= 1)
        {
            return ;
        }
        if(k>len)
        {
            k = k % len; //允许出现k大于len的情况
        }

        //系统自带函数
        std::rotate(nums.begin(), nums.end() - k, nums.end());
    }

注意:以下部分参考博文:http://www.cnblogs.com/flyinghearts/archive/2011/05/27/2060265.html
要将一个数组的所有元素向左旋转k位,通常有三种算法:

算法1(分组交换):
若a长度大于b,将ab分成a0a1b,交换a0和b,得ba1a0,只需再交换a1 和a0。
若a长度小于b,将ab分成ab0b1,交换a和b0,得b0ab1,只需再交换a 和b1。
不断将数组划分和交换,直到不能再划分为止。分组过程与求最大公约数很相似。
读写内存各 n到2*n次

算法2 (三次反转)
利用ba=(br)r(ar)r=(arbr)r,先分别反转a、b,最后再对所有元素进行一次反转。
读写内存各约2*n次

算法3 (使用循环链)
假设 n、k的最大公约数为M,则所有序号为 (i + j*k) % n (0<= i < M, 0 <= j < n/M)的元素,构成M个循环链(i值相同的在同一个循环链上), 每个循环链上的元素移动到前一个元素的元素,就可以交换到最终结果上的位置,因而总共只要读写内存各n次。(比如: 1 2 3 4 5 6,左移2位, 1 3 5 和 2 4 6分别构成两个循环链。)

事实上C++标准算法库提供了现成的函数:rotate函数。按理说,几种算法都比较简单,编译器的库函数又是经过时间检验的,效率即使比手写的差,也不会差太多。但如果对rotate函数进行测试的话,可能会发现标准库的版本慢得可不是一点点。
对VC 2010,运行后面的测试程序,自定义函数(采用算法2)要用99ms,而std::rotate却要1656ms。是库的实现者不懂得用这个简单的算法吗?检查下库的源代码,就会发现:标准算法库中,对C++的三种迭代器(前向迭代器、双向迭代器,随机访问迭代器),分别采用了上面三种算法。直接调用其内部的实现(std::_Rotat函数),重新测试下,可得到下面结果:

迭代器
前向(算法1)
双向(算法2)
随机访问(算法3)
时间(ms)
46
99
1651
(使用GCC的,请用版本号低于4.5的进行测试)

从结果可以看出,效率是:算法1 > 算法2 >>> 算法3。 

从理论上讲,算法3只要读写内存各n次,应该是效率最高的算法。这在每次内存读写的开销相差不大时成立。但实际上,由于硬件限制,CPU对内存的访问采用分级缓存机制:一级缓存容量很小但访问速度最快,存放程序的指令和最常用的数据,而二、三级缓存容量较大但访问速度要慢很多。CPU是无法绕过缓存直接访问内存数据(某些特殊指令可以不用一二三级缓存,但它也要用到其它专用缓存),对不在缓存中的数据,必须先载入到缓存中,这个操作是相当昂贵的。对大数组来说,不可能将所有数据都存放在缓存中,而对内存的不连续访问,CPU对内存定位的开销(各级缓存间数据的调整,反复移入或移出数据到缓存)是巨大的,这就造成了算法3的性能在该情况下非常差。测试发现,k = 3时,该算法的效率就已经相当差了。对小数组,尽管该算法读写次数少,但由于各种算法所用时间都很小,这种优势很难体现出来。可以说,算法3在数学上是非常优美的,但是在实际应用中,是一种相当差的算法。
对算法的选择,不应该忽视内存因素。在对随机访问迭代器版本的roate实现上犯这个错误的,可不仅仅是VC,还有著名的STL Port、GCC(GCC从4.5开始libstdc++改用算法1,并做了些优化),以及新兴的libc++。(其它的编译器/库没用过,也就没有测试。)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值