二分查找<万字详解>

news2024/12/29 11:23:44

目录

问题引入

二分查找的思路

二分查找的实现

左闭右闭写法

左闭右开写法

两种写法的对比

例题强化

常规的二分查找题目

猜数字大小

搜索插入位置

常规二分的变形题目

搜索二维矩阵

搜索二维矩阵 II

查找特定位置/特定数

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

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

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

搜索旋转排序数组

不在同一个数组的二分

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

最后


问题引入

        还记得在初中或者高中的时候就知道二分查找这个东西了,当时理解起来也不难,感觉挺简单的。后来在编程学习中也遇到了二分查找,刚开始觉得这是一个初高中就会的东西,也就没太重视。但到了编程实现的时候就不是那么回事了,边界问题很难处理好,换了问题模型就无从下手。可见,在学习过程中保持空杯心态还是很重要的。所以要把二分查找的问题整理成一篇博客总结下来。

        第一次接触二分查找的问题是一个“猜数字”的游戏,大体意思就是:小红心里想一个数字,小明去猜这个数字是多少,猜对了小红就奖励他,猜错了就再猜,直到猜中为止其中如果猜的数大于小红心里想的数,小红就说大了,反之就说小了,问小明怎么猜才最快。然后聪明的小明就想到了,先从最中间的数开始猜,不论是大了还是小了,小明都可以排除掉一半的数,这样最坏的情况也只需要7次就可以得到小红的奖励了。而这种每次折半查找的方式就叫做二分查找。

二分查找的思路

        把上述的“猜数字”游戏具象化就是:设有一个有序升序数组nums,其中存放的是1-100这100个数,我们需要在这个数组中查找目标值target。那么对应的我们需要设一个left和right变量,left表示我们所要查找区间的左边界,right为右边界,还需要一个mid变量表示left和right的中间位置。然后我们将nums[mid]与目标值target进行对比,如果这两者恰好相等,那么说明此时的mid位置就是我们需要找的数。否则,如果nums[mid]大于target,那么就将left和right的范围向左缩进(折半),那么就让right跑到mid的位置,随之更新mid,再重复上述操作,直至left与right“相撞”表示当前数组已经查找完了,说明nums中没有target。

        总结一下就是,二分查找又叫折半查找,思路很好理解,就是每次从指定范围内进行查找(这个范围内的数都是有序的),如果找到了就结束,没找到就根据当前范围内中间位置的数与要找的数对比,并将原范围折半缩小,这样每次就可以排除掉当前范围一半的数据。所以二分查找的次数就是logN次,即二分查找的时间复杂度为O(logN)。

二分查找的实现

        二分的思路很好理解,但二分查找的实现并不是很好把控的。二分查找常见写法的有两种,分别是左闭右闭和左闭右开。

左闭右闭写法

对于左闭右闭写法,数学形式表示就是 [left,right] 说明当前锁定的范围是包含left和right下标位置及它们之间的数据。例如对于数组nums(数据刚好等于下标)

nums:0 1 2 3 4 5 6 7 8 9

如果left为2,right为5,那么就相当于

nums:0 1 [ 2 3 4 5 ] 6 7 8 9

即我们当前锁定的范围就是 [ 2 3 4 5 ] 。那么我们就可以根据这个特性来控制循环条件,进而进行二分查找,即

while(left <= right)

如果你还不明白为什么要这样写,这里可以再啰嗦一些。由于左闭右闭写法的区间为 [left,right] 那么当left=right时,就相对于目前锁定的范围中只有一个数。例如对于上方的数组nums,那么如果left=right=3,那么就相当于

nums:0 1 2 4 5 6 7 8 9

那么当不满足 left <= right 时,left = right-1,如果此时left还是3的话,那么对应的right就是2,就相当于

nums:0 1 2 ] 3 4 5 6 7 8 9

如果此时我们二分查找的数是3的话,那么当循环结束时对应的left和right就是上面那个样子,target(要查找的数字3)所对应的下标就是left(right位置的是2)


代码实现如下:

