面试题系列

  1. 经典面试题 之 单链表找环
  2. 经典面试题 之 数组的循环右移
  3. 经典面试题 之 洗牌问题
  4. 经典面试题 之 在单调矩阵中查找元素
  5. 经典面试题 之 按单词逆序
  6. 经典面试题 之 查字典

题目的大意是将一个长度为n的数组A内的元素循环右移m位(当然左移也可以),比如数组 {1, 2, 3, 4, 5}右移3位之后就变成{3, 4, 5, 1, 2}。

这题最平凡的做法是开另一个大小一样的数组B,遍历一下,令B[(i + m) % n] = A[i],再将B的内容写回到A即可。这个方法的时间复杂度为O(N),空间复杂度也为O(N)。

很明显,需要优化空间的使用。有一种很优美但不太好懂的方法,是先将A的元素倒置,即{1, 2, 3, 4, 5}变成{5, 4, 3, 2, 1},然后将前m位倒置,即{3, 4, 5, 2, 1},再将后n-m位倒置,即{3, 4, 5, 1, 2}。完成。证明略,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void Reverse(int n, int *a){
    for (int i = 0; i < n / 2; ++i){
        int t = a[i];
        a[i] = a[n - i - 1];
        a[n - i - 1] = t;
    }
}

void ShiftRight1(int m, int n, int *a){
    m%=n;
    if (m == 0) return;

    Reverse(n, a);
    Reverse(m, a);
    Reverse(n - m, a + m);
}

不过这种方法需要对每个位置写入2次,看上去也不怎么好,那有没有更好的呢?

我们要做的只是把每个元素放到它应该在的位置,比如开头的例子,1应该放在4的位置,把1放好之后,4就没地方了,那4应该在哪呢,在2的位置,以此类推,就可以把所有的元素都放好,而且只放了一次。看上去这样做很完美,但仔细想想就能想出反例子,比如{1, 2, 3, 4, 5, 6, 7, 8, 9}右移3位,就是1放在4个位置,4放在7的位置,然后7放回1,这时候一圈兜完了,但只排好了3个元素,剩下的6个元素没有动过,怎么办呢?继续下个呗,就是2,然后2、5、8也排好了,继续3、6、9,这时候下一个元素是1了(因为1之前就被放在了4的位置),应该停止了,那程序怎么会知道停在这里了,于是就想到了最大公约数,9和3的最大公约数是3,于是做前3个数的循环就可以了,为什么上一个例子只需做一次,因为元素个数(5)和移动位数(3)互质。

具体的数学证明略,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int gcd(int a, int b){
    if (a == 0 || b == 0){
        return a + b;
    }

    int t;
    while(b > 0){
        t = a % b;
        a = b;
        b = t;
    }

    return a;
}

void ShiftRight2(int m, int n, int *a){
    m%=n;
    if (m == 0) return;

    for (int i = 0, _i = gcd(n, m); i < _i; ++i){
        int k = i;
        int t = a[k];
        do{
            k = (k + m) % n;
            int tt = a[k];
            a[k] = t;
            t = tt;
        }
        while(k != i);
    }
}

以下是测试数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void RunTest(int n, int m){
    int *a = new int[n];
    int *b = new int[n];
    int *c = new int[n];
    for (int i = 0; i < n; ++i){
        a[i] = i;
        b[i] = i;
        c[i] = i;
    }
    ShiftRight1(m, n, b);
    ShiftRight2(m, n, c);

    bool f = true;
    for (int i = 0; i < n; ++i){
        if (b[i] != c[i]){
            f = false;
            break;
        }
    }

    if (!f){
        cout << "A: ";
        for (int i = 0; i < n; ++i){
            cout << a[i] << " ";
        }
        cout << endl;
        cout << "B: ";
        for (int i = 0; i < n; ++i){
            cout << b[i] << " ";
        }
        cout << endl;
        cout << "C: ";
        for (int i = 0; i < n; ++i){
            cout << c[i] << " ";
        }
        cout << endl;

        cout << endl;
    }

    delete[]a;
    delete[]b;
    delete[]c;
}

int main(){
    for (int i = 1; i < 1000; ++i){
        for (int j = 1; j <= i; ++j){
            RunTest(i, j);
        }
    }

    return 0;
}

您还可能感兴趣的日志:

  1. 经典面试题 之 单链表找环
  2. 经典面试题 之 数组的循环右移
  3. 经典面试题 之 洗牌问题
  4. 经典面试题 之 按单词逆序
  5. 经典面试题 之 在单调矩阵中查找元素
  6. unordered_map
  7. C#中重载相等运算符
  8. 在C语言中使用MSXML
  9. 检测本机安装的MSXML的版本的方法
  10. Hyper-V的自动化 (0) 远程连接

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

   
© 2004 - 2011 Leona+Suffusion theme by Sayontan Sinha