leetcode-二分法+前缀和

一、二分法

双指针和滑动窗口的区别:
滑动窗口需要考虑左右端点之间的所有元素,双指针只考虑两个指针处的元素

在这里插入图片描述

这 3 个模板的不同之处在于:
左、中、右索引的分配。
循环或递归终止条件。
后处理的必要性。
模板 #1 和 #3 是最常用的,几乎所有二分查找问题都可以用其中之一轻松实现。模板 #2 更 高级一些,用于解决某些类型的问题。

这 3 个模板中的每一个都提供了一个特定的用例:

模板 #1 (left <= right)

二分查找的最基础和最基本的形式。
查找条件可以在不与元素的两侧进行比较的情况下确定(或使用它周围的特定元素)。
不需要后处理,因为每一步中,你都在检查是否找到了元素。如果到达末尾,则知道未找到该元素。

模板 #2 (left < right)

一种实现二分查找的高级方法。
查找条件需要访问元素的直接右邻居。
使用元素的右邻居来确定是否满足条件,并决定是向左还是向右。
保证查找空间在每一步中至少有 2 个元素。
需要进行后处理。 当你剩下 1 个元素时,循环 / 递归结束。 需要评估剩余元素是否符合条件。

模板 #3 (left + 1 < right)

实现二分查找的另一种方法。
搜索条件需要访问元素的直接左右邻居。
使用元素的邻居来确定它是向右还是向左。
保证查找空间在每个步骤中至少有 3 个元素。
需要进行后处理。 当剩下 2 个元素时,循环 / 递归结束。 需要评估其余元素是否符合条件。
  • 时间:O(log n) —— 算法时间
  • 空间:O(1) —— 常量空间
# 两次二分查找计算子数组内出现次数
l = bisect_left(pos, left)
r = bisect_right(pos, right)


    def binary_search_left(self,nums,target):   # 寻找最左边节点   
        left=0
        right=len(nums)-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]>=target:
                right=mid-1
            else:
                left=mid+1
        return left
    def binary_search_right(self,nums,target):   # 寻找最右边节点   
        left=0
        right=len(nums)-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]<=target:
                left=mid+1
            else:
                right=mid-1
        return left

二、题库

1.易

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

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

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

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

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

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # 二分法,第一要求有序,复杂度 ologn   这道题不需要和目标值周围进行比较,所以使用二分法的第一种类型,
        left=0
        right=len(nums)-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]<target:
                left=mid+1
            elif nums[mid]>target:
                right=mid-1
            else:
                return mid
        return left		# 根据题目要求进行处理

69. Sqrt(x)
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。

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

注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入:x = 4
输出:2
示例 2:

输入:x = 8
输出:2
解释:8 的算术平方根是 2.82842…, 由于返回类型是整数,小数部分将被舍去。

class Solution:
    def mySqrt(self, x: int) -> int:
        # 也是第一种类型,不需要和周围进行比较 ,这种题,一般后处理是比较难
        left=0
        right=x
        while left<=right:
            mid=(left+right)//2
            if mid*mid<x:
                left=mid+1
            elif mid*mid==x:
                return mid
            else:
                right=mid-1
        return int(left)-1		# 
  1. 两数之和 II - 输入有序数组
    给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        # 解法1:双指针思路
        left=0
        right=len(numbers)-1
        while left<=right:     # left right 肯定是不能相等的,因为不能用相同的数字,所以有区别
            sum_nums=numbers[left]+numbers[right]
            if sum_nums<target:
                left+=1
            elif sum_nums>target:
                right-=1
            else:
                return [left+1,right+1]

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

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

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

示例 1:

输入:n = 5, bad = 4
输出:4
解释:
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
示例 2:

输入:n = 1, bad = 1
输出:1

# The isBadVersion API is already defined for you.
# @param version, an integer
# @return an integer
# def isBadVersion(version):

class Solution:
    def firstBadVersion(self, n):
        """
        :type n: int
        :rtype: int
        """
        # 由题意 典型的二分法解决
        left=0
        right=n-1
        while left<=right:
            mid=(left+right)//2
            if isBadVersion(mid):
                right=mid-1
            else:
                left=mid+1
        return left

2089. 找出数组排序后的目标下标

给你一个下标从 0 开始的整数数组 nums 以及一个目标元素 target 。

目标下标 是一个满足 nums[i] == target 的下标 i 。

将 nums 按 非递减 顺序排序后,返回由 nums 中目标下标组成的列表。如果不存在目标下标,返回一个 空 列表。返回的列表必须按 递增 顺序排列。

示例 1:

输入:nums = [1,2,5,2,3], target = 2
输出:[1,2]
解释:排序后,nums 变为 [1,2,2,3,5] 。
满足 nums[i] == 2 的下标是 1 和 2 。

class Solution:
    def targetIndices(self, nums: List[int], target: int) -> List[int]:
    	# 思路1:直接排序,遍历
        nums.sort()
        res=[]
        for i in range(len(nums)):
            if nums[i]==target:
                res.append(i)
        return res

复杂度分析

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),其中 n 为 n u m s nums nums 的长度。排序的时间复杂度为 $ O ( n l o g n ) O(n logn) O(nlogn),遍历记录目标下标数组的时间复杂度为O(n)。

  • 空间复杂度: O ( l o g n ) O(logn) O(logn),即为排序的栈空间开销。

class Solution:
    def targetIndices(self, nums: List[int], target: int) -> List[int]:
        # 思路2:直接统计大于小于个数,然后遍历
        cnt_low = 0
        cnt_equal = 0
        for i in nums:
            if i < target:
                cnt_low += 1
            elif i == target:
                cnt_equal+=1
        return list(range(cnt_low,cnt_equal+cnt_low))

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n 为 n u m s nums nums 的长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)

1346. 检查整数及其两倍数是否存在
给你一个整数数组 arr,请你检查是否存在两个整数 N 和 M,满足 N 是 M 的两倍(即,N = 2 * M)。

更正式地,检查是否存在两个下标 i 和 j 满足:

i != j
0 <= i, j < arr.length
arr[i] == 2 * arr[j]

示例 1:

输入:arr = [10,2,5,3]
输出:true
解释:N = 10 是 M = 5 的两倍,即 10 = 2 * 5 。

class Solution:
    def checkIfExist(self, arr: List[int]) -> bool:
        # 解法1:将两倍和数字使用字典存储,然后进行遍历即可
        # 特殊形状,大于两个0,直接返回True
        if arr.count(0)>=2:
            return True
        dict_num={j*2:j for j in arr}
        for i in arr:
            if i in dict_num.keys() and i!=0:
                return True
        return False

1351. 统计有序矩阵中的负数
给你一个 m * n 的矩阵 grid,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。

请你统计并返回 grid 中 负数 的数目。

示例 1:

输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵中共有 8 个负数。
示例 2:

输入:grid = [[3,2],[1,0]]
输出:0
示例 3:

输入:grid = [[1,-1],[-1,-1]]
输出:3
示例 4:

输入:grid = [[-1]]
输出:1

class Solution:
    def countNegatives(self, grid: List[List[int]]) -> int:
        # 解法1:直接双循环解决:
        # 解法2: 找规律
        '''
        [4,3,2,-1],
        [3,2,1,-1],
        [1,1,-1,-2],
        [-1,-1,-2,-3]
        从右上角开始遍历,i = 0, j = grid[0].length - 1
        如果当前值大于等于 0,那么前面的值肯定都非负,那么直接跳过,进入下一行, 即 i++
        如果当前值小于 0,那么当前值以及同列下的值都是小于 0 的,那么直接添加,然后进行下一列,即 j--
        '''
        count=0
        row,col=0,len(grid[0])-1
        while col>=0 and len(grid)-1>=row>=0:
            if grid[row][col]>=0:
                row+=1
            else:
                col-=1
                count+=len(grid)-row
        return count
  • 时间复杂度:时间复杂度为 O(m+n)
  • 空间复杂度:O(1)。

