蓝桥杯算法竞赛系列第四章——二分算法

简介: 二分算法

欢迎回到:遇见蓝桥遇见你,不负代码不负卿!


目录

引入:二分查找

题目描述

题解

代码执行

复杂度分析

例题一:搜索插入位置

题目描述

题解

代码执行

复杂度分析

例题二:寻找峰值

题目描述

题解

代码执行

复杂度分析

例题三:搜索二维矩阵

题目描述

题解

代码执行

思考题

最大子序和

题目描述

代码执行

蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!


好久不见啦铁汁们,蓝桥杯更新咯,快来尝尝鲜叭。

【前言】:由于本章基础知识点不多,所以笔者直接讲解四道典型题让大家感受一下二分法的美妙。


准备开始咯,坐稳哈...

引入:二分查找

【敲黑板】:用二分算法解题的前提是该数组有序!!!

【注意】:查找一次砍掉一半,效率非常高!但是条件比较苛刻,一定要有序!

题目描述


给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。


示例1:

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

示例2:

输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1

题解


设定左右指针

找出中间位置,并判断该位置值是否等于 target

nums[mid] == target 则返回该位置下标

nums[mid] > target 则右侧指针移到中间

nums[mid] < target 则左侧指针移到中间


二分是一个比较简单的算法,只要大家记住上面这种套路就行啦。


代码执行


int search(int* nums, int numsSize, int target){
    //考虑特殊情况
    if(nums == NULL || numsSize == 0){
        return -1;
    }
    int left = 0;//起始元素的索引
    int right = numsSize - 1;//末尾元素的索引
    int mid = 0;
    while(left <= right){
        //应该有很多人会写成mid = (left + right) / 2;这种写法不谨慎
        //因为做的是加法运算,所以要考虑溢出的特殊情况
        mid = left + (right - left) / 2;
        if(nums[mid] < target){
            left = mid + 1;
        }else if(nums[mid] > target){
            right = mid - 1;
        }else{
            return mid;
        }
    }
    return -1;
}


【注意】:while判断表达式中是left <= right, 为什么还要加上left == right  的情况呢,当二者相等的时候说明还有一个元素需要被比较,所以当left >  right时停下来,因为此时中间已经没有元素需要被比较了。至于为什么将mid = left + (right - left) / 2;  的形式,上面代码中已经讲咯。


复杂度分析


时间复杂度:O(logN)

空间复杂度:O(1)

看,二分法是很高效的,大家在今后的训练中,如果遇到查找搜索类的题目要求时间复杂度是O(logN)的,要想到二分法哦。

好嘞,这就是二分查找,是不是很简单,下面再补充几道典型例题,让大家熟悉二分。

例题一:搜索插入位置

题目描述

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

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

题目中要求使用时间复杂度为O(logN)的算法解题,所以结合本题题意,很容易就想到了二分法。


示例1:


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


示例2:


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


题解

  • 整体思路和普通的二分查找几乎没有区别,先设定左侧下标 left 和右侧下标 right,再计算中间下标 mid
  • 每次根据 nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则 left 右移,nums[mid] > target 则 right 左移
  • 查找结束如果没有相等值则返回 left,该值为插入位置,注意哦,最后如果没有相等值,返回的是left

【注意】:

二分查找的思路不难理解,但是边界条件容易出错,比如 循环结束条件中 left 和 right 的关系,更新 left 和 right 位置时要不要加 1 减 1。这些都是大家自己动手画图理解,用代码去体会,只可意会不可言传哦。


代码执行


int searchInsert(int* nums, int numsSize, int target){
    //考虑特殊情况
    if(nums == NULL || numsSize == 0){
        return -1;
    }
    int left = 0;
    int right = numsSize - 1;
    int mid = 0;
    while(left <= right){
        mid = left + (right - left) / 2;
        if(nums[mid] > target){
            right = mid - 1;
        }else if(nums[mid] < target){
            left = mid + 1;
        }else{
            return mid;
        }
    }
    return left;
}


