leetcode-搜索总结

leetcode-278-第一个错误的版本(first bad version)-java
二分查找法中,mid不能用
int mid=(begin+last)/2;
要用
int mid=begin+(last-begin)/2; 也可以(begin+end)>>>1
因为第一种情况begin+last可能会溢出,导致出错

leetcode-215-数组中的第K个最大元素 (kth largest element in an array)-java
查找-数组中的第K个最大元素
方法1:
使用最小堆的方法,求第k大,建立一个规模为k的最小堆
首先将nums前k个元素加入堆,然后初始化最小堆,让heap[0]为这k个元素小的
然后将nums第k到length-1个,依次与heap[0]比较
如果比它大,则替代heap【0】,然后下沉,让heap[0]继续是这k个最小的
所以最后,heap为nums中前k大的,heap[0]为第k大的
该方法速度为o(nlogk),比全部排序o(nlogn)快

方法2:
利用快速排序的思想,在进行排序过程中每次可以确定一个元素的最终位置,若此位置为第K个最大元素,则直接返回此索引,否则继续分治进行快速排序。不用排列全部,只要每次只排序一段,找到即可

leetcode-162-寻找峰值(find peak element)-java
解法1:
直接顺序查找,看now>prev&&now>next即可
解法2:
在简单的二分查找中,我们处理的是一个有序数列,并通过在每一步减少搜索空间来找到所需要的数字。在本例中,我们对二分查找进行一点修改。首先从数组 nums 中找到中间的元素 mid。
若该元素恰好位于降序序列或者一个局部下降坡度中(通过将 nums[i] 与右侧比较判断),则说明峰值会在本元素的左边。于是,我们将搜索空间缩小为 mid的左边(包括其本身),并在左侧子数组上重复上述过程。
若该元素恰好位于升序序列或者一个局部上升坡度中(通过将 nums[i]与右侧比较判断),则说明峰值会在本元素的右边。于是,我们将搜索空间缩小为 mid的右边,并在右侧子数组上重复上述过程。

就这样,我们不断地缩小搜索空间,直到搜索空间中只有一个元素,该元素即为峰值元素。
**最左端和最右端元素均无限小,中间元素比两侧都要大,那么本题中一定存在一个峰元素。**所以不管中间有多少波峰,只要找到峰元素,我们只需找到刚刚开始下降而未下降的位置。采用二分查找,查出这样一个位置即可,我们知道二分查找要比较的是 target 元素,本题的 target 元素是 mid 的后一个元素,即 nums[mid] 与 nums[mid+1] 进行比较:

leetcode-34-在排序数组中查找元素的第一个和最后一个位置(find first and last position of element in sorted array)-java
解法:
先将target与nums[0]进行比较,得到target在前半段还是后半段,然后进行二分查找,将now与nums[0]进行判断,判断now在前半段还是后半段,如果不是target对应的波段,直接缩小范围,如果是target对应的波段,直接安装二分查找的方法进行查找。

leetcode-240-搜索二维矩阵 II(search a 2ds matrix 2)-java
从矩阵的正对角线的两端,他们的两个方向都是都变大或变小,从反对角线的两端,方向是一个变大,一个变小,就可以根据大小,向相应的方向跑过去,直到遇到或者超出矩阵的边界
首先判断(rows-1,0)处的元素,记为x,如果给定的target>x,则延行方向向右寻找,否则延列方向向上寻找。

leetcode-378-有序矩阵中第K小的元素-java
解法1
使用容量为k的最大堆。

解法2
思路非常简单:
1.找出二维矩阵中最小的数left,最大的数right,那么第k小的数必定在left~right之间
2.mid=(left+right) / 2;在二维矩阵中寻找小于等于mid的元素个数count
3.若这个count小于k,表明第k小的数在右半部分且不包含mid,即left=mid+1, right=right,又保证了第k小的数在left~right之间
4.若这个count大于k,表明第k小的数在左半部分且可能包含mid,即left=left, right=mid,又保证了第k小的数在left~right之间
5.因为每次循环中都保证了第k小的数在left~right之间,当left==right时,第k小的数即被找出,等于right
注意:这里的left mid right是数值,不是索引位置。