367. 有效的完全平方数
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。

进阶:不要 使用任何内置的库函数,如 sqrt 。

示例 1:

输入:num = 16
输出:true
示例 2:

输入:num = 14
输出:false

class Solution:
    def isPerfectSquare(self, num: int) -> bool:
        # 二分法
        left=0
        right=num
        while left<=right:
            mid=(left+right)//2
            square =mid*mid
            if square>num:
                right=mid-1
            elif square<num:
                left=mid+1
            else:
                return True
        return False
  • 时间复杂度:时间复杂度为 O(logn)
  • 空间复杂度:O(1)。

1337. 矩阵中战斗力最弱的 K 行
给你一个大小为 m * n 的矩阵 mat,矩阵由若干军人和平民组成,分别用 1 和 0 表示。

请你返回矩阵中战斗力最弱的 k 行的索引,按从最弱到最强排序。

如果第 i 行的军人数量少于第 j 行,或者两行军人数量相同但 i 小于 j,那么我们认为第 i 行的战斗力比第 j 行弱。

军人 总是 排在一行中的靠前位置,也就是说 1 总是出现在 0 之前。

示例 1:

输入:mat =
[[1,1,0,0,0],
[1,1,1,1,0],
[1,0,0,0,0],
[1,1,0,0,0],
[1,1,1,1,1]],
k = 3
输出:[2,0,3]
解释:
每行中的军人数目:
行 0 -> 2
行 1 -> 4
行 2 -> 1
行 3 -> 2
行 4 -> 5
从最弱到最强对这些行排序后得到 [2,0,3,1,4]

class Solution:
    def kWeakestRows(self, mat: List[List[int]], k: int) -> List[int]:
    	# 解法1
        # dict_soldiers={}
        # for i in range(len(mat)):
        #     dict_soldiers[i]=sum(mat[i])
        # return list(dict(sorted(dict_soldiers.items(),key=lambda x :x[1])))[:k]
        # 
        return sorted(range(len(mat)), key = lambda x: sum(mat[x]))[:k]

1385. 两个数组间的距离值
给你两个整数数组 arr1 , arr2 和一个整数 d ,请你返回两个数组之间的 距离值 。

「距离值」 定义为符合此距离要求的元素数目:对于元素 arr1[i] ,不存在任何元素 arr2[j] 满足 |arr1[i]-arr2[j]| <= d 。

示例 1:

输入:arr1 = [4,5,8], arr2 = [10,9,1,8], d = 2
输出:2
解释:
对于 arr1[0]=4 我们有:
|4-10|=6 > d=2
|4-9|=5 > d=2
|4-1|=3 > d=2
|4-8|=4 > d=2
所以 arr1[0]=4 符合距离要求

对于 arr1[1]=5 我们有:
|5-10|=5 > d=2
|5-9|=4 > d=2
|5-1|=4 > d=2
|5-8|=3 > d=2
所以 arr1[1]=5 也符合距离要求

对于 arr1[2]=8 我们有:
|8-10|=2 <= d=2
|8-9|=1 <= d=2
|8-1|=7 > d=2
|8-8|=0 <= d=2
存在距离小于等于 2 的情况,不符合距离要求

故而只有 arr1[0]=4 和 arr1[1]=5 两个符合距离要求,距离值为 2

class Solution:
    def findTheDistanceValue(self, arr1: List[int], arr2: List[int], d: int) -> int:
    	# 方法一:模拟
        count=0
        for i in arr1:
            flag=1
            for j in arr2:
                if abs(i-j)<=d:
                    flag=0
                    break
            if flag:
                count+=1
        return count
  • 时间复杂度:时间复杂度为 O(n*m)
  • 空间复杂度:O(1)。

class Solution:
    def findTheDistanceValue(self, arr1: List[int], arr2: List[int], d: int) -> int:

        # 思路2: 先对arr2排序,然后遍历arr1,找出每个arr1的元素在arr2中最接近的元素,即大于i的第一个元素,小于i的第一个元素,
        # 第二种 二分法返回 插入的位置
        def binary(arr,target):
            left=0
            right=len(arr)-1
            while left<=right:
                mid=(left+right)//2
                if arr[mid]>target:
                    right=mid-1
                else:
                    left=mid+1
            return left
        arr2.sort()
        count=0
        for i in arr1:
            num=binary(arr2,i)
            if num == len(arr2) or abs(i - arr2[num]) > d:
                if num == 0 or abs(i - arr2[num - 1]) > d:
                    count += 1
        return count

复杂度分析

  • 时间复杂度:给 arr2 排序的时间代价是 O ( m log ⁡ m ) O(m \log m) O(mlogm),对于 arr1 中的每个元素都在 arr2 中二分的时间代价是 O ( n log ⁡ m ) O(n \log m) O(nlogm),故渐进时间复杂度为 O ( ( n + m ) log ⁡ m ) O((n + m) \log m) O((n+m)logm)

  • 空间复杂度:这里没有使用任何的辅助空间,故渐进空间复杂度为 O ( 1 ) O(1) O(1)

剑指 Offer 57. 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        left=0
        right=len(nums)-1
        while left<=right :
            if nums[left]+nums[right]<target:
                left+=1
            elif nums[left]+nums[right]>target:
                right-=1
            else:
                return [nums[left],nums[right]]

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度:O(1)。

1446. 连续字符
给你一个字符串 s ,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。

请你返回字符串的能量。

示例 1:

输入:s = “leetcode”
输出:2
解释:子字符串 “ee” 长度为 2 ,只包含字符 ‘e’ 。

class Solution:
    def maxPower(self, s: str) -> int:
        count=1
        ans=1
        for i in range(1,len(s)):
            if s[i]==s[i-1]:
                count+=1
                ans=max(ans,count)
            else:
                count=1
        return ans

复杂度分析

  • 时间复杂度:O(n),其中 n 是字符串 s 的长度。遍历一次 s 的时间复杂度为 O(n)。

  • 空间复杂度:O(1)。我们只需要常数的空间保存若干变量。

1539. 第 k 个缺失的正整数
给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。

请你找到这个数组里第 k 个缺失的正整数。

示例 1:

输入:arr = [2,3,4,7,11], k = 5
输出:9
解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,…] 。第 5 个缺失的正整数为 9 。

class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        # 思路1:没有利用有序。
        num=0
        for i in range(1,len(arr)+k+1):
            if i not in arr:
                k-=1
            if k==0:
                num=i
                break
        return num

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:O(1)。
class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        # 思路2:利用有序特点,如果不相等,则表明有缺失值,k-=1,
        current=0
        lastmissing=-1
        for i in range(1,len(arr)+k):	
            if current<len(arr) and  i!=arr[current]:	# 防止越界,且值不等时
                k-=1
                lastmissing=i
            else:				# 相等则arr 的索引+1
                current+=1
            if k==0:
                break
        if k!=0:	# 后处理
            lastmissing=arr[-1]+k
        return lastmissing

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n + k ) O(n+k) O(n+k)
  • 空间复杂度:O(1)。
class Solution:
    def findKthPositive(self, arr: List[int], k: int) -> int:
        # 思路3:二分法,缺失个数=a[i]-i-1
        left=0
        right=len(arr)
        while left<right:
            mid=(left+right)//2
            print(mid)
            if arr[mid]-mid-1>=k:
                right=mid
            else:
                left=mid+1
        return k+left

复杂度分析

  • 时间复杂度:时间复杂度为 O ( l o g n ) O(logn) O(logn)
  • 空间复杂度:O(1)。

