作为本科非科班出身的CSer也经历了看到leetcode不知从何刷起的感觉,现在准备重新刷一下leetcode,写博客主要是记录下自己的思路,尽量保持每天两道,这篇主要总结二分查找所遇到的问题以及变种。
打个广告微博 @辣酱一直就这样 欢迎同学私信讨论
先说一下二分查找的模版
1. start + 1 < end //在相邻的时候退出避免死循环
2. start + (end - start) / 2 //找中间值,装逼写法避免mid比Integer.max_value还大导致溢出
3. A[mid] ==, <, >//判断要根据找target是第一次出现还是最后一次出现来决定把mid 给start 还是end
4. A[start] A[end] ? target//最后相邻退出同样要考虑是找target第一次出现还是最后一次出现.
二分查找基本题型,leetcode上面好像没有,我找了lintcode上面一道
http://www.lintcode.com/zh-cn/problem/first-position-of-target/#
这道题就是最基本的二分查找,target有重复,需要找到target第一次出现时所在数组的下标,很简单套模版
class Solution {
/**
* @param nums: The integer array.
* @param target: Target to find.
* @return: The first position of target. Position starts from 0.
*/
public int binarySearch(int[] nums, int target) {
//write your code here
if(nums.length == 0){
return -1;
}
int start = 0;
int end = nums.length - 1;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(nums[mid] == target){
end = mid;//注意是找第一个出现的位置,我们就认为找到一个他就是最右边的往左找看有没有相同的
}else if(nums[mid] < target){
start = mid;
}else{
end = mid ;
}
}
if(nums[start] == target){
return start;
}
if(nums[end] ==target){
return end;
}
return -1;
}
}
接着提高一点leetcode
34
.
Search for a Range
https://leetcode.com/problems/search-for-a-range/description/
这道题就是让你找到target在数组中的起始和结束位置,也不难,思路就是顺着上一题,先二分一次找到target第一次出现的位置,再二分一次找到target最后一次出现的位置
public class Solution {
public int[] searchRange(int[] nums, int target) {
int []res = {-1 , -1} ;
if(nums == null || nums.length == 0){
return res ;
}
//先找左边的位置;
int start = 0 ;
int end = nums.length - 1 ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(nums[mid] == target){
end = mid ;//左边可能还有要往左边找
}else if(nums[mid] < target){
start = mid ;
}else{
end = mid ;
}
}
if(nums[start] == target)//先判断左边的
{
res[0] = start ;
}else if(nums[end] == target){
res[0] = end ;
}
//右边同左边的全都反过来
start = 0 ;
end = nums.length - 1 ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(nums[mid] == target){
start = mid ;
}else if(nums[mid] < target){
start = mid ;
}else if(nums[mid] > target){
end = mid ;
}
}
if(nums[end] == target){
res[1] = end ;
}else if(nums[start] == target){
res[1] = start ;
}
return res ;
}
}
下面这道题是35. Search Insert Position
这道题就是给了一个数组把target插入到里面让数组还是有序排列,如果数组里面有这个值就返回数组下标,如果没有的就找到最后一个比他小的数插入到这个数后面,如果没有比target还小的数就直接插入到数组最前面,还是用二分查找,套上面的模板,最后处理end start 有些繁琐,注意就好了
public class Solution {
public int searchInsert(int[] nums, int target) {
if(nums == null || nums.length == 0 || nums[0] > target){
return 0 ;
}
int start = 0 ;
int end = nums.length - 1 ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
if(nums[mid] == target){
return mid ;
}else if (nums[mid] > target){
end = mid ;
}else if (nums[mid] < target){
start = mid ;
}
}
if(nums[end] == target){
return end ;
}else if(nums[end] < target){
return end + 1 ;
}else if(nums[start] == target){
return start ;
}
return start + 1 ;
}
}
接下是
74. Search a 2D Matrix 搜索矩阵中是否有目标值 , 矩阵的每一行和每一列都是按顺序排列,每一行的第一个数都比上一行的最后一个数大,思路还是用二分查找,一次先找到所在的行,再一次二分找到所在的列
public class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return false ;
}
int row = matrix.length ;
int colum = matrix[0].length ;
int mid ;
//先找行
int start = 0 ;
int end = row - 1 ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(matrix[mid][0] == target){
return true ;
}else if(matrix[mid][0] > target) {
end = mid ;
}else {
start = mid ;
}
}
//处理剩下的start 和 end
if(matrix[end][0] <= target){
row = end ;
}else if (matrix[start][0] <= target){
row = start ;
}else{
return false ;
}
//再找列
start = 0;
end = colum - 1 ;
while(start + 1 < end){
mid = start + (end - start) / 2;
if(matrix[row][mid] == target){
return true ;
}else if(matrix[row][mid] < target) {
start = mid ;
}else{
end = mid ;
}
}
// 只剩两个地方没处理,只需要判断是否等于就好
if(matrix[row][start] == target){
return true ;
}else if(matrix[row][end] == target){
return true ;
}
return false ;
}
}
240. Search a 2D Matrix II
这道题是上一题的提高版,还是一个二维矩阵,但是不保证下一行的数都比上一行的数大,只保证每行中后面比前面大, 每列中,下面比上面大,暴力搜索就是时间复杂度n2,肯定是不行的,我们来看这张图
先找到第一列的最后一个,他是最后一行最小的,如果比target大就说明最后一行都比target大,就往上挪一个,如果比target小,由于他是第一列最大的,就说明第一列都比target小,就往右挪一个,这样如图,最差要用O(n + m)时间就可以找到,以下是代码
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return false ;
}
int x = matrix.length - 1 ;
int y = 0 ;
while(x >= 0 && y < matrix[0].length){
if(matrix[x][y] > target){
x -- ;
}else if(matrix[x][y] < target){
y ++ ;
}else{
return true ;
}
}
return false ;
}
}
还有一道题278. First Bad Version没什么说的,找到第一个坏的版本号,基本的二分查找
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int start = 0 ;
int end = n ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
if(isBadVersion(mid)){
end = mid ;
}else{
start = mid ;
}
}
if(isBadVersion(start)){
return start ;
}
return end ;
}
}
162. Find Peak Element 没什么说的注意下细节就行了
class Solution {
public int findPeakElement(int[] nums) {
int start = 0 ;
int end = nums.length - 1 ;
int mid ;
while(start + 1 < end){
mid = start + (end - start) / 2 ;
// mid -1 < mid > mid + 1 说明mid就是峰值
if(nums[mid] > nums[mid + 1] && nums[mid] > nums[mid - 1]){
return mid ;
// mid - 1 < mid < mid + 1 说明 peak在mid右边
}else if(nums[mid] < nums[mid + 1] && nums[mid] > nums[mid - 1]){
start = mid ;
//剩下两种情况 凹的话 取左右哪边都一样,递减的话只能取左边,然后取交集 让end = mid
}else{
end = mid ;
}
}
if(nums[end] > nums[start]){
return end ;
}
return start ;
}
}
26. Remove Duplicates from Sorted Array没什么说的相等的留下不等的跳过
class Solution {
public int removeDuplicates(int[] nums) {
//不能有新的空间意思就是不能建立新的数组来存储
if(nums == null || nums.length == 0){
return 0 ;
}
int size = 0 ;
// 注意不能nums.length - 1 要不然最后一个没有遍历到
for(int i = 0 ; i < nums.length ; i++){
// 相等就跳过不相等留下
if(nums[size] != nums[i]){
size++ ;
nums[size] = nums[i] ;
// nums[++size] = nums[i]
}
}
return size + 1 ;
}
}
80. Remove Duplicates from Sorted Array II这个就是要做一个计数器记录是否达到了重复两次,没有就留下有就跳过
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0){
return 0 ;
}
int size = 0 ;
int count = 1 ;
// 注意i为什么要从1开始? i直接找的第二个数不是第一个 因为第一个nums[size] 和nums[i]肯定是相等的
for(int i = 1 ; i < nums.length ; i ++){
// 如果相等看有没有达到重复两次
if(nums[size] == nums[i]){
// 所以这里也要注意,并不是才加了一遍, 而是如果这里相等代表前面已经相等过一次了
if(count < 2){
size ++ ;
nums[size] = nums[i] ;
count ++ ;
}
}else{
size ++ ;
nums[size] = nums[i] ;
// 注意要把计数器复原,也可以看作是第一次相等
count = 1 ;
}
}
return size + 1 ;
}
}
88. Merge Sorted Array考验基本功的一题没什么难度
public class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int a = m - 1 ;
int b = n - 1 ;
int k = m + n - 1 ;
while(a >= 0 && b >= 0){
if(nums1[a] > nums2[b]){
nums1[k--] = nums1[a--];
}else{
nums1[k--] = nums2[b--];
}
}
while(a >= 0){
nums1[k--] = nums1[a--] ;
}
while(b >= 0){
nums1[k--] = nums2[b--] ;
}
}
}
4. Median of Two Sorted Arrays这个题很重要是重点题
题解:
首先我们先明确什么是median,即中位数。
引用Wikipedia对中位数的定义:
计算有限个数的数据的中位数的方法是:把所有的同类数据按照大小的顺序排列。如果数据的个数是奇数,则中间那个数据就是这群数据的中位数;如果数据的个数是偶数,则中间那2个数据的算术平均值就是这群数据的中位数。
因此,在计算中位数Median时候,需要根据奇偶分类讨论。
解决此题的方法可以依照:寻找一个unioned sorted array中的第k大(从1开始数)的数。因而等价于寻找并判断两个sorted array中第k/2(从1开始数)大的数。
特殊化到求median,那么对于奇数来说,就是求第(m+n)/2+1(从1开始数)大的数。
而对于偶数来说,就是求第(m+n)/2大(从1开始数)和第(m+n)/2+1大(从1开始数)的数的算术平均值。
那么如何判断两个有序数组A,B中第k大的数呢?
我们需要判断A[k/2-1]和B[k/2-1]的大小。
如果A[k/2-1]==B[k/2-1],那么这个数就是两个数组中第k大的数。
如果A[k/2-1]<B[k/2-1], 那么说明A[0]到A[k/2-1]都不可能是第k大的数,所以需要舍弃这一半,继续从A[k/2]到A[A.length-1]继续找。当然,因为这里舍弃了A[0]到A[k/2-1]这k/2个数,那么第k大也就变成了,第k-k/2个大的数了。
如果 A[k/2-1]>B[k/2-1],就做之前对称的操作就好。
这样整个问题就迎刃而解了。
当然,边界条件页不能少,需要判断是否有一个数组长度为0,以及k==1时候的情况。
以上是百度的别人的博客,思路就是这个思路,变形就是让你找第K小元素的时候别不会找了class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int len = nums1.length + nums2.length;
if (len % 2 == 1) {
return findKth(nums1, 0, nums2, 0, len / 2 + 1);
}
return (
findKth(nums1, 0, nums2, 0, len / 2) + findKth(nums1, 0, nums2, 0, len / 2 + 1)
) / 2.0;
}
// find kth number of two sorted array
public static int findKth(int[] nums1, int A_start,
int[] nums2, int B_start,
int k){
if (A_start >= nums1.length) {
return nums2[B_start + k - 1];
}
if (B_start >= nums2.length) {
return nums1[A_start + k - 1];
}
if (k == 1) {
return Math.min(nums1[A_start], nums2[B_start]);
}
int A_key = A_start + k / 2 - 1 < nums1.length
? nums1[A_start + k / 2 - 1]
: Integer.MAX_VALUE;
int B_key = B_start + k / 2 - 1 < nums2.length
? nums2[B_start + k / 2 - 1]
: Integer.MAX_VALUE;
if (A_key < B_key) {
return findKth(nums1, A_start + k / 2, nums2, B_start, k - k / 2);
} else {
return findKth(nums1, A_start, nums2, B_start + k / 2, k - k / 2);
}
}
}
151. Reverse Words in a String这个也比较简单
public class Solution {
public String reverseWords(String s) {
if(s == null || s.length() == 0){
return "" ;
}
String[] a = s.split(" ") ;
StringBuilder sb = new StringBuilder() ;
for(int i = a.length - 1 ; i >= 0 ; i --){
//注意这里不要写 ==
if(!a[i].equals("")){
sb.append(a[i]).append(" ") ;
}
}
return sb.length() == 0 ? "" :sb.substring(0 , sb.length() - 1) ;
}
}
下面出一个思考题,如何把一个Rotated sort array复原?
答:三步翻转法