Leetcode 二分题目总结

二分的本质是找到一个临界点,临界点满足一个条件,临界点右边不满足这个条件。

二分搜索一般有枚举下标和枚举值两种,mid表示下标或者某一个值。

这样的二分搜所通常涉及两个函数定义,一个叫upper_bound, 一个叫lower_bound,这里会产生边界问题。

 

从笔试/面试的角度来说, 笔试中,常常涉及到二分搜索的题目,这样的题目,由于时间充足,允许不断调试,有部分分,另外需要注意,笔试中往往不会给出出错数据,需要自己想办法调试,所以这类题目往往是必须要拿下的。

 

在国内面试中,往往不会涉及过于难的二分题目,因为除非背下模板,否则需要手动调试处理边界问题。这在有限的面试时间中是不现实的,特别是手写代码。所以一般只要实现比较简单的二分搜索即可。

 

Leetcode 69 sqrt(x)

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

 

这道题目一般使用二分搜索或者牛顿迭代,因为牛顿迭代涉及到公式的推导,面试中一般不会出现,所以一般就是用二分的方法做。

根据这里的思路,就是找临界点,临界点右边的数n*n>x, 临界点左边<=x,整数二分通常会涉及到临界点的处理问题,这里一种方法是背标准的模板,另一种方法是用一个标准的左闭右闭模板,然后自己调试。这里注意处理溢出问题。

class Solution {
    public int mySqrt(int x) {
        int left = 1, right = x;
        while(left<=right){
            int mid = left + (right-left)/2;
            if(mid==x/mid) return mid;
            if(mid<x/mid) left = mid+1;
            else right = mid-1;
        }
        return right;
    }
}

 

Leetcode 74 搜索二维矩阵

 

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。
每行的第一个整数大于前一行的最后一个整数。
示例 1:

输入:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
输出: true

这一题目就是最简单的二分搜索问题

func searchMatrix(matrix [][]int, target int) bool {
    if len(matrix) == 0{
        return false
    }
    left, right := 0, len(matrix)*len(matrix[0])-1
    for left<=right{
        mid := left + (right-left)/2
        row := mid / len(matrix[0])
        col := mid % len(matrix[0])
        val := matrix[row][col]
        if target == val{
            return true
        }
        if val > target {
            right = mid-1
        }else{
            left = mid+1
        }
    }
    return false
}

 

Leetcode 278. 第一个错误的版本

是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:

给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。 

 

这道题目是比较简单的,找一个临界点,左边是好的,右边是坏的。

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left=1, right = n;
        while(left<=right){
            int mid = left + (right-left)/2;
            if(isBadVersion(mid))
                right = mid-1;
            else
                left = mid+1;
        }
        return left;
    }
}

 

Leetcode 153. 寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

请找出其中最小的元素。

你可以假设数组中不存在重复元素。

示例 1:

输入: [3,4,5,1,2]
输出: 1
示例 2:

输入: [4,5,6,7,0,1,2]
输出: 0

 

找到一个临界点,临界点右边的数小于第一个数,临界点左边的数大于第一个数

func findMin(nums []int) int {
    left, right := 0, len(nums)-1
    if nums[left]<=nums[right]{
        return nums[left]
    }
    for left<=right{
        mid := left + (right-left)/2
        if(nums[mid]<nums[0]){
            right = mid-1
        }else{
            left = mid+1
        }
    }
    return nums[left]
}


Leetcode 33. 搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

 

这道题目直接做比较容易出错,可以分成两步做,首先用第153的做法,找到分解点,然后再做一次二分查找。

func search(nums []int, target int) int {
    end := len(nums)-1
    if end==-1{
        return -1
    }
    var left int
    var right int
    if target==nums[end]{
        return end
    }else if target<nums[end]{
        left, right = findMin(nums), end
    }else{
        left, right = 0, findMin(nums)
    }
    for left<=right{
        mid := left + (right-left)/2
        if nums[mid]== target{
            return mid
        }
        if nums[mid] > target{
            right = mid - 1
        }else{
            left = mid + 1
        }
    }
    return -1
}

func findMin(nums []int) int {
    left, right := 0, len(nums)-1
    if nums[left]<=nums[right]{
        return left
    }
    for left<=right{
        mid := left + (right-left)/2
        if(nums[mid]<nums[0]){
            right = mid-1
        }else{
            left = mid+1
        }
    }
    return left
}

 

 

Leetcode 35 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

这个函数就是实现upper_bound 的函数,这里涉及到边界问题的解决。如果找到数就返回数,否则就返回后面的数。

