目录
哪怕没有学过编程的同学,也许不知道二分法这个名字,但也一定接触过它的核心思想。不了解的同学也没关系,我用一句话就能概括出它的精髓:将一个区间一分为二,每次都舍弃其中的一部分。
二分法能够极大地降低我们在解决问题时的时间复杂度。假如你要在一个单调递增的数组a[n]中寻找一个数target,遍历的话时间复杂度是O(n)。但如果采用二分法,时间复杂度则是O(log n)。这种效率的提高无疑是巨大的!
1、while循环中是写 left < right 还是写 left <= right ?
2、如下图所示,if(nums[mid]>target),右边界更新区间时是写成 right = mid 还是 right = mid-1?

这两点是很容易弄混的。要弄清楚上面这两个问题的答案,首先要确定你想写的循环的区间到底是左闭右开还是左闭右闭?(题目没明确要求的话就看你个人选择,都是可以的)
左闭右闭的区间是这样写的:[left, right]
它包含了 left 和 right 这两个数
左闭右闭的区间是这样写的:[left, right)
它包含了 left,但不包含 right 。
假设我选择了左闭右闭,此时需要在数组nums中寻找一个数target,找到的话返回下标,没找到的话返回 -1,伪代码如下:
left=0;
right=nums.size()-1;
while(l <= r){ // 注意这里不是 l < r
int mid = l + (r - l)/2;
if(nums[mid] > target) right = mid-1;
else if(nums[mid] < target) left = mid+1;
else{
return mid;
}
}
首先,为什么选择左闭右闭的区间,第3行就要写成<=呢?因为写入while循环的判断条件应该与你选择的区间相吻合,而左闭右闭的区间包含了left和right,也就是说可能会出现 left== right的情况。如果写成<,那么就漏掉了left==right 这种情况,所以应该写成<=。
其次,第5行右边界为什么更新为 mid-1 而不是更新为 mid?因为我们已经确定了nums[mid] > target,所以mid一定不是我们要找的值,因此在下一轮搜索中,不应再包含mid这个数。而我们选择的是左闭右闭区间,它包含了左右边界,每次搜索时都会包含左右边界。所以为了使已经被排除的mid不被再次搜索,右边界应该更新为mid-1。
假设我选择了左闭右开,此时需要在数组nums中寻找一个数target,找到的话返回下标,没找到的话返回 -1,伪代码如下:
left=0;
right=nums.size();
while(l < r){ // 注意这里不是 l <=r
int mid = l + (r - l)/2;
if(nums[mid] > target) right = mid;
else if(nums[mid] < target) left = mid+1;
else{
return mid;
}
}
return -1;
左闭右开代表包含左边界,不包含右边界。右边界一定比左边界大,不存在 right == left 的情况。所以第3行只能写成<。
而第5行之所以写成right=mid,是因为下一次搜索中本就不会包含right,我们也确定了right不是要找的值,所以直接写成right=mid就可以了。
此时,注意第2行,因为左闭右开不包含右边界,所以右边界要设置为nums.size(),这样才能把nums.size()-1包含在搜索区间里。
如果你已经理解了上面的内容,可以尝试做下面这道题:

左闭右闭:
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1;
while(l <= r){
int m = l + (r - l)/2;
if(nums[m] > target)r = --m;
else if(nums[m] < target) l = ++m;
else{
return m;
}
}
return -1;
}
};
左闭右开:
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size();
while(l < r){
int m = l + (r - l)/2;
if(nums[m] > target)r = m;
else if(nums[m] < target) l = ++m;
else{
return m;
}
}
return -1;
}
};
尽管理解了上面的内容,但很多时候解题还是会遇到困难。
比如说给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
你会发现上面的两个代码很难应用到这种题,所以我找到了一个万能模板,跟大家分享一下:
class Solution {
/**
* 如果返回值等于 nums.size(),代表 nums 中不存在 满足 nums[i] >= target 的 i,也就是所有元素都小于 target。
* 否则返回满足 nums[i] >= target 的最小 i(最左 i)。也就是说,如果有多个连续的 target,会返回最靠左的那个的下标。
* nums为单调不减数组
* target为边界值
**/
public: int binarySearch(vector<int>& nums, int target) {
int l = 0, r = nums.size()- 1; // 二分查找的左右初始边界
while(l <= r){ // 注意这里不是 l < r
int m = l + (r - l)/2;
if(nums[m] >= target)r = --m;
else l = ++m;
}
return l; // 此时,l 代表arr[i] >= x 的最小 i。
}
};
如何运用这个模板去解决不同的问题呢?请往下看
1、查找某个数 x 首次出现的位置,如果不存在,返回-1。
如果 binarySearch(arr, x) == nums.size(),代表所有元素都小于x,也就无法查找到 x 首次出现的位置;如果有多个元素等于 x,则 binarySearch(arr, x) 代表 x 首次出现的位置的下标;如果binarySearch(arr, x) != x,则不存在某个数等于 x,binarySearch(arr, x) 代表最靠左的大于 x 的数。
2、查找某个数 x 最后出现的位置,如果不存在,返回-1。
将该问题转换为用 int ret = binarySearch(arr, x+1) - 1 来解决。 如果ret < 0,返回 -1;否则,如果nums[ret] == x,ret 就是答案;否则,返回 -1。
3、查找某个数 x 首次出现的位置,如果不存在 x,则求出适合插入 x 的位置。
binarySearch(arr, x)就是。
4、查找小于x的最后一个数。
转换为用binarySearch(arr, x) - 1来解决,注意判断存在性。
5、查找小于x的第一个数。
这是个伪问题。
6、查找大于x的第一个的数。
转换为用binarySearch(arr, x + 1)来解决,注意判断存在性。
7、查找大于x的最后一个的数。
这是个伪问题。
举例:
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
提示:
1 <= nums.length <= 1 0 4 10^4 104
- 1 0 4 10^4 104 <= nums[i] <= 1 0 4 10^4 104
nums 为 无重复元素 的 升序 排列数组
- 1 0 4 10^4 104 <= target <= 1 0 4 10^4 104
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size() - 1; // 二分查找的左右初始边界
while(l <= r){ // 注意这里不是 l < r
int m = l + (r - l)/2;
if(nums[m] >= target)r = --m;
else l = ++m;
}
return l; // 此时,l 代表arr[i] >= tarhget的最小 i。
}
};
1、二分查找万能模板
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我刚刚被困在这个问题上一段时间了。以这个基地为例:moduleTopclassTestendmoduleFooendend稍后,我可以通过这样做在Foo中定义扩展Test的类:moduleTopmoduleFooclassSomeTest但是,如果我尝试通过使用::指定模块来最小化缩进:moduleTop::FooclassFailure这失败了:NameError:uninitializedconstantTop::Foo::Test这是一个错误,还是仅仅是Ruby解析变量名的方式的逻辑结果? 最佳答案 Isthisabug,or
我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s
我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h
所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP
我有一个应用需要发送用户事件邀请。当用户邀请friend(用户)参加事件时,如果尚不存在将用户连接到该事件的新记录,则会创建该记录。我的模型由用户、事件和events_user组成。classEventdefinvite(user_id,*args)user_id.eachdo|u|e=EventsUser.find_or_create_by_event_id_and_user_id(self.id,u)e.save!endendend用法Event.first.invite([1,2,3])我不认为以上是完成我的任务的最有效方法。我设想了一种方法,例如Model.find_or_cr
我想找到给定字符串中的所有匹配项,包括重叠匹配项。我怎样才能实现它?#Example"a-b-c-d".???(/\w-\w/)#=>["a-b","b-c","c-d"]expected#Solutionwithoutoverlappedresults"a-b-c-d".scan(/\w-\w/)#=>["a-b","c-d"],but"b-c"ismissing 最佳答案 在积极的前瞻中使用捕获:"a-b-c-d".scan(/(?=(\w-\w))/).flatten#=>["a-b","b-c","c-d"]参见Rubyde
这应该是一个简单的问题,但我找不到任何相关信息。给定一个Ruby中的正则表达式,对于每个匹配项,我需要检索匹配的模式$1、$2,但我还需要匹配位置。我知道=~运算符为我提供了第一个匹配项的位置,而string.scan(/regex/)为我提供了所有匹配模式。如果可能,我需要在同一步骤中获得两个结果。 最佳答案 MatchDatastring.scan(regex)do$1#Patternatfirstposition$2#Patternatsecondposition$~.offset(1)#Startingandendingpo
一般来说,我是Middleman和ruby的新手。我已经安装了Ruby我已经安装了Middleman和gem以使其运行。我需要使用slim而不是默认的模板系统。所以我安装了Slimgem。Slim的网站只说我需要'slim'才能让它工作。中间人网站说我只需要在config.rb文件中添加模板引擎,但是没有给出例子...对于没有ruby背景的人来说,这没有帮助。我在git上找了几个config.rb,它们都有:require'slim'和#Setslim-langoutputstyleSlim::Engine.set_default_options:pretty=>true#Se
我有以下数组:arr=[1,3,2,5,2,4,2,2,4,4,2,2,4,2,1,5]我想要一个包含前三个奇数元素的数组。我知道我可以做到:arr.select(&:odd?).take(3)但我想避免遍历整个数组,而是在找到第三个匹配项后返回。我想出了以下解决方案,我相信它可以满足我的要求:my_arr.each_with_object([])do|el,memo|memo但是有没有更简单/惯用的方法来做到这一点? 最佳答案 使用lazyenumerator与Enumerable#lazy:arr.lazy.select(&:o