使用O(n)算法旋转整数数组


10

编写一个将整数数组旋转给定数k的函数。从结尾开始的k个元素应移到数组的开头,所有其他元素应向右移以留出空间。

旋转应就地完成。

算法的运行时间不能超过O(n),其中n是数组的大小。

此外,必须使用恒定内存来执行该操作。

例如,

如果数组使用元素arr = {1、2、3、4、5、6、7、8、9}初始化

rotation(arr,3)将导致元素为{7,8,9,1,2,2,3,4,5,6}

rotation(arr,6)将得到{4,5,6,7,8,9,1,1,2,3}


1
这里的恒定内存是什么意思?当然,为了存储正在处理的阵列,至少需要至少O(n)内存才能使O(1)内存无法使用。
Ad Hoc Garf Hunter

2
我投票结束这个问题是离题的,因为没有客观主要获胜标准的问题是离题的,因为它们无可争辩地决定哪个入围者应该赢。绝对没有理由应该举行人气竞赛。
詹姆斯

投票关闭。从普及竞赛 Wiki(此处)中,“给予参与者自由决定在关键部分做什么的动机,并激励他们使用这种自由。” 我不认为对任何算法开放挑战都不能鼓励创造力来应对如此简单的挑战,至少在某种程度上不能像popcon那样发挥作用。这将更适合作为代码高尔夫球挑战。
mbomb007 '17

Answers:


18

C(104)

void reverse(int* a, int* b)
{
    while (--b > a) {
        *b ^= *a;
        *a ^= *b;
        *b ^= *a;
        ++a;
    }
}

void rotate(int *arr, int s_arr, int by)
{
    reverse(arr, arr+s_arr);
    reverse(arr, arr+by);
    reverse(arr+by, arr+s_arr);
}

缩小:

v(int*a,int*b){while(--b>a){*b^=*a;*a^=*b;*b^=*a++;}}r(int*a,int s,int y){v(a,a+s);v(a,a+y);v(a+y,a+s);}

4
您应该将while循环条件写为a <-- b
justhalf 2014年

曾经有一段时间,C程序赢得了人气竞赛……
Anubian Noob

你是最棒的!多么优雅和优化。您能用位阵列做到这一点吗?

9

杀伤人员地雷(4)

¯A⌽B
  • A是旋转的位数
  • B是要旋转的数组的名称

我不确定APL是否真正需要它,但是在实现中,我已经看到(内部)这将花费与时间成正比的时间A,并且需要恒定的内存。


如果是高尔夫,则+1 :)
Glenn Teitelbaum 2014年

但是它并没有就位。
marinus 2014年

@marinus:我见过的实现中肯定有。
杰里·科芬

这个功能如何?可能是{⍵⌽⍨-⍺}{⌽⍺⌽⌽⍵}。在NARS2000中,它可能优雅地写为⌽⍢⌽
2015年

5

这是Colin想法的C语言版本。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int gcd(int a, int b) {
  int t;
  if (a < b) {
    t = b; b = a; a = t;
  }
  while (b != 0) {
    t = a%b;
    a = b;
    b = t;
  }
  return a;
}

double arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
int s_arr = sizeof(arr)/sizeof(double);

/* We assume 1 <= by < s_arr */
void rotate(double *arr, int s_arr, int by) {
  int i, j, f;
  int g = gcd(s_arr,by);
  int n = s_arr/g;
  double t_in, t_out;

  for (i=0; i<g; i++) {
    f = i;
    t_in = arr[f + s_arr - by];
    for (j=0; j<n; j++) {
      t_out = arr[f];
      arr[f] = t_in;
      f = (f + by) % s_arr;
      t_in = t_out;
    }
  }
}

void print_arr(double *arr, int s_arr) {
  int i;
  for (i=0; i<s_arr; i++) printf("%g ",arr[i]);
  puts("");
}

int main() {
  double *temp_arr = malloc(sizeof(arr));
  int i;

  for (i=1; i<s_arr; i++) {
    memcpy(temp_arr, arr, sizeof(arr));
    rotate(temp_arr, s_arr, i);
    print_arr(temp_arr, s_arr);
  }
}

它看起来不像是恒定的内存解决方案,对吗?
microbian 2014年

是的,这是一个恒定的内存解决方案。“已分配”的东西是数组的临时副本,因此我可以一次又一次地将原始数据复制到其中,以便可以测试不同的旋转量。
斯蒂芬·蒙哥马利史密斯

实际旋转的是函数“旋转”。它使用5个整数和两个双精度数。它还调用了一个函数“ gcd”,该函数使用一个整数,最多使用O(log(n))个操作。
斯蒂芬·蒙哥马利·史密斯

得到它了。我提高了你的答案。
microbian 2014年

@ StephenMontgomery-Smith- O(log(n))操作如何 看by为1,您的“ j”循环是s_arr / g或N-这是O(N)运算
Glenn Teitelbaum 2014年

