你真的会写二分查找吗
2016-07-14 20:50
253 查看
看到这个标题你可能感觉二分法太简单了,谁能不会写。的确,二分法是我们学习算法和数据结构路上最早提到的算法,看一遍人人都能理解。也正是因为大家都认为这个问题太简单了,所以基本上都是看看就过去了,根本就不屑于动手写,大家的目标都是徒手写红黑书。今天我在leetcode上看到一道简单的算法题就是用到了二分法,不动手不知道,一动手吓一跳,写了两个多小时(手动害羞)。题目的地址:Guess Number Higher or Lower 有兴趣的可以自己亲自去写一下,不动手是不会知道的啦。
这道题讲的是一个猜数字的游戏,游戏的内容是这样的:首先我从 1 到 n 这 n 个数中随意的选中一个数,然后记下这个数,然后由你来猜我选中的这个数,每当你猜一次,我就会告诉你你猜的数是大了还是小了。这里有一个已经写好的函数
要求实现函数
写的过程中总是出现各种错误,在这里就不一点点讲述,因为已经有前辈写了,觉得很好,先拿过来,虽然有一点点区别。(引用请注明出处:http://blog.csdn.net/int64ago/article/details/7425727)。
内容也搬过来,下面开始
。。.。。。。。。。。分割线。。。。。。。。。。。。。
看到这个标题无论你是处于怎样的心理进来看了,我觉得都是值得的。因为这个问题太简单,任何一个开始接触“真正”算法基本都是从二分查找开始的。至于二分查找都不知道是什么的可以先去找别的资料看下,再来看这篇文章。既然很简单,那么我们开始一起写一个吧,要求是对num[]={1,2,2,4,4,8,10}不减序列在区间[0,7)进行查找,当然我们得首先保证要查找的数e满足:num[0] <= e <= num[0],这个是很容易做到的,为了简化又不失去代表性,e选取2、3、4这三个数。我们就一起开始写吧:
首先,很容易的写下 int bSearch(int begin, int end, int e)
然后,很自然的定义 int mid, left = begin, right = end;
接下来怎么写呢?while(left < right)?while(left <= right)?while(mid == left)?while(mid == right)?………………真正一个写程序人会想纠结好一会儿,我们就选一个吧while(left < right)
下面,也很自然,min = (left + right) >> 1; 用位云算能节省一些时间呢!
现在呢?又是纠结的时候了吧?if(num[mid] > e)?if(num[mid] >= e)?我们也随便选一个吧,if(num[mid] > e)
其实,下面你会不断纠结……right = mid;这是正常人的写法,但是有时候也会看到别人写成right = mid - 1;我们也考虑下吧,但是现在我们就直接写right = mid;
有if了当然也会有else,然后理所当然 left = mid;同样记住还有一个选择left = mid + 1;
不错,整个while循环搞定了,最后就是返回了,写下return的时候是不是又纠结了?left?right?mid?算了,就写mid吧,整个程序就写好了,如下:
补充好整个程序后运行吧!查找2、3、4的时候都没有结果出现!!比如查4的时候,单步调试会发现当mid=4,left=4,right=5,接下来就一直在while里循环,保持不变!问题到底在哪?问题在很多地方,因为我们上面的遇到很多选择,没有结果是多个选择作用的共同的结果,通过修修补补也可以得到想要的结果,其它例子就不举了,直接说说本文章讨论的关键吧。我总结的二分无非就4种情况:YES_LEFT、YES_RIGHT、NO_LEFT、NO_RIGHT,分别代表:能找到且返回最左边的数的位置(如查找4的时候返回位置3)、能找到且返回最右边的数的位置(如查找4的时候返回位置4)、不能找到且返回左边与其接近的数的位置(如查找3的时候返回位置2)、不能找到且返回右边与其接近的数的位置(如查找3的时候返回位置3)。下面是我总结调试的代码:
对于YES_LEFT或者NO_RIGHT
对于YES_RIGHT或者NO_LEFT
不做过多说明,单步调试自然会发现执行过程,要说明的是,两个程序都用了right = mid - 1; left = mid +1;用这个的前提是终止条件要是left <= right。要注意的是,有的二分查找不是只需要四种情况中的一种,而是组合使用,比如查找一个数,如果找到则×××不然则×××,如果是YES_LEFT || NO_RIGHT组合或者YES_RIGHT || NO_LEFT组合就直接用上面代码即可,否则就要综合用了,加一些判断等说明,因为用的时候不多,就不给出代码了,自己如果遇到可以试着写写,当成模板,以后直接用~
。。.。。。。。。。。分割线。。。。。。。。。。。。。
如果你以为仅仅是这样那就太单纯了,你可以试试看能不能找出我的代码错误,代码如下:
很难发现错误吧,嗯,即使你拿到 IDE 上自己实现 guess 函数调试一下,也会发现,怎么可能错,我都试了这么多组数据。
我也是在提交后看到没有通过的测试数据才知道哪里错了。没有通过的测试是:
相信大家也看出来了,
好吧,改代码,把加法改成减法:
好的,提交,还是不对,更不对了!
原因在哪,
== 和 != 高于赋值符。eg:
好吧,改代码:
总算完成了。
这道简单题只有32%的通过率。。
这道题讲的是一个猜数字的游戏,游戏的内容是这样的:首先我从 1 到 n 这 n 个数中随意的选中一个数,然后记下这个数,然后由你来猜我选中的这个数,每当你猜一次,我就会告诉你你猜的数是大了还是小了。这里有一个已经写好的函数
int guess(int num);如果我选的数比你猜的数小就返回 -1,如果我选中的数比你猜的数大了就返回 1,如果猜对了就返回 0;
要求实现函数
int guessNumber(int n);,输入 n 是上面说到的 n ,返回值是我选中的数,这个函数中要调用
guess函数。
写的过程中总是出现各种错误,在这里就不一点点讲述,因为已经有前辈写了,觉得很好,先拿过来,虽然有一点点区别。(引用请注明出处:http://blog.csdn.net/int64ago/article/details/7425727)。
内容也搬过来,下面开始
。。.。。。。。。。。分割线。。。。。。。。。。。。。
看到这个标题无论你是处于怎样的心理进来看了,我觉得都是值得的。因为这个问题太简单,任何一个开始接触“真正”算法基本都是从二分查找开始的。至于二分查找都不知道是什么的可以先去找别的资料看下,再来看这篇文章。既然很简单,那么我们开始一起写一个吧,要求是对num[]={1,2,2,4,4,8,10}不减序列在区间[0,7)进行查找,当然我们得首先保证要查找的数e满足:num[0] <= e <= num[0],这个是很容易做到的,为了简化又不失去代表性,e选取2、3、4这三个数。我们就一起开始写吧:
首先,很容易的写下 int bSearch(int begin, int end, int e)
然后,很自然的定义 int mid, left = begin, right = end;
接下来怎么写呢?while(left < right)?while(left <= right)?while(mid == left)?while(mid == right)?………………真正一个写程序人会想纠结好一会儿,我们就选一个吧while(left < right)
下面,也很自然,min = (left + right) >> 1; 用位云算能节省一些时间呢!
现在呢?又是纠结的时候了吧?if(num[mid] > e)?if(num[mid] >= e)?我们也随便选一个吧,if(num[mid] > e)
其实,下面你会不断纠结……right = mid;这是正常人的写法,但是有时候也会看到别人写成right = mid - 1;我们也考虑下吧,但是现在我们就直接写right = mid;
有if了当然也会有else,然后理所当然 left = mid;同样记住还有一个选择left = mid + 1;
不错,整个while循环搞定了,最后就是返回了,写下return的时候是不是又纠结了?left?right?mid?算了,就写mid吧,整个程序就写好了,如下:
int bSearch(int begin, int end, int e) { int mid, left = begin, right = end; while(left < right) { mid = (left + right) >> 1; if(num[mid] > e) right = mid; else left = mid; } return mid; }
补充好整个程序后运行吧!查找2、3、4的时候都没有结果出现!!比如查4的时候,单步调试会发现当mid=4,left=4,right=5,接下来就一直在while里循环,保持不变!问题到底在哪?问题在很多地方,因为我们上面的遇到很多选择,没有结果是多个选择作用的共同的结果,通过修修补补也可以得到想要的结果,其它例子就不举了,直接说说本文章讨论的关键吧。我总结的二分无非就4种情况:YES_LEFT、YES_RIGHT、NO_LEFT、NO_RIGHT,分别代表:能找到且返回最左边的数的位置(如查找4的时候返回位置3)、能找到且返回最右边的数的位置(如查找4的时候返回位置4)、不能找到且返回左边与其接近的数的位置(如查找3的时候返回位置2)、不能找到且返回右边与其接近的数的位置(如查找3的时候返回位置3)。下面是我总结调试的代码:
对于YES_LEFT或者NO_RIGHT
int bSearch(int begin, int end, int e) { int mid, left = begin, right = end; while(left <= right) { mid = (left + right) >> 1; if(num[mid] >= e) right = mid - 1; else left = mid + 1; } return left; }
对于YES_RIGHT或者NO_LEFT
int bSearch(int begin, int end, int e) { int mid, left = begin, right = end; while(left <= right) { mid = (left + right) >> 1; if(num[mid] > e) right = mid - 1; else left = mid + 1; } return right; }
不做过多说明,单步调试自然会发现执行过程,要说明的是,两个程序都用了right = mid - 1; left = mid +1;用这个的前提是终止条件要是left <= right。要注意的是,有的二分查找不是只需要四种情况中的一种,而是组合使用,比如查找一个数,如果找到则×××不然则×××,如果是YES_LEFT || NO_RIGHT组合或者YES_RIGHT || NO_LEFT组合就直接用上面代码即可,否则就要综合用了,加一些判断等说明,因为用的时候不多,就不给出代码了,自己如果遇到可以试着写写,当成模板,以后直接用~
。。.。。。。。。。。分割线。。。。。。。。。。。。。
如果你以为仅仅是这样那就太单纯了,你可以试试看能不能找出我的代码错误,代码如下:
// Forward declaration of guess API. // @param num, your guess // @return -1 if my number is lower, 1 if my number is higher, otherwise return 0 int guess(int num); class Solution { public: int guessNumber(int n) { int left=1,mid=0,gus=0; while(left<=n){ mid=(left+n)>>1; gus=guess(mid); if(!gus) return mid; else if(gus==1){ left=mid+1; }else{ n=mid-1; } } return -1; } };
很难发现错误吧,嗯,即使你拿到 IDE 上自己实现 guess 函数调试一下,也会发现,怎么可能错,我都试了这么多组数据。
我也是在提交后看到没有通过的测试数据才知道哪里错了。没有通过的测试是:
2126753390 1702766719
相信大家也看出来了,
n的取值已经二十多亿了,而
int类型最大也就二十多亿,这样的话
n + left就溢出了!
好吧,改代码,把加法改成减法:
// Forward declaration of guess API. // @param num, your guess // @return -1 if my number is lower, 1 if my number is higher, otherwise return 0 int guess(int num); class Solution { public: int guessNumber(int n) { int left=1,mid=0,gus=0; while(left<=n){ mid=left+(n-left)>>1; gus=guess(mid); if(!gus) return mid; else if(gus==1){ left=mid+1; }else{ n=mid-1; } } return -1; } };
好的,提交,还是不对,更不对了!
原因在哪,
+的优先级要大于
>>,顺便说一下,类似的还有
==和
!=的优先级高于位操作符。eg:
val & mask != 0;被误认为
(val & mask) != 0
== 和 != 高于赋值符。eg:
c=getchar() != EOF被误认为
(c = getchar() ) != EOF
好吧,改代码:
// Forward declaration of guess API. // @param num, your guess // @return -1 if my number is lower, 1 if my number is higher, otherwise return 0 int guess(int num); class Solution { public: int guessNumber(int n) { int left=1,mid=0,gus=0; while(left<=n){ mid=left+((n-left)>>1); gus=guess(mid); if(!gus) return mid; else if(gus==1){ left=mid+1; }else{ n=mid-1; } } return -1; } };
总算完成了。
这道简单题只有32%的通过率。。
相关文章推荐
- 书评:《算法之美( Algorithms to Live By )》
- 动易2006序列号破解算法公布
- C#数据结构之顺序表(SeqList)实例详解
- C#递归算法之分而治之策略
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- C#算法之大牛生小牛的问题高效解决方法
- Lua教程(七):数据结构详解
- C#算法函数:获取一个字符串中的最大长度的数字
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- C#数据结构之队列(Quene)实例详解
- C#数据结构揭秘一
- C#数据结构之单链表(LinkList)实例详解
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- 经典排序算法之冒泡排序(Bubble sort)代码