//nums是一个升序数组,n是nums数组的长度,target是要查找的目标值
int BinarySearch(int* nums, int n, int target) 
{
    int left = 0, right = n - 1;
    while(left <= right) 
    {
        //这里的mid写成:int mid = left + (right - left) / 2; 的效果更好
        //因为left直接与right相加可能会出现int数据溢出的情况
        //但为了更好理解还是暂定写成下面的形式
        int mid = (left + right) / 2;   
    
        //mid位置的数大于target
        if (nums[mid] > target) 
            right = mid - 1; 

        //mid位置的数小于target
        else if (nums[mid] < target) 
            left = mid + 1 ;

        //mid位置的数等于target
        else 
            return mid;
    }
    return -1;
}

这里还需要解释一下代码中的一些细节问题,对于mid的写法代码注释中已经说明了这里就不再过多赘述。而对于 left = mid + 1 和 right = mid - 1,我们以后者( right = mid - 1)为例进行解释。当mid位置的大于target,那么说明mid位置的数是大于target(当然,这是一句废话),并且它右边的数都是大于target的,所以可以直接把mid及其右边的数直接排除,将锁定的范围更新为 [left,mid - 1] (简言之,此时的mid已经被排除了),由于这是左闭右闭的写法,那么right可以直接等同于mid-1。left的情况也是同理。

左闭右开写法

清楚了左闭右闭是怎么一回事,那么左闭右开就表示 [left,right) ,就是说当前锁定的范围并不包含right,也可以理解为锁定范围的内容就相当于 [left, right-1]

我们还是以上面的nums数组为例,如果left为2,right为5,那么就相当于(蓝色的表示right-1)

nums:0 1 [ 2 3 4 ] 5 ) 6 7 8 9

那么控制循环的条件就变成了

while(letf < right)

其实也不难理解,left < right 就相对于 left <= right-1,即结束循环时left=right。还是设我们要找的target是3,那么最后的结果left=right=3,就相对于(蓝色的表示right-1)

nums:0 1 2 ] 3 ) 4 5 6 7 8 9

所以,一般来说这与左闭右闭写法产生的效果是一样的。


有了上面的基础,那么对应的代码也就不难理解了。

int BinarySearch(int* nums, int n, int target) 
{
    int left = 0, right = n - 1;
    while(left < right) 
    {
        int mid = left + (right - left) / 2; 
        if (nums[mid] > target) 
            right = mid; 
        else if (nums[mid] < target) 
            left = mid + 1 ;
        else 
            return mid;
    }
    return -1;
}

这段代码与上面的代码基本上没有什么区别,只是循环条件变成了left<right(原因已经在上面阐释过了)。还有一点就是当nums[mid] > target 的时候变成了 right = mid 。这是因为这里的区间控制是 [left,right),虽然此时的有效范围是 [left,mid-1] 但由于 [left,right) 范围并不包含right,所以这里的有效范围就相当于 [left,right-1] ,或者也可以理解为 [left,mid) 。但不管是从哪种角度来理解,对应的right就相当于mid,它们是不包含在有效范围内的,right-1就相当于mid-1,它们是包含在有效范围内的。如果这里用的是right=mid-1,那么就会出现遗漏数据的情况。

两种写法的对比

        1、两种写法代码实现上基本上差不多,只有两处需要注意,循环条件上:左闭右闭的是while(left<=right),而左闭右开的是while(left<right)。right左移上:左闭右闭的是right=mid-1,而左闭右开的是right=mid。

       2、 左闭右闭和左闭右开虽然在写法上有所出入,但最终所达到的效果是差不多的。那么有人就会问了,既然两个效果都差不多,那么为什么还要有下面这种左闭右开的写法呢,全都用左闭右闭不久好了,何必如此鸡肋。其实不然,世间万物存在一定都有它的意义,虽然在一般情况下两种写法都差不多,但在一些特定的问题处理上,有时只能用左闭右开或者只能用左闭右闭的情况。

        3、其实二分的范围控制不是只有这两种,也可以写成左开右开或者左开右闭的情形,但这两种情形对应的应用场景很少,而且用起来很麻烦,所以基本上是不会用到的。

例题强化

二分查找的难点不在于理解,而在于应用,下面我们通过几组例题循序渐进的强化我们对二分查找的把控能力(题目均选自 力扣-leetcode )

常规的二分查找题目

猜数字大小

374. 猜数字大小 - 题目链接https://leetcode.cn/problems/guess-number-higher-or-lower/

题目描述:

猜数字游戏的规则如下:

