14天刷爆LeetCode算法学习计划——Day01 二分查找(内含三道力扣真题)

简介: 如果我们规定整数的最大值只能是100的话,如果有个老六偏要设数组头和尾的值都是99的话,99+99=198 > 100,芭比Q了,这不就没办法运行程序了嘛,所以为了避免出现这种错误,只能用减法,由于数组的下标值是依次递增的,要想知道他的一半是多少的话,直接拿最大值-最小值的差除以2再加上最小值(一秒回到小学),即 mid = left + (right - left) / 2

一、前言


盲目刷题只会让自己心态爆炸,所以本期14天算法学习计划,也是LeetCode上的 [算法] 学习计划,在本专栏的每一篇文章都会整理每一天的题目并给出详细题解,以及知识点的整理


二、知识点


第一天的知识点是二分查找,也是一个较简单的查找算法,但是在做题时不能只想着去套模板解题,而是要根据题目意思来使用二分查找的算法解决问题,有关于二分查找的知识点我已经整理在一篇文章里了,想要复习知识点或者对它还一知半解的可以阅读一下


算法查找——二分查找


三、LeetCode704. 二分查找


1.题目


LeetCode704. 二分查找

给定一个 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


2.解题示意图


原始定义的 left 、right 和 mid


72e0b5aeecbb46a18923a9a872553056.png


如果要找的 targe 比 mid 指向的值大,就在右半边找(绿色为新的 left 和 mid )


可见left的值应该修改为mid+1


f915489e9330439c8eca13c1bf15623a.png


如果要找的 targe 比 mid 指向的值小,就在右半边找(绿色为新的 left 和 mid )


可见 right 的值应该修改为 mid-1


30e391069e2846e6837bcbc5e3355360.png


3.解题思路


1.先查找数组中最中间的元素mid(下标值向下取整)

2.如果要查找的元素值比中间的元素大,就将最大值改为中间值减一

max = mid - 1

3.如果要查找的元素值比中间的元素小,就将最小值改为中间值加一

min = mid + 1

4.重复上述步骤,直至找到所要查找的元素


4.注意事项


很多小伙伴可能第一次刷题的时候会设mid = (left + right)/2,但是如果数据的值大就会溢出。想不明白为什么会溢出?别急,接着往下听我分析


如果我们规定整数的最大值只能是100的话,如果有个老六偏要设数组头和尾的值都是99的话,99+99=198 > 100,芭比Q了,这不就没办法运行程序了嘛,所以为了避免出现这种错误,只能用减法,由于数组的下标值是依次递增的,要想知道他的一半是多少的话,直接拿最大值-最小值的差除以2再加上最小值(一秒回到小学),即 mid = left + (right - left) / 2


如果不相信的话,可以举例试试,比如6到99的中间值是多少?(99-6)/ 2 + 6 = 52,(99+6)/ 2再向下取整(Java内部自动的)也是52,完全没问题


5.代码实现


class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int 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;
    }
}


6.验证代码


bce94662da1445b6ae73fae832307635.png


四、LeetCode278. 第一个错误的版本


1.题目


LeetCode278.第一个错误的版本


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


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


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


示例 1

输入:n = 5, bad = 4

输出:4

解释: 调用 isBadVersion(3) -> false

调用isBadVersion(5) -> true

调用isBadVersion(4) -> true

所以,4 是第一个错误的版本


示例2:

输入:n = 1, bad = 1

输出:1


2.解题示意图


这道题看完第一眼,这题目是什么意思???纯纯黑人问号,所以接下来我会一点一点剥开它,显出原形——二分查找


我们先定义n为10,如果一个个去查找他是不是正确的话,实在是太麻烦了,那么直接一下子砍一半,看中间,也就是第5个版本,发现他这个版本出错了(这道题比较容易让人晕乎的点在此:true表示这个版本是错误的)


e476feaa8c9c4e57abc401ffd684a0cb.png


既然我们要找出第一个错误的版本,题目里面又告诉我们 “错误的版本之后的所有版本都是错的 ”所以不用看5以后的版本,都是错的,只需要看5版本之前的,也同样先砍一半看最中间的,所以要修改right的值,即 mid = true 时,right = mid

发现是3是false:这个版本不是错误的(是正确的),也就是说3版本是正确的


8adadfe7dae141d5878940eb2ec33988.png


那此时我们就要修改left的值,即 left = mid+1去判断mid后一个的值,发现重合了,我们就返回left的值就好了


2b1bb05c01d245aea13bbb61f40e4ce8.png


3.解题思路


1.找出中间的元素mid,并判断是否是错误版本

2.如果是错误版本**(mid = true)**,则修改right的值,使得 right = mid

3.如果是正确版本(mid = false),则修改left的值,使得


4.注意事项


  • false:这个版本不是错误的,即该版本正确
  • true:这个版本是错误的
  • mid = left + (right - left) / 2


5.代码实现


/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */
public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        while(left < right){
            int mid = left + (right - left)/2;
            if(isBadVersion(mid)){
                right = mid;
            }
            else{
                left = mid +1;
            }
        }
        return left;
    }
}


6.验证代码


14737c8d82fb4df5ac86fc8d1e433dd9.png


五、LeetCode35. 搜索插入位置


1.题目


LeetCode35.搜索插入位置

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

请必须使用时间复杂度为 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


2.解题思路


1.先查找数组中最中间的元素mid(下标值向下取整)


2.当left <= right时,通过循环查找元素


3.如果要查找的元素值比中间的元素大,就将最大值改为中间值减一

right = mid - 1


4.如果要查找的元素值比中间的元素小,就将最小值改为中间值加一

left = mid + 1


5.重复上述步骤,直至找到所要查找的元素


