Leetcode刷题笔记(C++)——二分查找

Leetcode刷题笔记(C++)——二分查找

整理一下刷题过程中的思路,在这里进行一下总结与分享。
github地址:https://github.com/lvjian0706/Leetcode-solutions
github项目是刚刚新建的,陆续会将整理的代码以及思路上传上去,代码是基于C++与python的。同时会将基础的排序算法等也一并进行整理上传。

4. 寻找两个正序数组的中位数

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))
你可以假设 nums1 和 nums2 不会同时为空。

示例 1:
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例 2:
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

class Solution {
public:
        /*
        寻找两个数组第k小的元素
        1. 当nums1为空时,返回nums2中第k小的元素;当nums2为空时,返回nums1中第k小的元素;
        2. 当k==1时,nums1[0]和nums2[0]中的最小值为返回值;
        3.1 如果nums1[k/2-1]
    double findKth(vector<int>& nums1, vector<int>& nums2, int len1, int len2, int k){
        int left1 = 0, left2 = 0;
        while(true){
            if(left1==len1){
                return nums2[left2+k-1];
            }
            if(left2==len2){
                return nums1[left1+k-1];
            }
            if(k==1){
                return min(nums1[left1], nums2[left2]);
            }
            int new_left1 = min(left1+k/2-1, len1-1);
            int new_left2 = min(left2+k/2-1, len2-1);
            if(nums1[new_left1] <= nums2[new_left2]){
                k -= new_left1 - left1 + 1;
                left1 = new_left1 + 1;
            }
            else{
                k -= new_left2 - left2 + 1;
                left2 = new_left2 + 1;
            }
        }
    }
        /*
        寻找两个正序数组的中位数,即找到两个数组合并后第(nums1.size()+nums2.size())/2小的元素
        */
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        /*
        k = (nums1.size()+nums2.size())/2;
        当nums1.size()+nums2.size()为奇数时,返回第k+1小的元素;
        当nums1.size()+nums2.size()为偶数时,返回第k小的元素和第k+1小的元素均值;
        */
        int len1 = nums1.size();
        int len2 = nums2.size();
        int k = (len1+len2)/2;
        if((len1+len2)%2==0) return (findKth(nums1, nums2, len1, len2, k) + findKth(nums1, nums2, len1, len2, k+1)) / 2;
        else return findKth(nums1, nums2, len1, len2, k+1);
    }
};

33. 搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。

示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

class Solution {
public:
    /*
    有序数组查找目标值:二分查找
    1. 需要将nums[mid]与nums[left]进行比较,判断旋转区间
    2. 根据旋转区间范围以及target与nums[mid]的比较共同查找目标值
    */
    int search(vector<int>& nums, int target) {
        int left=0, right=nums.size()-1;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(nums[mid]==target) return mid;
            /*
            只有一个元素且不等于target时,返回-1;
            */
            else if(left==right){
                return -1;
            }
            /*
            nums[mid]大于nums[left]时,数组最小值在nums[mid]右边:
            1. nums[mid]大于target时,需要根据target与nums[left]大小判断target所在区间
            1.1 target大于nums[left]时,target位于nums[left:mid-1]
            1.2 target小于nums[left]时,target位于nums[mid+1:]
            2. nums[mid]小于target时,target位于nums[mid+1:]
            */
            if(nums[mid]>nums[left]){
                if(nums[mid]>target){
                    if(target>nums[left]) right = mid-1;
                    else if(target<nums[left]) left = mid+1;
                    else return left;
                }
                else if(nums[mid]<target){
                    left = mid+1;
                }
            }
            /*
            nums[mid]小于nums[left]时,数组最小值在nums[mid]左边:
            1. nums[mid]小于target时,需要根据target与nums[left]大小判断target所在区间
            1.1 target大于nums[left]时,target位于nums[left:mid-1]
            1.2 target小于nums[left]时,target位于nums[mid+1:]
            2. nums[mid]大于target时,target位于nums[left:mid-1]
            */
            else if(nums[mid]<nums[left]){
                if(nums[mid]<target){
                    if(target>nums[left]) right = mid-1;
                    else if(target<nums[left]) left = mid+1;
                    else return left;
                }
                else if(nums[mid]>target){
                    right = mid-1;
                }
            }
            /*
            nums[mid]等于nums[0]时,数组只有两个元素:
            1. nums[mid]不等于target并且nums[mid+1]不等于target时时,没有目标值
            2. 否则返回索引值;
            */
            else{
                if(nums[mid]!=target){
                    if(nums[mid+1]==target) return mid+1;
                    else return -1;
                }
                else return mid;
            }
        }
        /*
        如果没有找到,返回-1;
        */
        return -1;
    }
};

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

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