每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果返回值一共有 3 种可能的情况(-1,1 或 0):

-1:我选出的数字比你猜的数字小 pick < num
1:我选出的数字比你猜的数字大 pick > num
0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num 返回我选出的数字。

示例 1:
输入:n = 10, pick = 6
输出:6

示例 2:
输入:n = 1, pick = 1
输出:1

示例 3:
输入:n = 2, pick = 1
输出:1

示例 4:
输入:n = 2, pick = 2
输出:2
 

提示:
1 <= n <= 231 - 1
1 <= pick <= n
1 <= n <= 231 - 1
1 <= pick <= n

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/guess-number-higher-or-lower
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:这题与文章开头小红和小明的例子相差无几,这里就直接贴题解代码了。

参考题解:

/** 
 * Forward declaration of guess API.
 * @param  num   your guess
 * @return 	     -1 if num is higher than the picked number
 *			      1 if num is lower than the picked number
 *               otherwise return 0
 * int guess(int num);
 */

class Solution {
public:
    int guessNumber(int n) 
    {
        int left = 0, right = n;
        while(left <= right)
        {
            int mid = left + (right - left) / 2;
            if(guess(mid) == 0)
                return mid;
            else if(guess(mid) < 0)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return 0;
    }
};

搜索插入位置

35. 搜索插入位置https://leetcode.cn/problems/search-insert-position/

题目描述:

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

请必须使用时间复杂度为 O(log n) 的算法。


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

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

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


提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 为 无重复元素 的 升序 排列数组
-104 <= target <= 104

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-insert-position
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:要注意理解好题目中的“返回它将会被按顺序插入的位置”,其实说白了就是对这个数组进行二分,然后返回对应的left或者mid值(左闭右闭的情况),因为如果target不在有序数组nums中的话,二分之后的left就是第一个大于target的数的位置。我们通过“示例2”来感受一下,

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

输出: 1

首先,如果nums =  [1,2,3,5,6] 的输出还是1吗?答案是,是的。对应结果如下

1 ][ 2 3 5 6

所以如果把2去掉之后那么最后的left和right位置还是在那个地方,相当于把2移走了,那么left位置的元素自然就变成了3。

所以最终二分的结果就是下面这种,二分最后的left=1,right=0。可以看到,这时的left位置恰好为target(就是2)插入之后的位置。

nums:  ] [ 3  5  6

参考题解:

class Solution {
public: 
    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)
                right = mid - 1;
            else
                left = mid + 1;
        } 
        return left;
    }
};

常规二分的变形题目

搜索二维矩阵

74. 搜索二维矩阵https://leetcode.cn/problems/search-a-2d-matrix/

题目描述:

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

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

示例 1:

leetcode官方图


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

leetcode官方图


输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

m == matrix.length
n == matrix[i].length
1 <= m, n <= 100
-104 <= matrix[i][j], target <= 104

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-a-2d-matrix
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

这题是一个很常规的二分加了一点变化,由于这个矩阵比较特殊,很容易想到利用两次二分,先对每行的第一个数进行二分,找到第一个大于target所在的行row,然后对row-1行再进行一次二分,最终结果就是我们所要的结果。还有一种思路就是把这个二维数组的每一行合并成一个一维数组,然后对这个一维数组进行二分。这两种思路的时间复杂度都是O(logMN)

参考题解:

这里用的是两次二分的思路,如果觉得第一个代码理解起来稍复杂可以看下面手动二分的版本。

//写法1,直接利用内置算法
class Solution {
public:
    bool searchMatrix(vector<vector<int>> matrix, int target) 
    {
        //找到第一个首行大于target的行数
        auto row = upper_bound(matrix.begin(), matrix.end(), target,
                    [](const int tag, const vector<int>& matrixRow)
                    {
                        return tag < matrixRow[0];
                    } );
        //如果tag小于matrix[0][0],直接返回false
        if(row == matrix.begin())
            return false;
        //对row-1行进行二分
        row--;
        return binary_search(row->begin(), row->end(), target);
    }
};
//写法2,自己手动二分
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {
        //解法2:两次二分,时间复杂度 O(logN + logN) = O(logMN)
        //找符合条件的行
        int left1 = 0, right1 = matrix.size() - 1;
        while(left1 <= right1)
        {
            int mid = left1 + (right1 - left1) / 2; 
            if(matrix[mid][0] == target)
                return true;
            else if(matrix[mid][0] > target)
                right1 = mid - 1;
            else    
                left1 = mid + 1;
        } 
        //如果tag小于matrix[0][0],直接返回false   
        if(right1 < 0) 
            return false;
        cout<<right1<<endl;
        cout<<matrix[right1][0]<<endl;
        //对可能的行进行二分
        int left2 = 0, right2 = matrix[0].size() - 1;
        while(left2 <= right2)
        {
            int mid = left2 + (right2 - left2) / 2;
            if(matrix[right1][mid] == target) 
                return true;
            else if(matrix[right1][mid] > target)
                right2 = mid - 1;
            else    
                left2 = mid + 1;
        }
        return false;
    }
};

