【leetcode】二分查找

二分搜索

在有序数组中查找指定值。一般二分搜索需要注意两个点:

  1. 循环条件是l<r 还是 l<= r;
  2. 更新r值时,是r=mid还是r= mid-1;
  3. 具体问题具体分析。
#二分搜索源代码
def bisearch(arr, num):
    l = 0
    r = len(arr)-1
    while l <= r:
        mid = int((l+r)/2)
        if num == arr[mid]:
            return mid
        elif num < arr[mid]:
            r = mid-1
        else:
            l = mid + 1
    else:
        return 
print(bisearch([1,2,3,4,5],3))
2
  1. 寻找峰值
  1. 类似于[0,1,2,3,2,1]这种单峰数组,返回峰值对应index;
  2. 类似于[0,4,2,3,1]这种多峰数组,返回任一峰值对应index;

思路:
简便方法:直接返回最大值对应的index,都可以满足两个问题的解;
二分搜索:
找中点,若nums[mid] > nums[mid+1], 则从中点向左找;否则,向右找。注意边界问题。

class Solution:
    def findPeakElement(self, nums):
        l = 0
        r = len(nums)-1
        while l < r:
            mid = int((l+r)/2)
            if nums[mid] > nums[mid+1]:
                r = mid
            else:
                l = mid + 1
        return l

两数之和 I - 无序数组 twoSum
对无序数组nums,找到两数之和为target的两个index,若存在返回[index1, index2],若不存在,直接返回。

思路:
方法一: 根据python的list的特性做,O(n);
方法二: 设一个dic,key是target与nums[i]的差值,val是对应的index。边遍历nums边构建字典。当便利到的nums[i]已经在字典里了,就直接返回i和dic[nums[i]], 否则继续遍历。

class Solution:
    def twoSum1(self, nums, target):
        for i in range(len(nums)):
            temp = target - nums[i]
            if temp in nums and nums.index(temp) != i:
                return [i,nums.index(temp)]
        return
    
    def twoSum2(self, nums, target):
        dic = {}
        for i in range(len(nums)):
            if nums[i] in dic:
                return [dic[nums[i]], i]
            else:
                dic[target-nums[i]] = i
        return

两数之和 II - 输入有序数组 twoSum
对升序数组nums,找到两数之和为target的两个index,若存在返回[index1, index2], index1<index2; 若不存在,直接返回。

思路:
两个指针l,r:

  1. 若num[l] + num[r] == target,则返回[l,r];
  2. 若num[l] + num[r] < target,则r-=1;
  3. 否则, l+=1
class Solution:
    def twoSum(self, numbers, target):
        l = 0
        r = len(numbers) - 1
        while l < r:
            if numbers[l] + numbers[r] == target:
                return [l,r]
            elif numbers[l] + numbers[r] > target:
                r -= 1
            else:
                l += 1
        return

两数之和 IV - 二叉搜索树
给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。

思路:
方法一:based on towsum II,先中序遍历BST,得到递增序列,再根据towSum II的方法找;
方法二:based on towsum I,设置一个字典,边遍历边找。

#方法二
class Solution(object):
    def findTarget(self, root, k):
        dic = {}
        return self.pre(root, k, dic)
    
    def pre(self,root, k, dic):
        if root:
            if root.val in dic:
                return True
            else:
                dic[k-root.val] = root
                return self.pre(root.left, k,dic) or self.pre(root.right, k,dic)
        else:
            return False

三数之和-3sum
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

思路:
使用2sum思想,两层循环,第一层遍历每一个nums中元素,2sum = 3sum-nums[i], 第二层再以2sum为目标target遍历。注意为了提升速度:

  1. 先对数组排序,然后按照双指针有序2sum来做;
  2. 为了防止重复元素,循环时需要判断跳过重复元素。(不能为了简单直接在res.append时候判断,会超时)。
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        nums = sorted(nums)
        res = []
        i = 0
        while i < len(nums):
            l = i+1
            r = len(nums)-1
            while l < r:
                threeSum = nums[l] + nums[r] + nums[i]
                if threeSum == 0:
                    res.append([nums[i], nums[l],nums[r]])
                    #跳过重复元素
                    while l < r and nums[l+1] == nums[l]:
                        l+=1
                    while l < r and nums[r-1] == nums[r]:
                        r-=1
                    l+=1
                    r-=1
                elif threeSum < 0:
                    l += 1
                else:
                    r -= 1
            #跳过重复元素
            while i < len(nums)-1 and nums[i+1] == nums[i]:
                i += 1
            i+=1
        return res

最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

思路:
和3sum一样,只不过:

  1. 时刻更新最接近target的数;
  2. 不需要判断重复元素。
