首页 > 代码库 > [笔试题] 两个有趣的问题
[笔试题] 两个有趣的问题
有n瓶粉末,一瓶有毒。有毒的粉末融在水里一小时后水会变蓝。你有一些试管,问最少需要多少时间和多少试管就能确定毒粉末呢?不考虑粉末导入试管的时间。
这道题最基本的想法就是时间换空间或者空间换时间,即n个试管用1小时的时间,或者1个试管用n小时依次试验过来。在实际生产中应该偏向于空间换时间,因为硬性资源可以增加,而程序如果找不到好的优化方法运行时间基本也就定了,所以时间比空间重要的多。但是这两个显然都不是什么好办法。另外一个比较好的解决办法就是二分,把瓶子分两组,花一个小时排除掉一半的瓶子,再花一个小时排除剩下中一半的瓶子....由于每一个小时后的溶液都已验证过,可以倒掉,所以试管可重复利用,所以结果就是1支试管,log2(N)向上取整的时间。但是长时间的等待显然是不行的,还不如牺牲空间来节省时间,所以我们也可以三分四分或者N分来加速判断,只不过按照这种程度分下去,最后就变成了n-1个试管和1小时的时间......
初看这道题没什么特别好的想法,后来还是想出了个绝妙的办法:只需要1个小时,并且只需要log(N+1)向上取整的试管数量。这样时间已经是最短时间,并且空间也应该是最小空间
所有瓶子从1~n编号,摆上log(N+1)向上取整数量的试管,从右至左代表二进制的低位到高位,所有粉末编号转化成二进制,分别倒进相应的试管里,一个小时以后,变蓝试管为1,未变蓝试管为0,这个二进制序列转成10进制就是毒粉末的编号了。比如10瓶粉末,发现第2和第3支试管变蓝,即0110,显然6号瓶就是毒粉末了。这个方案好像还能继续优化,之前考虑编号从1开始是因为觉得所有粉末都应该参与实验,好像没必要。编号可以从0开始,如果试管一个都没蓝,那么未参与的0号瓶就是毒瓶了,这样有时候又能省下一支试管。
所以最终答案:1小时的时间,log(N)向上取整的试管数量,即N-1的二进制位数。1000瓶粉末也只需要10支试管和1小时就行了~
给定一个无环的单链表,如何快速定位位于链表中间的那个节点?返回值为指向中间节点的指针。
先来看看朴素的算法:首先需要遍历一遍整个链表,计算出节点数量N,然后再次遍历链表N/2次,这样就找出位于中间的结点了,这样就是O(N)的时间复杂度了,附带一个整形变量表示N。指针移动N+N/2次,第一次遍历时整形变量++N次。
我想到了个更好的办法:只需两个指针,只遍历一次就行了。
两个指针一开始先指向头,然后第一个指针每次走两步,第二个指针每次走一步,因为无环,当第一个指针走到末尾的时候,第二个指针自然就在中间了。
PS:下面是我写的比较简洁的代码,有更简洁的吗?
node* find(node *head) { node *s = head, *t = head; while(t = t->next) { s = s->next; if((t = t->next) == NULL) break; } return s; }
完整测试代码如下:
#include<iostream> using namespace std; struct node { int val; node* next; }; void CreateLinkList(node *LinkList) { node *s = LinkList; for(int i = 0; i < 10; i++) { node *t = new node; t->val = i; t->next = NULL; s = s->next = t; } } void ShowLinkList(node * LinkList) { node *s = LinkList->next; while(s = s->next) printf("%d ", s->val); putchar(10); } node* find(node *head) { node *s = head, *t = head; while(t = t->next) { s = s->next; if((t = t->next) == NULL) break; } return s; } int main() { node *LinkList = new node; CreateLinkList(LinkList); ShowLinkList(LinkList); node *re = find(LinkList->next); // 传入的参数是第一个有效节点 printf("mid:%d\n", re->val); getchar(); return 0; }