1608. 特殊数组的特征值
给你一个非负整数数组 nums 。如果存在一个数 x ,使得 nums 中恰好有 x 个元素 大于或者等于 x ,那么就称 nums 是一个 特殊数组 ,而 x 是该数组的 特征值 。

注意: x 不必 是 nums 的中的元素。

如果数组 nums 是一个 特殊数组 ,请返回它的特征值 x 。否则,返回 -1 。可以证明的是,如果 nums 是特殊数组,那么其特征值 x 是 唯一的 。

示例 1:

输入:nums = [3,5]
输出:2
解释:有 2 个元素(3 和 5)大于或等于 2 。

class Solution:
    def specialArray(self, nums: List[int]) -> int:
        # 思路1:简单题,暴力,
        count=0
        special_num=0
        for  i in range(len(nums)+1):
            for j in nums:
                if j>=i:
                    count+=1
            if count==i:
                special_num=i
                break
            count=0
        return special_num if special_num else -1

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:O(1)。
class Solution:
    def specialArray(self, nums: List[int]) -> int:

        # 思路2:对nums 排序,遍历nums的长度+1,如果当满足,当前点大于等于i,且前一个点小于i,则返回,又以遍历到0,且当前点大于等于i,则返回。
        nums.sort()
        special_num=0
        num_len=len(nums)
        for i in range(1,num_len+1):
            if nums[num_len-i]>=i:
                if num_len-i-1<0 or nums[num_len-i-1]<i:
                    special_num=i
                    break
        return special_num if special_num else -1

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度:O(1)。

LCP 18. 早餐组合
小扣在秋日市集选择了一家早餐摊位,一维整型数组 staple 中记录了每种主食的价格,一维整型数组 drinks 中记录了每种饮料的价格。小扣的计划选择一份主食和一款饮料,且花费不超过 x 元。请返回小扣共有多少种购买方案。

注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1

示例 1:

输入:staple = [10,20,5], drinks = [5,5,2], x = 15

输出:6

解释:小扣有 6 种购买方案,所选主食与所选饮料在数组中对应的下标分别是:
第 1 种方案:staple[0] + drinks[0] = 10 + 5 = 15;
第 2 种方案:staple[0] + drinks[1] = 10 + 5 = 15;
第 3 种方案:staple[0] + drinks[2] = 10 + 2 = 12;
第 4 种方案:staple[2] + drinks[0] = 5 + 5 = 10;
第 5 种方案:staple[2] + drinks[1] = 5 + 5 = 10;
第 6 种方案:staple[2] + drinks[2] = 5 + 2 = 7。

class Solution:
    def breakfastNumber(self, staple: List[int], drinks: List[int], x: int) -> int:
        # 思路:主要是一个排序,一个不排序,不排序的遍历,排序的二分法,采用第一种二分法,找到最佳的插入点
        def binary(nums,target):
            left=0
            right=len(nums)-1
            while left<=right:
                mid=(left+right)//2
                if nums[mid]<=target :			# 找到最佳的插入点
                    left=mid+1
                elif nums[mid]>target:
                    right=mid-1
            return left
        drinks.sort()
        count=0
        for i in staple:
            count+=binary(drinks,x-i)
        return count%(10**9+7)

复杂度分析

  • 时间复杂度:时间复杂度为 O ( m l o g n ) O(mlogn) O(mlogn),m为staple的长度,drinks长度为n, l o g n logn logn为每次对drinks进行一次二分查找。
  • 空间复杂度:O(1)。

167. 两数之和 II - 输入有序数组
给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        # 解法1:双指针思路
        left=0
        right=len(numbers)-1
        while left<right:
            sum_nums=numbers[left]+numbers[right]
            if sum_nums<target:
                left+=1
            elif sum_nums>target:
                right-=1
            else:
                return [left+1,right+1]

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度:O(1)。

LCP 28. 采购方案
小力将 N 个零件的报价存于数组 nums。小力预算为 target,假定小力仅购买两个零件,要求购买零件的花费不超过预算,请问他有多少种采购方案。

注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1

示例 1:

输入:nums = [2,5,3,5], target = 6

输出:1

解释:预算内仅能购买 nums[0] 与 nums[2]。

class Solution:
    def purchasePlans(self, nums: List[int], target: int) -> int:
        '''
        # 暴力:超时
        count=0
        for  i in range(len(nums)-1):
            for j in range(i+1,len(nums)):
                if nums[i]+nums[j]<=target:
                    count+=1
        return count
        '''
        # 双指针,先排序,然后一个一个元素,遍历,统计个数
        nums.sort()
        left=0
        count=0
        right=len(nums)-1
        while left<right:
            if nums[left]+nums[right]>target:
                right-=1
            else:
                count+=right-left
                left+=1
        return count%1000000007

复杂度分析

  • 时间复杂度:时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn) ,排序时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn) ,遍历的时间复杂度 O ( n ) O(n) O(n)
  • 空间复杂度:O(1)。

剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

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

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        # return min(numbers)
        # 方法二:二分法,改造的二分法,画图很好理解
        if len(numbers)==1:
            return numbers[0]
        left=0
        right=len(numbers)-1
        while left<right:
            mid=(left+right)//2
            if numbers[mid]>numbers[right]:
                left=mid+1
            elif numbers[mid]<numbers[right]:
                right=mid
            else:
                right-=1
        return numbers[left]

复杂度分析

  • 时间复杂度:平均时间复杂度为 O ( l o g n ) O(log n) O(logn)

  • 空间复杂度: O ( 1 ) O(1) O(1)

888. 公平的糖果棒交换
爱丽丝和鲍勃有不同大小的糖果棒:A[i] 是爱丽丝拥有的第 i 根糖果棒的大小,B[j] 是鲍勃拥有的第 j 根糖果棒的大小。

因为他们是朋友,所以他们想交换一根糖果棒,这样交换后,他们都有相同的糖果总量。(一个人拥有的糖果总量是他们拥有的糖果棒大小的总和。)

返回一个整数数组 ans,其中 ans[0] 是爱丽丝必须交换的糖果棒的大小,ans[1] 是 Bob 必须交换的糖果棒的大小。

如果有多个答案,你可以返回其中任何一个。保证答案存在。

示例 1:

输入:A = [1,1], B = [2,2]
输出:[1,2]

class Solution:
    def fairCandySwap(self, aliceSizes: List[int], bobSizes: List[int]) -> List[int]:
        # 思路 数学公式推导,   
        sumA, sumB = sum(aliceSizes), sum(bobSizes)
        delta = (sumA - sumB) // 2
        rec = set(aliceSizes)
        ans = None
        for y in bobSizes:
            x = y + delta
            if x in rec:
                ans = [x, y]
                break
        return ans
       

复杂度分析

  • 时间复杂度:O(n + m),其中 nn 是序列 \aliceSizes 的长度,mm 是序列bobSizes 的长度。
  • 空间复杂度:O(n),其中 nn 是序列 aliceSizes 的长度。我们需要建立一个和序列 aliceSizes 等大的哈希表。

2.中

153. 寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

class Solution:
    def findMin(self, nums: List[int]) -> int:
        # 二分法:本身是有序的,旋转之后部分是有序的
        # 范围的包含取决于 取最小值,那么在最小值的位置进行保留
        left=0
        right=len(nums)-1
        while left<right:
            mid=(left+right)//2
            if nums[mid]>nums[right]:
                left=mid+1
            else:
                right=mid               
        return nums[left]
  • 时间复杂度:平均时间复杂度为 O(log n),最高为o(n)
  • 空间复杂度:O(1)。

1855. 下标对中的最大距离
给你两个 非递增 的整数数组 nums1​​​​​​ 和 nums2​​​​​​ ,数组下标均 从 0 开始 计数。

下标对 (i, j) 中 0 <= i < nums1.length 且 0 <= j < nums2.length 。如果该下标对同时满足 i <= j 且 nums1[i] <= nums2[j] ,则称之为 有效 下标对,该下标对的 距离 为 j - i​​ 。​​