你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。

示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

class Solution {
public:
    
    /*
    查找数组中元素的起始位置即左端点
    1. 当nums[mid]==target时,需要根据nums[mid-1]判断是否为左端点
    1.1 nums[mid-1]
    int searchLeft(vector<int>& nums, int target){
        int left=0, right=nums.size()-1;
        while(right>=left){
            int mid = left + (right - left) / 2;
            if(nums[mid]==target){
                if(mid==0 || nums[mid-1]<target) return mid;
                else if(nums[mid-1]==target) right = mid - 1;
            }
            else if(nums[mid]>target) right = mid - 1;
            else if(nums[mid]<target) left = mid + 1;
        }
        /*
        没有找到target时,返回-1;
        */
        return -1;
    }
    
    /*
    查找数组中元素的起始位置即左端点
    1. 当nums[mid]==target时,需要根据nums[mid+1]判断是否为右端点
    1.1 nums[mid+1]>target,是右端点,返回索引值
    1.2 mid==nums.size()-1, 位于数组边界,是右端点,返回len(nums-1)
    1.3 nums[mid-1]==target, 不是左端点,将left更新为mid+1继续判断
    2. 当nums[mid]不等于target时,缩小搜索区间继续查找
    */
    int searchRight(vector<int>& nums, int target){
        int left=0, right=nums.size()-1;
        while(right>=left){
            int mid = left + (right - left) / 2;
            if(nums[mid]==target){
                if(mid==nums.size()-1 || nums[mid+1]>target) return mid;
                else if(nums[mid+1]==target) left = mid + 1;
            }
            else if(nums[mid]>target) right = mid - 1;
            else if(nums[mid]<target) left = mid + 1;
        }
        /*
        没有找到target时,返回-1;
        */
        return -1;
    }

    /*
    有序数组查找元素:二分查找
    数组中存在重复数字,因此需要对要查找元素的左端点和右端点进行分别处理;
    */
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> ans;
        int left = searchLeft(nums, target);
        int right = searchRight(nums, target);
        ans.push_back(left);
        ans.push_back(right);
        return ans;
    }
};

35. 搜索插入位置

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

你可以假设数组中无重复元素。

示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0

class Solution {
public:
    /*
    有序数组查找目标值:二分查找
    1. nums[mid]==target时,返回索引值
    2. nums[mid]>target时,需要根据nums[mid-1]的值与target的比较结果进行判定:
    2.1 nums[mid-1]>=target时,目标值在mid左侧,right更新为mid-1;
    2.2 nums[mid-1]target时,目标值应该插入在mid-1和mid中间,返回mid+1;
    3.2 nums[mid+1]<=target时,目标值在mid右侧,left更新为mid+1;
    3.3 考虑越界情况,mid==nums.size()-1时,目标值大于数组最后一个元素,应该插入到nums[nums.size()]处;
    */
    int searchInsert(vector<int>& nums, int target) {
        int left = 0, right = nums.size()-1;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(nums[mid]==target){
                return mid;
            }
            else if(nums[mid]>target){
                if(mid==0 || nums[mid-1]<target) return mid;
                else if(nums[mid-1]>=target) right = mid - 1;
            }
            else if(nums[mid]<target){
                if(mid==nums.size()-1 || nums[mid+1]>target) return mid+1;
                else if(nums[mid+1]<=target) left = mid + 1;  
            }
        }
        return -1;
    }
};

74. 搜索二维矩阵

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。

示例 1:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
示例 2:
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false

//方法1
class Solution {
public:
    
