一个算法面试题 & 面试题库
一个面试题,号称是微软的
输入
,如何在O(n)的时间,用O(1)的空间,将这个序列顺序改为
。
刚一眼看上去觉得很容易,做了一回儿才发现深不可测。题目大致是要求在线性时间,常数空间实现下面的置换
x -> 2x mod 2n+1
我做了两小时没做出来,上网一搜,最近这个题目很热,已经有人在讨论这个题目,还翻出了问题的发源地http://www.cs.uvic.ca/~jellis/perfect.html,一个完美洗牌的构造问题。此网页上一篇长达12页的论文Computing the Cycles in the Perfect Shuffle Permutation给出了完整的算法和证明。
不知道有没有人给出直接而简便的方法?
在搜索过程中发现两个很好的程序设计类面试题库(英文),共享一下,如果你想面试Microsoft,Google或者Goldman Sachs,看看这两个网站上的题目就可以了。
- techInterview Discussion
- CareerCup:网站似乎被G-F-W了,幸好RSS还能用。
,如何在O(n)的时间,用O(1)的空间,将这个序列顺序改为
。
我在Google 遇到类似的题目:
用最小的内存做下题
假设p(k) 可以将 k=1..n 映射成1..n 的一个排列, 如何将一维数组(x1, x2,..., xn) 排成 (x_p(1), x_p(2), ... , x_p(n))
其实,此问题被称为 In Situ Permutation (Knuth, 1972) TAOCP 有比较详细的讲解,大约就是说这些置换可以成环,关键是找到置换群的生成元。 知道了这个,我当时很幸运的秒杀了这个题目 ...
你那个问题不要求运算时间么?不要求运算时间的话,都可以在O(1)空间内搞定。
文章中的那个问题,似乎不是能够秒杀的。分治法可以在O(nlogn)时间,O(1)空间搞定,但线性时间就不容易了。你有什么好方法么?
该置换问题的最坏情况下时间是O(nlogn),呵呵。
比如20个的情况:
1 11 2 12 3 13 4 14 5 15 6 16 7 17 8 18 9 19 10 20
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
第1个是不需要动的;
第2个需要和2对应的置换即第11个位置所在元素恰好是11对换,换完之后数组有变化了;
第3个需要和3所对应的2对换,找法是先找到3对应的置换2,在位置2寻找,而位置2已经是11了,不是所需要的,再在位置11寻找,刚好是2,对换之;
...
这就是Knuth给出的方法:)写成程序稍微麻烦点,呵呵,用白话描述一下更好懂。
对于这种奇偶的问题,有其特殊性,应该可以在O(n)时间内完成,有空可以看看Sedgewick的Analysis of algorithms,里面有in situ permutation的一节,他证明了平均情况下是n logn。
它的时间依赖于该置换中的cycles数,而你给的那个论文就是计算它的,呵呵:)发在IPL上,档次不低。
回头有时间再想想......
你说的20个的情况,是上一行变成下一行还是下一行变成上一行?看着犯晕了~~
该置换问题的最坏情况下时间是O(nlogn)?错了。只需要O(n).
那篇paper 我细心读了一下,他无非就是使用了超级复杂的数论定理想办法把每个循环群的生成元挑出了一个。只能听他说的有道理,我们这些凡人估计是想不出来的了⋯⋯
PS: 论文中Section 3 第 (5) 式就是对cycle 数量的估计.
test latex
Test
is the order of 2 (mod d).
The total number of cycles is
where
真的非常好用的插件 我太喜欢了
上面发的公式就是那篇文章中的主要结论, 对cycle 的数量估计
问题难度不在于对cycle数量的估计,而在于从每个cycle抽出一个生成元。
我大致浏览了那篇论文,用到一些数论上的计算技巧。过程倒也挺natural的。
这是我想到的一个解法
用m代表整个数组,p,q分别代表两个变量
第i次迭代(i从1开始)
对p,q赋值:
当i为奇数,p赋值为m[2n - 1 - [i/2] ],q赋值为m[ 2 + [i/2] ]([x]为下取整)
i为偶数,p赋值为m[n - i/2 ],q赋值为m[ n + i/2 ]
然后m[i + 1]与变量p迭代,m[2n- i]与q迭代
重复迭代n-1次即可
时间O(N),空间O(1)的算法
想法就是这样了,还没上机验证,应该不会错
全文在:http://blog.csdn.net/zfy0701/archive/2008/01/09/2031162.aspx
不好意思,有错,献丑了
这题拿来面试?不会是让用链表实现吧?然后被人云亦云,就成这样了。
就题目本身而言,可以这么设定
将1...n...2n,的数字换成1,n+1,2,n+2,3,n+3,...,n,2n
所以,分两种情况
x=n+1,移动到(x-n)*2
但是上面的这个过程和你给出的
2x mod (2n+1)在有不一致的情况,我觉得很奇怪
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int[] arra = new int[4000000];
for (int i = 0; i < arra.Length; i++) arra[i] = i + 1;
ReArrange(arra);
}
#region Rearrange the array
///
/// The Test
///
public static bool ReArrange(int[] array)
{
List list = new List();
list = getGenerator(array.Length);
if (list == null) return false;
// Call the exchange Methods;
revert(array, list);
return true;
}
#endregion
#region Get the generator for a number
///
/// Get the generator for a number,and the generator can generate all the numbers from 1 to 2*number
///
/// The number which used to generator the all the numbers from 1 to 2*number
/// Return an list which constains all the generators.
private static List getGenerator(int number)
{
if (number % 2 != 0) return null;
List list = new List();
int[] array = new int[number];
int start = 1;
loop:
int index = start;
array[index] = 1;
do
{
index = index * 2 % (array.Length - 1);
array[index] = 1;
} while (index != start);
list.Add(index);
for (int j = start+2; j < array.Length / 2; j += 2)
{
if (array[j] != 1)
{
start = j;
goto loop;
}
}
return list;
}
#endregion
#region Rearrange the all the items in the array such that it satisfy a1,b1...an ,bn.
///
/// Rearrange the all the items in the array such that it satisfy a1,b1...an ,bn
///
/// The array which is used to rearrange.
/// a set contains all the generators.
/// This parameter represent exchange times.If conter
/// equals array.Length-2 ,it indicates that all the rearrange completed.
private static void revert(int[] array, List set)
{
int conter = 0;
int temp;
int index;
int next;
int dinsinction;
int M = array.Length - 1;
foreach (int generator in set)
{
temp = array[generator];
index = generator;
next = generator * 2;
do
{
if (index % 2 == 0) dinsinction = index>> 1;//如果是偶数,它的值被index/2的值代替
else dinsinction = (index + M) / 2;// 如果是奇数,它的值被(index + M) / 2处的值代替
array[index] = array[dinsinction];
index = dinsinction;
}
while (index != next);
array[next] = temp;
}
}
#endregion
}
}
public class Merge {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
// FileReader fr = new FileReader();
int[] A={0,1,2,3,4,5,6,7,8,9,10,11,12};
int n = 6;
int tmp1,tmp2;
// tmp2 = 0;
int x = 7;
tmp1=A[x];
for (int j=0;j<2*n-1;j++){
if (1<x&&x<=n){
tmp2 = A[2*x-1];
A[2*x-1]=tmp1;
tmp1 = tmp2;
x = 2*x-1;
continue;
}
if (n<x && x<2*n+1){
tmp2 = A[2*(x-n)];
A[2*(x-n)]=tmp1;
tmp1 = tmp2;
x = 2*(x-n);
continue;
}
}
for (int j=0;j<13; j++){
System.out.println(A[j]);
}
System.out.println(x);
System.out.println(tmp1);
}
}
初始 x=7
{0 1 7 2 8 3 9 4 10 5 11 6 12} 2 7
初始 x=2
{0 1 7 2 8 3 9 4 10 5 11 6 12} 3 2
数据是 a1=1,a2=2,....; b1=7,b2=8,...
添加个0是为了让数组从下标[1]开始运算,使程序简洁,便于理解,A[0]可以不输出。