面试题之10亿正整数问题--完整解答

本文探讨了在有限内存条件下对10亿个正整数进行排序及查找重复数的方法,提出了归并排序、多趟排序及位图法,并分析了各自的时间与空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好了,不说这个了。下面进入正题。

这个题目来源于某公司的面试题,是absolute同学在我的“面试题收集贴”中提出的,之后CMGS同学在回复中提到,腾讯今年的面试题中有类似题目,问题规模扩大了10倍,但是本质相同。下面我们来看一下题目:

10亿个正整数,只有其中1个数重复出现过,要在O(n)的时间里面找出这个数,内存要尽可能少(小于100M)。

这个问题,下面有同学提出题目不严谨,我看了一下,的确是和我希望表达的意思有些模糊,我这里将题目从我自己的角度澄清一下:

对于正整数,其范围为1-10亿,然后从中随机的选择出k个数(可以重复,也可以不重复,从具体题目的要求来看),选择完之后开始做题目,无论是对其排序,还是找重复数。

不知道这样表达是不是清楚。

我们首先不急着来解决这个问题,而是从其来源来慢慢看。这里提到的题目与面试题有一些不同(在面试题中是要求找重复的数,而这里的题目是对其进行排序),所以不要感觉疑惑。

[来源]

这种类型的题目,其来源为《编程珠玑》这本书,这里推荐这本书一下,但是由于这本书比较薄,所以里面涉及到的知识只能简单提到,但是并不能够完全覆盖,所以读这本书还是要经历将其读厚的过程,对涉及到的知识点,通过其它参考书来得到关于其的全部知识。另外,尽管这本书的翻译还不错,但是还是推荐下载或得到其英文版进行参考,对于中文版中感觉了解不是很清楚的地方,再来对照英文版看看。

该题的原型是在《编程珠玑》的开头,题目在“面试题之10亿正整数问题”中我已经详细介绍过了,而且园子中应该大部分兄弟都有这本书吧,我就不详细将书中内容重新打一遍了。

大概介绍一下,作者通过和程序员交谈,了解到程序员需要在一个大系统中实现一个电话号码的文本数据库,读入电话号码,输出排序好的以800开头的文件。

通过作者的整理,得到精确的问题陈述如下

输入:

所输入的是一个文件,至多包含n个正整数,每个正整数都要小于n,这里的n10^7。如果输入时某一个整数出现了两次,就会产生一个致命的错误。这些整数与其他任何数据都不关联。

输出:

以增序形式输出经过排序的整数列表。(这里应该补充一下是文件形式吗?)

约束:

至多(大概)只有1MB的可用主存,但是可用磁盘空间非常充足。运行时间至多只允许几分钟,最适宜的时间大概为10秒钟。

[解答]

从上面的这些描述来看一下,题110亿正整数的问题,题2为《编程珠玑》中的问题。

1中问题规模为10亿,也就是10^9,内存要求为小于100M,问题要求是找出重复的数;

2中问题规模为10^7,而内存要求为1M左右,问题要求是对这些数进行排序。

同时题1中要求算法的时间复杂度为O(n)

这里我们就来看一下《编程珠玑》中是怎样来解决这个问题的。

《编程珠玑》中使用了三种方法来对其进行解决。一种为Merge Sort,一种为Multi-pass Sort,还有一种为作者起名的Wonder Sort。这里我们会一个个分析一下这些算法,包括时间复杂度和空间复杂度,同时在后面还会有实例的说明。

Merge Sort

Merge Sort大家应该很熟悉,是惯常使用的一种外排序方法,其主要思想是采用了分而治之的思想,该思想被应用于多个算法领域。其中文称为归并排序,在大部分的算法书中都会提到,同时也是外排序中经常使用到的排序方法。

归并排序可以通过迭代和递归两种方式来实现。

这些实现在殷人昆的那本黄书中可以找到(大家应该知道我说的是哪本书吧),但是他的那本书的实现有些不太好,使用了dataliststaticlinklist这两个与归并排序本身无关的数据结构,而这里我们用来示例的话只需要int的数组即可。(另外加上文件读写操作,针对题目的话),所以这里我们仅仅参考一部分黄书中的实现,来自己实现归并排序方法。

