《编程之美》学习笔记——2.17数组循环移位

原创 2015年01月13日 15:57:37

一、问题

设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为O(N),且只允许使用两个附加变量。

问题分析:

 输入:长度为N的原数组,K

 输出:循环右移K位后的数组

 约束:时间复杂度要求O(N),且只允许两个附加变量

二、解法

  版本一:数组中的每个元素一次移到位(利用数据移位的变化规律)

  思考:既然时间复杂度要求时O(n),且只允许两个附加变量,那么就是说,算法可以按照某种顺序对数组利用一个附加变量进行遍历,每次移位的结果必须移动到正确的最终位置。同时,我们利用一个附加变量把最开头的数组数据先存储下来,全部移位结束后我们再最把最后的一个数组中数利用这个附加变量赋值为正确的数据。

  在某个数组长度下,我们可以发现,对于数组中某一位置a,不断循环向右移动K位,最后移动到的这些位置(a, a+K.a+2K...)不一定能覆盖数组的全部位置,但是在位置a进行X次偏移(a, a+ 1, a+2, ..., a+i  (i < X))后并重复上述操作后,可以将数组的全部位置覆盖。遍历时只要我们将能够将数组的全部位置覆盖,就可以通过赋值完成整个数组的循环右移。因此,我们在数组中,对某一位置a,可以以K为间隔对数组中的数据进行移位,一次循环,然后对a再进行X-1次偏移并重复上述操作,最后整个数组循环右移K位。

  关键是上面X值的求解,我们通过举例进行测试和分析可以发现,X值实际上是数组长度N和位移值K的最大公约数。

  另外,我们可以发现:当K > N时,我们实际上只需要移动 K % N位即可,不必完整移动K位。这个比较好理解,K > N时数组向右移动K位和向右移动K % N位的效果是相同的。

最终设计了如下的算法:

/**
 * @brief get greatest commont divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    if (m == 0)
        return n;
    if (n == 0)
        return m;
    if (m < n) {
        TYPE temp = m;
        m = n;
        n = temp;
    }
    while (n != 0) {
        TYPE temp = m % n;
        m = n;
        n = temp;
    }
    return m;
}
/**
 * @brief cyclic shift for an array
 *
 * @param[in,out]  array   shift array
 * @param[in]      count   array length
 * @param[in]      right_shift  right shift count
 */
void array_cyclic_shift(TYPE* array, TYPE count, TYPE right_shift)
{
    assert(array != NULL && right_shift >= 0 && count >= 1);
    /// let right shift smaller than count
    right_shift %= count;
    TYPE i, k, temp;
    /// get the gcd value from right_shift and count to set cyclic shift times
    TYPE cyclic_count = gcd(right_shift, count);
    for (k = 0; k < cyclic_count; k++) {
        /// index begin from the top value
        i = count - 1 - k;
        /// save top value to buffer
        temp =  array[i];
        /// set value to value right_shift distance before it in a cycle
        while (((i - right_shift + count) % count) != count - 1 - k) {
            array[i] = array[(i - right_shift + count) % count];
            i = (i - right_shift + count) % count;
        }
        /// set last value from buffer
        array[i] = temp;
    }
    return;
}

分析:

附加变量:很显然这个算法用了不止两个附加变量,不考虑GCD函数调用的附加变量,算法也用去4个附加变量,优化后可以变为3个(减去上面的k和cyclic_count其中一个),但仍不满足问题约束条件。

时间复杂度:我们不去考虑GCD对算法运算效率的影响(设O(1)),由于我们每次移位时均能把数组中的数据从某一位置正确移动到另一个偏移后的相应位置,并不会进行多余的移动,因此这部分的算法复杂度是O(N)的。

 这个算法是通过寻找数组数据进行移位时他位置的变化规律而写成的,里面也对数组长度N和位移K进行最小公倍数的求解,这个最小公倍数起初并没有发现,一开始是从数组长度和位移K的奇偶性出发的,在测试过程中发现了错误,发现数组并不总能进行正确的循环移位,然后用例子深入探讨上面讲述的X趟移位的这个X值的特点,最后分析才发现X就是数组长度N和位移K的最小公倍数。

下面的版本二和版本三是参考着《编程之美》一书进行学习:

  版本二:数组中的每个元素逐渐移位

  把一个含有N个元素的数组循环右移K位,可以这么做:每次数组都整体向右移动1位,共移动K次。

  这里也考虑 K > N的情况,令K = K % N(和版本一一样)。

算法C实现:

/**
 * @brief cyclic shift for an array
 *
 * @param[in,out]  array   shift array
 * @param[in]      count   array length
 * @param[in]      right_shift  right shift count
 */
void array_cyclic_shift(TYPE* array, TYPE count, TYPE right_shift)
{
    assert(array != NULL && right_shift >= 0 && count >= 1);
    /// let right shift smaller than count
    right_shift %= count;
    TYPE i, temp;
    /// shift right_shift times
    while (right_shift--) {
        /// save top value to buffer
        temp = array[count - 1];
        /// set higher value by lower value
        for (i = count - 1; i > 0; i--)
            array[i] = array[i - 1];
        /// set bottom value from buffer
        array[0] = temp;
    }
    return;
}
附加变量:这里只用了两个附加变量,满足题目要求。