6.如果没有,就返回left的值;因为要插入的元素是要把比这个元素大的向后移位,而比这个元素小的是不会改变的,所以其下标值为最后搜索的两个元素的后面,如[1,3,5,7,9],target=4,

第一次mid的值为2,元素为5,

第二次mid值为1,值为3,由于搜索不到,但是left(下标0)和right(下标1)并没有重合,且 target > nums[mid] 所以又要修改left的值为 left = mid+1 = 2,那么left > right 所以退出循环,返回的值为left

这里第五步有点绕,建议配合代码食用


3.代码实现


class Solution {
    public int searchInsert(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while(left <= right){
            int mid = left + (right - left) / 2;
            if(target > nums[mid]){
                left = mid + 1;
            }
            else{
                right = mid - 1;
            }
        }
        return left;
    }
}


4.验证代码


c9b96d13525e4de2b9880d652f352fcc.png


六、结语


最后一道题的解题方法是我自己优化了官方的解题方法后实现的,虽然理解比较困难,但是一定程度上优化了代码,如果有更加高效的解题方法,欢迎评论留言

相关文章
|
4月前
|
存储 算法 搜索推荐
软考算法破壁战:从二分查找到堆排序,九大排序核心速通指南
专攻软考高频算法,深度解析二分查找、堆排序、快速排序核心技巧,对比九大排序算法,配套动画与真题,7天掌握45%分值模块。
246 1
软考算法破壁战:从二分查找到堆排序,九大排序核心速通指南
|
3月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
8月前
|
Go 开发者 索引
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣33 / 81/ 153/154)(Go语言版)
本文深入探讨了LeetCode中四道关于「搜索旋转排序数组」的经典题目,涵盖了无重复和有重复元素的情况。通过二分查找的变形应用,文章详细解析了每道题的解题思路和Go语言实现代码。关键点包括判断有序区间、处理重复元素以及如何缩小搜索范围。文章还总结了各题的异同,并推荐了类似题目,帮助读者全面掌握二分查找在旋转数组中的应用。无论是初学者还是有经验的开发者,都能从中获得实用的解题技巧和代码实现方法。
350 14
|
7月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
327 1
|
7月前
|
分布式计算 算法 Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本文讲解了两道经典的图论问题:**岛屿数量(LeetCode 200)** 和 **腐烂的橘子(LeetCode 994)**,分别通过 DFS/BFS 实现。在“岛屿数量”中,利用深度或广度优先搜索遍历二维网格,标记连通陆地并计数;“腐烂的橘子”则采用多源 BFS,模拟腐烂传播过程,计算最短时间。两者均需掌握访问标记技巧,是学习网格搜索算法的绝佳实践。
352 1
|
7月前
|
Go
【LeetCode 热题100】BFS/DFS 实战:岛屿数量 & 腐烂的橘子(力扣200 / 994 )(Go语言版)
本篇博客详细解析了三道经典的动态规划问题:198. 打家劫舍(线性状态转移)、279. 完全平方数与322. 零钱兑换(完全背包问题)。通过 Go 语言实现,帮助读者掌握动态规划的核心思想及其实战技巧。从状态定义到转移方程,逐步剖析每道题的解法,并总结其异同点,助力解决更复杂的 DP 问题。适合初学者深入理解动态规划的应用场景和优化方法。
251 0
|
7月前
|
算法 Go 索引
【LeetCode 热题100】回溯:括号生成 & 组合总和(力扣22 / 39 )(Go语言版)
本文深入解析了LeetCode上的两道经典回溯算法题:**22. 括号生成**与**39. 组合总和**。括号生成通过维护左右括号数量,确保路径合法并构造有效组合;组合总和则允许元素重复选择,利用剪枝优化搜索空间以找到所有满足目标和的组合。两者均需明确路径、选择列表及结束条件,同时合理运用剪枝策略提升效率。文章附有Go语言实现代码,助你掌握回溯算法的核心思想。
314 0
|
9月前
|
算法 Go
【LeetCode 热题100】深入理解二叉树结构变化与路径特性(力扣104 / 226 / 114 / 543)(Go语言版)
本博客深入探讨二叉树的深度计算、结构变换与路径分析,涵盖四道经典题目:104(最大深度)、226(翻转二叉树)、114(展开为链表)和543(二叉树直径)。通过递归与遍历策略(前序、后序等),解析每题的核心思路与实现方法。结合代码示例(Go语言),帮助读者掌握二叉树相关算法的精髓。下一讲将聚焦二叉树构造问题,欢迎持续关注!
250 10
|
9月前
|
Go
【LeetCode 热题100】路径与祖先:二叉树中的深度追踪技巧(力扣437 / 236 )(Go语言版)
本文深入探讨二叉树中路径与祖先问题,涵盖两道经典题目:LeetCode 437(路径总和 III)和236(最近公共祖先)。对于路径总和 III,文章分析了双递归暴力解法与前缀和优化方法,后者通过哈希表记录路径和,将时间复杂度从O(n²)降至O(n)。在最近公共祖先问题中,采用后序遍历递归查找,利用“自底向上”的思路确定最近公共祖先节点。文中详细解析代码实现与核心要点,帮助读者掌握深度追踪技巧,理解树结构中路径与节点关系的本质。这类问题在面试中高频出现,掌握其解法意义重大。
232 4
|
11月前
|
算法 Java 索引
算法系列之搜素算法-二分查找
二分查找是一种在`有序`数组中查找特定元素的算法。它的基本思想是通过将数组分成两半,逐步缩小查找范围,直到找到目标元素或确定目标元素不存在。
195 9
算法系列之搜素算法-二分查找

热门文章

最新文章