搜索二维矩阵 II

240. 搜索二维矩阵 IIhttps://leetcode.cn/problems/search-a-2d-matrix-ii/

题目描述:

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

        每行的元素从左到右升序排列。
        每列的元素从上到下升序排列。

示例 1:


输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true
示例 2:


输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示:

m == matrix.length
n == matrix[i].length
1 <= n, m <= 300
-109 <= matrix[i][j] <= 109
每行的所有元素从左到右升序排列
每列的所有元素从上到下升序排列
-109 <= target <= 109

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-a-2d-matrix-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:乍一看这题和“搜索二维矩阵”好像没什么区别,所以小白上来直接把“搜索二维矩阵”的代码给提交上去了,结果可想而知的报错了。这题与“搜索二维矩阵”的一个不同之处就在于这题的矩阵层与层之间没有必然的联系,所以上一题的“两次二分”与“合并+一次二分”的思路在这里就行不通了。这题的一个二分解法是对每一行进行二分,寻找两个正序数组的中位数复杂度为O(MlogN)。当然这题用二分并不是最好的解法,还有一个很妙的 “Z字形查找” 的思路。但由于这里是以讲解二分为主,所以就不展开说这个 “Z字形查找”了,感兴趣的可以看一下题解部分的第二个代码。

参考题解:

//m次二分
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        for (const auto& row: matrix) {
            auto it = lower_bound(row.begin(), row.end(), target);
            if (it != row.end() && *it == target) {
                return true;
            }
        }
        return false;
    }
};

//Z字查找
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) 
    {
        int m = 0, n = matrix[0].size() - 1; //m-行,n-列
        while(m < matrix.size() && n > -1)
        {
            if(matrix[m][n] == target)
                return true;
            else if(matrix[m][n] > target)
                n--;
            else
                m++;
        }
        return false;
    }
};

查找特定位置/特定数

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

34. 在排序数组中查找元素的第一个和最后一个位置https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

题目描述:

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

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

 

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]


示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]


示例 3:

输入:nums = [], target = 0
输出:[-1,-1]
 

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

参考题解:

//写法1:
class Solution {
public:
    int searchFirst(vector<int>& nums, int target)
    {
        if(nums.empty())
            return -1;
        int left = 0, right = nums.size() - 1, mid = 0;
        //控制区间是 [left,right] 而不是 [left,right)
        while(left <= right) 
        {
            //防止范围过大导致int溢出
            mid = left + (right - left) / 2; 
            if(nums[mid] == target)
                right = mid - 1;
            else if(nums[mid] > target)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return left;
    }
    int searchEnd(vector<int>& nums, int target)
    {
        if(nums.empty())
            return -1; 
        int left = 0, right = nums.size() - 1, mid = 0;
        while(left <= right) 
        {
            mid = left + (right - left) / 2; 
            if(nums[mid] == target)
                left = mid + 1;
            else if(nums[mid] > target)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return right;
    }
    vector<int> searchRange(vector<int>& nums, int target)  
    {
        int first = searchFirst(nums, target); 
        int end = searchEnd(nums, target); 
        if(first > end)
            return vector{-1, -1}; 
        return vector{first, end}; 
    }
};
//写法2:
class Solution {
public:
    int searchEnd(vector<int>& nums, int target)
    {
        if(nums.empty())
            return -1; 
        int left = 0, right = nums.size() - 1, mid = 0;
        while(left <= right) 
        {
            mid = left + (right - left) / 2; 
            if(nums[mid] == target)
                left = mid + 1;
            else if(nums[mid] > target)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return left;
    }
    vector<int> searchRange(vector<int>& nums, int target)  
    {
        int first = searchEnd(nums, target - 1);
        int end = searchEnd(nums, target) - 1;
        if(first > end)
            return vector{-1, -1};
        return vector{first, end};
    }
};

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

153. 寻找旋转排序数组中的最小值https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/

题目描述:

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。


示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。


示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。
 

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 中的所有整数 互不相同
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

参考题解:

class Solution {
public:
    int findMin(vector<int>& nums) 
    
    {
        int left = 0;
        int right = nums.size() - 1;
        //前闭后开的写法
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[right])
                left = mid + 1;
            else
                right = mid;
        }
        return nums[right];
    }
};


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

154. 寻找旋转排序数组中的最小值 IIhttps://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/

题目描述:

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须尽可能减少整个过程的操作步骤。

示例 1:

输入:nums = [1,3,5]
输出:1


示例 2:

输入:nums = [2,2,2,0,1]
输出:0
 

提示:

n == nums.length
1 <= n <= 5000
-5000 <= nums[i] <= 5000
nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转
 

进阶:这道题与 寻找旋转排序数组中的最小值 类似,但 nums 可能包含重复元素。允许重复会影响算法的时间复杂度吗?会如何影响,为什么?

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

参考题解:

class Solution {
public:
    int findMin(vector<int>& nums) 
    
    {
        int left = 0;
        int right = nums.size() - 1;
        //前开后开的写法
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] > nums[right])
                left = mid + 1;
            else if(nums[mid] < nums[right])
                right = mid;
            else
                right--;
        }
        return nums[right];
    }
};

搜索旋转排序数组

33. 搜索旋转排序数组https://leetcode.cn/problems/search-in-rotated-sorted-array/

题目描述:

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -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


示例 3:

输入:nums = [1], target = 0
输出:-1
 

提示:

1 <= nums.length <= 5000
-104 <= nums[i] <= 104
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

参考题解:

class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int left = 0, right = nums.size() - 1;
        int mid = 0;
        while(left <= right)
        {
            //更新mid值
            mid = (left + right) / 2;
            //查找到就返回
            if(nums[mid] == target)
                return mid;     
            //0-mid范围内为有序正常二分的情况,按照二分的情况正常处理
            else if(nums[0] <= nums[mid]) //无重复元素,所以这样用
            {
                if(nums[mid] > target && target >= nums[0]) //严格控制区间
                    right = mid - 1;
                else
                    left = mid + 1;
            }
            //0-mid范围内为乱序旋转序列的情况,需要根据target的位置按情况处理 
            else/*此情况下mid一定是在乱序数组的中间*/
            {
                //target在mid右边,向右缩进
                if(nums[mid] < target && target <= nums[nums.size()-1])
                    left = mid + 1;           
                else           
                    right = mid - 1;
            }      
        }
        return -1;
    }
};

不在同一个数组的二分

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

4. 寻找两个正序数组的中位数https://leetcode.cn/problems/median-of-two-sorted-arrays/

题目描述:

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2


示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
 

提示:

nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题目分析:

参考题解:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
    {
        //保证nums1的长度小于nums2的长度
        if(nums1.size() > nums2.size())
            return findMedianSortedArrays(nums2, nums1);
        int len = nums1.size() + nums2.size();
        int mid = (len + 1) / 2; //理论中位数的下标 
        int left = 0, right = nums1.size(); //控制nums1
        while(left <= right)
        {
            int i = (left + right) / 2; 
            int j = mid - i;
            double LeftMax = max((i == 0) ? INT_MIN : nums1[i - 1] , (j == 0) ? INT_MIN : nums2[j -  1]); 
            double RightMin = min((i == nums1.size()) ? INT_MAX : nums1[i] , (j == nums2.size()) ? INT_MAX : nums2[j]);  
            if(LeftMax <= RightMin)        
                return len % 2 == 0 ? (LeftMax + RightMin) / 2 : LeftMax;          
            if(((i == 0) ? INT_MIN : nums1[i - 1]) > ((j == 0) ? INT_MIN : nums2[j - 1]))
                right = i - 1;
            else
                left = i + 1;
        }
        return 0;
    }
};