归并排序可以使用多种方法来进行实现,这里只谈常规方式下的归并排序,在这种情况下,我们需要和被排序数组同样大小的一个额外空间来辅助进行排序。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 #include < iostream >
2 using namespace std;
3
4 void merge( int initlist[], int mergedlist[], int l, int m, int n)
5 {
6 int i = l,j = m + 1 ,k = l;
7 while (i <= m && j <= n)
8 {
9 if (initlist[i] <= initlist[j])
10 {
11 mergedlist[k] = initlist[i];
12 i ++ ;
13 k ++ ;
14 }
15 else
16 {
17 mergedlist[k] = initlist[j];
18 j ++ ;
19 k ++ ;
20 }
21 }
22 if (i <= m)
23 {
24 for ( int n1 = k,n2 = i;n1 <= n && n2 <= m;n1 ++ ,n2 ++ )
25 {
26 mergedlist[n1] = initlist[n2];
27 }
28 }
29 else
30 {
31 for ( int n1 = k,n2 = j;n1 <= n && n2 <= n;n1 ++ ,n2 ++ )
32 {
33 mergedlist[n1] = initlist[n2];
34 }
35 }
36 }
37
38 void mergepass( int initlist[], int mergedlist[], const int len, const int listlen)
39 {
40 int i = 0 ;
41 while (i + 2 * len <= listlen - 1 )
42 {
43 merge(initlist,mergedlist,i,i + len - 1 ,i + 2 * len - 1 );
44 i += 2 * len;
45 }
46 if (i + len <= listlen - 1 )
47 {
48 merge(initlist,mergedlist,i,i + len - 1 ,listlen - 1 );
49 }
50 else
51 {
52 for ( int j = i;j <= listlen - 1 ;j ++ )
53 {
54 mergedlist[j] = initlist[j];
55 }
56 }
57 }
58
59 void mergesort( int list[], int listlen)
60 {
61 int * templist = new int [listlen];
62 int len = 1 ;
63 while (len < listlen - 1 )
64 {
65 mergepass(list,templist,len,listlen);
66 len *= 2 ;
67 mergepass(templist,list,len,listlen);
68 len *= 2 ;
69 }
70 delete[]templist;
71 }
72
73 int main()
74 {
75 int initlist[] = { 21 , 25 , 49 , 25 , 93 , 62 , 72 , 8 , 37 , 16 , 54 };
76
77 int i;
78 for (i = 0 ;i < sizeof (initlist) / sizeof ( int );i ++ )
79 cout << initlist[i] << " " ;
80 cout << endl;
81
82 mergesort(initlist, sizeof (initlist) / sizeof ( int ));
83 for (i = 0 ;i < sizeof (initlist) / sizeof ( int );i ++ )
84 cout << initlist[i] << " " ;
85 cout << endl;
86
87
88 return 0 ;
89 }

归并排序的时间复杂度和空间复杂度:

时间复杂度为O(nlgn),空间复杂度为O(n)。实例说明见后。

Multi-pass Sort

在中文翻译中,称其为多通道排序。但是从其描述来看,似乎是多趟排序更为确切。

补充:eaglet提到所谓多通道排序的原型就是B+树。对于B+树没有具体研究过,不过multipass在算法书中似乎没有查找到,倒是B+树会出现,而且看描述似乎的确是一样的。

伪码(using tmp file)

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 while (nottheendoffile)
2 readonerecordfrominputfile(I / OOperation)
3 if (record in theregion)
4 puttherecordintomemory
5 else
6 writetherecordintothefile(s).(I / OOperation)
7 end if
8 end while
9
10 sorttherecords in thememory using someinnersortmethod.
11 writethesortedrecordsintotheoutputfile.(I / OOperation)
12 while (therearetmpfilesleft)
13 dumponetmpfile(beginfromthesmallest)tomemory
14 sorttherecords in thememory using someinnersortmethod.
15 writethesortedrecordsintotheoutputfile.(I / OOperation)
16 end while
17 DONE.

