![c87e7debf0bc4a0c8f998197ebda1b70.png](https://i-blog.csdnimg.cn/blog_migrate/1b022ee3cefeaffa9fc083a926d315ca.jpeg)
最近春招开始了,面试面着面着一言不合就开始手撕代码
![fc4adbce7b92f231a6f9f4fde97906b0.png](https://i-blog.csdnimg.cn/blog_migrate/77f8e1dd8b3cba263b7d256318286700.png)
手撕就手撕,接下来我打算写几个专题讲讲面试中手撕的常见题目 这些都是LeetCode上有的题目 手撕无非就是 树、链表、二分、字符串这些常用的数据结构
所以接下来请关注我们的专题吧
二分法
二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。
二分法查找的思路如下:(1)首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。(2)如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。(3)如果某一步数组为空,则表示找不到目标元素。二分法查找的时间复杂度O(logn)
。
![50e41674885e0c3ce1985ecb16f94113.png](https://i-blog.csdnimg.cn/blog_migrate/a7b4f9ddbcdac96a2664789f6b3c6569.png)
感觉很抽象的样子 举一个简单通俗易懂的例子你不知道自己长得怎么样,然后去找了10个人 ,评分分别为1-10分首先把5号拉出来,让大家看看是不是这个人和你比哪个优秀如果你比五号选手长得帅,那么你就不用和1-4号比了,这个区间就缩短成[5-10]如果不幸的比五号选手长得丑,那么这个区间就缩短成了[1-4]这里介绍两个二分法的模板 主要区别在于当前数是被划分到左区间
还是右区间
模板
boolcheck(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
intbsearch_1(int l, int r){
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
intbsearch_2(int l, int r){
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
二分法作为一种搜索上快速的算法,可以把复杂度从O(N)
变成O(log N)
,要满足二分法搜索的条件是搜索区间内满足单调性为什么会有两个模板呢可以看到在区分id的时候可以分为mid=(l+r)/2
和(l+r)/2+1
与此同时带来了当前数字索引划分区间的不同话不多说先来两个题目试一试水
69 X的平方根
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
这个题目使用二分法还是比较明显的使用二分法搜索平方根的思想很简单,就类似于小时候我们看的电视节目中的“猜价格”游戏,高了就往低了猜,低了就往高了猜,范围越来越小。因此,使用二分法猜算术平方根就很自然。存在三种情况:
- 平方小于目标数 往高走
- 平方大于目标数 往低走
- 平方和恰好等于目标数 跳出循环
//采用二分模板 mid划到了左区间
long left=1;
long right=x;
while(left<right){
long mid=(left+right)/2+1;
if(mid*mid>x)//平方大于目标数 往低走
right=mid-1;
else if(mid*mid<x)//平方大于目标数 往低走
left=mid;
else {//平方和恰好等于目标数 跳出循环
left=mid;
break;
}
}
return (int)(left);
74 搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:每行中的整数从左到右按升序排列。每行的第一个整数大于前一行的最后一个整数。
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 3
输出: true
输入:
matrix = [
[1, 3, 5, 7],
[10, 11, 16, 20],
[23, 30, 34, 50]
]
target = 13
输出: false
这个题目也可以采用二分 先找一下规律发现 每行的第一个整数大于前一行的最后一个整数 所以这个数组 从上到下 从左到右是依次递增的 所以我们可以采用两次二分法第一次二分的目的是锁定这个数在哪一行上
int left=0;
int right=Hang-1;
while(left<right){
int mid=(left+right)/2+1;
if(matrix[mid][0]>target)//当前这个行首元素大于target 说明不可能在这一行
right=mid-1;
else if(matrix[mid][0]<=target)//小于或者等于的时候 说明可能是在这一行上
left=mid;
}//最后这个left就是具体哪一行
第二次二分的目的是锁定这个数在这一行的哪一列上主要判断索引为(Hang,mid)这个元素与target的关系
Hang = left;
left =0;
right=Lie-1;
while(left<right){
int mid=(left+right)/2+1;
if(matrix[Hang][mid]>target)//如果这一行上坐标为(Hang,mid)的元素大于target 说明目标数可能在左边
right=mid-1;
else if(matrix[Hang][mid]<target)//如果这一行上坐标为(Hang,mid)的元素小于target 说明目标数可能在右边
left=mid;
else //相等的话就是True
return true;
}
//最后找到这个数但是不一定是和目标数相等 要判断一下
if(matrix[Hang][left]==target)
return true;
else
return false;
查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
因为数组已经排过序了,我们可以使用二分查找的方法去定位左右下标。1,使用二分法判断是否存在目标值2,使用二分法找到第一个 大于等于 target 的位置i3,使用二分法找到最后一个 小于等于 target的位置j这个过程类似什么 类似我们高中学过的夹逼准则吧
![80519fc5591c4c1cb2a1623fff0826c4.png](https://i-blog.csdnimg.cn/blog_migrate/7d57b0b1561b2d8a34b4974ac150a331.png)
由于数组有序且从小到大排序 那么找到的[i,j]区间里必定是目标区间 当然还得判断目标区间是否存在使用二分法找到第一个 大于等于 target 的位置i
int left=0;
int right=nums.length-1;
while(left<right){
int mid=(left+right)/2+1;
if(nums[mid]<=target)
left=mid;
else
right=mid-1;
}
使用二分法找到最后一个 小于等于 target 的位置j
left=0;
right=nums.length-1;
while(left<right){
int mid=(left+right)/2;
if(nums[mid]>=target)
right=mid;
else
left=mid+1;
}
最后验证区间是否符合我们的要求即可
287 寻找重复数
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
输入: [1,3,4,2,2]
输出: 2
输入: [3,1,3,4,2]
输出: 3
说明:
- 不能更改原数组(假设数组是只读的)。
- 只能使用额外的 O(1) 的空间。
- 时间复杂度小于 O(n^2) 。
- 数组中只有一个重复的数字,但它可能不止重复出现一次。
这个题目看上去可以用暴力的做法去做,但是暴力做法的复杂度是在O(n^2),肯定是不行的既然要小于O(n^2) 灵机一动 要不我先排个序
![5588bfb6e1004f784ba900f8308f63e9.png](https://i-blog.csdnimg.cn/blog_migrate/0d037f81003844068b62d567ad872f13.jpeg)
且慢 这题排序好像帮助也不大 上头了上头了抽根烟冷静一下
![42612460b0226a34e9f6b9e15b50540d.png](https://i-blog.csdnimg.cn/blog_migrate/b8b9d6a914c11c0c6e82e0808d0e1b79.png)
n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n)那就可以保证至少有一个数是重复的慢着 这不就是抽屉原理嘛所谓抽屉原理就是n+1个苹果 放到n个抽屉里 肯定有一个抽屉是放了两个苹果以上 而且题目中说只有一个抽屉是放了两个苹果 其他抽屉放了一个苹果好了我先生成一个抽屉 把苹果放进去不就好了
且慢 这个题不能使用额外空间
我们回到刚开始的暴力做法 暴力做法是指从1遍历到n看看1-n上哪个数是出现两次的假设这个重复出现的数是x+1 那么1-x 出现的次数就是x1-(x+1)出现的次数就是x+2好像可以看到二分的性质了二分的依据就是 1-x 出现的次数是不是x
- 如果是 那么就说明1-x上每个数都只出现了一次 目标数还得往大找
- 如果不是 那么说明1-x上有一个数是出现了两次 目标数得往小了找
那么我们要找出这个引起质变的数即可
int n=nums.length;
int l=0;
int r=n-1;
while(l<r){
int mid=(l+r)/2;
int cnt=0;
for(int i=0;i<nums.length;i++){//计算1-mid 上每个数出现的次数
if(nums[i]<=mid)
cnt+=1;
}
if(cnt<=mid)//如果是每个数都出现了一次 那么往大找
l=mid+1;
else//否则往小找
r=mid;
}
return l;
162 寻找峰值
峰值元素是指其值大于左右相邻值的元素。给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。你可以假设 nums[-1] = nums[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。
说明:
- 你的解法应该是 O(logN) 时间复杂度的。
这个题目算是二分法里相对抽象的模型了 O(logN) 也在提示你往二分方面想
不过还好题目给了点提示nums[-1] = nums[n] = -∞由于峰值元素是指其值大于左右相邻值的元素我们可以把这个数组的最左端和最右端看作是负无穷那么只要是任意一个实数 他小于两边的元素 或者大于两边的元素 他就是峰值
举个例子数组[1] 它的峰值就是1 因为 nums[-1] = nums[n] = -∞
数组[1,2] 它的峰值就是2 因为1<2< -∞给大家画一个山峰可以知道山峰的确定和他的绝对高度 无关 和他的相对高度有关
只要他是比它的邻居都高 那它就是山峰
![841a305241ff313066bef32b54033a1e.png](https://i-blog.csdnimg.cn/blog_migrate/470929a8ecf65eae5bacdd616eadb25e.png)
抽象成二分法怎么理解 如这个图所示 我就比较nums[mid]和nums[mid+1]之间的关系无非就是只有两个情况
- nums[mid]<nums[mid+1] 也就是右边的元素大于左边的 那从左往右看就是上坡 上坡路必定存在一个顶峰
- nums[mid]>nums[mid+1] 也就是右边的元素小于等于左边的 那从右往左看就是一个平滑的下坡,那这个顶峰必定是在mid这个位置之前,不然我也不可能有下坡的空间
所以抽象成二分模型就是
int l=0;
int r=nums.length-1;
while(l<r){
int mid=(l+r)/2;
if(nums[mid]<nums[mid+1])//如果上坡 继续往前走 看看顶峰在哪里
l=mid+1;
else//如果下坡了 往回走 直到找到顶峰
r=mid;
}
return l;
最后推荐几道题目,也是使用二分的,相信你看到这里也对二分有了更高的认识,不再是有序才可以用二分
![0b38202252d91a5cf6cbfb2203a07dc2.png](https://i-blog.csdnimg.cn/blog_migrate/4ccb55617877f3644c7877800f58bf5c.png)
Leetcode-278 第一个错误的版本
Leetcode-275 H指数II
Leetcode-35 搜索插入位置
Leetcode-53 寻找旋转排序数组中的最小值
往期精彩回顾
三月算法岗面经
阿里笔试题两道
扫码关注我,加入每日一题算法交流群/秋招备战群吧
请备注学校+昵称才可以加群噢
![50eb70c7e3da4445f7e7156730f93461.png](https://i-blog.csdnimg.cn/blog_migrate/e59f3bcb511960c659a36d83ac3143dc.jpeg)