leetcode-4-寻找两个有序数组的中位数-java
解法1
可以一半儿一半儿的排除。假设我们要找第 k 小数,我们可以每次循环排除掉 k/2 个数。看下边一个例子。
比较1349,12345678910
我们比较两个数组的第 k/2 个数字,如果 k 是奇数,向下取整。也就是比较第 3 个数字,上边数组中的 4 和下边数组中的 3,如果哪个小,就表明该数组的前 k/2 个数字都不是第 k 小数字,所以可以排除。也就是 1,2,3这三个数字不可能是第 7小的数字,我们可以把它排除掉。将 1349 和 45678910 两个数组作为新的数组进行比较。
更一般的情况 A[1] ,A[2] ,A[3],A[k/2] … ,B[1],B[2],B[3],B[k/2] … ,如果 A[k/2]<B[k/2] ,那么A[1],A[2],A[3],A[k/2]都不可能是第 k 小的数字。
A 数组中比 A[k/2] 小的数有 k/2-1 个,B 数组中,B[k/2] 比 A[k/2] 小,假设 B[k/2] 前边的数字都比 A[k/2] 小,也只有 k/2-1 个,所以比 A[k/2] 小的数字最多有 k/1-1+k/2-1=k-2个,所以 A[k/2] 最多是第 k-1 小的数。而比 A[k/2] 小的数更不可能是第 k 小的数了,所以可以把它们排除。
比较1349,(123)45678910
由于我们已经排除掉了 3 个数字,就是这 3 个数字一定在最前边,所以在两个新数组中,我们只需要找第 7 - 3 = 4 小的数字就可以了,也就是 k = 4。此时两个数组,比较第 2 个数字,3 < 5,所以我们可以把小的那个数组中的 1 ,3 排除掉了。
比较(13)49,(123)45678910
我们又排除掉 2 个数字,所以现在找第 4 - 2 = 2 小的数字就可以了。此时比较两个数组中的第 k / 2 = 1 个数,4 == 4,怎么办呢?由于两个数相等,所以我们无论去掉哪个数组中的都行,因为去掉 1 个总会保留 1 个的,所以没有影响。为了统一,我们就假设 4 > 4 吧,所以此时将下边的 4 去掉。
比较(13)49,(1234)5678910
由于又去掉 1 个数字,此时我们要找第 1 小的数字,所以只需判断两个数组中第一个数字哪个小就可以了,也就是 4。
所以第 7 小的数字是 4。

解法2
一个长度为 m 的数组,有 0 到 m 总共 m + 1 个位置可以切。
我们把数组 A 和数组 B 分别在 i 和 j 进行切割。
将 i 的左边和 j 的左边组合成「左半部分」,将 i 的右边和 j 的右边组合成「右半部分」。
当 A 数组和 B 数组的总长度是偶数时,如果我们能够保证
左半部分的长度等于右半部分
i + j = m - i + n - j , 也就是 j = ( m + n ) / 2 - i
左半部分最大的值小于等于右半部分最小的值 max ( A [ i - 1 ] , B [ j - 1 ])) <= min ( A [ i ] , B [ j ]))
那么,中位数就可以表示如下
(左半部分最大值 + 右半部分最小值 )/ 2。
(max ( A [ i - 1 ] , B [ j - 1 ])+ min ( A [ i ] , B [ j ])) / 2
当 A 数组和 B 数组的总长度是奇数时,如果我们能够保证
左半部分的长度比右半部分大 1
i + j = m - i + n - j + 1也就是 j = ( m + n + 1) / 2 - i
左半部分最大的值小于等于右半部分最小的值 max ( A [ i - 1 ] , B [ j - 1 ])) <= min ( A [ i ] , B [ j ]))
那么,中位数就是
左半部分最大值,也就是左半部比右半部分多出的那一个数。
max ( A [ i - 1 ] , B [ j - 1 ])
上边的第一个条件我们其实可以合并为 j=(m+n+1)/2−i,因为如果 m+n是偶数,由于我们取的是 int 值,所以加 1 也不会影响结果。当然,由于 0<=i<=m ,为了保证 0<=j<=n,我们必须保证 m<=n。
m≤n,i<m,j=(m+n+1)/2−i≥(m+m+1)/2−i>(m+m+1)/2−m=0
m≤n,i>0,j=(m+n+1)/2−i≤(n+n+1)/2−i<(n+n+1)/2=n
最后一步由于是 int 间的运算,所以 1/2=0
而对于第二个条件,奇数和偶数的情况是一样的,我们进一步分析。为了保证 max ( A [ i - 1 ] , B [ j - 1 ])) <= min ( A [ i ] , B [ j ])),因为 A 数组和 B 数组是有序的,所以 A [ i - 1 ] <= A [ i ],B [ i - 1 ] <= B [ i ] 这是天然的,所以我们只需要保证 B [ j - 1 ] < = A [ i ] 和 A [ i - 1 ] <= B [ j ] 所以我们分两种情况讨论:
B [ j - 1 ] > A [ i ],并且为了不越界,要保证 j != 0,i != m
此时很明显,我们需要增加 i ,为了数量的平衡还要减少 j ,幸运的是 j = ( m + n + 1) / 2 - i,i 增大,j 自然会减少。
A [ i - 1 ] > B [ j ] ,并且为了不越界,要保证 i != 0,j != n
此时和上边的情况相反,我们要减少 i ,增大 j 。