伪码(not using tmp file)

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 for (region ++ )
2 while (nottheendoffile)
3 readonerecordfrominputfile(I / OOperation)
4 if (record in theregion)
5 puttherecordintomemory
6 end if
7 end while
8 sorttherecords in thememory using someinnersortmethod.
9 writethesortedrecordsintotheoutputfile.(I / OOperation)
10 end for
11DONE

多趟排序的时间复杂度和空间复杂度

可以看到,通过存储到硬盘文件,我们可以控制使用空间的大小,但是这样同样也就加大了I/O读写的次数,而大家都很清楚,I/O操作的效率远远低于内存操作,这样在整个耗时方面,I/O读写次数越多的,其实际运行时间就越长。要提高算法的效率,就需要减少I/O操作的次数。

实例说明见后。

Wonder Sort

OK,现在就进入最精彩的部分,也是这道题目最希望的解法。还记得张柏芝在有一部电影中叫wonderful,很喜欢这个名字,看来作者也很喜欢这个名字哈~

在上面读入文件记录的时候,我们可以使用string, int等类型来表示我们所读入的这一条记录的具体值。而对于32位的机器,其int值为32位,也就是4byte。而1M空间则有1024*1024=1048576个字节,一共能够表达的int值为262144。而对于107次方也就是1千万这样的数,我们除下来得到38.14697265625也就是差不多要做40次。

Wonder Sort中使用位图来做,以每一位是0还是1表示数是否存在,这样1M空间共有8388608个位,也就能表示大概800万个数。这里我们暂时考虑不存在一个对内存的严格限制。所以如果要表示1000万,我们需要的内存空间大概是1.25M

伪码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 setupabit - mapwhichhave 1000 , 0000 bits.And set allbitstozero.
2 while (notendoftheinputfile)
3 readonerecordfrominputfile
4 if (correspondbit in thebit - map is 0 )
5 changethecorrespondbit in thebit - mapto 1 .(bit - map[record] = 1 )
6 else
7 // ifwewanttosort,justdonothing.
8 // ifwewanttofindthenumthatcametwice,justprintitoutandbreak.
9 end if
10 end while
11
12 writetherecordsintotheoutputfile.(I / OOperation)
13

精彩排序的时间复杂度和空间复杂度

空间复杂度肯定是最低的,而时间复杂度为O(n)。实例见后。

【在该问题上面的扩展】

上面三个解法中,当然是Wonder Sort最好。但是作者也提到了,使用Wonder Sort时,我们需要1.25M的空间(加上其他一些操作,还要更多些,不过最大的需求肯定是1.25M的用来进行位图法的区域),但是如果严格要求使用1M空间的话,我们怎么做?

从上面解法的描述来看,我们可以将第二种解法与第三种解法结合起来,这样就可以解决。

伪码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 setupabit - mapwhichhave 800 , 0000 bits.And set allbitstozero.
2 while (nottheendoffile)
3 readonerecordfrominputfile(I / OOperation)
4 if (record in theregion,whichmeans 0 800 , 0000 )
5 if (correspondbit in thebit - map is 0 )
6 changethecorrespondbit in thebit - mapto 1 .(bit - map[record] = 1 )
7 else
8 // ifwewanttosort,justdonothing.
9 // ifwewanttofindthenumthatcametwice,justprintitoutandbreak.
10 end if
11 else
12 writetherecordintothefile(s).(I / OOperation)
13 end if
14 end while
15
16 writetherecordsintotheoutputfile.(I / OOperation)
17
18 // dumpthetmpfile(800,0000–1000,0000)tomemory
19 while (nottheendoffile)
20 if (correspondbit in thebit - map is 0 )
21 changethecorrespondbit in thebit - mapto 1 .(bit - map[record] = 1 )
22 else
23 // ifwewanttosort,justdonothing.
24 // ifwewanttofindthenumthatcametwice,justprintitoutandbreak.
25 end if
26 end while
27 writetherecordsintotheoutputfile.(I / OOperation)
28
29 DONE.
30