算法复杂度:O(N^2),(因为算法中循环次数T = K % N,得:T < N)

(如果对循环次数T不采用:T = K % N 而直接使用 T = K,那么算法复杂度将会变为:O(KN),K可能大于N。)

  版本三: 数组中的元素以段为整体进行翻转

举例:abcd1234 右移3位 后得到 234abcd1(N = 8 ,K = 3)

把数组分成两段,一段长度为N-K,一段长度为K,把这两段看成整体考虑,右移K位的过程就是把数组的这两段交换位置,可用如下算法完成:

1.对数组首(N-K)=5个元素段进行逆序排列,得到:1dcba234;

2.对数组尾K=3个元素段进行逆序排列,得到:1dcba432;

3.对整个数组进行逆序排列:得到最终结果:234abcd1。

算法C实现:

/**
 * @brief reverse an array from index_begin to index_end
 *
 * @param[in,out]  array       array to be reversed
 * @param[in]      index_begin index begin of the array(included)
 * @param[in]      index_end   index end of the array(included)
 */
void reverse(TYPE* array, TYPE index_begin, TYPE index_end)
{
    TYPE temp;
    /// swap between top index value and bottom index value
    for (; index_begin < index_end; index_begin++, index_end--) {
        temp = array[index_begin];
        array[index_begin] = array[index_end];
        array[index_end] = temp;
    }
}
/**
 * @brief cyclic shift for an array
 *
 * @param[in,out]  array   shift array
 * @param[in]      count   array length
 * @param[in]      right_shift  right shift count
 */
void array_cyclic_shift(TYPE* array, TYPE count, TYPE right_shift)
{
    assert(array != NULL && right_shift >= 0 && count >= 1);
    /// let right shift smaller than count
    right_shift %= count;
    /// reverse the first (count - right_shift) elements
    reverse(array, 0, count - right_shift - 1);
    /// reverse the remain elements
    reverse(array, count - right_shift, count - 1);
    /// reverse all the elements
    reverse(array, 0, count - 1);
    return;
}

附加变量:这里只用了两个附加变量,满足题目要求。

算法复杂度:第一次逆序排列需要扫描数组(N-K)/2长度,第二次逆序排列需要扫描数组K/2长度,最后一次逆序排列需要扫描数组N/2长度,算法总共扫描数组N长度,故时间复杂度为O(N)。

关键:这里实际上分析了数组位移的特点,利用已有的数组空间,通过逆序操作(空间翻转)使数组循环右移。

三、拓展学习

更多的求解方法学习参考资料:

http://blog.chinaunix.net/uid-21228455-id-2406481.html


相关文章推荐

编程之美:第二章 数字之魅 2.17数组循环移位

/* 数组循环移位: 设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为O(N),且只允许使用两个附加变量。 此题已经做过,但本题的收获是注意循环移位,即,当K大于N时,注意到移N位...

[编程之美] PSet2.17 数组循环移位

设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为O(N),且只允许使用两个附件变量。比如abcd1234右移4位后为:1234abcd。...

编程之美2.17 数组循环移位

问题描述把一个含有N个元素的数组循环右移K位,如K=4的时候abcd1234->1234abcd。一般考虑K>0的情况,即右移;同时K=N的话可以通过K%=N得到一样的结果 解法思路详见代码注释/**...

2.17 数组循环移位

1. 前言本文的一些图片, 资料 截取自编程之美2. 问题描述3. 问题分析对于 这个问题, 书中给出了两种思路 解法一 : 将移动的位数模上数组的长度, 获取最后需要移动的位数[得到移动位数k],...

编程之美---数组循环移位

设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为 O(N), 且只允许使用两个附加变量。 解法一:简单的办法是,每次将数组中的元素右移一位,循环K次。abcd1234 -> 4ab...

编程之美6:数组循环移位

楼主又来~(≧▽≦)/~啦啦啦,科研,就是要这么一鼓作气。额,其实楼主的老本行是推公式啊,做这些算法题,其实是楼主在偷懒。额,话不多说了,快请出我们今天的主角吧!还是关于数组的-数组循环移位。下面我们...

每天学习一点编程(14)(数组/字符串循环移位)

设计一个算法,把一个含有N个元素的数组循环右移K位,要求时间复杂度为O(N),且只允许使用两个附加变量。 不合题意的解法如下: 我们先试验简单的办法,可以每次将数组中的元素右移一位,循环K次。...

旋转字符串;编程珠玑第二章;rotate a one-dimensional vector;循环移位数组;编程之美

一:问题描述: 编程珠玑第二章的第二个问题是字符串(或者理解为向量)旋转问题,具体描述如下: rotate a one-dimensional vector of n elements left...

PHP学习笔记——使用list(),each(),while()循环遍历数组

]
  • iheyu
  • iheyu
  • 2017年05月29日 19:07
  • 148

【C++】学习笔记十九——嵌套循环和二维数组

嵌套循环和二位数组int maxtemps[4][5];该声明意味maxtemps是一个包含4个元素的数组,其中每个元素都是一个由5个整数组成的数组。 可以将mantemps看作有4行组成,其中每一...
  • budf01
  • budf01
  • 2016年12月19日 20:49
  • 2370
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:《编程之美》学习笔记——2.17数组循环移位
举报原因:
原因补充:

(最多只允许输入30个字)