剑指offer-3-二维数组中的查找-java
从左下角,或者右上角开始,它们的两侧是一个递增和递减的状态,直接查询即可。

剑指offer-8-旋转数组的最小数字-java
Step1.和二分查找法一样,我们用两个指针分别指向数组的第一个元素和最后一个元素。
Step2.接着我们可以找到数组中间的元素:
如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中最小的元素应该位于该中间元素的后面。我们可以把第一个指针指向该中间元素,这样可以缩小寻找的范围。移动之后的第一个指针仍然位于前面的递增子数组之中。如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时该数组中最小的元素应该位于该中间元素的前面。

Step3.接下来我们再用更新之后的两个指针,重复做新一轮的查找。
按照上述的思路,第一个指针总是指向前面递增数组的元素,而第二个指针总是指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针会指向后面子数组的第一个元素。也就是它们最终会指向两个相邻的元素,而第二个指针指向的刚好是最小的元素。这就是循环结束的条件。
如果碰到了left = mid的情况,直接在left与right的范围内找min

剑指offer-29-数组中出现次数超过一半的数字-java
计数法,将出现次数存入Map,如果出现次数大于len/2次,那么结束。
排序,直接校验第[i]位是否等于[i+len/2]位即可。

同归于尽法:已知可能存在某个数的数量比其他数还多,那么两个不相同的应该互相抵消,最后剩下的那个元素(或两元素)可能就是目标值,遍历一次进行校验即可.

leetcode-035-搜索插入位置
解法1(成功,0ms,极快)
注意:
题目要找的元素是:第一个大于等于 target 的元素的下标;
数组的长度 len 也有可能是问题的答案,「参考代码 2」设置 right = len 不是因为设置区间是「左闭右开」,而是因为 len 本来就有可能是问题的答案。
上面 2 个小点,都需要仔细分析题意和几个示例得到,任何模板都不能回答这样的问题。

根据示例,分析题目要我们返回的「插入元素的位置」是什么。
根据「示例 3」:
输入: [1, 3, 5, 6], 7
输出: 4
我们知道:如果目标元素 严格大于 输入数组中的最后一个元素,题目需要我们返回数组的最后一个元素的下标 +1(也就是数组的长度)。

又根据「示例 2」:
输入: [1, 3, 5, 6], 2
输出: 1
我们知道:题目要我们返回第 1 个 大于等于 目标元素 2 的下标(分析出这一点非常重要),因此返回 1。等于的情况可以看「示例 1」。

思路分析
在有序数组中查找,可以使用「二分查找」。
根据「题意分析」中对示例的描述:

情况 1:如果当前 mid 看到的数值严格小于 target,那么 mid 以及 mid 左边的所有元素就一定不是「插入元素的位置」,因此下一轮搜索区间是 [mid + 1…right],下一轮把 left 移动到 mid + 1 位置,因此设置 left = mid + 1;
情况 2:否则,如果 mid 看到的数值大于等于 target,那么 mid 可能是「插入元素的位置」,mid 的右边一定不存在「插入元素的位置」。如果 mid 的左边不存在「插入元素的位置」,我们才可以说 mid 是「插入元素的位置」。因此下一轮搜索区间是 [left…mid],下一轮把 right 移动到 mid 位置,因此设置 right = mid。
说明:上面的两点中,「情况 2」其实不用分析得那么细致, 因为只要「情况 1」的区间分析是正确的,「情况 2」一定是「情况 1」得到的区间的反面区间。
说明:while (left < right) 表示当 left 与 right 重合的时候,搜索终止。根据题意和示例,区间 nums[left…right] 里一定存在「插入元素的位置」,且 while 循环里只把区间分成两个部分,退出循环的时候一定有 left == right 成立,因此返回 left 或者 right 都可以。
既然 len 也有可能是答案,可以在初始化的时候,把 right 设置成 len,在一开始的时候就不需要特殊判断了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值