下面就是实例了,在做实例之前,我们有一个问题,就是如何生成题目中要求的测试数据?

测试数据的生成,必须比较随机,我们需要生成范围从0-10000000的数,这里我生成为8000000个数。同时这些数要求不重复。

[回答]

在《编程珠玑》的第12章中给出了回答,这里我直接写出代码。我使用这个生成了一个random.txt文件,一共67.8M。

测试文件生成代码:

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 #include < iostream >
2 #include < fstream >
3 using namespace std;
4
5 #include < ctime >
6 // totaluse126seconds
7
8 ofstreamofs( " random.txt " );
9
10 void myswap( int & a, int & b)
11 {
12 int tmp = a;
13 a = b;
14 b = tmp;
15 }
16
17 int bigrand()
18 {
19 return RAND_MAX * rand() + rand();
20 }
21
22 int randint( int l, int u)
23 {
24 return l + bigrand() % (u - l + 1 );
25 }
26
27 void generate( int x[], int k, int n)
28 {
29 int i = 0 ;
30 for (i = 0 ;i < n;i ++ )
31 {
32 x[i] = i;
33 }
34 for (i = 0 ;i < k;i ++ )
35 {
36 // myswap(i,randint(i,n-1));
37 int j = randint(i,n - 1 );
38 int t = x[i];
39 x[i] = x[j];
40 x[j] = t;
41 ofs << x[i] << endl;
42 }
43 }
44
45 int main()
46 {
47 clock_tStart,Finish;
48 Start = clock();
49 int * x = new int [ 10000000 ];
50 generate(x, 8000000 , 10000000 );
51 // generate(x,100000,1000000);
52 delete[]x;
53 Finish = clock();
54 int second = double (Finish - Start) / CLOCKS_PER_SEC;
55 cout << " totaluse " << second << " seconds " << endl;
56 return 0 ;
57 }

该代码生成八百万的测试数据共用时126秒,机器配置为1G内存,P4处理器。这里,能否更快生成测试数据,当然,要保证随机性和正确性。

问题

这里我们得到程序的时间是使用clock函数,那如何得到程序占用的内存呢?

还有一个问题就是,我们如何像linux中那样得到用户时间和系统调用时间?

这个还不清楚,这里提出来,大家有知道的可以共享一下。

在测试文件生成好之后,我们就可以来实际的看一下这3种方法所具体使用的时间了。

因为是使用debug版来测试,同时还有其他程序运行,所以可能不是十分精准。但是都是在同一台机器上面测试,所以数量级上面还是可以参考的。

结果:

使用merge sort来排序800万的测试数据,我们共用了337.7

使用multipass sort来排序800万的测试数据,我们共用了581.67秒,其实大家仔细看一下,这里我做得不太公平,我这里一趟是使用了一半的数,也就是400万,这样来达到两趟的目的,所以这里来比较是有些问题的。

使用wonder sort时,居然也用了很长时间,共为334.171秒。稍微换了一下,将函数调用去掉一层,最后还是得到total time is 333.328 seconds

这和我们的预期相差还是比较大的。到底是什么原因?

是否是I/O操作历时比较长的原因?

单纯I/O操作total time is 354.343seconds。

下面列出我使用的代码

