二分法查找平方和_面试手撕算法系列:二分法

最近春招开始了,面试面着面着一言不合就开始手撕代码 6a7fb6d725a6f321b000ae85b57823ca.png 手撕就手撕,接下来我打算写几个专题讲讲面试中手撕的常见题目  这些都是LeetCode上有的题目 手撕无非就是 树、链表、二分、字符串这些常用的数据结构所以接下来请关注我们的专题吧 二分法 二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。 二分法查找的思路如下: (1)首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。 (2)如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。 (3)如果某一步数组为空,则表示找不到目标元素。 二分法查找的时间复杂度 O(logn)

c0525386f5c07d48f01c94353b13c118.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     {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...,
由于返回类型是整数,小数部分将被舍去。
这个题目使用二分法还是比较明显的 使用二分法搜索平方根的思想很简单,就类似于小时候我们看的电视节目中的“猜价格”游戏,高了就往低了猜,低了就往高了猜,范围越来越小。 因此,使用二分法猜算术平方根就很自然。 存在三种情况:
  1. 平方小于目标数 往高走

  2. 平方大于目标数 往低走

  3. 平方和恰好等于目标数 跳出循环

//采用二分模板 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 这个过程类似什么 类似我们高中学过的 夹逼准则

8b99362ae98e298dba7537ae184d4774.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;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) 。

  • 数组中只有一个重复的数字,但它可能不止重复出现一次。

这个题目看上去可以用暴力的做法去做,但是暴力做法的复杂度是在O(n^2),肯定是不行的 既然要小于O(n^2) 灵机一动 要不我先排个序

1faeb5f6ba5b2b20e188b628f591e1a2.png

且慢 这题排序好像帮助也不大 上头了上头了抽根烟冷静一下

a7b89830b0fbdb1a478222d4d4fa0e1c.png

n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n) 那就可以保证至少有一个数是重复的 慢着 这不就是抽屉原理嘛 所谓抽屉原理就是 n+1个苹果 放到n个抽屉里 肯定有一个抽屉是放了两个苹果以上 而且题目中说只有一个抽屉是放了两个苹果 其他抽屉放了一个苹果 好了我先生成一个抽屉 把苹果放进去不就好了 且慢 这个题不能使用额外空间 我们回到刚开始的暴力做法 暴力做法是指从1遍历到n看看1-n上哪个数是出现两次的 假设这个重复出现的数是x+1  那么1-x 出现的次数就是x 1-(x+1)出现的次数就是x+2 好像可以看到二分的性质了 二分的依据就是 1-x 出现的次数是不是x
  1. 如果是 那么就说明1-x上每个数都只出现了一次 目标数还得往大找

  2. 如果不是 那么说明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) 时间复杂度的。

这个题目算是二分法里相对抽象的模型了 O(logN) 也在提示你往二分方面想 不过还好题目给了点提示  nums[-1] = nums[n] = -∞ 由于峰值元素是指其值大于左右相邻值的元素 我们可以把这个数组的最左端和最右端看作是负无穷 那么只要是任意一个实数 他小于两边的元素 或者大于两边的元素 他就是峰值 举个例子 数组[1]        它的峰值就是1     因为 nums[-1] = nums[n] = -∞
数组[1,2]      它的峰值就是2      因为1<2< -∞ 给大家画一个 山峰 可以知道山峰的确定和他的绝对高度 无关 和他的相对高度有关 只要他是比它的邻居都高 那它就是山峰

104ea9d2f66c57d0f8da48bed27d5c1e.png

抽象成二分法怎么理解 如这个图所示 我就比较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;
   最后推荐几道题目,也是使用二分的,相信你看到这里也对二分有了更高的认识,不再是有序才可以用二分

9b6e04a23468bc88c2533ec876a4a879.png

Leetcode-278 第一个错误的版本 Leetcode-275 H指数II Leetcode-35 搜索插入位置 Leetcode-53 寻找旋转排序数组中的最小值更多精彩算法文章:手把手教你写归并排序算法 (Java代码)总结了一些算法二叉树操作的干货 (附Python代码)递归算法图文详细总结,收藏了!十大经典排序算法(Python版本) 1c568624cfd82f1612bec1dc0ded3ad9.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值