【问题标题】:Fastest algorithm for circle shift N sized array for M position用于 M 位置的圆移位 N 大小数组的最快算法
【发布时间】:2010-10-26 22:51:42
【问题描述】:

M个位置的圆移位数组最快的算法是什么?
例如,[3 4 5 2 3 1 4] shift M = 2 个位置应该是[1 4 3 4 5 2 3]

非常感谢。

【问题讨论】:

    标签: algorithm arrays math puzzle programming-pearls


    【解决方案1】:

    如果您想要 O(n) 时间并且不需要额外的内存使用(因为指定了数组),请使用 Jon Bentley 的书“Programming Pearls 2nd Edition”中的算法。它交换所有元素两次。不如使用链表快,但使用的内存更少,而且概念上很简单。

    shiftArray( theArray, M ):
        size = len( theArray )
        assert( size > M )
        reverseArray( theArray, 0, size - 1 )
        reverseArray( theArray, 0, M - 1 )
        reverseArray( theArray, M, size - 1 )
    

    reverseArray( anArray, startIndex, endIndex ) 将元素的顺序从 startIndex 反转到 endIndex,包括。

    【讨论】:

    • 我想知道你什么时候真正需要进行物理阵列移位。
    • @Vinko:也许作为计算应用于数组不同重叠部分的多个循环移位的更大任务的一部分。
    • 我将assert(size>M) 替换为M = M % size 并检查M==0。这将使函数更加灵活。
    • 就交换次数而言,该算法不是最优的。
    • @Vinko 我使用这个算法。我们有大约 100 个元素的小数组,并对它们执行大量操作。它必须是一个数组,因为我们需要对大多数操作进行随机访问。创建一个链表会比就地交换慢得多。所以对我们来说这是一个性能问题。分配内存很昂贵。
    【解决方案2】:

    最优解

    要求最快的问题。反转 3 次是最简单的,但每个元素恰好移动两次,需要 O(N) 时间和 O(1) 空间。也可以在 O(N) 时间和 O(1) 空间内对数组进行循环移位,将每个元素仅移动一次。

    想法

    我们可以将长度为N=9 的数组循环移位M=1 一个循环:

    tmp = arr[0]; arr[0] = arr[1]; ... arr[7] = arr[8]; arr[8] = tmp;
    

    如果N=9, M=3 我们可以用三个循环循环移位:

    1. tmp = arr[0]; arr[0] = arr[3]; arr[3] = tmp;
    2. tmp = arr[1]; arr[1] = arr[4]; arr[4] = tmp;
    3. tmp = arr[2]; arr[2] = arr[5]; arr[5] = tmp;

    注意每个元素被读取一次,写入一次。

    换档示意图N=9, M=3

    第一个循环以黑色显示,数字表示操作顺序。第二个和第三个循环以灰色显示。

    所需的周期数是NMGreatest Common Divisor (GCD)。如果 GCD 为 3,我们在每个 {0,1,2} 处开始一个循环。使用binary GCD algorithm 可以快速计算 GCD。

    示例代码:

    // n is length(arr)
    // shift is how many place to cycle shift left
    void cycle_shift_left(int arr[], int n, int shift) {
      int i, j, k, tmp;
      if(n <= 1 || shift == 0) return;
      shift = shift % n; // make sure shift isn't >n
      int gcd = calc_GCD(n, shift);
    
      for(i = 0; i < gcd; i++) {
        // start cycle at i
        tmp = arr[i];
        for(j = i; 1; j = k) {
          k = j+shift;
          if(k >= n) k -= n; // wrap around if we go outside array
          if(k == i) break; // end of cycle
          arr[j] = arr[k];
        }
        arr[j] = tmp;
      }
    }
    

    任何数组类型的 C 代码:

    // circle shift an array left (towards index zero)
    // - ptr    array to shift
    // - n      number of elements
    // - es     size of elements in bytes
    // - shift  number of places to shift left
    void array_cycle_left(void *_ptr, size_t n, size_t es, size_t shift)
    {
      char *ptr = (char*)_ptr;
      if(n <= 1 || !shift) return; // cannot mod by zero
      shift = shift % n; // shift cannot be greater than n
    
      // Using GCD
      size_t i, j, k, gcd = calc_GCD(n, shift);
      char tmp[es];
    
      // i is initial starting position
      // Copy from k -> j, stop if k == i, since arr[i] already overwritten
      for(i = 0; i < gcd; i++) {
        memcpy(tmp, ptr+es*i, es); // tmp = arr[i]
        for(j = i; 1; j = k) {
          k = j+shift;
          if(k >= n) k -= n;
          if(k == i) break;
          memcpy(ptr+es*j, ptr+es*k, es); // arr[j] = arr[k];
        }
        memcpy(ptr+es*j, tmp, es); // arr[j] = tmp;
      }
    }
    
    // cycle right shifts away from zero
    void array_cycle_right(void *_ptr, size_t n, size_t es, size_t shift)
    {
      if(!n || !shift) return; // cannot mod by zero
      shift = shift % n; // shift cannot be greater than n
      // cycle right by `s` is equivalent to cycle left by `n - s`
      array_cycle_left(_ptr, n, es, n - shift);
    }
    
    // Get Greatest Common Divisor using binary GCD algorithm
    // http://en.wikipedia.org/wiki/Binary_GCD_algorithm
    unsigned int calc_GCD(unsigned int a, unsigned int b)
    {
      unsigned int shift, tmp;
    
      if(a == 0) return b;
      if(b == 0) return a;
    
      // Find power of two divisor
      for(shift = 0; ((a | b) & 1) == 0; shift++) { a >>= 1; b >>= 1; }
    
      // Remove remaining factors of two from a - they are not common
      while((a & 1) == 0) a >>= 1;
    
      do
      {
        // Remove remaining factors of two from b - they are not common
        while((b & 1) == 0) b >>= 1;
    
        if(a > b) { tmp = a; a = b; b = tmp; } // swap a,b
        b = b - a;
      }
      while(b != 0);
    
      return a << shift;
    }
    

    编辑:由于缓存局部性,该算法也可能比数组反转(当N 很大而M 很小)具有更好的性能,因为我们在小范围内循环数组步骤。

    最后说明:如果你的数组很小,三重反转很简单。如果您有一个大数组,那么计算 GCD 以将移动次数减少 2 倍是值得的。 参考:http://www.geeksforgeeks.org/array-rotation/

    【讨论】:

    • (看看Han's answer如何隐式处理。)
    • 这段代码比“韩的答案”简单得多,也更容易理解。但是,C 代码不是。只需坚持答案顶部的“示例代码”即可。计算 GCD 是一个递归单行:size_t gcd(size_t a, size_t b) {return b == 0 ? a : gcd(b, a % b);}
    • 在我刚刚进行的快速测试中,对于 1 的班次,此代码也快 10 倍,对于其他随机班次,至少快 3 倍。它减少了复制。
    • 另请参阅 my answer 以了解此解决方案的进一步直觉和理由。
    • 我认为 gcd 可以隐式计算为在第一个循环中达到的最小非零索引。这可能是灰胡子所指的。
    【解决方案3】:

    这只是表示的问题。将当前索引保留为整数变量,并在遍历数组时使用模运算符来知道何时回绕。然后,移位仅更改当前索引的值,将其包裹在数组的大小周围。这当然是 O(1)。

    例如:

    int index = 0;
    Array a = new Array[SIZE];
    
    get_next_element() {
        index = (index + 1) % SIZE; 
        return a[index];
    }
    
    shift(int how_many) {
        index = (index+how_many) % SIZE;
    }
    

    【讨论】:

    • 这可以写得更清楚一些。也许类似于“而不是更新数组,而是更新存储数组当前开始的整数”。此外,这种方法将 O(1) 操作 - push/pop - 转换为 O(n) 操作,因此存在明显的权衡。
    • 这是一个很棒的“真实世界”解决方案,我希望每个人都能达到。不过,我认为这个问题的含义是这是一个编程面试,你应该在适当的位置改变数组。
    【解决方案4】:

    用指针设置它,几乎不需要时间。每个元素都指向下一个,而“最后一个”(没有最后一个;毕竟,您说它是循环的)指向第一个。一个指向“开始”(第一个元素)的指针,也许还有一个长度,你就有了你的数组。现在,要进行轮班,您只需沿着圆圈移动起始指针即可。

    要求一个好的算法,你就会得到明智的想法。要求最快,你会得到奇怪的想法!

    【讨论】:

    • 但是你不会在遍历列表时每次都检查 end 吗?
    • 是的,但这很快。或者你可以使用模数(或者如果列表是 2 的幂,则按位与)。
    • 无论如何你都会检查 end ,即使是传统的数组。但如果你保留一个长度,它就像编写一个循环或将一个计数减到零一样简单。
    • 问题要求数组不是链表。
    【解决方案5】:

    该算法在 O(n) 时间和 O(1) 空间中运行。 这个想法是跟踪移位中的每个循环组(由nextGroup变量编号)。

    var shiftLeft = function(list, m) {
        var from = 0;
        var val = list[from];
        var nextGroup = 1;
        for(var i = 0; i < list.length; i++) {
            var to = ((from - m) + list.length) % list.length;
            if(to == from)
                break;
    
            var temp = list[to];
            list[to] = val;
            from = to;
            val = temp;
    
            if(from < nextGroup) {
                from = nextGroup++;
                val = list[from];
            }
        }
        return list;
    }
    

    【讨论】:

    • 不错的算法。但是发生了太多的复制:list[] -&gt; vallist[] -&gt; tmpval -&gt; list[]tmp -&gt; val。如果你颠倒移动东西的顺序,你可以将循环的第一个元素复制到val,然后将下一个元素向前复制(直接list[] -&gt; list[]),重复,直到你到达最后一个元素,你写的地方val。看到这个答案:stackoverflow.com/a/32698823/7328782
    【解决方案6】:
    def shift(nelements, k):       
        result = []
        length = len(nelements)
        start = (length - k) % length
        for i in range(length):
            result.append(nelements[(start + i) % length])
        return result
    

    此代码即使在负移位 k 上也能正常工作

    【讨论】:

      【解决方案7】:

      C arrayShiftRight 函数。如果 shift 为负,则函数将数组左移。 它针对更少的内存使用进行了优化。运行时间为 O(n)。

      void arrayShiftRight(int array[], int size, int shift) {
          int len;
      
          //cut extra shift
          shift %= size;
      
          //if shift is less then 0 - redirect shifting left
          if ( shift < 0 ) {
              shift += size;
          }
      
          len = size - shift;
      
          //choosing the algorithm which needs less memory
          if ( shift < len ) {
              //creating temporary array
              int tmpArray[shift];
      
              //filling tmp array
              for ( int i = 0, j = len; i < shift; i++, j++ ) {
                  tmpArray[i] = array[j];
              }
      
              //shifting array
              for ( int i = size - 1, j = i - shift; j >= 0; i--, j-- ) {
                  array[i] = array[j];
              }
      
              //inserting lost values from tmp array
              for ( int i = 0; i < shift; i++ ) {
                  array[i] = tmpArray[i];
              }
          } else {
              //creating temporary array
              int tmpArray[len];
      
              //filling tmp array
              for ( int i = 0; i < len; i++ ) {
                  tmpArray[i] = array[i];
              }
      
              //shifting array
              for ( int i = 0, j = len; j < size; i++, j++ ) {
                  array[i] = array[j];
              }
      
              //inserting lost values from tmp array
              for ( int i = shift, j = 0; i < size; i++, j++ ) {
                  array[i] = tmpArray[j];
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        一个非常简单的解决方案。这是一种非常快速的方法,这里我使用一个大小相同或原始的临时数组,并在最后附加到原始变量。 该方法使用O(n)时间复杂度和O(n)空间复杂度,实现起来非常简单。

        int[] a  = {1,2,3,4,5,6};
            int k = 2;
            int[] queries = {2,3};
        
            int[] temp = new int[a.length];
            for (int i = 0; i<a.length; i++)
                temp[(i+k)%a.length] = a[i];
        
            a = temp;
        

        【讨论】:

        • 临时数组是 O(n) 空间而不是 O(1)
        • 感谢您的建议。
        【解决方案9】:

        根据您使用的数据结构,您可以在 O(1) 中完成。我认为最快的方法是以链表的形式保存数组,并有一个哈希表可以在数组中的“索引”到条目的“指针”之间转换。这样你可以在 O(1) 中找到相关的头和尾,并在 O(1) 中进行重新连接(并在 O(1) 中切换后更新哈希表)。这当然是一个非常“混乱”的解决方案,但如果你感兴趣的只是移位的速度,那会做(以在数组中更长的插入和查找为代价,但它仍然会保持 O( 1))

        如果你有纯数组中的数据,我认为你不能避免 O(n)。

        编码方面,这取决于您使用的语言。

        例如,在 Python 中,您可以“切片”它(假设 n 是移位大小):

        result = original[-n:]+original[:-n]
        

        (我知道哈希查找在理论上不是 O(1),但我们在这里是实用的而不是理论上的,至少我希望如此......)

        【讨论】:

          【解决方案10】:

          这应该可以循环移动数组: 输入:{ 1, 2, 3, 5, 6, 7, 8 }; 输出值出现在 forloops 之后的数组中:{8,7,1,2,3,5,6,8,7}

           class Program
              {
                  static void Main(string[] args)
                  {
                      int[] array = { 1, 2, 3, 5, 6, 7, 8 };
                      int index = 2;
                      int[] tempArray = new int[array.Length];
                      array.CopyTo(tempArray, 0);
          
                      for (int i = 0; i < array.Length - index; i++)
                      {
                          array[index + i] = tempArray[i];
                      }
          
                      for (int i = 0; i < index; i++)
                      {
                          array[i] = tempArray[array.Length -1 - i];
                      }            
                  }
              }
          

          【讨论】:

          • @JerryPenner 解决方案不使用 O(n) 额外内存
          【解决方案11】:

          这里有一个简单高效的通用C++就地旋转函数,不到10行。

          摘自我对另一个问题的回答。 How to rotate an array?

          #include <iostream>
          #include <vector>
          
          // same logic with STL implementation, but simpler, since no return value needed.
          template <typename Iterator>
          void rotate_by_gcd_like_swap(Iterator first, Iterator mid, Iterator last) {
              if (first == mid) return;
              Iterator old = mid;
              for (; mid != last;) {
                  std::iter_swap(first, mid);
                  ++first, ++mid;
                  if (first == old) old = mid; // left half exhausted
                  else if (mid == last) mid = old;
              }
          }
          
          int main() {
              using std::cout;
              std::vector<int> v {0,1,2,3,4,5,6,7,8,9};
              cout << "before rotate: ";
              for (auto x: v) cout << x << ' '; cout << '\n';
              int k = 7;
              rotate_by_gcd_like_swap(v.begin(), v.begin() + k, v.end());
              cout << " after rotate: ";
              for (auto x: v) cout << x << ' '; cout << '\n';
              cout << "sz = " << v.size() << ", k = " << k << '\n';
          }
          

          【讨论】:

            【解决方案12】:

            为数组保留两个索引,一个索引从数组的开头到数组的结尾。另一个索引从 last 的第 M 个位置开始,并循环遍历最后的 M 个元素任意次数。在任何时候都需要 O(n)。不需要额外的空间。

            circleArray(Elements,M){
             int size=size-of(Elements);
            
             //first index
             int i1=0;
            
             assert(size>M)
            
             //second index starting from mth position from the last
             int i2=size-M;
            
             //until first index reaches the end
             while(i1<size-1){
            
              //swap the elements of the array pointed by both indexes
              swap(i1,i2,Elements);
            
              //increment first pointer by 1
              i1++;
            
              //increment second pointer. if it goes out of array, come back to
              //mth position from the last
              if(++i2==size) i2=size-M;
            
             }
            }
            

            【讨论】:

            • 您的实现中存在错误!看我上面的帖子!
            【解决方案13】:

            如果您对 Java 实现感兴趣,请查看此内容:

            Programming Pearls: Circular Left/Right Shift Operation

            【讨论】:

              【解决方案14】:
              static int [] shift(int arr[], int index, int k, int rem)
              {
                  if(k <= 0 || arr == null || arr.length == 0 || rem == 0 || index >= arr.length)
                  {
                      return arr;
                  }
              
                  int temp = arr[index];
              
                  arr = shift(arr, (index+k) % arr.length, k, rem - 1);
              
                  arr[(index+k) % arr.length] = temp;
              
                  return arr;
              }
              

              【讨论】:

                【解决方案15】:

                Ruby 示例:

                def move_cyclic2 array, move_cnt
                  move_cnt = array.length - move_cnt % array.length 
                  if !(move_cnt == 0 || move_cnt == array.length)            
                    array.replace( array[move_cnt..-1] + array[0...move_cnt] )  
                  end   
                end
                

                【讨论】:

                  【解决方案16】:

                  理论上最快的循环是这样的:

                  if (begin != middle && middle != end)
                  {
                      for (i = middle; ; )
                      {
                          swap(arr[begin++], arr[i++]);
                          if (begin == middle && i == end) { break; }
                          if (begin == middle) { middle = i; }
                          else if (i == end) { i = middle; }
                      }
                  }
                  

                  在实践中,您应该对其进行剖析并查看。

                  【讨论】:

                    【解决方案17】:

                    这是另一个(C++):

                    void shift_vec(vector<int>& v, size_t a)
                    {
                        size_t max_s = v.size() / a;
                        for( size_t s = 1; s < max_s; ++s )
                            for( size_t i = 0; i < a; ++i )
                                swap( v[i], v[s*a+i] );
                        for( size_t i = 0; i < a; ++i )
                            swap( v[i], v[(max_s*a+i) % v.size()] );
                    }
                    

                    当然它不如著名的反向三倍解决方案那么优雅,但根据机器的不同,它可以是similary fast

                    【讨论】:

                      【解决方案18】:

                      circleArray 有一些错误,并非在所有情况下都有效!

                      循环必须继续 while i1 &lt; i2 而不是 i1 &lt; last - 1

                      void Shift(int* _array, int _size, int _moves)
                      {
                          _moves = _size - _moves;
                          int i2 = _moves;
                               int i1 = -1;
                               while(++i1 < i2)
                          {
                              int tmp = _array[i2];
                              _array[i2] = _array[i1];
                              _array[i1] = tmp;
                              if(++i2 == _size) i2 = _moves;
                          }
                      }
                      

                      【讨论】:

                      • 此代码仅适用于特定情况,因此不好。例如,如果您有 5, 9, 4, 0, 11 并将其移动 3,它将为您提供 4, 0, 11, 9, 5 而不是 4, 0, 11, 5, 9
                      【解决方案19】:

                      我的一个朋友在开玩笑时问我如何移动数组,我想出了这个解决方案(见 ideone 链接),现在我看到了你的,有人似乎有点深奥。

                      看看here

                      #include <iostream>
                      
                      #include <assert.h>
                      
                      #include <cstring>
                      
                      using namespace std;
                      
                      struct VeryElaboratedDataType
                      {
                          int a;
                          int b;
                      };
                      
                      namespace amsoft
                      {
                          namespace inutils
                          {
                              enum EShiftDirection
                              {
                                  Left,
                                  Right
                              };
                      template 
                      <typename T,size_t len>
                      void infernalShift(T infernalArray[],int positions,EShiftDirection direction = EShiftDirection::Right)
                      {
                          //assert the dudes
                          assert(len > 0 && "what dude?");
                          assert(positions >= 0 && "what dude?");
                      
                          if(positions > 0)
                          {
                          ++positions;
                          //let's make it fit the range
                          positions %= len;
                      
                          //if y want to live as a forcio, i'l get y change direction by force
                          if(!direction)
                          {
                              positions = len - positions;
                          }
                      
                          // here I prepare a fine block of raw memory... allocate once per thread
                          static unsigned char WORK_BUFFER[len * sizeof(T)];
                          // std::memset (WORK_BUFFER,0,len * sizeof(T));
                          // clean or not clean?, well
                          // Hamlet is a prince, a prince does not clean
                      
                          //copy the first chunk of data to the 0 position
                          std::memcpy(WORK_BUFFER,reinterpret_cast<unsigned char *>(infernalArray) + (positions)*sizeof(T),(len - positions)*sizeof(T));
                          //copy the second chunk of data to the len - positions position
                          std::memcpy(WORK_BUFFER+(len - positions)*sizeof(T),reinterpret_cast<unsigned char *>(infernalArray),positions * sizeof(T));
                      
                          //now bulk copy back to original one
                          std::memcpy(reinterpret_cast<unsigned char *>(infernalArray),WORK_BUFFER,len * sizeof(T));
                      
                          }
                      
                      }
                      template 
                      <typename T>
                      void printArray(T infernalArrayPrintable[],int len)
                      {
                              for(int i=0;i<len;i++)
                          {
                              std::cout << infernalArrayPrintable[i] << " ";
                          }
                          std::cout << std::endl;
                      
                      }
                      template 
                      <>
                      void printArray(VeryElaboratedDataType infernalArrayPrintable[],int len)
                      {
                              for(int i=0;i<len;i++)
                          {
                              std::cout << infernalArrayPrintable[i].a << "," << infernalArrayPrintable[i].b << " ";
                          }
                          std::cout << std::endl;
                      
                      }
                      }
                      }
                      
                      
                      
                      
                      int main() {
                          // your code goes here
                          int myInfernalArray[] = {1,2,3,4,5,6,7,8,9};
                      
                          VeryElaboratedDataType myInfernalArrayV[] = {{1,1},{2,2},{3,3},{4,4},{5,5},{6,6},{7,7},{8,8},{9,9}};
                          amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
                          amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4);
                          amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
                          amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,4,amsoft::inutils::EShiftDirection::Left);
                          amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
                          amsoft::inutils::infernalShift<int,sizeof(myInfernalArray)/sizeof(int)>(myInfernalArray,10);
                          amsoft::inutils::printArray(myInfernalArray,sizeof(myInfernalArray)/sizeof(int));
                      
                      
                          amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
                          amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4);
                          amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
                          amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,4,amsoft::inutils::EShiftDirection::Left);
                          amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
                          amsoft::inutils::infernalShift<VeryElaboratedDataType,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType)>(myInfernalArrayV,10);
                          amsoft::inutils::printArray(myInfernalArrayV,sizeof(myInfernalArrayV)/sizeof(VeryElaboratedDataType));
                      
                          return 0;
                      }
                      

                      【讨论】:

                      • (即使是在设计infernal,也要考虑如何与天使交谈。我可以推荐一个用于涉及计算机的文本通信的拼写检查器吗?)
                      • 如果您在网络浏览器中输入公开文本的频率超过一年一次,请使用插件或添加您最喜欢的浏览器。警惕 internetexplorer 或有毒的重金属,我经常使用 firefox - 它提供从新西兰到加拿大和返回的英语风味。 (我没有想出一个使用黑名单来避免的单词/短语的检查器。)
                      • 感谢老兄的建议。爆炸机?只是有时,对我的健康没有好处。我将尝试使用 chrome 在语法上(只是为了向全球大市场添加一些个人数据)。如果你制作黑名单会很有帮助。取决于时间?如果我几个小时不睡觉,我真的不知道我能写什么
                      【解决方案20】:

                      这个方法可以完成这项工作:

                      public static int[] solution1(int[] A, int K) {
                          int temp[] = new int[A.length];
                      
                          int count = 0;
                      
                          int orignalItration = (K < A.length) ? K :(K%A.length); 
                      
                      
                          for (int i = orignalItration; i < A.length; i++) {
                              temp[i] = A[count++];
                          }
                          for (int i = 0; i < orignalItration; i++) {
                              temp[i] = A[count++];
                          }
                      
                          return temp;
                      }
                      

                      【讨论】:

                        【解决方案21】:

                        与@IsaacTurner 类似,但由于不必要的复制而不那么优雅,但实现很短。

                        这个想法 - 将索引 0 上的元素 A 与位于 A 目的地的元素 B 交换。现在 B 是第一个。将其与位于 B 的目的地的元素 C 交换。继续直到目的地不为 0。

                        如果最大公约数不是 1,那么您还没有完成 - 您需要继续交换,但现在在起点和终点使用索引 1。

                        继续直到你的起始位置不是 gcd。

                        int gcd(int a, int b) => b == 0 ? a : gcd(b, a % b);
                        
                        public int[] solution(int[] A, int K)
                        {
                            for (var i = 0; i < gcd(A.Length, K); i++)
                            {
                                for (var j = i; j < A.Length - 1; j++)
                                {
                                    var destIndex = ((j-i) * K + K + i) % A.Length;
                                    if (destIndex == i) break;
                                    var destValue = A[destIndex];
                                    A[destIndex] = A[i];
                                    A[i] = destValue;
                                }
                            }
                        
                            return A;
                        }
                        

                        【讨论】:

                          【解决方案22】:

                          这是我的 Java 解决方案,它让我在 Codility 获得了 100% 的任务分数和 100% 的正确性:

                          class Solution {
                              public int[] solution(int[] A, int K) {
                                  // write your code in Java SE 8
                                  if (A.length > 0)
                                  {
                                      int[] arr = new int[A.length];
                                      if (K > A.length)
                                          K = K % A.length;
                          
                                      for (int i=0; i<A.length-K; i++)
                                          arr[i+K] = A[i];
                          
                                      for (int j=A.length-K; j<A.length; j++)
                                          arr[j-(A.length-K)] = A[j];
                          
                                      return arr;
                                  }
                                  else
                                      return new int[0];
                              }
                          }
                          

                          请注意,尽管看到了两个 for 循环,但整个数组的迭代只进行了一次。

                          【讨论】:

                            【解决方案23】:

                            用于左移数组的 Swift 4 版本。

                            func rotLeft(a: [Int], d: Int) -> [Int] {
                            
                               var result = a
                               func reverse(start: Int, end: Int) {
                                  var start = start
                                  var end = end
                                  while start < end {
                                     result.swapAt(start, end)
                                     start += 1
                                     end -= 1
                                  }
                               }
                            
                               let lenght = a.count
                               reverse(start: 0, end: lenght - 1)
                               reverse(start: lenght - d, end: lenght - 1)
                               reverse(start: 0, end: lenght - d - 1)
                               return result
                            }
                            

                            例如输入数组为a = [1, 2, 3, 4, 5],左移偏移为d = 4,则结果为[5, 1, 2, 3, 4]

                            【讨论】:

                              【解决方案24】:

                              @IsaacTurner 的回答 (C) https://stackoverflow.com/a/32698823/4386969

                              和@SomeStrangeUser 的答案(Java): https://stackoverflow.com/a/18154984/4386969

                              提供一个简单的 O(N) 时间、O(1) 空间算法,该算法可以回答问题并且需要准确地分配 N 个元素。我相信(如果我错了,有人纠正我)计算 N 和 M 之间的 gcd 是不必要的;简单地计算我们放置在正确位置的元素数量就足够了。这是因为一旦我们将一个元素放在正确的位置,我们就可以保证在当前周期和后续周期中都不必再次访问它。

                              这是一个经过额外简化的 Python 3 实现:

                              # circle shift an array to the left by M 
                              def arrayCircleLeftShift(a, M):
                                  N = len(a)
                                  numAccessed = 0
                                  cycleIdx = 0
                                  while numAccessed != N:
                                      idx = cycleIdx
                                      swapIdx = (idx + M) % N
                                      tmp = a[idx]
                                      while swapIdx != cycleIdx:
                                          a[idx] = a[swapIdx]
                                          numAccessed += 1
                                          idx = swapIdx
                                          swapIdx = (idx + M) % N
                                      a[idx] = tmp
                                      numAccessed += 1
                                      cycleIdx += 1
                              

                              【讨论】:

                                【解决方案25】:

                                我知道这是一篇旧文章,但是这里有一个 O(n) 中的最佳解决方案:每个元素只移动一次,不需要额外的空间。它与 Isaac Turner 提出的解决方案非常相似,但不需要 gcd 计算。

                                public static void shiftArray(int[] A, int k) {
                                    if (A.length == 0) {
                                        return;
                                    }
                                    k = k % A.length;
                                    k = (k + A.length) % A.length; // ensure k is positive
                                    if (k == 0) {
                                        return;
                                    }
                                    int i = 0, i0 = 0;
                                    int x = A[0];
                                    for (int u = 0; u < A.length; u++) { // count number of shifted elements
                                        int j = (i - k + A.length) % A.length; // ensure modulo is positive
                                        if (j == i0) { // end of a (sub-)cycle, advance to next one
                                            A[i] = x;
                                            x = A[i = ++i0];
                                        } else {
                                            A[i] = A[j];
                                            i = j;
                                        }
                                    }
                                }
                                

                                【讨论】:

                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2023-02-10
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2016-08-06
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多