1. merge sort

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 // totalrunningtimeis337.751seconds
2
3 #include < iostream >
4 #include < fstream >
5 using namespace std;
6
7 #include < ctime >
8
9 ifstreamrandfile( " random.txt " );
10 ofstreamsortedfile( " sorted.txt " );
11
12 const int NUMS = 8000000 ;
13
14 void merge( int initlist[], int mergedlist[], int l, int m, int n)
15 {
16 int i = l,j = m + 1 ,k = l;
17 while (i <= m && j <= n)
18 {
19 if (initlist[i] <= initlist[j])
20 {
21 mergedlist[k] = initlist[i];
22 i ++ ;
23 k ++ ;
24 }
25 else
26 {
27 mergedlist[k] = initlist[j];
28 j ++ ;
29 k ++ ;
30 }
31 }
32 if (i <= m)
33 {
34 for ( int n1 = k,n2 = i;n1 <= n && n2 <= m;n1 ++ ,n2 ++ )
35 {
36 mergedlist[n1] = initlist[n2];
37 }
38 }
39 else
40 {
41 for ( int n1 = k,n2 = j;n1 <= n && n2 <= n;n1 ++ ,n2 ++ )
42 {
43 mergedlist[n1] = initlist[n2];
44 }
45 }
46 }
47
48 void mergepass( int initlist[], int mergedlist[], const int len, const int listlen)
49 {
50 int i = 0 ;
51 while (i + 2 * len <= listlen - 1 )
52 {
53 merge(initlist,mergedlist,i,i + len - 1 ,i + 2 * len - 1 );
54 i += 2 * len;
55 }
56 if (i + len <= listlen - 1 )
57 {
58 merge(initlist,mergedlist,i,i + len - 1 ,listlen - 1 );
59 }
60 else
61 {
62 for ( int j = i;j <= listlen - 1 ;j ++ )
63 {
64 mergedlist[j] = initlist[j];
65 }
66 }
67 }
68
69 void mergesort( int list[], int listlen)
70 {
71 int * templist = new int [listlen];
72 int len = 1 ;
73 while (len < listlen - 1 )
74 {
75 mergepass(list,templist,len,listlen);
76 len *= 2 ;
77 mergepass(templist,list,len,listlen);
78 len *= 2 ;
79 }
80 delete[]templist;
81 }
82
83 int main()
84 {
85 clock_tstart,finish;
86 start = clock();
87 int * initlist = new int [NUMS];
88
89 int i;
90 for (i = 0 ;i < NUMS;i ++ )
91 randfile >> initlist[i];
92
93 mergesort(initlist,NUMS);
94 for (i = 0 ;i < NUMS;i ++ )
95 sortedfile << initlist[i] << endl;
96
97 delete[]initlist;
98
99 finish = clock();
100 double seconds = ( double )(finish - start) / CLOCKS_PER_SEC;
101 cout << " totalrunningtimeis " << seconds << " seconds " << endl;
102 return 0 ;
103 }

2. multipass Sort

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 // totaltimeis581.67seconds
2
3 #include < iostream >
4 #include < fstream >
5 using namespace std;
6
7 #include < ctime >
8
9 ifstreamrandfile( " random.txt " );
10 ofstreamsortedfile( " sorted.txt " );
11 ifstreamhelpfileout;
12 ofstreamhelpfile( " tmp.txt " );
13
14 int cmp( const void * a, const void * b)
15 {
16 return * ( int * )a - * ( int * )b;
17 }
18
19 void multipassSort( int x[])
20 {
21 int record;
22 int i = 0 ;
23 int j;
24 while (randfile >> record)
25 {
26 if (record < 4000000 )
27 x[i ++ ] = record;
28 else
29 helpfile << record << endl;
30 }
31 qsort(x,i, sizeof ( int ),cmp);
32 for (j = 0 ;j < i;j ++ )
33 sortedfile << x[j] << endl;
34 i = 0 ;
35 helpfile.close();
36 helpfileout.open( " tmp.txt " );
37 while (helpfileout >> record)
38 {
39 x[i ++ ] = record;
40 }
41 qsort(x,i, sizeof ( int ),cmp);
42 for (j = 0 ;j < i;j ++ )
43 sortedfile << x[j] << endl;
44 }
45
46 int main()
47 {
48 clock_tstart,finish;
49 start = clock();
50 int * x = new int [ 8000000 ];
51 multipassSort(x);
52 delete[]x;
53
54 finish = clock();
55 double secs = ( double )(finish - start) / CLOCKS_PER_SEC;
56 cout << " totaltimeis " << secs << " seconds " << endl;
57 return 0 ;
58 }
59

