当前位置:   article > 正文

专题三——二分算法

专题三——二分算法

目录

原理

模板

朴素二分算法

非朴素二分算法

一二分查找

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

三点名

四x的平方根

五搜索插入位置 

六山脉数组的峰顶索引

七寻找峰值 

八寻找旋转排序数组中的最小值


原理

定义两个指针:left指向数组第一个元素,right指向数组的最后一个元素;通过某种条件的判断,让left与right向之间靠拢;最终left与right所指的元素来确定最后的答案

模板

二分算法的实现有两个模板:朴素二分算法与非朴素二分算法

朴素二分算法

  1. while(left <= right)
  2. {
  3. int middle = left+(right-left)/2 //写成(right-left)/2有越界的风险
  4. if(...)
  5. {
  6. left =middle + 1;
  7. }
  8. else
  9. {
  10. right = middle - 1;
  11. }
  12. }

非朴素二分算法

  1. //左端点
  2. while(left < right) //left <= right会越界
  3. {
  4. int middle = left + (right - left) / 2;//left +(right - left + 1) /2会死循环
  5. if(...)
  6. {
  7. left = middle + 1;
  8. }
  9. else
  10. {
  11. right = middle;
  12. }
  13. }
  14. //右端点
  15. while(left < right) //left <= right会越界
  16. {
  17. int middle = left + (right - left +1) / 2;
  18. if(...)
  19. {
  20. left = middle;
  21. }
  22. else
  23. {
  24. right = middle - 1;
  25. }
  26. }

大部分情况的二分算法题用的都是非朴素二分算法模板来实现的!

二分查找

oj链接:二分查找

思路:懂了模板直接套用朴素二分算法解决:

当nums[middle] < target ; left=middle + 1;else right = middle - 1

  1. class Solution {
  2. public:
  3. int search(vector<int>& nums, int target) {
  4. int left=0,right=nums.size()-1;
  5. while(left<=right)
  6. {
  7. int middle=left+(right-left)/2;//防溢出
  8. if(nums[middle]<target)
  9. {
  10. left=middle+1;
  11. }
  12. else if(nums[middle]>target)
  13. {
  14. right=middle-1;
  15. }
  16. else
  17. {
  18. return middle;
  19. }
  20. }
  21. return -1;
  22. }
  23. };

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

oj链接:在排序数组中查找元素的第一个和最后一个位置

朴素二分算法解决不了,用非朴素二分模板来解决:

题目要求第一个与最后一个位置,直接用两个非朴素模板求出与target相同的左右两个端点

  1. class Solution
  2. {
  3. public:
  4. vector<int> searchRange(vector<int>& nums, int target)
  5. {
  6. //越界判断
  7. if(nums.size() == 0) return {-1 , -1};
  8. //开始元素下标(求左端点)
  9. vector<int> ans(2);
  10. int left = 0 , right = nums.size() - 1;
  11. while(left < right)
  12. {
  13. int middle = left + (right - left) / 2;
  14. if(nums[middle] < target) left = middle + 1;
  15. else right = middle;
  16. }
  17. //有可能没有target
  18. if(nums[left] != target) return {-1 , -1};
  19. ans[0] = left;
  20. //最后元素下标(求右端点)
  21. //有的话在left的右边,left不用再次走到开始位置
  22. right = nums.size() -1;
  23. while(left < right)
  24. {
  25. int middle = left +(right - left +1) /2;
  26. if(nums[middle] <= target) left = middle;
  27. else right = middle -1;
  28. }
  29. ans[1] = right;
  30. return ans;
  31. }
  32. };

三点名

oj链接:点名

思路:此题的思路有很多:等差数列求和,哈希表,异或...

但这道题给出的条件适合来用二分算法解决:

