最近春招开始了,面试面着面着一言不合就开始手撕代码
手撕就手撕,接下来我打算写几个专题讲讲面试中手撕的常见题目
这些都是LeetCode上有的题目 手撕无非就是 树、链表、二分、字符串这些常用的数据结构所以接下来请关注我们的专题吧
二分法
二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。
二分法查找的思路如下:
(1)首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。
(2)如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。
(3)如果某一步数组为空,则表示找不到目标元素。
二分法查找的时间复杂度
数组[1,2] 它的峰值就是2 因为1<2< -∞ 给大家画一个 山峰 可以知道山峰的确定和他的绝对高度 无关 和他的相对高度有关 只要他是比它的邻居都高 那它就是山峰 抽象成二分法怎么理解 如这个图所示 我就比较nums[mid]和nums[mid+1]之间的关系 无非就是只有两个情况
O(logn)
。
感觉很抽象的样子 举一个简单通俗易懂的例子
你不知道自己长得怎么样,然后去找了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 {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 {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 long mid=(left+right)/2+1;if(mid*mid>x)//平方大于目标数 往低走
right=mid-1;else if(mid*mid//平方大于目标数 往低走
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 int mid=(left+right)/2+1;if(matrix[Hang][mid]>target)//如果这一行上坐标为(Hang,mid)的元素大于target 说明目标数可能在左边
right=mid-1;else if(matrix[Hang][mid]//如果这一行上坐标为(Hang,mid)的元素小于target 说明目标数可能在右边
left=mid;else //相等的话就是Truereturn 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 的位置i
3,使用二分法找到最后一个 小于等于 target的位置j
这个过程类似什么 类似我们高中学过的
夹逼准则
吧
由于数组有序且从小到大排序 那么找到的[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;elseleft=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) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
如果是 那么就说明1-x上每个数都只出现了一次 目标数还得往大找
如果不是 那么说明1-x上有一个数是出现了两次 目标数得往小了找
int n=nums.length;int l=0;int r=n-1;while(l int mid=(l+r)/2;int cnt=0;for(int i=0;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) 时间复杂度的。
数组[1,2] 它的峰值就是2 因为1<2< -∞ 给大家画一个 山峰 可以知道山峰的确定和他的绝对高度 无关 和他的相对高度有关 只要他是比它的邻居都高 那它就是山峰 抽象成二分法怎么理解 如这个图所示 我就比较nums[mid]和nums[mid+1]之间的关系 无非就是只有两个情况
nums[mid] 也就是右边的元素大于左边的 那从左往右看就是上坡 上坡路必定存在一个顶峰
nums[mid]>nums[mid+1] 也就是右边的元素小于等于左边的 那从右往左看就是一个平滑的下坡,那这个顶峰必定是在mid这个位置之前,不然我也不可能有下坡的空间
int l=0;int r=nums.length-1;while(l int mid=(l+r)/2;if(nums[mid]1])//如果上坡 继续往前走 看看顶峰在哪里
l=mid+1;else//如果下坡了 往回走 直到找到顶峰
r=mid;
}return l;
最后推荐几道题目,也是使用二分的,相信你看到这里也对二分有了更高的认识,不再是有序才可以用二分
Leetcode-278 第一个错误的版本
Leetcode-275 H指数II
Leetcode-35 搜索插入位置
Leetcode-53 寻找旋转排序数组中的最小值更多精彩算法文章:手把手教你写归并排序算法 (Java代码)总结了一些算法二叉树操作的干货 (附Python代码)递归算法图文详细总结,收藏了!十大经典排序算法(Python版本)