今天是秋招预备队算法篇打卡第十一天,已经状态不太好,先写两道题压压惊
问题1:二分查找-Ⅰ
描述:
解题方法:
1、暴力遍历(仅思路)
遍历一遍数组元素,和目标值作比较,若能找到,则返回数组下标,反之,则返回-1
1)遍历数组
2)比较数组当前值与目标值
3)若存在与目标值相等的元素,则返回当前值的下标,若遍历完整个数组都不存在,则返回-1
时间复杂度:O(n),需要遍历一遍数组
空间复杂度:O(1),不需要额外空间
2、二分查找(推荐使用)
因为数组为无重复升序的,所以若存在目标值,那么它一定位于数组的某个区间,若不存在,则在整个区间中都找不到。我们每次以当前区间的中点划分区间,若目标值小于中点值,那么目标值位于中点前半部分,若目标值大于中点值,那么目标值位于中点后半部分,反复递归,直到确定目标值是否存在
1)从数组中取出当前区间的中点值
2)比较中点值和目标值,如果中间值等于目标即找到了,可返回下标,如果中点值大于目标,说明中点以后的都大于目标,因此目标在中点左半区间,如果中点值小于目标,则相反
3)根据第二步的比较调整当前区间,直到不存在元素,即左边界大于右边界
import java.util.*;
public class Solution {
public int search (int[] nums, int target) {
//确定数组区间,即左右边界
int left = 0;
int right = nums.length - 1;
//寻找目标值
while(left <= right){
//中点
int middle = (right - left) / 2 + left;
//存在,返回目标值下标
if(target == nums[middle]){
return middle;
}
//目标值的前半部分
else if(target < nums[middle]){
right = middle - 1;
}
//目标值在后半部分
else{
left = middle + 1;
}
}
return -1;
}
}
时间复杂度:O(log2n),对长度为nnn的数组进行二分,最坏情况就是取2的对数
空间复杂度:O(1),常数级变量,无额外辅助空间
问题2:二维数组中的查找
描述:
解题方法:
1、暴力遍历法(仅思路)
遍历二维数组中每个一维数组的元素值,与目标值进行比较,若存在,则返回true,若不存在,则返回false
时间复杂度:O(mn),遍历二维数组所有元素m*n
空间复杂度:O(1),不需要辅助空间
2、二分法(仅思路)
因为二维数组每一行和列都按照从左到右递增的顺序排序,所以可以利用列排除一维数组第一个值大于目标值的,然后在剩余的一维数组中使用二分法查找目标值
时间复杂度:O(mlogn),遍历二维数组所有元素遍历一遍二维数组,并且同时在每个一维数组中使用二分法查找目标值
空间复杂度:O(1),不需要辅助空间
3、线性查找(推荐使用)
首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做
1)首先获取矩阵的两个边长,判断特殊情况。
2)首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是它大于目标元素,则往上移动去找小的。
3)若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。
public class Solution {
public boolean Find(int target, int [][] array) {
//判断特殊
if(array.length == 0 || array[0].length == 0){
return false;
}
//获取二维数组边界
int r = array.length - 1;
int c = array[0].length - 1;
//向左移动步数
int left = 0;
//左下元素值
int index = array[r][left];
//在二维数组内
while(r >= 0 && c >= left){
//找到目标值
if(index == target){
return true;
}
//来到第一行或者最后一列,即边界,且当前值与目标值不等,返回false
//此处的目的是防止数组越界,第一行或者最后一列查找后,没其他可找的行或列了
else if(r == 0 || left == c){
return false;
}
//当前值大于目标值
else if(index > target){
//向上移动当前值
r--;
index = array[r][left];
}
//当前值小于目标值
else{
//右移当前值
left++;
index = array[r][left];
}
}
//不存在目标值
return false;
}
}
上述的while循环体较为冗余,可以进行一定的优化,如下所示:
因为是先获取了下标值,再判断下标值是否合法,所以不用担心数组越界问题,而上面的while循环体内,虽然会先判断是否在数组范围内,但是在内部又改变了数组下标,并且直接取获取数组下标值,有可能会导致数组越界
while(r >= 0 && c >= left){
index = array[r][left];
//找到目标值
if(index == target){
return true;
}
//当前值大于目标值
else if(index > target){
//向上移动当前值
r--;
}
//当前值小于目标值
else{
//右移当前值
left++;
}
}
或者:
for(int i = r , j = 0; i >= 0 && j <= c; ){
//元素较大,往上走
if(array[i][j] > target)
i--;
//元素较小,往右走
else if(array[i][j] < target)
j++;
else
return true;
}
时间复杂度:O(m+n),遍历矩阵的时候,最多经过矩阵的一行一列
空间复杂度:O(1),常数级变量,无额外辅助空间
问题3:寻找峰值
描述:
解题方法:
1、二分法(推荐使用)
首先想到的就是遍历数组,获取最大值,或者比较当前元素值和其左右元素值,若大于相邻元素,则是一个峰值,反之,则不是,但是这样的效率很低,需要将整个链表遍历完,并且比较其左右两边的值。那么有什么办法可以提高效率呢?
由题目对于所有有效的 i 都有 nums[i] != nums[i + 1]可知,在数组中相邻的元素一定存在大小之分,并且我们发现峰值一定会出现在比当前元素值大的相邻元素一边(如果 1 2 3 为数组中相邻的三个元素,其中2为当前元素,那么一定能在元素3这边找到一个峰值),也就是说我们随机指定一个当前节点,那么就只需要找比它的相邻元素一边,但是随机节点可以每次都是边界元素,造成最坏的情况(遍历整个数组),所以我们通过指定随机节点的位置,即数组的中点,可以避免随机的最坏情况,且便于我们收敛区间于一点,退出循环。
每次取当前区间的中间元素,判断中间元素与相邻元素的大小情况,若左相邻元素大于中间元素,则一定有峰值在左边区间,反之,则右边区间一定存在峰值,直到区间收敛到一个元素值,这个元素就是峰值
import java.util.*;
public class Solution {
public int findPeakElement (int[] nums) {
//区间
int left = 0, right = nums.length - 1;
//二分缩小区间
while(left < right){
//中点
int middle = (right - left) / 2 + left;
//这段注释内的方法错误,无法解决问题,需要添加一定的条件
// //左邻元素大于中间元素,峰值存在左半区间
// if(nums[middle] > nums[middle - 1]){
// left = middle;
// }
// //左邻元素小于中间元素,峰值存在右半区间
// else{
// right = middle -1;
// }
//右相邻元素较小,不一定有峰值
if(nums[middle] > nums[middle + 1]){
right = middle;
}
//右边相邻元素较大,一定存在峰值
else{
left = middle + 1;
}
}
return left;
}
}
在解题过程中,我发现若是用左边的邻接元素来作为比较的参考值(即下面代码被注释的部分),那么可能会进入死循环或者数组越界,我猜测原因应该是当和不管为奇数还是偶数时,取它的中间值要么刚好为中间元素(奇数),要么是中点的前一个元素(偶数),这个时候左邻元素可能不存在(middle = 0),此时会产生数组越界,而且当区间只剩下两个元素时(假设:2 3),中间元素为前一个元素(2),那么此时比较的一直都是前一个元素(2)和元素(2)的前一个元素(此元素不在区间内了),若元素(2)前面的元素小于元素(2),那么if条件恒成立,此时middle一直等于left(left=middle,那么这两个元素计算出的middle恒等于left),无法比较到剩下的两个元素,形成死循环,而如果使用右邻接元素就可以避免这个问题,不管如何middl都会小于right,所以不会数组越界,并且能保证每次都是和区间内(middle+1一定在区间内)的元素进行比较,所以推荐直接使用右邻元素进行比较
时间复杂度:O(log2n),二分法最坏情况对整个数组连续二分,最多能分log2nlog_2nlog2n次
空间复杂度:O(1),常数级变量,无额外辅助空间