判断数值与下标是否对应来进行left与right的移动 

  1. class Solution {
  2. public:
  3. int takeAttendance(vector<int>& records)
  4. {
  5. int left=0,right=records.size()-1;
  6. while(left<right)
  7. {
  8. int middle=left+(right-left)/2;
  9. if(middle==records[middle]) left=middle+1;
  10. else right=middle;
  11. }
  12. //有可能数组里面与下标都对应上了返回后一个数字->[0,1]
  13. if(records[left]==left) return left+1;
  14. return left;
  15. }
  16. };

四x的平方根

oj链接:x 的平方根 

思路:从[1,x]中来找出符合某数的平方根<=x:如果在里面的某个值的平方根>x的,说明目标值是在这个值的左边区域,缩小范围更新right的值:是要等于它还是在它前一个位置就要自己来画图分析了;最后用非朴素模板套进去就解决本道题的求

五搜索插入位置 

oj链接:搜索插入位置

思路: 套用非朴素模板解决,注意最后结果的处理

  1. class Solution
  2. {
  3. public:
  4. int searchInsert(vector<int>& nums, int target)
  5. {
  6. int left = 0,right = nums.size() - 1;
  7. while(left < right)
  8. {
  9. int middle = left +(right -left)/2;
  10. if(nums[middle] < target) left = middle + 1;
  11. else right = middle;
  12. }
  13. //left与right走到数组最后的处理
  14. if(right == nums.size() - 1 && nums[right] < target) return right + 1;
  15. else return right;
  16. }
  17. };

六山脉数组的峰顶索引

oj链接:山脉数组的峰顶索引

 思路:山脉数组将数组分为两个区间:

左半边是递增的:arr[i] > arr[i-1](包含峰值);右半边是递减的:arr[i] < arr[i-1](不包含)

有了这个二段性,我们就可以来利用二分算法来解决问题,使得最后left与right指向峰顶

  1. class Solution {
  2. public:
  3. int peakIndexInMountainArray(vector<int>& arr)
  4. {
  5. int left = 0,right = arr.size() - 1;
  6. while(left < right)
  7. {
  8. int middle = left + (right - left + 1) / 2;
  9. //middle在左半区间
  10. if(arr[middle] > arr[middle - 1]) left = middle;
  11. //middle在右半区间
  12. else if(arr[middle] < arr[middle -1]) right = middle -1;
  13. }
  14. return left;
  15. }
  16. };

七寻找峰值 

 oj链接:寻找峰值

思路:要找数组其中一个峰值转化为求数组的峰值,与上面思路是一样的!

  1. class Solution {
  2. public:
  3. int findPeakElement(vector<int>& nums)
  4. {
  5. int left = 0,right =nums.size() - 1;
  6. while(left < right)
  7. {
  8. int middle = left + (right - left) / 2;
  9. if(nums[middle] < nums[middle+1]) left = middle + 1;
  10. else if(nums[middle] > nums[middle + 1]) right = middle;
  11. }
  12. return left;
  13. }
  14. };

八寻找旋转排序数组中的最小值

oj链接:寻找旋转排序数组中的最小值

思路: 将旋转数组分为两段;AB段前n(旋转的次数)个数值;CD是剩余的个数;我们来拿最后一个元素(nums[n - 1])作参照物,有这样的一个规律:

AB段的值nums[i]一定>nums[n - 1];CD段的值nums[j]<=nums[n - 1];

有了二段性,我们就可以用二分算法来解决问题,只需来判断left与right的移动就完成

  1. class Solution {
  2. public:
  3. int findMin(vector<int>& nums)
  4. {
  5. int left = 0,right = nums.size() - 1;
  6. while(left < right)
  7. {
  8. int middle = left + (right - left) / 2;
  9. if(nums[middle]>nums[nums.size()-1]) left=middle+1;
  10. else right=middle;
  11. }
  12. return nums[right];
  13. }
  14. };

那上面的参照物能用最左边的数来解决吗?好像也类似?

问题转换为能过测试用例[1,2]和[2,1]来思考!(坑帮你们填了)QAQ 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/399177
推荐阅读
相关标签
  

闽ICP备14008679号