返回所有 有效 下标对 (i, j) 中的 最大距离 。如果不存在有效下标对,返回 0 。

一个数组 arr ,如果每个 1 <= i < arr.length 均有 arr[i-1] >= arr[i] 成立,那么该数组是一个 非递增 数组。

示例 1:

输入:nums1 = [55,30,5,4,2], nums2 = [100,20,10,10,5]
输出:2
解释:有效下标对是 (0,0), (2,2), (2,3), (2,4), (3,3), (3,4) 和 (4,4) 。
最大距离是 2 ,对应下标对 (2,4) 。

class Solution:
    def maxDistance(self, nums1: List[int], nums2: List[int]) -> int:
        # 思路1:暴力加优化,优化1:非递增,则表明,当出现一个大于,则中止,优化2:由于是获得最大距离,那么下一次出现的距离要比原先的距离还要打。则i+max_distance,而不是i。
        max_distance=0
        for i in range(len(nums1)):
            for j in range(i+max_distance,len(nums2)):
                if nums1[i]<=nums2[j]:
                    max_distance=max(max_distance,j-i)
                else:
                    break
        return max_distance
  • 时间复杂度:平均时间复杂度为 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度:O(1)。
class Solution:
    def maxDistance(self, nums1: List[int], nums2: List[int]) -> int:
        # 思路2:双指针
        nums1_len,nums2_len=len(nums1),len(nums2)
        i=0
        ans=0
        for j in range(nums2_len):
            while i<nums1_len and nums1[i]>nums2[j]:
                i+=1
            if i<=j and i<nums1_len:
                ans=max(ans,j-i)
        return ans

复杂度分析

  • 时间复杂度: O ( n 1 + n 2 ) O(n_1 + n_2) O(n1+n2),其中 n 1 , n 2 n_1, n_2 n1,n2分别为 nums1与nums 2的长度。在双指针寻找最大值的过程中,我们最多会遍历两个数组各一次。
  • 空间复杂度:O(1),我们使用了常数个变量进行遍历。

81. 搜索旋转排序数组 II
已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。

给你 旋转后 的数组 nums 和一个整数 target ,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums 中存在这个目标值 target ,则返回 true ,否则返回 false 。

示例 1:

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

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

class Solution:
    def search(self, nums: List[int], target: int) -> bool:
        n=len(nums)
        i=0
        j=n-1
        while i<=j:
            mid=(i+j)//2
            if nums[mid]==target:
                return True
            else:
                # 左边有序
                if nums[i]<nums[mid]:
                    if nums[i]<=target<nums[mid]:
                        j=mid-1
                    else:
                        i=mid+1
                elif nums[i]>nums[mid]:
                    # 右边有序
                    if nums[mid]<target<=nums[j]:
                        i=mid+1
                    else:
                        j=mid-1
                else:
                    if nums[i]==nums[j]:
                        i+=1
                        j-=1
                    else:
                        i+=1
        return False
  • 时间复杂度:时间复杂度为o(n),最坏情况下,即全部元素都是重复且target不在数组中。
  • 空间复杂度:O(1)。

33. 搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

示例 1:

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

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 第一个二分查找是查找旋转点, 旋转点将两本分分为两个有序的数组
        left=0
        right=len(nums)-1
        while left<right:
            mid=(left+right)//2
            if nums[mid]>nums[right]:
                left=mid+1
            else:
                right-=1
        if left==0:                         # 没有旋转点,直接二分查找
            return self.binary(nums,target)
        if target>=nums[0]:                 # 找到旋转点,
            return self.binary(nums[:left],target)
        else:
            index=self.binary(nums[left:],target)
            return index+left if index!=-1 else index
    def binary(self,nums,target):
        left=0
        right=len(nums)-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]>target:
                right=mid-1
            elif nums[mid]==target:
                return mid
            else:
                left=mid+1
        return -1

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left=0
        right=len(nums)-1
        while left<=right:
            mid=(left+right)//2
            if nums[mid]==target:
                return mid
            else:
                # 左边有序
                if nums[left]<nums[mid]:
                    if nums[left]<=target<nums[mid]:
                        right=mid-1
                    else:
                        left=mid+1
                # 右边有序
                elif nums[left]>nums[mid]:
                    if nums[mid]<target<=nums[right]:
                        left=mid+1
                    else:
                        right=mid-1
                else:               # 这里是判断最后left=right的节点是否等于target
                    print(left,right,mid)
                    return right if nums[right]==target else -1
        return -1
                    

时间复杂度

  • 时间复杂度:时间复杂度为o(log n)
  • 空间复杂度:O(1)。

2024. 考试的最大困扰度
一位老师正在出一场由 n 道判断题构成的考试,每道题的答案为 true (用 ‘T’ 表示)或者 false (用 ‘F’ 表示)。老师想增加学生对自己做出答案的不确定性,方法是 最大化 有 连续相同 结果的题数。(也就是连续出现 true 或者连续出现 false)。

给你一个字符串 answerKey ,其中 answerKey[i] 是第 i 个问题的正确结果。除此以外,还给你一个整数 k ,表示你能进行以下操作的最多次数:

每次操作中,将问题的正确答案改为 ‘T’ 或者 ‘F’ (也就是将 answerKey[i] 改为 ‘T’ 或者 ‘F’ )。
请你返回在不超过 k 次操作的情况下,最大 连续 ‘T’ 或者 ‘F’ 的数目。

示例 1:

输入:answerKey = “TTFF”, k = 2
输出:4
解释:我们可以将两个 ‘F’ 都变为 ‘T’ ,得到 answerKey = “TTTT” 。
总共有四个连续的 ‘T’ 。

class Solution:
    def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
        #思路1:滑动窗口 定义一个窗口为一个字符且可替换的长度最大为k的字符串  o(n) o(1)
        def max_answer(ch):
            left,ans,sum_k=0,0,0
            for right in range(len(answerKey)):
                if answerKey[right]!=ch:
                    sum_k+=1
                while sum_k>k:
                    if answerKey[left]!=ch:
                        sum_k-=1
                    left+=1
                ans=max(ans,right-left+1)
            return ans
        return max(max_answer('T'),max_answer('F'))

540. 有序数组中的单一元素
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 1:

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

输入: nums = [3,3,7,7,10,11,11]
输出: 10

class Solution:
    def singleNonDuplicate(self, nums: List[int]) -> int:
        # 思路1:暴力
        # return sum(set(nums))*2-sum(nums)
        # 思路2:二分法,因为只有一个单一元素,那么我们每次二分法取的一边必定是偶数或者奇数

        left = 0
        right = len(nums)-1
        while left < right:  # 不能相等 ,相等会报错 mid+1溢出
            mid = (left+right)//2
            mid_node = (mid%2==0)
            if nums[mid+1] == nums[mid]:
                if mid_node:
                    left=mid+2
                else:
                    right=mid-1
            elif nums[mid-1]==nums[mid]:
                if mid_node:
                    right=mid-2
                else:
                    left=mid+1
            else:
                return nums[mid]
        return nums[left]
  • 时间复杂度:时间复杂度为o(logn)
  • 空间复杂度:O(1)。

275. H 指数 II
给你一个整数数组 citations ,其中 citations[i] 表示研究者的第 i 篇论文被引用的次数,citations 已经按照 升序排列 。计算并返回该研究者的 h 指数。

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

提示:如果 h 有多种可能的值,h 指数 是其中最大的那个。

请你设计并实现对数时间复杂度的算法解决此问题。

示例 1:

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

