博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

两道TB面试题

Posted on 2011-04-13 10:53  ¥忘%风  阅读(3391)  评论(37编辑  收藏  举报

1:有一个数列,它由3个数列复合而成,并升序排列。三个数列分别是2的n次,3的n次,5的n次,0 <= n < 无穷大

给出前几项:1,2,3,4,5,8,9,16,25,27………………即2^0(3^0, 5^0) , 2^1, 3^1, 2^2, 5^1, 2^3, 3^2, 4^2, 5^2, 3^3 。 。 。 

问你如何快速得到第1000个数的值。。。。
 
问题 1 思考扩展: 假设是第1000,000,000项呢?

2:用C语言写一个函数,将字符串按空格拆分。比如 Hello, Welcome to forget-wind's blog. 拆分成"Hello," , "Welcome", "to",
  "forget-wind's", "blog."5个串。


 

第一题:

我的思路如下:

  首先,考虑到数列的指数表现形式,第1000个数必定是一个大数,做大数运算的龟速必然不能快速求得结果,所以必须避开大数运算。

  然后,要求的是数列的第1000个数,如果避开大数运算,则O(1000)的复杂度也是满足题目要求的。
 

考虑到这两点,问题就可以转换为:如何比较以指数形式表示的两个数的大小?(如2^i 与 3^j 比较大小)。

(⊙_⊙)既然不可以直接比较,那就只能想法去掉指数次幂,所以. . .两边同时取对数就可以了。。。

化简一下:2^i 与 3^j 的比较

  i 与 log2 (3) * j 的比较 (以2为底3的对数)-------- i 与 lg 3 / lg 2 * j -------- i * lg2 与 j * lg3。
 

用C++代码实现了一下,如下:

 

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;

struct Node {
	int base;
	int exp;
	Node (int base, int exp) {
		this->base = base;
		this->exp = exp;
	}
};

Node* Min(Node *a, Node *b) {
	if (a->exp * log(a->base) >  b->exp * log(b->base)) {
		return b;	
	}
	return a;
}

Node* Min(Node *a, Node *b, Node *c) {
	Node *min = Min(a, b);
	return Min(min, c);
}

int main() {
	Node *a = new Node(2, 0);
	Node *b = new Node(3, 1);
	Node *c = new Node(5, 1);
	Node *min = NULL;
	for (int t = 1; t <= 1000; t++) {
		min = Min(a, b, c);
		min->exp++;
	}
	cout << "The 1000th number is : " << endl;	cout << "base: " << min->base << endl;
	cout << "exp" << " : " << min->exp - 1 << endl << endl;	
	delete a; a = 0 ;
	delete b; b = 0;
	delete c; c = 0;
	return 0;
}
 
 


问题一扩充:
假设面试官要你求的不是数列的第1000个,而是... ...  第1000,000,000个数呢?

  那么O(n)的复杂度则显得有点力不从心了。

还好,某龙大神说了一句:
  二分枚举2的倍数,然后去找3,5的比枚举小的(这里是O(log(n)) --------- log(n)*log(n)的复杂度
有想法了么?
 


龙神一句话,顿悟了。
1: 问题求的是数列中的第len个数。
    因为数列是有2^n, 3^n, 5^n 符合而成, 所以第len个数必然可以以2^n, 3^n, 5^n 其中之一的形式表示。
  我们可以转换为 求 2^i <= 第len个数,求满足条件的最大的 i 。
  同理:也可以求 3^j <= 第len个数, 求满足条件的最大的 j, 同理最大的k 。
 (因为数列的构造性质,上诉必然有一个可以取得 等于号,即确定了第len个数的值。)
------------问题转换为怎样求这个  i 的最大值。


2:假设一直数列中某个数为2^i 次方,那么它是数列中的第几个数呢? 正因为有序性,我们可以在log(n)内确定这个值。
  即求满足 3^j < 2^i  (此时 i 值确定)时 j 的最大值。-----  二分 j 就可以了。
  同理, 二分 k 求得满足条件的k的最大值。  此时就求得 2^i 是数列的第 i + j + k +1个数(1是因为 0 次方)。
  然后 1 中要求2^i <= 第len个数时 i 的最值,因为数列是复合有序的,所以 i 增大时,j 增大(3^j < 2^i ),k 增大,
  i + j + k + 1的值也随之增大。
   则有 2^i <= 第len个数时 i 的最值 等价于 i + j + k + 1 <= len时 i 的最值。 也因此可以通过二分 i 求解。


下面是log(n) * log(n)算法-我的C++实现:

 

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;

struct Node {	//指数形式存贮一个数 
	int base;	//基数域 
	int exp; 	//指数域 
	Node () : base(2), exp(0) {}
	Node (int base, int exp) {
		this->base = base;
		this->exp = exp;
	}
};

Node *Min(Node *a, Node *b) {
	/*返回用指数表示的两个数中较小的那个数的指针*/
	if (a->exp * log(a->base) >  b->exp * log(b->base)) {
		return b;	
	}
	return a;
}

Node *Min(Node *a, Node *b, Node *c) {
	/*返回用指数表示的三个数中最小的那个数的指针*/
	Node *min = Min(a, b);
	return Min(min, c);
}

int Binary_Search(int base, int len, Node *a, Node *b) {
	int low = 1, high = len;/*
	/*b值确定的情况下,求一个用指数形式表示的数a,
	  满足(a->base)^(a->exp) < b,使得a最大,返回数a的指数域(a->exp)*/ 
	while (low <= high) {
		int mid = (low + high) >> 1;
		a->base = base;
		a->exp = mid;
		Node *min = Min(a, b); 
		if (min->base == a->base) {
			low = mid + 1;
		} else {
			high = mid - 1;
		}
	} 
	return low - 1;
}

int Binary_Solve(int base0, int base1, int base2, int len, int &ans) {
	//求满足条件“base0^t <= 第len个数”下, t的最大值,t值用引用值ans返回。
	int low = 0, high = len;
	Node *p = new Node;
	Node *q = new Node(base0, 0);
	int mid = 0, m = 0, n = 0;
	int ret = 0;
	while (low <= high) {
		mid = (low + high) >> 1;
		q->exp = mid;
		m = Binary_Search(base1, len, p, q);
		n = Binary_Search(base2, len, p, q);
		
		if (mid + m + n + 1 <= len) {
			low = mid + 1;
			ret = low + m + n;
		} else {
			high = mid - 1;
		}
	}
	ans = low - 1;
	delete p; p = 0;
	delete q; q = 0;
	//ret表示base0^t次是数列中的第ret个数 
	return ret;
}

int main() {
	int n, ans;  
	cout << "Enter the index of the number: ";
	while (cin >> n) {
		cout << "The " << n << "th number is : " << endl;	
		if (Binary_Solve(2, 3, 5, n, ans) == n) {
			cout << "base: " << 2 << endl;
		} else if (Binary_Solve(3, 5, 2, n, ans) == n) {
			cout << "base: " << 3 << endl;
		} else if (Binary_Solve(5, 2, 3, n, ans) == n) {
			cout << "base: " << 5 << endl;
		}
		cout << "exp" << " : " << ans << endl << endl << endl;
		system("pause");
		system("cls");
		cout << "Enter the index of the number: ";
	}
	return 0;
}

 

 
 

 

------------------------------------------------------------------------------------------------------

Update: 根据 lzyzizi ,owlish ,assiwe, 徐少侠等园友的讨论,对第一题进行了优化。

 

/*
同时感谢园友们不吝提供的高效算法 ---- O(1)实现。
总结如下 : 
a. 对于求3^j < 2^i( i 确定时) 求 j 的最大值,不需要进行二分查找,两边取对数再取整即可。
    ------ 即 j = (int)(i * log3(2) ).
     ------ 我们根据3 = 2^a; 5 = 2^b;  (即 a = log2(3), b = log2(5) )
  可以得到 j = (int)(i / a); k = (int)(i / b);
 
b. 由a的结论可知,假设第 m 项是2^i 次方,
  则 m == i + j + k + 1 = i + (int)(i / a) + (int)(i / b) + 1;
 
c. 又因为 ( i / a - 1 ) <  [i / a] < (i / a)  
    ---- 因为 i 是整数, 所以 i / a 必是小数, 因此[i / a] <= (i / a)取不到等号。
   即得到 i + ( i / a - 1 ) + ( i / b - 1 )  + 1 < m < i + ( i / a ) + ( i / b ) +1; (1是以为0次方。)
   令n = m - 1; 化简不等式, 即可得到 i 的上下界。
    即 (a * b * n) / (a * b + a + b) < i < (a * b * n + 2 * a * b) / (a * b + a + b);
   得到了上下界的差值为: (2 * a * b) / (a * b + a + b)) < (2 * a * b) / (a * b) = 2。
 
d. 因此可以用尝试法,假定第 m 项分别是以2, 3, 5为底得情况,则此时根据枚举的三种情况。
    ( 即a = log2(3) || a = log3(2) || a = log5(2)) )
      ----- 推得上下界得差值 (2 * a * b) / (a * b + a + b)),根据底数不同,可以取三种值。 但三种情况下其值都 < 1;
        ----- 上下界差值 < 1,即 i 值可以唯一确定。(即假定第 m 项是2^i, 则 i = ceil (a * b * n) / (a * b + a + b) );
      (pass:但仅限于底数是2,3,5时, i 值唯一确定, 否则可以从上下界之间去找(其也只有两种取值))。

---推论:任意三个两两互质的数列,都可用以上方法求解,其上下界差值不超过2;
我的程序跑出来的数据。
第1000项为:2^485;
第1000000000想为: 2^485058662。
*/

 

 

一次二分算法实现:

1:根据总结中 a,内部第二个二分显得多余,j = (int)(i / a); k = (int)(i / b);可直接确定。

2:一次二分的复杂度为O(log(n)),不及O(1)算法,之所以保留。
  因为,少掉内层的二分,则算法还算简单且复杂度尚可,还有就是,对于我这种数学盲,可以不需要去求解数学不等式。

 

#include <iostream>
#include <cstdlib>
#include <cmath>
using namespace std;

int getValueK(int base0, int base1, int base2, int n) {
	int low = 0, high = n;
	int ret = 0, mid = 0, exp1 = 0, exp2 = 0;
	double a = log(base1) / log(base0);
	double b = log(base2) / log(base0);
	while (low <= high) {
		mid = (low + high) >> 1;
		exp1 = (int)(mid / a);
		exp2 = (int)(mid / b);
		if (mid + exp1 + exp2 + 1<= n) {
			low = mid + 1;
			ret = low + exp1 + exp2;
		} else {
			high = mid - 1;
		}
	}
	if (ret == n)
		return (low - 1);
	return -1;
}

void get_nth(int n) {
	int k = 0;
	if ( (k = getValueK(2, 3, 5, n)) != -1 ) {
		cout << "base: " << 2 << endl;
	} else if ( (k = getValueK(3, 2, 5, n)) != -1 ) {
		cout << "base: " << 3 << endl;
	} else if ( (k = getValueK(5, 2, 3, n)) != -1 ) {
		cout << "base: " << 5 << endl;
	}
	cout << "exp : " << k << endl << endl;
}

int main() {
	int n;
	cout << "Enter the index of the number: ";
	while (cin >> n) {
		get_nth(n);
		cout << "Enter the index of the number: ";
	}
	return 0;
}



以下是O(1)算法的C++实现:

#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cstdio>
using namespace std;

int getValueK(int base0, int base1, int base2, int n) {
	n--;
	double a = log(base1) / log(base0);
	double b = log(base2) / log(base0);
	int k = (int)ceil( a * b * n / (a * b + a + b) );
	if (k + (int)(k / a) + (int)(k / b) == n) {
		return k;
	}
	return -1;
}

void get_nth(int n) {
	int k = 0;
	if ( (k = getValueK(2, 3, 5, n)) != -1 ) {
		cout << "base: " << 2 << endl;
	} else if ( (k = getValueK(3, 2, 5, n)) != -1 ) {
		cout << "base: " << 3 << endl;
	} else if ( (k = getValueK(5, 2, 3, n)) != -1 ) {
		cout << "base: " << 5 << endl;
	}
	cout << "exp : " << k << endl << endl;
}

int main() {
	int n;
	cout << "Enter the index of the number: ";
	while (cin >> n) {
		get_nth(n);
		cout << "Enter the index of the number: ";
	}
	return 0;
}

 


第二题:

不在于考察你实现的速度和功能,而在于考察代码质量。
空格拆分?=。=   这个简单。 --------  我传一个二维数组进去,把字符串拆分后依次保存在这个二维数组中不就行了么?

 

我的想法是:

  既然二维数组的大小不好控制,那么,动态分配空间是可以解决这个问题的。

  (由于以前做acm不常用指针,甚至是避免用指针 ---- 因为在有限的时间里,侧重于追求代码的高效性和稳定性(指针用的不熟很容易造成程序崩溃)。

   --- 这样分析一下,这大概也就是我去面试的同学挂在这道题的内因吧。) 


我的具体做法:

建立存储结构如下,用链表实现:

struct Node {
	char *ch;                //指向字符串按' '拆分的各首字符指针
	struct Node *next;   //下一个结点
};

则函数传参时只需要传递链表的头结点。

if (str[i] 为拆分后单词的首字符) {

  在表尾建立新结点,并让其ch域指向单词首字符的地址;

  让指针指向表尾结点。

}

这样就避免了如,二维数组大小是多少那样的不确定性问题。
 

以下是我的C语言实现:

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

typedef struct Node {
	char *ch;
	struct Node *next;
}Node, *PNode;

void calWord(PNode head, char str[], int lstr) {
	PNode p = head;
	int i;
	if (lstr > 0 && str[0] != ' ') {
		p->next = (PNode)malloc(sizeof(Node));
		p = p->next;
		p->ch = &str[0];
		p->next = NULL;
	}

	for (i = 0; i < lstr; i++) {
		if (' ' == str[i])
			str[i] = 0;
	}
	for (i = 1; i < lstr; i++) {
		if (0 == str[i-1] && str[i] != 0) {
			p->next = (PNode)malloc(sizeof(Node));
			p = p->next;
			p->ch = &str[i];
			p->next = NULL;	
		}
	}
	p = head;
}

void del(PNode p) {
	while (p->next != NULL) {
		PNode q = p;
		p = p->next;
		free(q);
	}
	free(p);
}

int main() {
	char str[] = "Hello, Welcome to forget-wind's blog.";
	PNode p = (PNode)malloc(sizeof(Node));
	int len = strlen(str);
	p->next = NULL;
	calWord(p, str, len);
	while (p->next != NULL) {
		p = p->next;
		printf("%s\n", p->ch);
	}
	del(p);
	return 0;
}

 

虽然没去面试,但总结一下前人的经验还是有好处的吧。。。

原创文章如转载请注明:转自¥忘%风 {http://www.cnblogs.com/slave_wc}

本文地址: 两道TB面试题