C++算法学习笔记:Day2

16 阅读6分钟

二分搜索(改进版)

一.知识点总结。

1.改进版使返回值得以利用,并解决重复值的查找问题,控制查找下标最小(最左)的重复值或者下标最大(最右)的重复值。

2.leetcode题目引入:

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

题解:

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

3.改进版代码:

(1.)未利用返回值版本:

版本一:取最左边的重复值

int binarySearchLeftMost(vector<int>& nums, int target) {
    int first=0,last=nums.size(); 
    int condidate=-1;
    while(first<last){ 
        int middle=(first+last)>>1; 
        if(nums[middle]<target) first=middle+1;
        else if(nums[middle]>target) last=middle; 
        else{ 
            condidate=middle;
            last=middle; 
        } 
    }
    return condidate;
} 

版本二:取最右边的重复值

int binarySearchLRightMost(vector<int>& nums, int target) {
    int first=0,last=nums.size(); 
    int condidate=-1;
    while(first<last){ 
        int middle=(first+last)>>1; 
        if(nums[middle]<target) first=middle+1;
        else if(nums[middle]>target) last=middle; 
        else{ 
            condidate=middle;
            first=middle+1;
        } 
    }
    return condidate;
} 

(2.)利用返回值版本(返回值指定查找值该插入到数组的哪个位置)

版本一:返回值指向最左边的相同元素或指向大于目标值的最左边的元素索引。

int binarySearchLeftMost(vector<int>nums,int target){
     int first=0,last=nums.size();
     while(first<last){
         int middle =(first+last)>>1;
         if(nums[middle]<target)first=middle+1;
         else last=middle;
     }
     return first;       //如果没找到,first指向大于target的最左边的元素。
                         //如果找到了,first指向与target相等的最左边的元素。
}

版本二:返回值指向最右边的相同元素或指向小于目标值的最右边的元素索引。

int binarySearchRightMost(vector<int>nums,int target){
    int first=0,last=nums.size();
    while(first<last){
        int middle=(first+last)>>1;
        if(nums[middle]<=target)first=middle+1;
        else last=middle;
    }
    return first-1;//如果没找到,first-1指向小于target的最右边的元素,
                   //如果找到了,first-1指向与target相等的最右边的元素。
}

二.语法细节

1.未利用返回值代码:

版本一(最左重复值):

int binarySearchLeftMost(vector<int>& nums, int target) {
    int first=0,last=nums.size(); 
    int condidate=-1;
    while(first<last){ 
        int middle=(first+last)>>1; 
        if(nums[middle]<target) first=middle+1;
        else if(nums[middle]>target) last=middle; 
        else{ 
            condidate=middle;
            last=middle; 
        } 
    }
    return condidate;
} 

版本二(最右重复值):

int binarySearchLRightMost(vector<int>& nums, int target) {
    int first=0,last=nums.size(); 
    int condidate=-1;
    while(first<last){ 
        int middle=(first+last)>>1; 
        if(nums[middle]<target) first=middle+1;
        else if(nums[middle]>target) last=middle; 
        else{ 
            condidate=middle;
            first=middle+1;
        } 
    }
    return condidate;
} 
问题一:为什么多一个condidate?

答:解决带重复值的问题。不论是找最左还是最右的重复值,都必须搜索完整个数组。而condidate记录 最新找到的元素。middle找到之后,如果找最左重复值,就向左找,就要缩小last的范围,即else中last=middle。如果找最右重复值,就向右找,就要缩小first的范围,即版本二else中first=middle+1。(至于为什么一个不+1一个+1,Day1已解答)。condidate记录最新找到的元素,循环结束后,condidate就指向最左(版本一)或者最右(版本二) 查找到的重复元素。

其他问题在Day1中已有解答。改进版代码无非是在基础版代码中,加入了“记录最新查到的值(condidate)”的操作,从原来“查到即停止”变成了“查到一个继续查找,直到查完整个数组”。

2.利用返回值版本:

版本一:返回值指向最左边的相同元素或指向大于目标值的最左边的元素索引。

int binarySearchLeftMost(vector<int>nums,int target){
     int first=0,last=nums.size();
     while(first<last){
         int middle =(first+last)>>1;
         if(nums[middle]<target)first=middle+1;
         else last=middle;
     }
     return first;       //如果没找到,first指向大于target的最左边的元素。
                         //如果找到了,first指向与target相等的最左边的元素。
}

(1.)处理方式:“=”时往左找,其他不变; (2.)return规律:查得到时返回的first指向重复元素的最左边元素的下标;查不到时返回的是大于查找值的最近元素的索引。

版本二:返回值指向最右边的相同元素或指向小于目标值的最右边的元素索引。

int binarySearchRightMost(vector<int>nums,int target){
    int first=0,last=nums.size();
    while(first<last){
        int middle=(first+last)>>1;
        if(nums[middle]<=target)first=middle+1;
        else last=middle;
    }
    return first-1;//如果没找到,first-1指向小于target的最右边的元素,
                   //如果找到了,first-1指向与target相等的最右边的元素。
}

(1.)处理方式:“=”时往右找,其他不变; (2.)return规律:查得到时返回的是first-1,对应的是最右边重复值的下标,查不到时返回的是小于查找值的最右边元素的索引。

三.leetcode34题,题解分析

int binarySearch(vector<int>& nums, int target, bool lower) {
        int left = 0, right = (int)nums.size() - 1, ans = (int)nums.size();
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] > target || (lower && nums[mid] >= target)) {
                right = mid - 1;
                ans = mid;
            } else {
                left = mid + 1;
            }
        }
        return ans;
    }
    
 vector<int> searchRange(vector<int>& nums, int target) {
        int leftIdx = binarySearch(nums, target, true);
        int rightIdx = binarySearch(nums, target, false) - 1;
        if (leftIdx <= rightIdx 
            && rightIdx < nums.size() 
            && nums[leftIdx] == target 
            && nums[rightIdx] == target) {
            return vector<int>{leftIdx, rightIdx};
        } 
        return vector<int>{-1, -1};
    }

作者:力扣官方题解
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solutions/504484/zai-pai-xu-shu-zu-zhong-cha-zhao-yuan-su-de-di-3-4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1.思路:

用一个binarySearch函数进行二分搜索,其中函数参数lower控制找最左边重复值下标还是找最右边重复值下标的下一个。当lower为true时,大于等于都往左找,ans会记录最终mid的位置,即是大于等于target的第一个元素。当lower为false时,小于等于都向右找,不论有没有target存在数组中,ans最后都会对应到第一个大于target的元素。

2.语法细节:

(1.)left和right是左闭右闭区while的判断条件left<=right。

(2.)if的条件是nums[mid]>target||(lower&&nums[mid]>=target),意思是:nums[mid]>target,就让right=mid-1,过大往左找;lower==true且nums[mid]>=target,说明要查找最左边的重复值元素,还是往左找。

(3.)else的条件:nums[mid]<target||(!lower&&nums[mid]<=target)

(4.)看searchRange函数的if的判断条件。 当lower为true时,返回的是第一个大于等于target的元素下标。

当lower为false时,返回的是第一个大于target的元素下标。

所以leftIdx指向第一个大于等于target的元素下标,rightIdx指向第一个大于target的元素下标-1。

如果下标范围符合题意,就要满足三个条件:

1.leftIdx和rightIdx下标对应的元素都与target相等;

2.leftIdx<=rightIdx

3.rightIdx<nums.size()

第三个条件是必要条件的,第一,二个条件是充分条件。

四.总结:

当问题由“查找是否有元素”变成“查找最左或最右重复值”时,代码的执行就要由“查到即停止”变成了“查完整个数组,记录最新查到的元素下标”。

在利用返回值的代码中: binarySearchLeftMost返回的是>=target的最右索引。(returt first) binarySearchRightMost返回的是<=target的最左索引。(return first-1)