3. wonder Sort

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 #include < iostream >
2 #include < fstream >
3 using namespace std;
4
5 #include < ctime >
6
7 ifstreamrandfile( " random.txt " );
8 ofstreamsortedfile( " sorted.txt " );
9
10 const int NUMS = 8000000 ;
11
12 const int BITSPERINT = 32 ;
13 const int SHIFT = 5 ;
14 const int MASK = 0x1f ;
15
16 void seti( int x[], int i)
17 {
18 x[i >> SHIFT] |= ( 1 << (i & MASK));
19 }
20
21 void clri( int x[], int i)
22 {
23 x[i >> SHIFT] &= ~ ( 1 << (i & MASK));
24 }
25
26 int test( int x[], int i)
27 {
28 return x[i >> SHIFT] & ( 1 << (i & MASK));
29 }
30
31 void wonderSort( int x[])
32 {
33 int tmp;
34 int i = 0 ;
35 for (i = 0 ;i < NUMS;i ++ )
36 {
37 randfile >> tmp;
38 if (test(x,tmp) == 0 )
39 {
40 seti(x,tmp);
41 }
42 }
43 for (i = 0 ;i < 10000000 ;i ++ )
44 {
45 if (test(x,i) != 0 )
46 {
47 sortedfile << i << endl;
48 }
49 }
50 }
51
52 int main()
53 {
54 clock_tstart,finish;
55 start = clock();
56 int * x = new int [ 10000000 / BITSPERINT];
57 memset(x, 0 , 10000000 / BITSPERINT * sizeof ( int ));
58
59 wonderSort(x);
60 delete[]x;
61 finish = clock();
62 double seconds = ( double )(finish - start) / CLOCKS_PER_SEC;
63 cout << " totalusetimeis " << seconds << " seconds " << endl;
64
65 return 0 ;
66 }

4. 单纯读写文件

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

--> 1 #include < iostream >
2 #include < fstream >
3 using namespace std;
4
5 #include < ctime >
6
7 ifstreamrandfile( " random.txt " );
8 ofstreamoutfile( " out.txt " );
9
10 #define NUMS8000000
11
12 int main()
13 {
14 clock_tstart,finish;
15 start = clock();
16 int i;
17 int tmp;
18 for (i = 0 ;i < NUMS;i ++ )
19 {
20 randfile >> tmp;
21 outfile << tmp;
22 }
23 finish = clock();
24 double secs = ( double )(finish - start) / CLOCKS_PER_SEC;
25 cout << " totaltimeis " << secs << " seconds " << endl;
26
27 return 0 ;
28 }

如果不使用文件读写,就可以看出算法的效率来了,但是如何做到呢?

排序部分结束,然后回到面试题,如果只是来查找是否有重复数的话,是否有其他解法,如何做?

在上次的回复中,winter-cn提到了字典树和哈希表的解决方案。

字典树的确是一个好办法,而且仅仅是对于查找,如果是排序,字典树就不合适了。

而哈希还没有想到好的哈希函数,具体也没有细想,但是应该也是可以的。

【在其基础上衍生的面试问题】

其实上面的内容都解决了的话,那一开始的面试题也就解决了,因为其尽管问题规模变大了,但是其能够使用的内存也一样变大了,其基本方法还是一样的。

现在很多公司,因为其筛选人员的目的,同时也由于其工作是处理海量数据,所以在面试的时候,会出一些这样的关于海量数据处理的问题,但是我们很多人,包括我,都一般不会接触到这样海量的数据,所以,适量的减小问题的规模,但是同时将其内存限制变得更加严格,其实其本质还是一样的。

TODO – 海量数据处理的话题】

这里的话题可以归入“海量数据处理”,关于海量数据处理,现在有很多公司的面试题会提相关的问题,如果没有对这个话题思考过的话,是不太可能会有很好的答案的,关于这个话题,以后还可以找到其他的问题来进行讨论,当问题规模不大的时候,体现不出算法的优势,但是当问题的规模到达一定数量级的时候,O(n)或者O(lgn)的算法的优势就能够体现出来了。

因为内容比较多,整理也花了一些时间,还有编写代码,如果其中有错误,欢迎大家指出,本来想分为几篇来写的,最后还是一下子写成一篇写完算了,篇幅就比较长,而且代码也较多,多谢大家能够看到最后~

原文链接:http://www.cnblogs.com/cnyao/archive/2009/11/26/interview8.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值