• 【算法】二分相关题目


    二分相关

    二分查找

    https://leetcode.cn/problems/binary-search/

    在一个有序数组当中,查找值为target的元素,返回下标

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            int left = 0,right = nums.size()-1;
            //当二者指向同一个元素的时候也要判断
            //例如:nums:[5] target:5 如果写成left < right 那么返回-1是错误的!应该返回的是0
            while(left <= right) 
            {
                int mid = (left + right) / 2;
                if(nums[mid] > target)
                    right = mid - 1; //去左侧找
                else if(nums[mid] < target)
                    left = mid + 1; //去右侧找
                else 
                    return mid;
            }   
            return -1;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

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

    https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

    给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置

    • 本质是:在有序数组当中,找>=某个数最左侧的位置,找<=某个数最右侧的位置
    • 但是这里要求必须要找到严格等的位置

    image-20230911214124788

    class Solution {
    public:
        vector<int> searchRange(vector<int>& nums, int target) {
            int n = nums.size();
            vector<int> ans;
            if(n == 0) return {-1,-1};
            //1.找>=tarhet元素的最左边位置
            int left = 0,right = n-1;
            int index = 0;//记录位置
            while(left <= right)
            {
                int mid = (left + right) / 2;
                if(nums[mid] >= target)
                {
                    index = mid;
                    right = mid -1; //去左侧找更接近的
                }
                else 
                    left = mid + 1;
            }
            //判断数组是否存在值为target的元素
            if(nums[index] != target) return {-1,-1};
            
            ans.push_back(index);
    
            //2.找<=target元素的最右边位置
            left = 0,right = n - 1;
            while(left <= right)
            {
                int mid = (left + right) / 2;
                if(nums[mid] <= target)
                {
                    index = mid;
                    left = mid +1;//去右侧找更接近的答案
                }
                else 
                    right = mid - 1;
            }
            ans.push_back(index);
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    寻找峰值

    https://www.nowcoder.com/practice/fcf87540c4f347bcb4cf720b5b350c76?tpId=295&tqId=2227748&ru=/exam/company&qru=/ta/format-top101/question-ranking&sourceUrl=%2Fexam%2Fcompany

    给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。

    1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于

    2.假设 nums[-1] = nums[n] = − ∞ −∞

    3.对于所有有效的 i 都有 nums[i] != nums[i + 1]

    image-20230911215013466


    方法1:直接遍历数组,如果当前元素比周围两个元素都大,说明当前元素就是峰值元素

    int findPeakElement(vector<int>& nums) 
    {
        //特判第一个元素和最后一个元素
        int n = nums.size();
        if(nums[0] > nums[1]) return 0;
        if(nums[n-1] > nums[n-2]) return n - 1;
        //判断[1,n-2]位置的元素,看哪个位置的元素满足比周围元素都大
        for(int i = 1;i<=n-2;i++)
        {
            if(nums[i] > nums[i-1] && nums[i] > nums[i+1])
                return i;
        }
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    方法2:二分查找

    只要出现 /\这种形式,就是峰值,如果中间位置不是峰值,那么肯定有一边的值比中间位置的值大

    int findPeakElement(vector<int>& nums) {
        //特判第一个元素和最后一个元素
        int n = nums.size();
        if (nums[0] > nums[1]) return 0;
        if (nums[n - 1] > nums[n - 2]) return n - 1;
        //在[1,n-2]范围上进行二分
        int left = 1,right = n-2;
        while(left <= right)
        {
            int mid = (left + right)  /2;
            if(nums[mid] < nums[mid+1]) //从mid到mid+1呈现上升趋势(/)
            {
                left = mid + 1;//去右边找下降(\)趋势,就能找到峰值
            }
            else if(nums[mid] < nums[mid-1]) //mid-1到mid呈现下降趋势(\)
            {
                right = mid - 1;//去左边找上升(/)趋势,就能找到峰值
            }
            else  //nums[mid] > nums[mid+1] && nums[mid] > nums[mid-1]
                return mid;
        }
        return -1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    x 的平方根

    https://leetcode.cn/problems/sqrtx/

    image-20220610213413702

    方法1:暴力求解:枚举 [ 0 , x ] [0,x] [0,x]的所有数进行判断

    class Solution {
        public:
        int mySqrt(int x) {
            // 由于两个较⼤的数相乘可能会超过 int 最⼤范围  因此⽤ long long
            long long i = 0;
            for (i = 0; i <= x; i++)
            {
                // 如果两个数相乘正好等于 x,直接返回 i
                if (i * i == x) return i;
                // 如果第⼀次出现两个数相乘⼤于 x,说明结果是前⼀个数
                if (i * i > x) return i - 1;
            }
            // 为了处理oj题需要控制所有路径都有返回值
            return -1;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    方法2:使用二分。用long是为了防止mid*mid过大导致溢出!当然也可以将判断条件写成下面这种: i f ( m i d ∗ m i d > x ) 等价于 = > i f ( m i d > ( x / m i d ) ) if(mid*mid>x) 等价于=> if(mid>(x/mid)) if(midmid>x)等价于=>if(mid>(x/mid))

    image-20230907221513216

    class Solution {
    public:
        int mySqrt(int x) {
            //二分法
            if(x == 0)    return 0;
            if(x<=3) return 1;
            //在[1,x]范围进行二分
            long left = 1;
            long right = x;
            long ans = 1;//记录答案
            while(left <= right)//当left和right指向同一个数也需要判断
            {
                long mid = left +(right - left)/2;
                if(mid*mid > x)
                {
                    //去左边二分
                    right = mid-1;
                }
                else
                {
                    //记录答案,继续去右边查找
                    left = mid+1;
                    ans = mid;
                }
            }
            return (int)ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    0~n-1中缺失的数字

    https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/description/

    一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字

    输入: [0,1,3]		输出: 2		输入: [0,1,2,3,4,5,6,7,9]		输出: 8
    
    • 1

    因为数组是有序的,方法1:使用哈希表 方法2:直接遍历 方法3:使用位运算 方法 4:高斯求和公式

    class Solution {
    public:
        //方法1:使用哈希表
        int missingNumber(vector<int>& nums) {
          unordered_map<int,int> um;
          int n = nums.size();
          for(auto& x:nums)  //在哈希表当中做映射
            um[x]++;
          for(int i = 0;i<=n;i++)
          {
            if(um[i] == 0)  //该元素不在哈希表当中存在=>说明是缺失的数字
              return i;
          }
          return -1;
        }
    };
    //方法2:直接遍历
    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
          int n = nums.size();
          for(int i = 0;i<n;i++)
          {
            //数组是有序的
            if(nums[i] != i) return i;  
          }
          return n;//说明缺失的元素是n
        }
    };
    //方法3:位运算
    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
          int n = nums.size();
          int miss = 0;
          for(auto& x:nums) miss ^= x; //先和原数组的所有元素进行以异或
          for(int i = 0;i<=n;i++) miss ^= i; //和[0,n]范围上的数进行异或
          return miss;
        }
    };
    
    //方法4:高斯求和公式
    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
          //高斯求和公式:和= (首项+末项)x项数÷2
          int n = nums.size();
          int total = (0+n) * (n+1) / 2; 
          int sum = 0;
          for(auto& x:nums) sum+=x;
          return total - sum;//缺失的数
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    方法5:二分算法

    因为数组是升序排列的:

    • 在第⼀个缺失位置的左边,数组内的元素都是与数组的下标相等的
    • 在第⼀个缺失位置的右边,数组内的元素与数组下标是不相等

    可以利⽤这个「⼆段性」,来使⽤「⼆分查找」算法

    image-20230920152700473

    • 如果当前 n u m s [ m i d ] = = m i d nums[mid] == mid nums[mid]==mid :说明当前是在左边,要去右边找答案 l e f t = m i d + 1 left = mid + 1 left=mid+1
    • 如果当前 n u m s [ m i d ] ! = m i d nums[mid] != mid nums[mid]!=mid :说明当前在右边,要去左边找答案 r i g h t = m i d right = mid right=mid

    最后:当跳出循环的时候,还要判断此时指向的值和数组当中的值是否相同,如果相同,那么说明缺失的是下一个数

    class Solution {
    public:
        int missingNumber(vector<int>& nums) {
          int left = 0,right = nums.size()-1;
          while(left < right) //如果取等,可能会造成死循环 
          {
              int mid = (left + right) / 2;
              if(nums[mid] == mid)
                left = mid + 1;
              else 
                right = mid;
          }
          return left == nums[left] ? left + 1 : left;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ## 搜索插入位置

    https://leetcode.cn/problems/search-insert-position/description/

    给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置

    输入: nums = [1,3,5,6], target = 5		输出: 2
    输入: nums = [1,3,5,6], target = 2		输出: 1    
    
    • 1
    • 2

    插入位置的特点:第一次出现比它大的数的前一个位置 || 数组的最后一个位置 =>转化为找到 > = >= >=target的左端点即可

    • 直到我们的查找区间的⻓度变为1,也就是当$left == right 的时候,此时 的时候, 此时 的时候,此时left 或者 right$所在的位置就是我们要找的结果

    细节:当跳出循环的时候,要判断当前 l e f t ( r i g h t ) left(right) left(right)位置的数和 t a r g e t target target的关系,来判断到底是插入到 l e f t ( r i g h t ) left(right) left(right)位置,还是最后一个位置

    class Solution {
    public:
        int searchInsert(vector<int>& nums, int target) {
          int left = 0,right = nums.size()-1;
          int ans = 0;
          //找到>=target的最左边位置
          while(left < right)
          {
            int mid = (left + right) / 2;
            if(nums[mid] >= target)
              right = mid;
            else 
              left = mid + 1;
          }
          //如果nums[left] < target 说明应该插入到最后一个位置的后面
          if(nums[left] < target) return left+1;
          else return left;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

  • 相关阅读:
    创新能力 | 产品经理实践中常犯的七大错误
    神经网络模型画图工具,神经网络模型图怎么画
    线程 yield()方法有什么用?
    springboot之quartz动态可控定时任务
    Ubuntu系统初始设置
    Spring Security(六) —— CSRF
    线性规划在多种问题形式下的应用
    Dubbo之Adaptive注解用法
    清华大佬力荐的JVM学习路线+实战笔记+阿里真题,吃透吊打面试官
    获取图像的属性、图像通道拆分合并实现
  • 原文地址:https://blog.csdn.net/chuxinchangcun/article/details/133500121