3

C

不确定条件是什么,但是由于我对算法很感兴趣,因此请输入以下内容:

void rotate(int* b, int size, int shift)
{
    int *done;
    int *p;
    int i;
    int saved;
    int c;

    p = b;
    done = p;
    saved = *p;
    for (i = 0; i < size; ++i) {
        c = saved;
        p += shift;
        if (p >= b+size) p -= size;
        saved = *p;
        *p = c;
        if (p == done) {
            p += 1;
            done = p;
            saved = *p;
        }
    }
}

我也要打高尔夫。126个字节,可以缩短:

void r(int*b,int s,int n){int*d,*p,i,t,c;d=p=b;t=*p;for(i=0;i<s;++i){c=t;p+=n;if(p>=b+s)p-=s;t=*p;*p=c;if(p==d){d=++p;t=*p;}}}

3

我在这里看不到太多的C ++解决方案,所以我想尝试这种方法,因为它不计算字符数。

这是真正的“就地”旋转,因此使用0的额外空间(从技术上来讲,交换和3个整数除外),并且由于循环正好是N,因此也满足了O(N)的复杂性。

template <class T, size_t N>
void rot(std::array<T,N>& x, int shift)
{
        size_t base=0;
        size_t cur=0; 
        for (int i = 0; i < N; ++i)
        {
                cur=(cur+shift)%N; // figure out where we are going
                if (cur==base)     // exact multiple so we have to hit the mods when we wrap
                {
                        cur++;
                        base++;
                }
                std::swap(x.at(base), x.at(cur)); // use x[base] as holding area
        }
}

注意:我故意不使用std::rotate是因为这样会破坏目的
Glenn Teitelbaum 2014年

2

如果依次轮流执行每个可能的旋转周期n(其中有GCD(n,len(arr))个),则只需要一个数组元素的单个临时副本和几个状态变量。像这样,在Python中:

from fractions import gcd

def rotate(arr, n):
    total = len(arr)
    cycles = gcd(n, total)
    for start in range(0, cycles):
        cycle = [i % total for i in range(start, abs(n * total) / cycles, n)]
        stash = arr[cycle[-1]]
        for j in reversed(range(1, len(cycle))):
            arr[cycle[j]] = arr[cycle[j - 1]]
        arr[cycle[0]] = stash

1
我认为您的想法正确,但是cycle变量的大小不是固定的。您必须随手生成此数组。
基思·兰德尔

2

C(137个字符)

#include <stdio.h>

void rotate(int * array, int n, int k) {
    int todo = (1<<n+1)-1;
    int i = 0, j;
    int tmp = array[0];

    while (todo) {
        if (todo & 1<<i) {
            j = (i-k+n)%n;
            array[i] = todo & 1<<j ? array[j] : tmp;
            todo -= 1<<i;
            i = j;
        } else tmp = array[++i];
    }
}

int main() {
    int a[] = {1,2,3,4,5,6,7,8,9};
    rotate(a, 9, 4);
    for (int i=0; i<9;i++) printf("%d ", a[i]);
    printf("\n");
}

函数rotate缩减为137个字符:

void r(int*a,int n,int k){int m=(1<<n+1)-1,i=0,j,t=a[0];while(m)if(m&1<<i){j=(i-k+n)%n;a[i]=(m&1<<j)?a[j]:t;m-=1<<i;i=j;}else t=a[++i];}

2

Factor具有可旋转数组的内置类型<circular>,因此实际上是O(1)操作:

: rotate ( circ n -- )
    neg swap change-circular-start ;

IN: 1 9 [a,b] <circular> dup 6 rotate >array .
{ 4 5 6 7 8 9 1 2 3 }
IN: 1 9 [a,b] <circular> dup 3 rotate >array .
{ 7 8 9 1 2 3 4 5 6 }

与Ben Voigt令人印象深刻的C解决方案相比,没有那么狡猾的因素:

: rotate ( n s -- ) 
    reverse! swap cut-slice [ reverse! ] bi@ 2drop ;

IN: 7 V{ 0 1 2 3 4 5 6 7 8 9 } [ rotate ] keep .
V{ 3 4 5 6 7 8 9 0 1 2 }

2

JavaScript的45

还是去高尔夫,因为我喜欢高尔夫。只要t<=数组大小,它最大为O(N)。

function r(o,t){for(;t--;)o.unshift(o.pop())}

要处理tO(N)中的任何比例,可以使用以下内容(称重58个字符):

function r(o,t){for(i=t%o.length;i--;)o.unshift(o.pop())}

不返回,就地编辑数组。


1
+1r(o,t) => rot
Conor O'Brien

1

叛军 -22

/_(( \d+)+)( \d+)/$3$1

输入:k用_一个数字表示为一元整数,后跟一个空格,然后是一个以空格分隔的整数数组。