复杂度分析

时间复杂度:O(logN)

空间复杂度:O(1)

是不是很简单,下面开始蹭加点难度咯,有点绕,需要仔细想哦,加油加油。


例题二:寻找峰值

题目描述

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞ 。

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


示例1:


输入:nums = [1,2,3,1]
输出:2
解释:3 是峰值元素,你的函数应该返回其索引 2。


示例2:


输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5 
解释:你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。


题解


【注意】:二分法解题的前提要求是有序的,这题看起来没说有序呀,那怎么还能用二分法呢,我们也不能先进行排序,因为会把索引打乱,那该怎么办呢,请朝后看...


描述这个规律就是:

  • 规律一:如果nums[mid] > nums[mid+1],则在mid之前一定存在峰值元素
  • 规律二:如果nums[mid] < nums[mid+1],则在mid+1之后一定存在峰值元素


代码执行


int findPeakElement(int* nums, int numsSize){
    //考虑特殊情况
    if(nums == NULL || numsSize == 0){
        return -1;
    }
    int left = 0;
    int right = numsSize - 1;
    int mid = 0;
    while(left <= right){
        if(left == right){
            return left;
        }
        mid = left + (right - left) / 2;
        if(nums[mid] > nums[mid + 1]){
            right = mid;//mid之前一定存在峰值元素
        }else{
            left = mid + 1;//mid+1之后一定存在峰值元素
        }
    }
    return left;
}


复杂度分析

时间复杂度:O(logN)

空间复杂度:O(1)


例题三:搜索二维矩阵

题目描述

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

每行中的整数从左到右按升序排列。

每行的第一个整数大于前一行的最后一个整数。

也就是说,整个二维数组都是升序的。

示例1:

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


示例2:


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


题解

【注意】:本题的重点就在于一维索引和二维索引间的互换


代码执行


bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target){
    //考虑特殊情况
    if(matrix == NULL || matrixSize == 0){
        return false;
    }
    int row = matrixSize;//行数
    int col = *matrixColSize;//列数
    int left = 0;//起始元素的索引
    int right = row * col - 1;//最后一个元素的索引
    int mid = 0;
    int element = 0;
    while(left <= right){
        mid = left + (right - left) / 2;
        element = matrix[mid / col][mid % col];//将一维的索引化成二维的索引
        if(element == target){
            return true;
        }else if(element > target){
            right = mid - 1;
        }else{
            left = mid + 1;
        }
    }
    return false;
}


复杂度分析

时间复杂度:O(logM*N), M,N分别是矩阵的行数和列数

空间复杂度:O(1)

好喽,铁汁把上面四道题目练习一遍,肯定就能对二分算法有一定的认识,加油加油哦。

思考题

大家先将上篇博文(分治算法)看一下,现在讲解那道留下的思考题。

蓝桥杯算法竞赛系列第三章——细谈递归的bro分治_安然无虞的博客-CSDN博客

最大子序和

题目描述

给定一个整数数组,找到一个具有最大和的连续子数组(子数组中至少包含一个数),要求返回其最大和。

注意:本题要求的是连续的子数组,可能有的题目要求可以是断开的,但本题不是。

将数组nums由中点mid分为三种情况:

1. 最大子串在左边

2. 最大子串在右边

3. 最大子串跨中点,左右两边元素都有(理解上的难点)



lSum 表示 [l,r] 内以 l 为左端点的最大子段和

rSum 表示 [l,r] 内以 r 为右端点的最大子段和

mSum 表示 [l,r] 内的最大子段和

iSum 表示 [l,r] 的区间和


代码执行

