先注声明:解题思路:借鉴、归纳及总结 意在记录学习过程(非原创)
但文字部分及所用图片均来自个人 谢绝转载
1.二分法
标签:不甘心用暴力算法缓慢遍历,企图通过二分不断缩小搜索区间,以降低时间复杂度
使用前提:一般为有序数组
时间复杂度:O(logn)
基础款:LeetCode 704 704. 二分查找 - 力扣(Leetcode)
tip: 二分法确实有许多写法,了解思路后,熟悉一种写法即可
变种1:返回插入位置 LeetCode 35 35. 搜索插入位置 - 力扣(Leetcode)
小结:仔细对比,会发现和基础款几乎没有区别。
唯一修改的地方就是,未找到的return值由-1 改成了 left。
这其实很好理解,因为二分法最终情况,左右指针一定会逼近。
变种2:查找上下界 LeetCode 34 34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(Leetcode)
小结:首先这种题也存在很多解法,这里仅记叙我最习惯思考的一种(不一定最简便)。
以卡下界为例,本质是查找第一个与之相等的元素。
与基础款作对比,修改的代码看上去仅仅是return middle 的 if 条件。
首先,与目标值相等必是返回的条件之一。但如何是保证出现的第一个?
利用数组的有序性就可以——第一个相等元素的左端值一定比目标值target 小,
即需满足nums[middle-1] < target。
*但这里需要仍需注意middle-1的合法性。(这很重要)
*存在特殊情况:nums[middle-1]不合法,即middle == 0。此时元素一定满足返回条件。
因此,判断条件为 if(nums[middle]==target && (middle == 0||nums[middle-1] < target))。
*还需要注意的一点是:倘若交换 || 前后顺序,写成:
if(nums[middle]==target && (nums[middle-1] < target || middle == 0))也是不正确的。
因为机器执行是从左往右判断,所以仍会出现数组访问越界的情况。
所以必须middle==0 写在前,nums[middle-1] < target 写在后。
这里有一个不经意的小心机。注意看else。
不同于基础款的二分法,这里由于if 扩充了条件,else 包含的条件会变多。
但因为我们的目标是搜索下界,所以会有一个往左搜索的趋势。
所以else 的情况下,修改的一定是right的边界。(这点记好)
现在回过头来看卡上界,与下界是一个道理。
首先是if 条件扩充:middle 走到右侧尽头或者middle 右侧的数比target大。
其次是注意else 下,修改的是left的边界。
变种3:查找第一个/最后一个 大于/小于目标值的元素下标
说明:实际上这种情况和变种2相差不太多。
查找第一个就相当于查下界。查最后一个相当于查上界。
变种2查找相等情况,变种3查找大于/小于也仅仅是修改判断条件。
现已,查找第一个大于目标值为例:
小结:注意几个事项即可(以下讨论基于“第一个大于”的题干)
1.修改 if 条件,确保middle位置的数满足大于目标值的条件。
同时确保左侧的数若存在,则必小于等于目标值target。
2.修改之后的else if 及else.但要确保最后else 下修改的是right,已达到往左逼近的目的。
其它变种情况类似,此处不再赘述。
2.快慢指针
定性描述:快慢指针是双指针法中的一种,可用于解决(链表上)环的问题。
名词解释:使用速度不同的指针用于链表、数组等 快指针:fast 满指针:slow
经典题型:LeetCode 27 移除元素 27. 移除元素 - 力扣(Leetcode)
思路:用两层for循环暴力求解可以,但是时间复杂度过高,为O(n²)。
不如用双指针原地修改数组,可使时间复杂度降为O(n)。
小结: 核心想法就是,以 fast 获取“新数组”的“新元素”,赋值在slow的数组之上。
实质是对两个“相同的数组”进行操作。