class Solution:
    def hIndex(self, citations: List[int]) -> int:

        #思路:有序 二分法
        left=0
        right=len(citations)-1
        while left<=right:
            mid=(left+right)//2
            if citations[mid]>=len(citations)-mid:
                right=mid-1
            else:
                left=mid+1
        return len(citations)-left
  • 时间复杂度:时间复杂度为o(logn)
  • 空间复杂度:O(1)。
    658. 找到 K 个最接近的元素
    给定一个排序好的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。

整数 a 比整数 b 更接近 x 需要满足:

|a - x| < |b - x| 或者
|a - x| == |b - x| 且 a < b

示例 1:

输入:arr = [1,2,3,4,5], k = 4, x = 3
输出:[1,2,3,4]

class Solution:
    def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
        # 思路1:双指针思路,
        if len(arr)<=k:
            return arr[:k]
        left=0
        max_num=0
        approximate_arr=arr[:k]
        for i in range(k):
            max_num=max(max_num,abs(arr[i]-x))
        for right in range(k,len(arr)):
            if abs(arr[right]-x)<max_num:               # 当前值-x大于数据中最大值
                approximate_arr=arr[right-k+1:right+1]
                max_num=max(abs(min(approximate_arr)-x),abs(arr[right]-x))      
        return approximate_arr
        #思路2:排序
        return sorted(sorted(arr,key=lambda a:abs(a-x))[:k])
        # 思路3:二分法 找到当前点的位置
        if len(arr)<=k:
            return arr[:k]
        def binary_search(nums):
            left=0
            right=len(nums)-1
            while left<=right:
                mid=(left+right)//2
                if nums[mid]>x:
                    right=mid-1
                elif nums[mid]<x:
                    left=mid+1
                else:
                    break
            return left
        left=binary_search(arr)
        #  两种思路:1.则该最接近的全部元素应该都在   left-k-1   left+k+1  之间   2.从left开始一步步往左右移动,直到等于k为止
        right=left+k+1
        left=left-k-1
        if left<0:
            left=0
        if right>=len(arr):
            right=len(arr)-1
        while right-left!=k-1:                      # 这里不能为k,如果为k则最终返回是不包含右节点,
            if abs(arr[left]-x) <=abs(arr[right]-x):
                right-=1
            elif abs(arr[left]-x)>abs(arr[right]-x):
                left+=1
        return arr[left:right+1]

复杂度分析
思路1:

  • 时间复杂度: O ( k ∗ n ) O(k*n) O(kn), 排序时间0(n),每次排序需要遍历大小0(k),则时间复杂度为0(k*n)
  • 空间复杂度: O ( k ) O(k) O(k)。排序不需要额外的空间(快排),但是返回需要k的子列表长度

思路2:

  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
  • 空间复杂度: O ( k ) O(k) O(k)。排序不需要额外的空间(快排),但是返回需要k的子列表长度

思路3:

  • 时间复杂度: O ( l o g n + k ) O(logn+k) O(logn+k),二分查找需要o(logn),
  • 空间复杂度: O ( k ) O(k) O(k)。排序不需要额外的空间(快排),但是返回需要k的子列表长度

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

示例 1:

输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13
示例 2:

输入:matrix = [[-5]], k = 1
输出:-5

class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        # 方法1 : 拼接在排序 
        # a=[]
        # for i in matrix:
        #     a.extend(i)
        # a.sort()
        # return a[k-1]
        '''
        思路2:官方的思路,解释:
        1. 由矩阵的特性,我们可以从矩阵中获取最小值和最大值,即matrix[0][0],matrix[n-1][n-1]
        2. 将最小值和最大值定义为left ,right 二分法的左右点, 取mid=(left+right)//2,进行二分判断
        3. 对每次二分判断采用,统计数字的方式,如mid=8,则遍历整个矩阵  统计小于8的个数与k比较,进行判断
        4. 对判断的结果返回True False,True 即当前值大于k,则令right=mid-1  False 则令left=mid+1
        5. 综上过程,可以发现最后点必定为left
        '''

        # 由于矩阵存在每行每列元素升序排序,所以在实际使用的过程中,无需全部遍历,从后往前遍历即可
        def check(mid):
            i,j=0,len(matrix)-1
            count=0
            while i<len(matrix) and j>=0:
                if matrix[i][j]<=mid:
                    count+=j+1
                    i+=1
                else:
                    j-=1
            return count>=k

        left=matrix[0][0]
        right=matrix[-1][-1]
        while left<=right:
            mid=(left+right)//2
            if check(mid):
                right=mid-1
            else:
                left=mid+1
        return left

复杂度分析

  • 时间复杂度: O ( n l o g ( r − l ) ) O(nlog(r-l)) O(nlog(rl)),二分查找进行次数为 O ( l o g ( r − l ) ) O(log(r-l)) O(log(rl)),每次操作时间复杂度为 O ( n ) O(n) O(n)

  • 空间复杂度: O ( 1 ) O(1) O(1)

532. 数组中的 k-diff 数对
给定一个整数数组和一个整数 k,你需要在数组里找到 不同的 k-diff 数对,并返回不同的 k-diff 数对 的数目。

这里将 k-diff 数对定义为一个整数对 (nums[i], nums[j]),并满足下述全部条件:

0 <= i < j < nums.length
|nums[i] - nums[j]| == k
注意,|val| 表示 val 的绝对值。

示例 1:

输入:nums = [3, 1, 4, 1, 5], k = 2
输出:2
解释:数组中有两个 2-diff 数对, (1, 3) 和 (3, 5)。
尽管数组中有两个1,但我们只应返回不同的数对的数量。

633. 平方数之和
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c 。

示例 1:

输入:c = 5
输出:true
解释:1 * 1 + 2 * 2 = 5

import math
class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        # 方法1:暴力加二分法
        for i in range(int(math.sqrt(c))+1):
            left=i
            right=math.sqrt(c)+1
            target=c-i**2
            while left<=right:
                mid=(left+right)//2
                if mid**2>target:
                    right=mid-1
                elif mid**2<target:
                    left=mid+1
                else:
                    return True
        return False

复杂度分析

  • 时间复杂度: O ( ( n ) l o g ( n ) ) O(\sqrt(n)log(n)) O(( n)log(n))

  • 空间复杂度: O ( 1 ) O(1) O(1)

import math
class Solution:
    def judgeSquareSum(self, c: int) -> bool:
        # 方法2:双指针,其实和两数之和一模一样
        left=0
        right=int(math.sqrt(c))+1
        while left<=right:
            sum_nums=left**2+right**2
            if sum_nums>c:
                right-=1
            elif sum_nums<c:
                left+=1
            else:
                return True
        return False

复杂度分析

  • 时间复杂度: O ( ( n ) ) O(\sqrt(n)) O(( n))

  • 空间复杂度: O ( 1 ) O(1) O(1)

611. 有效三角形的个数
给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。

示例 1:

输入: [2,2,3,4]
输出: 3
解释:
有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3

class Solution:
    def triangleNumber(self, nums: List[int]) -> int:
        # 思路1:排序,先遍历两个点,然后对剩下的区间 进行二分 找到最佳插入点,即nums[i]+nums[j]>nums[mid]
        if len(nums)<=2:
            return 0
        count=0
        nums.sort()
        for i in range(len(nums)-2):
            for j in range(i+1,len(nums)-1):
                right=len(nums)-1
                left=j+1
                k=j
                while left<=right:
                    mid=(left+right)//2
                    if nums[mid]<nums[i]+nums[j]:
                        k=mid
                        left=mid+1
                    else:
                        right=mid-1
                count+=k-j
        return count

复杂度分析

  • 时间复杂度: O ( n 2 l o g n ) O(n^2 log n) O(n2logn),其中 nn 是数组 \textit{nums}nums 的长度。我们需要 O ( n l o g n ) O(nlog n) O(nlogn)的时间对数组 n u m s nums nums 进行排序,随后需要 O ( n 2 l o g n ) O(n^2 log n) O(n2logn) 的时间使用二重循环枚举 a , b a, b a,b的下标以及使用二分查找得到 c 的下标范围。
  • 空间复杂度: O ( l o g n ) O(log n) O(logn),即为排序需要的栈空间。