func searchInsert(nums []int, target int) int {
    nLen := len(nums)
    left, right := 0, nLen-1

    for right>left{
        mid := left + (right-left)/2;
        if target < nums[mid]{
            right = mid-1;
        }else if target>nums[mid]{
            left = mid+1;
        }else{
            return mid;
        }
    }

    if target<=nums[left]{
        return left;
    }
    return left+1;
}

 

Leetcode 34. 在排序数组中查找元素的第一个和最后一个位置

这道题目就是upper_bound 和lower_bound的函数,涉及到很多边界处理问题

func searchRange(nums []int, target int) []int {
    if len(nums) == 0 || nums[0] > target || nums[len(nums)-1] < target {
        return []int{-1, -1}
    }
    nLen := len(nums)
    left, right := 0, nLen-1
    var start, end int;                  // 答案
    for right>=left{
        mid := left + (right-left)/2;
        if target > nums[mid]{
             left = mid+1
        }else{
             right = mid-1
        }
    }
    if nums[left]!=target{
        return []int{-1,-1};
    }

    start = left;
    left, right = 0, nLen-1
    for right>=left{
        mid := left + (right-left)/2
        if(target<nums[mid]){
            right = mid-1
        }else{
            left = mid+1
        }
    }
    end = right
    return []int{start, end}

}

Leetcode 287 寻找重复的数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2
示例 2:

输入: [3,1,3,4,2]
输出: 3
说明:

不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

 

这个题目有一点难度,最直接的O(1)空间复杂度的方法是枚举1到n的数,扫一遍看看,是否出现两次,时间复杂度为O(n^2)

这里优化的方法是每次看一个区间[left,mid] [ mid+1,right], 遍历一般数组,如果在[left,mid]中的数大个数大于mid-left+1, 那么说明重复的数在[left,mid]区间内,否则在[mid+1,right]区间内,这道题目的时间复杂是O(NlogN)

func findDuplicate(nums []int) int {
    left, right := 1, len(nums)-1
    for left<=right{
        mid := left + (right-left)/2
        if(check(nums,left,mid)){
            right = mid-1
        }else{
            left = mid+1
        }
    }
    return left
}

func check(nums []int, left int, mid int) bool{
    count := 0
    for i:=0;i<len(nums);i++{
        if nums[i]>=left && nums[i]<=mid{
            count++
        }
    }
    return count>mid-left+1
}

 

Leetcode 275. H指数 II
 

给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列。编写一个方法,计算出研究者的 h 指数。

h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)至多有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数不多于 h 次。)"

 

示例:

输入: citations = [0,1,3,5,6]
输出: 3 
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
     由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。

 

这道题是有一定难度的,首先如果直接枚举h指数,那么就要遍历一遍数组,时间复杂度至少是O(nlogn),事实上,这到题目有O(logn) 的做法,因为h的含义和论文篇数/下标有关,所以枚举的是下标,这里更新的条件有些难想。同时,不难按照通常那样返回left或right,而要用一个变量记录满足要求的res

func hIndex(citations []int) int {
    n := len(citations)
    var res int
    if n==0{
        return 0
    }
    left, right :=0, n-1
    for left<=right{
        mid := left + (right-left)/2
        if(citations[mid]>=n-mid){
            res = n-mid
            right = mid-1
        }else{
             left = mid+1
        }
    }
    return res
}

Leetcode 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) 时间复杂度的。

 

这道题目是比较难得,   优化结论是,如果nums[mid]<nums[mid-1],  那么左边一定有峰值,可以用反证法证明

nums[mid]<nums[mid+1], 那么右边一定有峰值

如果 nums[mid]>nums[mid-1] && nums[mid]>nums[mid+1] 那么mid是峰值。
由于这里涉及到mid-1和mid+1,会出现大量得边界问题需要解决,注意看程序得代码

所以处理方法是下面这样

func findPeakElement(nums []int) int {
    left, right :=0, len(nums)
    n := len(nums)
    if n==1{
        return 0
    }
    for left<=right{
        mid := left + (right-left)/2
        if(mid-1>=0){
            if (nums[mid-1]>nums[mid]){
                right = mid-1
            }else if mid ==n-1|| nums[mid]>nums[mid+1]{
                return mid
            }else{
                left = mid+1
            }
        }else if mid+1<n{
            if(nums[mid]<nums[mid+1]){
                left = mid+1
            }else if mid == 0 || nums[mid]>nums[mid-1]{
                return mid
            }else{
                right = mid-1
            }
        }
    }
    return left
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值