最后

碍于本人才疏学浅,这篇博客只能写到这个水平了,如有不足,还请大家对小白指正。如果有大佬可以对小白指点一二,那么小白一定不胜感激。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/454891.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DIN11系列 大电流输出信号隔离模块线性驱动器0~100mA/0~500mA/0~2A/0-4A

主要特性 精度、线性度误差等级&#xff1a; 0.1、0.2、0.5 级4-20mA/0-5V/0-10V 等标准信号输入0~100mA/0~500mA/0~1A/0-5A 等电流信号输出0~1V(max 5A)/0~10V/0-24V(max 5A) 等电压信号输出信号输入/信号输出 3000VDC 隔离辅助电源&#xff1a;12V、15V 或 24V 直流单电源供…

IJKPLAYER源码分析-音视频同步

前言 本文来介绍下IJKPLAYER的音视频同步策略及方法。IJKPLAYER所支持的同步策略&#xff0c;源自FFPLAY&#xff0c;因此有以下3种&#xff1a; 音视同步视频&#xff1a;这是缺省的同步策略&#xff0c;以audio为主时钟-参考时钟&#xff0c;采取video同步audio的方式&#x…

电子模块|外控集成 LED 光源 WS2812模块---硬件介绍和stm32驱动

电子模块|外控集成 LED 光源 WS2812模块 模块简介模块特点机械尺寸单线归零码通讯方式24bit 数据结构 stm32 驱动 模块简介 WS2812是一个集控制电路与发光电路于一体的智能外控LED光源。其外型与一个5050LED灯珠相同&#xff0c;每个元件即为一个像素点。像素点内部包含了智能…

【小样本分割 2020 ICCV】PANet

文章目录 【小样本分割 2020 ICCV】PANet1. 简介2. 网络2.1 整体架构2.2 原型学习2.3 非参数度量学习2.4 原型对齐正则化 3. 代码3.1 backbone3.2 模型代码 【小样本分割 2020 ICCV】PANet 论文题目&#xff1a;PANet: Few-Shot Image Semantic Segmentation with Prototype Al…

IDEA下SpringBoot指定配置文件启动项目

目录 一. idea下的SpringBoot启动&#xff1a;指定配置文件 二. 项目已打包&#xff0c;运行配置 1&#xff09;.使用java -jar启动基于(一)下的配置文件启动 2&#xff09;指定项目内其它配置文件application-pro.yml启动项目 3&#xff09; Linux服务器上启动基于(三)的…

OneFlow源码解析:Eager模式下Tensor的存储管理

作者&#xff5c;郑建华 1 不同Tensor类型的存储管理方式 Lazy Tensor 的存储是由 Runtime 和 Actor 等对象管理的。静态图完成编译后&#xff0c;需要多少个对象、多少存储空间都是确定的&#xff0c;Runtime 等在初始化时会分配存储&#xff0c;在退出时回收资源。 Eager 模…

SpringBoot--图片验证码kaptcha

本文介绍如何在SpringBoot中整合kaptcha&#xff0c;以及如何配置kaptcha&#xff0c;生成验证码和校验等 文章目录 前言环境搭建项目结构添加依赖 代码实现KaptchaConfigKnife4jConfigdomainServiceutilcontroller 测试生成验证码校验验证码 前言 参考链接&#xff1a; Gith…

设计模式:结构型模式 - 适配器模式

文章目录 1.概述2.结构3.类适配器模式4.对象适配器模式5.应用场景6.JDK源码解析 - Reader 与 InputStream 1.概述 如果去欧洲国家去旅游的话&#xff0c;他们的插座如下图最左边&#xff0c;是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑&#xff0c;手机…

【Android车载系列】第9章 车载通信-Socket实现IPC通信机制(实现仿FdBus效果)

1 FDBus简介 FDBus 基于 Socket (TCP 和 Unix domain) 之上的IPC机制, 采用 Google protobuf 做序列化和反序列化。 FDBus还支持字符串形式的名字作为server地址。通过 name server 自动为 server 分配Unix domain 地址和 TCP 端口号, 实现 client 和server 之间用服务名字寻址…

【C++】哈希的应用:位图和布隆过滤器