class Solution:
    def triangleNumber(self, nums: List[int]) -> int:
        # 方法2:排序,遍历i,对小于i的进行寻找满足的两个边。
        nums.sort()
        n = len(nums)
        ans = 0
        for i in range(2,n):
            l , r = 0 , i - 1
            while l < r:
                if nums[l] + nums[r] > nums[i]:
                    ans += r - l
                    r -= 1
                else:
                    l += 1
        return ans

复杂度分析

  • 时间复杂度: O ( n 2 ) O(n^2 ) O(n2)
  • 空间复杂度: O ( l o g n ) O(log n) O(logn),即为排序需要的栈空间。

1283. 使结果不超过阈值的最小除数
给你一个整数数组 nums 和一个正整数 threshold ,你需要选择一个正整数作为除数,然后将数组里每个数都除以它,并对除法结果求和。

请你找出能够使上述结果小于等于阈值 threshold 的除数中 最小 的那个。

每个数除以除数后都向上取整,比方说 7/3 = 3 , 10/2 = 5 。

题目保证一定有解。

示例 1:

输入:nums = [1,2,5,9], threshold = 6
输出:5
解释:如果除数为 1 ,我们可以得到和为 17 (1+2+5+9)。
如果除数为 4 ,我们可以得到和为 7 (1+1+2+3) 。如果除数为 5 ,和为 5 (1+1+1+2)。

class Solution:
    def smallestDivisor(self, nums: List[int], threshold: int) -> int:
        # 思路1:二分法,选择一个正整数作为除数,则选择nums中最大的数作为右节点
        left=1
        right=max(nums)
        while left<=right:
            mid=(left+right)//2
            sum_threshold=0
            for i in nums:                      # 依次遍历,求和
                if i%mid:
                    sum_threshold+=i//mid+1
                else:
                    sum_threshold+=i//mid
            if sum_threshold>threshold:         
                left=mid+1
            elif sum_threshold<=threshold:
                right=mid-1
        return left

复杂度分析

时间复杂度: O ( N l o g C ) O(Nlog C) O(NlogC),其中 C 是一个常数,为二分查找的上下限之差。在本题给定的数据范围的限制下,CC 不会超过
1 0 6 10^6 106。N指的是遍历, l o g c logc logc 为二分查找
空间复杂度:O(1)。

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

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

示例 1:

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

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 思路1:对每一行进行二分,缺点只利用了行有序,未利用列有序
        def binary_search(matrix):
            left=0
            right=len(matrix)-1
            while left<=right:
                mid=(left+right)//2
                if matrix[mid]>=target:
                    right=mid-1
                else:
                    left=mid+1
            return left
        row=0
        while row<len(matrix):
            left=binary_search(matrix[row])
            if left<0 :
                return False
            if left<len(matrix[row]) and matrix[row][left]==target:
                return True
            row+=1
        return False

复杂度分析

  • 时间复杂度: O ( m l o g n ) O(mlog n) O(mlogn),其中 m 是为行数,log n为每行需要进行二分
  • 空间复杂度:O(1)。
class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 思路2:找规律,从右上角开始进行遍历
        row,col=0,len(matrix[0])-1
        while row<len(matrix) and col >=0:
            if matrix[row][col]>target:
                col-=1
            elif matrix[row][col]<target:
                row+=1
            else:
                return True
        return False

复杂度分析

  • 时间复杂度: O ( m + n ) O(m+ n) O(m+n),其中 m 是为行数,n为列数
  • 空间复杂度:O(1)。

15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # #     优化,第一步对元数组进行排序,可以避免重复项的产生,给定一个数,那么另外两个数的和一定与该数相反,那么对另外两个数进行双向指针对撞进行求解
        # 时间复杂度0(n^2) 空间复杂度o(n)
        if len(nums)<3:
            return []
        nums.sort()
        ans=[]
        for i in range(len(nums)):
            if i>=1 and nums[i]==nums[i-1]:   # 主要是为了,去除,上一个数与当前数一样,而造成重复结果
                continue
            left=i+1
            right=len(nums)-1
            while left<right:
                f=nums[i]+nums[left]+nums[right]
                if f==0:
                    ans.append([nums[i],nums[left],nums[right]])
                    while left<right and nums[left]==nums[left+1]:          # 跳过重复的元素
                        left+=1
                    while left<right and nums[right]==nums[right-1]:
                        right-=1
                    left+=1
                    right-=1

                elif f<0:
                    left+=1
                else:
                    right-=1
        return ans

18. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        # 时间复杂度0(n^2)  空间复杂度o(n)
        ans=[]
        nums.sort()
        if len(nums)<4:
            return []
        for i in range(len(nums)-3):
            if i>0 and nums[i]==nums[i-1]:              # 去重
                continue
            for j in range(i+1,len(nums)-2):
                if j>i+1 and nums[j]==nums[j-1]:        # 去重 条件大于i+1(即是第二个元素范围内的相等,则continue)
                    continue
                left=j+1
                right=len(nums)-1
                while left<right:
                    f=nums[i]+nums[j]+nums[left]+nums[right]
                    if f==target :
                        ans.append([nums[i],nums[j],nums[left],nums[right]])
                        while left<right and nums[left]==nums[left+1]:
                            left+=1
                        while left<right and nums[right]==nums[right-1]:
                            right-=1
                        left+=1
                        right-=1
                    elif f<target:
                        left+=1
                    else:
                        right-=1
        return ans
                    

16. 最接近的三数之和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

class Solution:
    def threeSumClosest(self, nums: List[int], target: int) -> int:
        # 时间复杂度o(n^2)  空间复杂度o(n)
        nums.sort()
        min_sum=9999
        re_min=9999
        for i in range(len(nums)-2):
            left=i+1
            right=len(nums)-1
            while left<right:
                f=nums[i]+nums[left]+nums[right]
                diff=target-f
                if abs(diff)<re_min:
                    re_min=abs(diff)
                    min_sum=f
                if diff==0:
                    return f
                elif diff>0:            # 大于0,即target》三数之和,所以  left+=1 找更大的数
                    left+=1
                else:
                    right-=1
        return min_sum



                

5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 双指针解法,  时间复杂度  o(n^2)  空间复杂度0(1)  其中 n 是字符串的长度。长度为 1 和 2 的回文中心分别有 n 和 n−1 个,每个回文中心最多会向外扩展 O(n) 次。
        def double_search(left,right):
            while left>=0 and right<len(s) and s[left]==s[right]:
                left-=1
                right+=1
            return (right-left,left+1,right)    # 加1:left+1,因为跳出循环后left,和right已经不满足要求了,则left需要+1,right不需要-1,是因为python右是开,而不是闭
        res=''
        for i in range(len(s)):
            diff1=double_search(i,i)            # 以i为中心
            diff2=double_search(i,i+1)          # 以i和i+1为中心
            if diff1[0]>len(res):
                res=s[diff1[1]:diff1[2]]
            if diff2[0]>len(res):
                res=s[diff2[1]:diff2[2]]
        return res
        # 动态规划   时间空间复杂度o(n^2)  利用二维数据进行存储dp,并且回文单个肯定是回文,因此i==j肯定是回文 ,存储为True
        # 
        n = len(s)
        if n < 2:
            return s
        
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        # 递推开始
        # 先枚举子串长度
        for L in range(2, n + 1):
            # 枚举左边界,左边界的上限设置可以宽松一些
            for i in range(n):
                # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                j = L + i - 1
                # 如果右边界越界,就可以退出当前循环
                if j >= n:
                    break
                    
                if s[i] != s[j]:                        # 左右边界元素不相等,那么当前以左右为边界元素的就不是回文
                    dp[i][j] = False 
                else:
                    if j - i < 3:                       # 如果相等,则当前回文长度小于3,那么该一定是回文
                        dp[i][j] = True
                    else:                               # 否则,判断该内部的的字串是否是回文
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]