class Solution(object):
    def threeSumClosest(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        nums = sorted(nums)
        i = 0
        minv = nums[0] + nums[1] + nums[2]#与target最接近的值
        for i in range(len(nums)):
            l = i+1
            r = len(nums)-1
            while l < r:
                threeSum = nums[i] + nums[l] + nums[r]
                if abs(threeSum - target) < abs(minv - target):
                    minv = threeSum
                if threeSum < target:
                    l += 1
                elif threeSum > target:
                    r -= 1
                elif threeSum == target:
                    return target
        return minv

四数之和 - 4sum

思路:
同3sum,只不过是三层循环,注意多处都需要判断是否有重复元素。

class Solution(object):
    def fourSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[List[int]]
        """
        nums = sorted(nums)
        res = []
        i = 0
        while i < len(nums):
            j = i+1
            while j < len(nums):
                l = j+1
                r = len(nums)-1
                while l < r:
                    fourSum = nums[i]+nums[j]+nums[l] + nums[r]
                    if fourSum == target:
                        res.append([nums[i], nums[j], nums[l], nums[r]])
                        #跳过重复元素
                        while l<r and nums[l+1] == nums[l]:
                            l += 1
                        while l < r and nums[r-1] == nums[r]:
                            r -= 1
                        l += 1
                        r -= 1
                    elif fourSum < target:
                        l += 1
                    else:
                        r -= 1
                #去掉重复元素
                while j < len(nums)-1 and nums[j+1] == nums[j]:
                    j+=1
                j+=1
            #去掉重复元素
            while i < len(nums)-1 and nums[i+1] == nums[i]:
                i +=1 
            i+=1
        return res

四数相加 II
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。

class Solution(object):
    def fourSumCount(self, A, B, C, D):
        """
        :type A: List[int]
        :type B: List[int]
        :type C: List[int]
        :type D: List[int]
        :rtype: int
        """
        dic = {}
        n = len(A)
        for i in range(n):
            for j in range(n):
                twoSum = 0 - (A[i] + B[j])
                if twoSum in dic:
                    dic[twoSum] += 1
                else:
                    dic[twoSum] = 1
        res = 0
        for i in range(n):
            for j in range(n):
                twoSum = C[i] + D[j]
                if twoSum in dic:
                    res += dic[twoSum]
        return res
        

取暖器问题
冬季已经来临。 你的任务是设计一个有固定加热半径的供暖器向所有房屋供暖。
现在,给出位于一条水平线上的房屋和供暖器的位置,找到可以覆盖所有房屋的最小加热半径。
所以,你的输入将会是房屋和供暖器的位置。你将输出供暖器的最小加热半径。

说明:
给出的房屋和供暖器的数目是非负数且不会超过 25000。
给出的房屋和供暖器的位置均是非负数且不会超过10^9。
只要房屋位于供暖器的半径内(包括在边缘上),它就可以得到供暖。
所有供暖器都遵循你的半径标准,加热的半径也一样。

示例 1:
输入: [1,2,3],[2]
输出: 1
解释: 仅在位置2上有一个供暖器。如果我们将加热半径设为1,那么所有房屋就都能得到供暖。
示例 2:
输入: [1,2,3,4],[1,4]
输出: 1
解释: 在位置1, 4上有两个供暖器。我们需要将加热半径设为1,这样所有房屋就都能得到供暖。

思路:

  1. 对于每个房屋,要么用前面的暖气,要么用后面的,二者取近的,得到距离;
  2. 对于所有的房屋,选择最大的上述距离。
class Solution(object):
    def findRadius(self, houses, heaters):
        """
        :type houses: List[int]
        :type heaters: List[int]
        :rtype: int
        """
        #对每个房屋,其供暖的可能是前一个暖气,或后一个暖气,取离得最近的(找最小距离)
        #算出所有房屋对应的离得最近的暖气的距离,然后取最大的
        houses = sorted(houses)
        heaters = sorted(heaters)
        res = 0
        for i in range(len(houses)):
            nearHeater = self.findHeater(heaters, houses[i])
            res = max(res, abs(nearHeater-houses[i]))
        return res
                
                
    def findHeater(self, heaters, house):
        nearHeater = heaters[0]
        l = 0
        r = len(heaters)-1
        while l <= r:
            mid = int((l+r)/2)
            if abs(heaters[mid]-house) < abs(nearHeater-house):
                nearHeater = heaters[mid]
            if heaters[mid] < house:
                l = mid + 1
            elif heaters[mid] > house:
                r = mid - 1
            elif heaters[mid] == house:
                return heaters[mid]
        return nearHeater

寻找重复数
给定一个包含 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) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。

思路:
方法一:设所有数的最小值l为1,最大值r为n,每次取中间值mid,然后遍历数组nums,若<=mid的数多于mid本身的值,说明重复数字在(mid+1, r)之间,否则则在(r, mid)之间。
方法二:用链表里快慢指针的思想。假设数组nums中每个值代表了其对应的下一个节点的位置(注意数组index是从0开始的,所以要减去1),则整个数组组成一个带有环的链表,使用快慢指针找到环的起始点即可。

class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #方法一:找mid
        l = 1
        r = len(nums)
        while l < r:
            mid = int((l+r)/2)
            #找到小于等于mid的数的个数
            cnt = 0
            for i in range(len(nums)):
                if nums[i] <= mid:
                    cnt += 1
            if cnt <= mid:
                l = mid + 1
            else:
                r = mid
        return l
    def findDuplicate2(self, nums):
        #快慢指针的思想
        f = 0
        s = 0
        while 1:
            s = nums[s]
            f = nums[nums[f]]
            if s == f:
                break
        f = 0
        while 1:
            s = nums[s]
            f = nums[f]
            if f == s:
                return f
        
        

有序矩阵中第k小的元素:
给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第k小的元素。
请注意,它是排序后的第k小元素,而不是第k个元素。

示例:
matrix = [
[ 1, 5, 9],
[10, 11, 13],
[12, 13, 15]
],
k = 8,

返回 13。

思路:
方法一:二分搜索:
https://www.cnblogs.com/grandyang/p/5727892.html
1.初始条件: l为矩阵中最小的元素,r为最大的。
2.取中间值mid,遍历矩阵中<=mid的元素个数cnt(目的是找到mid是第几小的元素), 若cnt <= k, 则l = mid + 1; 否则r= mid.知道l和r重合。最后返回l即可

class Solution(object):
    def kthSmallest(self, matrix, k):
        """
        :type matrix: List[List[int]]
        :type k: int
        :rtype: int
        """
        #二分搜索
        m = len(matrix)
        n = len(matrix[0])
        l = matrix[0][0]
        r = matrix[-1][-1]
        while l <  r:
            mid = int((l+r)/2)
            cnt = 0
            #找出mid是第几小的元素
            for i in range(m):
                for j in range(n):
                    if matrix[i][j] <= mid:
                        cnt += 1
                    else:
                        break
            if cnt >= k:
                r = mid
            elif cnt < k:
                l = mid + 1
        return l
            

寻找旋转排序数组中的最小值
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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

思路:二分搜索

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #return min(nums)
        #二分
        l = 0
        r = len(nums) - 1
        while l < r:
            mid = int((l+r)/2)
            if nums[mid] > nums[r]:
                l = mid + 1
            else:
                r = mid
        return nums[r]

寻找旋转排序数组中的最小值 II
旋转数组中允许有重复元素。

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        #return min(nums)
        l = 0
        r = len(nums)-1
        while l < r:
            mid = int((l+r)/2)
            if nums[mid] > nums[r]:
                l = mid + 1
            elif nums[mid] < nums[l]:
                r = mid
            else:
                r -= 1
        return nums[l]

搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [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

思路:
1.先二分法找到旋转轴点,则左右各有序。
2.判断target在哪个有序区间里面;
3.在该有序区间里面二分搜索。

class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        #先排除只有0个或1个元素的情况
        if nums == []:
            return -1
        if len(nums) == 1:
            if nums[0] == target:
                return 0
            else:
                return -1
        #若不是旋转数组,则直接二分
        if nums[0] < nums[-1]:
            i = 0
            j = len(nums)-1
        #若是旋转数组,找到旋转的轴点,左右各有序
        elif nums[0] > nums[-1]:
            l = 0
            r = len(nums)-1
            while l < r:
                mid = int((l+r)/2)
                if nums[mid] > nums[r]:
                    l = mid + 1
                else:
                    r = mid
            #找到target所属的有序区间,然后继续二分
            if target >= nums[0]:#说明target在第一个有序区间
                i = 0
                j = l-1
            else:
                i = l
                j = len(nums)-1         
        while i <= j:
            mid = int((i+j)/2)
            if nums[mid] == target:
                return mid
            elif target > nums[mid]:
                i = mid + 1
            else:
                j = mid - 1
        return -1

长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。

示例:
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。

思路:
1.左右指针均指向0;
2.每次当目前的和小于s时先移动右指针,直到区间和大于等于s,记录区间长度;
3.在2的基础上停止移动右指针,移动左指针,同时和减去左指针值,判断区间和是否大于等于s,是则更新最短区间长度;

class Solution(object):
    def minSubArrayLen(self, s, nums):
        """
        :type s: int
        :type nums: List[int]
        :rtype: int
        """
        #左右指针滑动窗口
        l = 0
        r = 0
        res = len(nums)+1
        total = 0
        while l < len(nums):
            if r < len(nums) and total < s:
                total += nums[r]
                r += 1
            else:
                total -= nums[l]
                l += 1
            if total >= s:
                res = min(res, r-l)
        if res == len(nums)+1:
            return 0
        else:
            return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值