struct Status {
    int lSum, rSum, mSum, iSum;
};
struct Status pushUp(struct Status l, struct Status r) {
    int iSum = l.iSum + r.iSum;
    int lSum = fmax(l.lSum, l.iSum + r.lSum);
    int rSum = fmax(r.rSum, r.iSum + l.rSum);
    int mSum = fmax(fmax(l.mSum, r.mSum), l.rSum + r.lSum);
    return (struct Status){lSum, rSum, mSum, iSum};
};
struct Status get(int* a, int l, int r) {
    if (l == r) {
        return (struct Status){a[l], a[l], a[l], a[l]};
    }
    int m = l + (r - l) / 2;
    struct Status lSub = get(a, l, m);
    struct Status rSub = get(a, m + 1, r);
    return pushUp(lSub, rSub);
}
int maxSubArray(int* nums, int numsSize) {
    return get(nums, 0, numsSize - 1).mSum;
}


由于想让大家搞懂知识点,所以这里的题目可能用这种解法不是最优解,没关系,我会在后面的算法中补充到,在每日一题中也会涉及到。今天就不布置思考题咯,铁汁们好好把上面题目自己尝试做出来。


蓝桥结语:遇见蓝桥遇见你,不负代码不负卿!

嘿嘿,期待铁汁们留言点评,如果能够再动动小手,给博主来个三连那就更好啦,您的认可就是我最大的动力!求求啦~~


相关文章
|
6月前
|
机器学习/深度学习 算法
【数学建模竞赛】评价类赛题常用算法解析
【数学建模竞赛】评价类赛题常用算法解析
119 0
|
6月前
|
算法 Java Serverless
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-444 算法训练 求和问题
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-444 算法训练 求和问题
56 1
|
6月前
|
算法 Java C语言
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-439 算法训练 简单字符变换
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-439 算法训练 简单字符变换
54 1
|
1月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
1月前
|
存储 机器学习/深度学习 算法
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
蓝桥杯Python编程练习题的集合,涵盖了从基础到提高的多个算法题目及其解答。
60 3
蓝桥杯练习题(三):Python组之算法训练提高综合五十题
|
5月前
|
存储 机器学习/深度学习 算法
第十五届蓝桥杯pb组国赛E题[马与象] (15分)BFS算法 详解
第十五届蓝桥杯pb组国赛E题[马与象] (15分)BFS算法 详解
61 3
|
6月前
|
存储 算法 Java
蓝桥杯递归算法
蓝桥杯中的递归算法涉及将问题分解为子问题,通过函数自身调用来求解。优点是简洁易懂,但效率低且可能引发栈溢出。示例包括:数组求和、数的阶乘、斐波那契数列及最大公约数计算,以及字符串翻转。代码展示了各种递归场景的Java实现,如`f3`计算数组和,`f`求阶乘,`f1`计算斐波那契数,`f2`找最大公约数,和`f1`字符串反转。
36 1
|
5月前
|
人工智能 算法 搜索推荐
蓝桥杯宝藏排序题目算法(冒泡、选择、插入)
以下是内容的摘要: 本文介绍了三种排序算法:冒泡排序、选择排序和插入排序。冒泡排序通过不断交换相邻的逆序元素逐步排序,最坏情况下需要 O(n^2) 次比较。选择排序在每轮中找到剩余部分的最小元素并放到已排序序列的末尾,同样具有 O(n^2) 时间复杂度。插入排序则是将每个元素插入到已排序序列的正确位置,时间复杂度也是 O(n^2),但空间复杂度为 O(1)。
|
6月前
|
算法 安全 定位技术
【刷题】备战蓝桥杯 — dfs 算法
dfs算法在数据较小的情况下可以使用。 一定一定要确定好终止条件,避免栈溢出。 相应做好回溯,保证每次的遍历都是不一样的选择,避免少结果。 针对题目进行对应细节处理,有能力的话可以进行剪枝优化!!!
68 0
|
6月前
|
机器学习/深度学习 监控 算法
【数学建模竞赛】优化类赛题常用算法解析
【数学建模竞赛】优化类赛题常用算法解析
254 2
下一篇
无影云桌面