leetcode学习记录_二分法

虽然写了二分法的技巧文章,但是我才发现我还没有二分法题目的文章,都放到其他文章里去了。


先来个简单的:
69. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
来源:力扣(LeetCode)
本来是想按照惯例用左闭右开区间的,但考虑到给的x可能是INT_MAX,如果要开区间,R就得用long型了,所以还是用闭区间吧,所以while就得用(L <= R)

class Solution {
public:
    int mySqrt(int x) {
        int L = 0, R = x, res = 0;
        while(L <= R)
        {
            int mid = L+((R-L)>>1);
//这里mid*mid别用括号括起来,因为强制转换的优先级比括号低一级,
//如果括了起来,在计算时就有可能溢出            
            long  temp = (long)mid*mid;
            if(temp < x) L = mid+1;
            else if(temp > x)   R = mid-1;
            else return mid;
        }
        return L-1;
    }
};


41. 缺失的第一个正数

这一题用二分法其实时间复杂度并不满足进阶条件
要满足进阶条件得用原地哈希(置换)法👉这里

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

进阶:你可以实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案吗?
示例 1:
输入:nums = [1,2,0]
输出:3
来源:力扣(LeetCode)

思路:
先排序,然后从1到N= nums.size(),一个个的查找,看在数组中是否有当前数字,找到了就继续寻找下一个,要是没找到就return,

特殊情况:要是1到N都找完了,发现每一个都存在,那就返回N + 1

可能有人会疑问,为什么枚举1到N就可以了,数组中的正数并不局限在1到N的范围内啊?
那是因为题目要求的是第一个缺失的正数,如果数组中有大于N的数字,那么肯定相对的有缺失的数字,且这个缺失数字就在1到N之间

假如数组的正数全部都比N大呢?那不就缺失1嘛?所以就这一题来说,直接枚举1到N是没问题的

代码:

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        int N = nums.size();
        for(int i = 1;i <= N;i++)
        {//枚举1到N
            bool flag = false;
            int L = 0, R = N;//左闭右开
            while( L < R)
            {
                int mid = L + ((R - L)>>1);
                if(nums[mid] == i)
                {//找到了就做个标记,并且跳出循环准备开始下一个数字的判断
                    flag = true;break;
                } 
                else if(nums[mid] > i) R = mid;
                else L = mid + 1;
            }
            if(!flag) return i;//如果没找到就返回当前数字
        }
        return N+1;
    }
};


34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
来源:力扣(LeetCode)

没啥好说的,这题一看见有序+查找,自然而然的想到二分,只要注意一下特殊情况就好

特殊情况1
数组长度为0

特殊情况2
数组中没有目标值

没有目标值也可以分为两种情况
特殊情况2.1
目标值不在数组的范围中,即小于最小值,或大于最大值
特殊情况2.2
目标值在数组的范围中,但是缺失了这个值

思路:
排除特殊情况1特殊情况2.1后,用二分法寻找目标值,然后再次排除特殊情况2.2

然后就能确定目标值一定在数组中,并且我们的边界已经定位到了目标值

最后就在不越界的情况下,让两个指针从L出发,一个往左,一个往右,确定相同目标值的边界

代码:

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.empty())return vector<int>{-1,-1};//特殊情况1
        //特殊情况2.1
        if(target<nums[0] || target>nums[nums.size()-1])return vector<int>{-1,-1};
        int L = 0, R = nums.size();
        while(L<R)
        {//二分法定位目标值,找到了就返回
            int mid = L+((R-L)>>1);
            if(nums[mid]==target)
            {L = mid;break;}
            else if(nums[mid]<target)L = mid+1;
            else R = mid;
        }
        if(nums[L] != target) return vector<int>{-1,-1};//特殊情况2.2
        int start = L,end = L;//定义两个指针
        while(start>=0&&nums[start]==target)--start;
        while(end<nums.size()&&nums[end]==target)++end;
        //别忘了+1和-1,因为经过while()后,两指针指向的值已经不等于目标值了,甚至可能越界
        return vector<int>{start+1,end-1};
    }
};


1011. 在 D 天内送达包裹的能力

传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。

传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。

返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。

示例 1:

输入:weights = [1,2,3,4,5,6,7,8,9,10], D = 5
输出:15
解释:
船舶最低载重 15 就能够在 5 天内送达所有包裹,如下所示:
第 1 天:1, 2, 3, 4, 5
第 2 天:6, 7
第 3 天:8
第 4 天:9
第 5 天:10	

请注意,货物必须按照给定的顺序装运,因此使用载重能力为 14 的船舶并将包装分成 (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) 是不允许的。

来源:力扣(LeetCode)

思路:

在最小运载量和最大运载量之间疯狂的二分搜索就行,二分的条件是,当前运载量所需天数need与days的比较

那这题除了二分以外,主要问题就是求最小运载量和最大运载量,还有求need

最小运载量

很明显,最小运载量是货物中最重的那个,不然遇到这个货物的话,根本运不了嘛

最大运载量就是货物重物总和,以防days给出的时间限制是一天

need也很好算,遍历一次货物数组就行,默认need为1,不用解释吧?
然后计算货物的总和sum,一旦总和大于当前运载量,need+1,因为今天运不完了,只能留到明天了,然后总和更新为当前货物的重量,因为这个货物在昨天没运走,留到了今天,自然得用sum记录下来

然后还得注意一下,不是满足need == days就行了的,因为此时的运载量不是最佳的,所以need = = days归在 need < days一类里,把它当作需要优化的状况 ,所谓优化就是当前运载量还能再减少

即if(need <= days) right = mid;

最后说一说开区间还是闭区间

对于这题来说,按理来说是闭区间更好,因为最大运载量也是有效值,而开区间得话,边界的取值是无意义的,但是就算无意义,最后L也会达到那个边界,反正这里也没数组,不会越界所以无所谓了,当然也可以在右边界最后再++,强行让它无意义,不过结果没什么区别就是了

所以这一题用开还是必都无所谓

代码:(强行无意义开区间)

class Solution {
public:
    int shipWithinDays(vector<int>& weights, int days) {
        int left = INT_MIN, right = 0;
        for(int &num : weights)
        {//求出两个边界
            left = max(left, num);
            right += num;
        }
        ++right;//完全无意义的一步
        while(left < right)//别忘了这里是开闭区间的 区别之一
        {
            int mid = left+((right-left)>>1);
            int need = 1, sum = 0;
            for(int&num : weights)
            {
                sum += num;
                if(sum>mid)
                {//计算need
                    sum = num;
                    ++need;
                }
            }
            if(need>days)left = mid+1;//左边界收缩
            else if(need<=days)right = mid;//右边界收缩
        }
        return left;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值