    /*
    在第一列中查找目标值应该属于哪一行:在一维数组中查找目标值,有则返回索引,没有返回小于且最接近目标值的索引;
    1. 如果vec[mid]==target, 返回mid;
    2. 如果vec[mid]target,返回mid;
    2.3 如果mid==vec.size()-1,说明在右边界,返回vec.size()-1;
    3. 如果vec[mid]>target:
    2.1 如果vec[mid-1]=target,在mid左边继续查找;
    3.3 如果mid==0,说明在左边界,返回0;
    */
    int searchCol(vector<int> vec, int target){
        int left = 0, right = vec.size()-1;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(vec[mid]==target) return mid;
            else if(vec[mid]<target){
                if(mid==vec.size()-1) return mid;
                else if(vec[mid+1]<=target) left = mid + 1;
                else return mid;
            }
            else if(vec[mid]>target){
                if(mid==0) return mid;
                else if(vec[mid-1]>=target) right = mid - 1;
                else return mid-1;
            }
        }
        return -1;
    }

    /*
    在对应行中查找目标值:在一维数组中查找目标值,有则返回索引,没有返回-1;
    */
    int searchTarget(vector<int> vec, int target){
        int left = 0, right = vec.size()-1;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(vec[mid]==target) return mid;
            else if(vec[mid]<target) left = mid + 1;
            else if(vec[mid]>target) right = mid - 1;
        }
        return -1;
    }

    /*
    有序数组搜索目标值:二分查找
    由于是二维数组,所以需要在两个维度进行搜索,由于每行都是升序,且下一行的第一个数大于该行最后一个数,因此可以将问题转化为:
    1. 搜索第一列,判断目标值存在的情况下,应该属于哪一行(即查找目标值或小于且最接近目标值的值);
    2. 找到目标值所在行后,简化问题为在升序的一维数组中查找目标值,使用标准二分查找方法即可;
    */
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        /*
        如果数组为空,则返回false;
        */
        if(matrix.size()==0 || matrix[0].size()==0) return false;
        /*
        定义vector存放第一列元素
        */
        vector<int> firstcol;
        int m = matrix.size();
        for(int i=0; i<m; i++){
            firstcol.push_back(matrix[i][0]);
        }
        /*
        找到对应行
        */
        int col = searchCol(firstcol, target);
        cout<<col<<endl;
        /*
        在该行中找target
        */
        int row = searchTarget(matrix[col], target);
        cout<<row<<endl;
        return true ? row!=-1 : false;
    }
};

//方法2
class Solution {
public:
    /*
    将二维数组拉伸为一位数组进行搜索,其中,一位数组中matrix[mid]在二维数组中的位置为matrix[mid/row][mid%row]
    */
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        if(matrix.size()==0 || matrix[0].size()==0) return false;
        int col = matrix.size(), row = matrix[0].size();
        int left = 0, right = col * row - 1;
        while(right>=left){
            int mid = left + (right - left) / 2;
            if(matrix[mid/row][mid%row]==target) return true;
            else if(matrix[mid/row][mid%row]<target) left = mid + 1;
            else if(matrix[mid/row][mid%row]>target) right = mid - 1;
        }
        return false;
    }
};

81. 搜索旋转排序数组 II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。

编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。

示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false

class Solution {
public:
    /*
    旋转升序数组中查找目标值:二分查找;遇到重复元素数组,可以在循环过程中先去掉两端重复元素,方便判断
    1. 当数组为空时,返回false;
    2. 为了方便判断,循环过程中先将两端的重复元素去掉
    2.1 当nums[left]==nums[left+1]时,left++;
    2.2 当nums[right]==nums[right--]时,right--;
    3. 当只有一个元素时,比较是否一致即可;
    4. nums[mid]==target,返回true;
    5. nums[mid]nums[left],说明从左到右依次为nums[left],nums[mid],target,left=mid+1;
    5.2 nums[mid]nums[left]时, 说明从左到右依次为nums[left],target,nums[mid],right=mid-1;
    5.2.2 targettarget时,结合nums[mid],target,nums[left]三者关系共同判断:
    6.1 nums[mid]nums[left]时:
    6.2.1 target>nums[left]时, 说明从左到右依次为nums[left],target,nums[mid],right=mid-1;
    6.2.2 target
    bool search(vector<int>& nums, int target) {
        if(nums.size()==0) return false;
        int left=0, right=nums.size()-1;
        while(left<=right){
            while(left+1<=right && nums[left]==nums[left+1]) left++;
            while(right-1>=left && nums[right]==nums[right-1]) right--;
            if(left==right) return nums[left]==target;
            int mid = left + (right - left) / 2;
            if(nums[mid]==target) return true;
            else if(nums[mid]<target){
                if(nums[mid]==nums[left]) return nums[mid+1]==target;
                else if(nums[mid]>nums[left]) left = mid + 1;
                else if(nums[mid]<nums[left]){
                    if(target>nums[left]) right=mid-1;
                    else if(target<nums[left]) left=mid+1;
                    else if(target==nums[left]) return true; 
                }
            }
            else if(nums[mid]>target){
                if(nums[mid]==nums[left]) return nums[mid+1]==target;
                else if(nums[mid]<nums[left]) right=mid-1;
                else if(nums[mid]>nums[left]){
                    if(target>nums[left]) right=mid-1;
                    else if(target<nums[left]) left=mid+1;
                    else if(target==nums[left]) return true; 
                }
            }
        }
        return false;
    }
};

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

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0

class Solution {
public:
    /*
    查找旋转数组中最小值:由于原始数组是升序数组,所以使用二分查找,找原始数组中起始点
    原始数组最小点应该小于左右两侧元素,具体的最小点应该小于左侧元素
    1. 当nums[0]nums[mid+1],第一个元素为最大值,返回nums[1];
    3. nums[mid]nums[mid-1],需要判断当前点在最小点左边还是右边,可以结合与nums[0]的关系进行判断:
    nums[mid]>nums[0],由于数组旋转过,所以最小点左边是升序数组,且最小值右边恒小于左边,因此当前元素位于最小点左边,left=mid+1;否则,right=mid-1;
    */
    int findMin(vector<int>& nums) {
        /*
        只有一个元素时,返回该元素
        */
        if(nums.size()==1) return nums[0];
        int left = 0, right = nums.size()-1;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(nums[0]<nums[nums.size()-1]) return nums[0];
            else if(mid==0 && nums[mid]>nums[mid+1]) return nums[1];
            else if(nums[mid] < nums[mid-1]) return nums[mid];
            else if(nums[mid] > nums[mid-1]){
                if(nums[mid] > nums[0]) left = mid + 1;
                else if(nums[mid] < nums[0]) right = mid - 1;
            }
        }
        return -1;
    }
};