目录 1. 位图1.1 位图的概念1.2 位图的结构1.3 位图的实现 2. 布隆过滤器2.1 概念2.2 结构2.3 布隆过滤器的实现 1. 位图 1.1 位图的概念 &#x1f4ad;位图&#xff08;bitset&#xff09;是一种基于哈希思想设计的数据结构&#xff0c;其功能主要用于判断数据是否已存在。适…

【设计模式】模板方法模式--让你的代码更具灵活性与可扩展性

文章目录 前言模板方法模式的定义核心组成模板方法模式与其他设计模式的区别 代码实现抽象类具体类Client 经典类图spring中的例子 总结 前言 在软件开发中&#xff0c;设计模式是一种经过实践检验的、可复用的解决方案&#xff0c;它们可以帮助我们解决某一特定领域的典型问题…

【2023 · CANN训练营第一季】应用开发深入讲解——第一章 模型转换(1)

学习目标 学习资源 1.模型转换 模型转换基础 在线课程 文档AIPP功能说明 文档 应用开发&#xff08;初级&#xff09; 课程目标&#xff1a; 了解AscendCL的使用场景、基本概念、基本开发流程。 学会使用AscendCL接口开发基础推理应用&#xff0c;应用中包含对图片的基本处理…

javaScript常见面试题(二)

一、浅拷贝和深拷贝 浅拷贝&#xff1a;&#xff08;1&#xff09;对于基本数据类型&#xff0c;拷贝的是值&#xff0c;修改的时候数据互不影响&#xff1b;&#xff08;2&#xff09;对于引用数据类型&#xff0c;拷贝的是在堆内存中存储的内存地址&#xff0c;修改的时候&a…

mapreduce基础: 手写本地wordcount案例

文章目录 一、源代码1. WordCountMapper类2. WordCountReducer类3. WordCountDriver类 二、运行截图 一、源代码 1. WordCountMapper类 package org.example.wordcount;import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apac…

yum库和nfs共享服务

yum库 redhat&#xff0c;centos用的是.rpm的包&#xff0c;用yum解决依赖包关系下载 ubuntu&#xff0c;debian用的是.deb的包&#xff0c;用apt解决依赖包的关系下载 yum软件仓库的提供方式 FTP服务: ftp://… HTTP服务: http://…或者https&#xff1a;//… 本地目录: fi…

母婴品牌内容输出怎么做?“四板斧”送你

新媒体时代&#xff0c;信息大爆炸&#xff0c;人们的注意力有限&#xff0c;有噱头和亮点的内容才能博得注意&#xff0c;成为用户关注的焦点。 母婴行业重视品牌效益和产品的质量&#xff0c;毕竟类似“三聚氰胺”的惨剧谁也不希望再发生。母婴产品的质量依赖技术和生产线支…

C语言从入门到精通第9天(循环结构的使用)

循环结构的使用 while语句do-while语句for语句嵌套循环 循环结构可以重复的执行一段代码块&#xff0c;在C语言中提供了三种不同类型的循环结构&#xff1a;for、while和do-while。 while语句 语法&#xff1a; while(表达式){ 语句&#xff1b; } 如果表达式为真则执行结构体…

并发编程学习笔记

为什么学&#xff1f; 只要去做一个技术含量稍微好点的系统&#xff0c;并发包下面的东西是很容易会要用到的 synchronized(Object){ } 主要是用来给对象加锁 synchronized底层原理 首先监视器的计数器是0&#xff0c;然后线程一访问&#xff0c;会将值改为1&#xff0c;…

[Gitops--4] OpenELB

OpenELB OpenELB是一个开源的负载均衡器,功能和metalLB类似 OpenELB主要两种工作模式: Layer2和BGP模式.目前OpenELB的BGP不支持ipv6 OpenELB核心思想就是通过某种方式将特定的VIP的流量引导k8s集群中,然后通过Kube-proxy将流量转发到后面的特定服务. 1. OpenELB介绍 1.1 La…

对于python文件,敲下回车后发生了什么

引言 我们常说 Python 一是门解释型语言&#xff0c;只需要敲下 python code.py就可以运行编写的代码&#xff0c;而无需使用类似于 javac 或者 gcc 进行编译。那么&#xff0c;Python 解释器是真的一行一行读取 Python 源代码而后执行吗? 实际上&#xff0c;Python 在执行程序…