647. 回文子串
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

示例 1:

输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”

class Solution:
    def countSubstrings(self, s: str) -> int:
        '''
        思路1:dp解法
        布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
        情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
        情况二:下标i 与 j相差为1,例如aa,也是文子串
        情况三:下标:i 与 j相差大于1的时候,例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文就可以了,那么aba的区间就是 i+1 与 j-1区间,这个区间是不是回文就看dp[i + 1][j - 1]是否为true。

        '''
        # dp = [[False] * len(s) for _ in range(len(s))]
        # result = 0
        # for i in range(len(s)-1, -1, -1): #注意遍历顺序
        #     for j in range(i, len(s)):
        #         if s[i] == s[j]:
        #             if j - i <= 1: #情况一 和 情况二
        #                 result += 1
        #                 dp[i][j] = True
        #             elif dp[i+1][j-1]: #情况三
        #                 result += 1
        #                 dp[i][j] = True
        # return result

        #思路2: 动态规划   时间空间复杂度o(n^2)  利用二维数据进行存储dp,并且回文单个肯定是回文,因此i==j肯定是回文 ,存储为True
        # 
        n = len(s)
        if n < 2:
            return len(s)
        res=0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):              # 默认i==j即只有一个元素都是回文
            dp[i][i] = True
            res+=1
        # 回文字符串的长度
        for L in range(2,n+1):
            # 从左往右遍历,以L为长度
            for l in range(n):          # l为左边界
                r=l+L-1                 # l为右边界
                if r>=n:
                    break
                if s[r]!=s[l]:
                    continue
                else:
                    if r-l<3:
                        res+=1
                        dp[l][r]=True
                    else:
                        dp[l][r]=dp[l+1][r-1]
                        if dp[l][r]:
                            res+=1
        return res
        # # 思路2:双指针解法,中间扩散   有两种区别:以1.i,i+1两者相等为中心,则同时扩散,2.以i为中心扩散。
        # def double_search(left,right):
        #     res=0
        #     while left>=0 and right<len(s) and s[left]==s[right]:
        #         left-=1
        #         right+=1
        #         res+=1
        #     return res
        # res=0
        # for i in range(len(s)):
        #     res+=double_search(i,i+1)
        #     res+=double_search(i,i)                     # 
        # return res


3.难

154. 寻找旋转排序数组中的最小值 II
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:
若旋转 4 次,则可以得到 [4,5,6,7,0,1,4]
若旋转 7 次,则可以得到 [0,1,4,4,5,6,7]
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个可能存在 重复 元素值的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
示例 1:

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

输入:nums = [2,2,2,0,1]
输出:0

# 解法1: 不用管什么旋转 直接最小值完事
class Solution:
    def findMin(self, nums: List[int]) -> int:
        return min(nums)
  • 时间复杂度:复杂度为o(n)
  • 空间复杂度:O(1)。
class Solution:
    def findMin(self, nums: List[int]) -> int:
        # 1.对中间元素进行判断,如果大于最右边的,则右-中是无序,即最小值,就在这范围内,则进行赋值left=mid+1,如果小于最右边,则当前位置是比右边元素全部都小,所以进行赋值为right=mid,否则即相等,则right-=1
        left=0
        right=len(nums)-1
        while left < right:
            pivot = (left+right) // 2
            if nums[pivot] < nums[right]:
                right = pivot 
            elif nums[pivot] > nums[right]:
                left = pivot + 1
            else:
                right -= 1
        return nums[left]
  • 时间复杂度:平均时间复杂度为 O(log n),最高为o(n)
  • 空间复杂度:O(1)。

2.前缀和

一般是用来解决带有(连续子数组)。
303. 区域和检索 - 数组不可变
给定一个整数数组 nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )

示例 1:

输入:
[“NumArray”, “sumRange”, “sumRange”, “sumRange”]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
输出:
[null, 1, -1, -3]

解释:
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return 1 ((-2) + 0 + 3)
numArray.sumRange(2, 5); // return -1 (3 + (-5) + 2 + (-1))
numArray.sumRange(0, 5); // return -3 ((-2) + 0 + 3 + (-5) + 2 + (-1))

今天这个题目让我们求一个区间 [i, j] 内的和,求区间和可以用 preSum 来做。

preSum 方法能快速计算指定区间段 i - ji−j 的元素之和。它的计算方法是从左向右遍历数组,当遍历到数组的 ii 位置时,preSum 表示 ii 位置左边的元素之和。
假设数组长度为 NN,我们定义一个长度为 N+1N+1 的 preSum 数组,preSum[i] 表示该元素左边所有元素之和(不包含 i 元素)。然后遍历一次数组,累加区间 [0, i)[0,i) 范围内的元素,可以得到 preSum 数组。
求 preSum 的代码如下:

class NumArray:

    def __init__(self, nums: List[int]):
        self.presum=[0]+[0]*len(nums)
        for i in range(len(nums)):
            self.presum[i+1]=self.presum[i]+nums[i]

利用 preSum 数组,可以在 O(1) 的时间内快速求出 nums 任意区间 [i, j] (两端都包含) 的各元素之和。

sum(i, j) = preSum[j + 1] - preSum[i]

对于本题,可以在 NumArray 类的构造函数的里面,求数组每个位置的 preSum;当计算sumRange(i, j)的时候直接返回 preSum[j + 1] - preSum[i] 可以得到区间和。

下面以数组 [1, 12, -5, -6, 50, 3] 为例,展示了求 preSum 的过程。

class NumArray:

    def __init__(self, nums: List[int]):
        self.presum=[0]+[0]*len(nums)
        for i in range(len(nums)):
            self.presum[i+1]=self.presum[i]+nums[i]

    def sumRange(self, left: int, right: int) -> int:

        return self.presum[right+1]-self.presum[left]

如下图:
在这里插入图片描述
2016. 增量元素之间的最大差值
给你一个下标从 0 开始的整数数组 nums ,该数组的大小为 n ,请你计算 nums[j] - nums[i] 能求得的 最大差值 ,其中 0 <= i < j < n 且 nums[i] < nums[j] 。

返回 最大差值 。如果不存在满足要求的 i 和 j ,返回 -1 。

示例 1:

输入:nums = [7,1,5,4]
输出:4
解释:
最大差值出现在 i = 1 且 j = 2 时,nums[j] - nums[i] = 5 - 1 = 4 。
注意,尽管 i = 1 且 j = 0 时 ,nums[j] - nums[i] = 7 - 1 = 6 > 4 ,但 i > j 不满足题面要求,所以 6 不是有效的答案。

class Solution:
    def maximumDifference(self, nums: List[int]) -> int:
        # 前缀最小值,
        min_nums=nums[0]    
        diff=-1
        for i in nums:
            if i>min_nums:
                diff=max(diff,i-min_nums)
            else:
                min_nums=i
        return diff

复杂度分析

  • 时间复杂度:O(n)。我们只需要对数组 nums 进行一次遍历。

  • 空间复杂度:O(1)。

剑指 Offer II 010. 和为 k 的子数组
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2
解释: 此题 [1,1] 与 [1,1] 为两种不同的情况
示例 2 :

输入:nums = [1,2,3], k = 3
输出: 2