输出:一个空格,然后旋转数组。

例:

___ 1 2 3 4 5/_(( \d+)+)( \d+)/$3$1

最终状态:

 3 4 5 1 2

说明:

在每次迭代中,它取代一个_和阵列[array] + tailtail + [array]

例:

___ 1 2 3 4 5
__ 5 1 2 3 4
_ 4 5 1 2 3
 3 4 5 1 2

我不认为这是O(n)。复制数组是O(n),然后执行n一次。
Ben Voigt 2014年

1

爪哇

public static void rotate(int[] arr, int by) {
    int n = arr.length;
    int i = 0;
    int j = 0;
    while (i < n) {
        int k = j;
        int value = arr[k];
        do {
            k = (k + by) % n;
            int tmp = arr[k];
            arr[k] = value;
            value = tmp;
            i++;
        } while (k != j);
        j++;
    }
}

演示在这里

精简Java脚本114

function rotate(e,r){n=e.length;i=0;j=0;while(i<n){k=j;v=e[k];do{k=(k+r)%n;t=e[k];e[k]=v;v=t;i++}while(k!=j);j++}}

1

哈斯克尔

这实际上是θ(n),因为分割为θ(k),连接为θ(nk)。虽然不确定内存。

rotate 0 xs = xs
rotate n xs | n >= length xs = rotate (n`mod`(length xs)) xs
            | otherwise = rotate' n xs

rotate' n xs = let (xh,xt) = splitAt n xs in xt++xh

1

Python 3

from fractions import gcd
def rotatelist(arr, m):
    n = len(arr)
    m = (-m) % n # Delete this line to change rotation direction
    for i0 in range(gcd(m, n)):
        temp = arr[i0]
        i, j = i0, (i0 + m) % n
        while j != i0:
            arr[i] = arr[j]
            i, j = j, (j + m) % n
        arr[i] = temp

恒定内存
O(n)时间复杂度



0

蟒蛇

   import copy
    def rotate(a, r):
        c=copy.copy(a);b=[]
        for i in range(len(a)-r):   b.append(a[r+i]);c.pop();return b+c

复制数组不是恒定的空间。@MadisonMay的答案与该代码的作用基本相同,但字符少得多。
Blckknght 2014年

0

vb.net O(n)(不是常量内存)

Function Rotate(Of T)(a() As T, r As Integer ) As T()     
  Dim p = a.Length-r
  Return a.Skip(p).Concat(a.Take(p)).ToArray
End Function

0

红宝石

def rotate(arr, n)
  arr.tap{ (n % arr.size).times { arr.unshift(arr.pop) } }  
end

0

C(118)

某些规范可能太宽容了。使用与内存成正比的内存shift % length。如果传递了负位移值,则也可以沿相反方向旋转。

r(int *a,int l,int s){s=s%l<0?s%l+l:s%l;int *t=malloc(4*s);memcpy(t,a+l-s,4*s);memcpy(a+s,a,4*(l-s));memcpy(a,t,4*s);}

0

Python 2,57

def rotate(l,n):
 return l[len(l)-n:len(l)]+l[0:len(l)-n]

如果只是l[-n:len(l)-n]像我期望的那样工作。它只是[]出于某种原因而返回。


0
def r(a,n): return a[n:]+a[:n]

有人可以检查一下是否确实符合要求吗?我认为可以,但是还没有研究CS。


0

C ++,136

template<int N>void rotate(int(&a)[N],int k){auto r=[](int*b,int*e){for(int t;--e>b;t=*b,*b++=*e,*e=t);};r(a,a+k);r(a+k,a+N);r(a,a+N);}

0

爪哇

将最后k个元素与前k个元素交换,然后将其余元素旋转k个。如果最后剩余的元素少于k个,则将它们旋转剩余元素的k%数量。我认为以上没有人采用这种方法。对每个元素仅执行一个交换操作,将所有操作都放在适当的位置。

public void rotate(int[] nums, int k) {
    k = k % nums.length; // If k > n, reformulate
    rotate(nums, 0, k);
}

private void rotate(int[] nums, int start, int k) {
    if (k > 0) {
        if (nums.length - start > k) { 
            for (int i = 0; i < k; i++) {
                int end = nums.length - k + i;
                int temp = nums[start + i];
                nums[start + i] = nums[end];
                nums[end] = temp;
            }
            rotate(nums, start + k, k); 
        } else {
            rotate(nums, start, k % (nums.length - start)); 
        }
    }
}

0

Perl 5,42个字节

sub r{$a=pop;map{unshift@$a,pop@$a}1..pop}

在线尝试!

子例程将旋转距离作为第一个参数,将对数组的引用作为第二个参数。运行时间基于旋转距离是恒定的。数组大小不影响运行时间。通过从右侧删除元素并将其放在左侧来对数组进行适当的修改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.