154. 寻找旋转排序数组中的最小值 II

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

注意数组中可能存在重复的元素。

示例 1:
输入: [1,3,5]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0

class Solution {
public:
    /*
    寻找旋转的升序数组最小值:二分查找,数组中的最小值恒小于左右两侧的第一个不同元素;
    在本题中,适合使用nums[mid]与nums[right]的值进行判断;且需要将数组两端重复元素去重;
    1. nums中没有元素返回-1;nums中只有一个元素,直接返回;
    2. 如果nums[0]=nums[mid-1],还在升序区间,且最小值在nums[mid]左侧,right=mid-1;
    5.2 nums[mid]nums[right],最小值在nums[mid]右侧,left=mid+1;
    */
    int findMin(vector<int>& nums) {
        if(nums.size()==1) return nums[0];
        if(nums[0]<nums[nums.size()-1]) return nums[0];
        int left=0, right=nums.size()-1;
        while(left<=right){
            while(left+1<=right && nums[left]==nums[left+1]){
                left++;
            }
            while(right-1>=left && nums[right]==nums[right-1]){
                right--;
            }
            if(left==right) return nums[left];
            int mid = left + (right - left) / 2;
            if(nums[mid]<nums[right]){
                if(nums[mid]>=nums[mid-1]) right = mid - 1;
                else if(nums[mid]<nums[mid-1]) return nums[mid];
            }
            else if(nums[mid]>nums[right]) left = mid + 1;
        }
        return -1;
    }
};


278. 第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。

// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    /*
    由于错误版本之后的所有版本都是错的,因此,需要找到第一个isBadVersion(mid)==false的版本:
    1. isBadVersion(mid)==true,说明当前版本或当前版本之前的版本出现错误,需要根据上一个版本的情况进行判断
    1.1 isBadVersion(mid-1)==true,说明当前版本之前的版本出现错误,right=mid-1;
    1.2 isBadVersion(mid-1)==false,说明当前版本之前的版本都是正确的,当前版本为第一个出错的版本,返回mid;
    1.3 mid==1时,为边界情况,说明从第一个版本就出错了;
    2. isBadVersion(mid)==false,说明当前版本没有问题,left=mid+1;
    */
    int firstBadVersion(int n) {
        int left = 1, right = n;
        while(left<=right){
            int mid = left + (right - left) / 2;
            if(isBadVersion(mid)==false) left = mid + 1;
            else if(isBadVersion(mid)==true){
                if(mid==1 || isBadVersion(mid-1)==false) return mid;
                else if(isBadVersion(mid-1)==true) right = mid - 1;
            }
        }
        return 0;
    }
};

你可能感兴趣的:(Leetcode刷题笔记,数据结构,leetcode,c++,算法)