class Solution:
    def subarraySum(self, nums: List[int], k: int) -> int:
        # 滑动窗口  只能解决正例
        # count=0
        # left=0
        # sum_nums=nums[0]
        # for i in range(1,len(nums)):
        #     sum_nums+=nums[i]
        #     while sum_nums>k:
        #         sum_nums-=nums[left]
        #         left+=1
        #     if sum_nums==k:
        #         count+=1
        # return count
        # 前缀和+字典,利用字典来实现前缀和,[前n个数的和]  [次数]
        presum_map= defaultdict(int)
        presum_map[0]=1
        ans=0
        presum=0
        for i in nums:
            presum+=i
            target=presum-k
            if target in presum_map:
                ans+=presum_map[target]
            presum_map[presum]+=1
        return ans

复杂度分析

  • 时间复杂度:O(n)。我们只需要对数组 nums 进行一次遍历。

  • 空间复杂度:O(n)。前n个数和的不同数,最大为n。

剑指 Offer II 011. 0 和 1 个数相同的子数组
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:

输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。

class Solution:
    def findMaxLength(self, nums: List[int]) -> int:
        # 前缀和+字典,利用字典来实现前缀和,[前n个数的和]  [第一次位置]  将1修改为-1,那么只要当前和,在历史和上有出现过,则计算最大长度
        presum_map=defaultdict(int)
        presum_map[0]=-1
        pre_sum_num=0
        ans=0
        for i in range(len(nums)):
            pre_sum_num+= -1 if nums[i]==0 else 1
            if pre_sum_num in presum_map:           # 当前和在前缀和字典中存在,则计算最大索引
                ans=max(ans,i-presum_map[pre_sum_num])
            else:                                   # 当前和不在索引里面
                presum_map[pre_sum_num]=i
        return ans

复杂度分析

  • 时间复杂度:O(n)。我们只需要对数组 nums 进行一次遍历。

  • 空间复杂度:O(n)。前n个数和的不同数,最大为n。

剑指 Offer II 013. 二维子矩阵的和
给定一个二维矩阵 matrix,以下类型的多个请求:

计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
实现 NumMatrix 类:

NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。

示例 1:

输入:
[“NumMatrix”,“sumRegion”,“sumRegion”,“sumRegion”]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出:
[null, 8, 11, 12]

解释:
NumMatrix numMatrix = new NumMatrix([[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]]);
numMatrix.sumRegion(2, 1, 4, 3); // return 8 (红色矩形框的元素总和)
numMatrix.sumRegion(1, 1, 2, 2); // return 11 (绿色矩形框的元素总和)
numMatrix.sumRegion(1, 2, 2, 4); // return 12 (蓝色矩形框的元素总和)

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        # 求取每行的前缀和(不是全部的前缀和) 一维前缀和
        self.pre_matrix=[[0]*len(matrix[0])+[0] for i in range(len(matrix))]
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                self.pre_matrix[i][j + 1] = self.pre_matrix[i][j] + matrix[i][j]


    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        total=0
        for i in range(row1,row2+1):
            total+=self.pre_matrix[i][col2+1]-self.pre_matrix[i][col1]
        return total


# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

复杂度分析

  • 时间复杂度:初始化 O(mn),每次检索 O(m),其中 m 和 n 分别是矩阵matrix 的行数和列数。
    初始化需要遍历矩阵 matrix 计算二维前缀和,时间复杂度是 O(mn)。
    每次检索需要对二维区域中的每一行计算子数组和,二维区域的行数不超过 m,计算每一行的子数组和的时间复杂度是 O(1),因此每次检索的时间复杂度是 O(m)。

  • 空间复杂度:O(mn),其中 m 和 n 分别是矩阵 matrix 的行数和列数。需要创建一个 m 行 n+1 列的前缀和数组 sums。

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        # 2维矩阵求和,利用2和的前缀和
        self.pre_matrix=[[0]*len(matrix[0])+[0] for i in range(len(matrix)+1)]
        for i in range(len(matrix)):
            for j in range(len(matrix[0])):
                # 如果以直接前k行k列求和,超时,
                self.pre_matrix[i + 1][j + 1] = self.pre_matrix[i][j + 1] + self.pre_matrix[i + 1][j] - self.pre_matrix[i][j] + matrix[i][j]



    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        return int(self.pre_matrix[row2+1][col2+1]+self.pre_matrix[row1][col1]-self.pre_matrix[row2+1][col1]-self.pre_matrix[row1][col2+1])


# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

复杂度分析

  • 时间复杂度:初始化 O(mn),每次检索 O(m),其中 m 和 n 分别是矩阵matrix 的行数和列数。
    初始化需要遍历矩阵 matrix 计算二维前缀和,时间复杂度是 O(mn)。
    每次检索的时间复杂度为o(1)。
  • 空间复杂度:O(mn),其中 m 和 n 分别是矩阵 matrix 的行数和列数。需要创建一个 m 行 n+1 列的前缀和数组 sums。

剑指 Offer 66. 构建乘积数组
给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

class Solution:
    def constructArr(self, a: List[int]) -> List[int]:
        #  前缀和   计算前序的乘积,计算后序的乘积,两者相乘,  时空否为o(n)
        # pre_muti=[1]*len(a)
        # last_muti=[1]*len(a)
        # for i in range(1,len(a)):
        #     pre_muti[i]=a[i-1]*pre_muti[i-1]
        # for i in range(len(a)-2,-1,-1):
        #     last_muti[i]=a[i+1]*last_muti[i+1]
        # res=[1]*len(a)
        # for i in range(len(a)):
        #     res[i]=pre_muti[i]*last_muti[i]
        # return res
        # 不要额外空间  即时间为o(n) 空间为o(1)
        res=[1]*len(a)
        for i in range(1,len(a)):
            res[i]=a[i-1]*res[i-1]
        pre=1
        for i in range(len(a)-1,-1,-1):
            res[i]*=pre
            pre*=a[i]
        return res

719. 找出第 K 小的数对距离
数对 (a,b) 由整数 a 和 b 组成,其数对距离定义为 a 和 b 的绝对差值。

给你一个整数数组 nums 和一个整数 k ,数对由 nums[i] 和 nums[j] 组成且满足 0 <= i < j < nums.length 。返回 所有数对距离中 第 k 小的数对距离。

示例 1:

输入:nums = [1,3,1], k = 1
输出:0
解释:数对和对应的距离如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
距离第 1 小的数对是 (1,1) ,距离为 0 。


class Solution:
    def smallestDistancePair(self, nums: List[int], k: int) -> int:
        # 堆  一看n=10^4,O(n2)基本是超时的,O(n)又不太现实,所以想到O(nlogn),自然就知道用二分了  10^8量级的时间复杂度基本都会超时,10^7一般都不会超,记住就好    一般来说 第K个,基本上就是简单中等就是用堆,  难就是用二分
        # 堆超时
        # res=[]
        # for i in range(len(nums)):
        #     for j in range(i+1,len(nums)):
        #         heapq.heappush(res,-abs(nums[i]-nums[j]))
        #         if len(res)>k:
        #             heapq.heappop(res)
        # return -heapq.heappop(res)
        #  难使用二分法
        nums.sort()
        # 找到左右取值范围
        right=nums[-1]-nums[0]
        left=0
        ans=0
        # 二分找第K小
        while left<=right:
            mid=(left+right)//2
            i=0
            cnt=0
            # 计算当前小于mid的数对有多少
            for j in range(len(nums)):
                while nums[j]-nums[i]>mid:
                    i+=1
                cnt+=j-i     # 数对的加法
            # count小于则left = mid + 1,   找到符合要求的最左节点
            # 大于则right = mid - 1
            if cnt>=k:
                ans=mid
                right=mid-1
            else:
                left=mid+1
            
        return ans

复杂度分析
时间复杂度:O(n×(logn+logD)),其中n是数组nums的长度,D=max(nums)−min(nums)。排序O(nlogn),二分查找需要O(logD),双指针需要O(n)。
空间复杂度:O(logn)。排序的平均空间复杂度为O(logn)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值