Leetcode 二分查找(一)

    技术2022-07-11  95

    目录

    744. 寻找比目标字母大的最小字母

    69. x 的平方根

    475. 供暖器

    287. 寻找重复数

    1539. 第 k 个缺失的正整数


     

    744. 寻找比目标字母大的最小字母

    https://leetcode-cn.com/problems/find-smallest-letter-greater-than-target/

    给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。在比较时,字母是依序循环出现的。举个例子:如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a' 示例:

    输入:letters = ["c", "f", "j"],target = "a",输出: "c"

    输入:letters = ["c", "f", "j"],target = "c",输出: "f"

    输入:letters = ["c", "f", "j"],target = "d",输出: "f"

    输入:letters = ["c", "f", "j"],target = "g",输出: "j"

    输入:letters = ["c", "f", "j"],target = "j",输出: "c"

    输入:letters = ["c", "f", "j"],target = "k",输出: "c" 提示:letters长度范围在[2, 10000]区间内。letters 仅由小写字母组成,最少包含两个不同的字母。目标字母target 是一个小写字母。

    题解

    一:简化版,用一个临时变量res记住候选答案,这样比较简单。

    class Solution(object): def nextGreatestLetter(self, letters, target): """ :type letters: List[str] :type target: str :rtype: str """ l, r, res = 0, len(letters) - 1, -1 while l <= r: mid = l + (r - l) // 2 if letters[mid] > target: r = mid - 1 res = mid else: l = mid + 1 if res == -1: return letters[0] return letters[res]

    二:没有用临时变量,故这边r赋值的是,这样如果不存在,r就不会改变,也就不会是合法下标,便于后面的判断。若存在,r必定是答案,这边是在[l,r)中二分查找,,也是确保了mid取到l且能取不到r。

    class Solution(object): def nextGreatestLetter(self, letters, target): l, r = 0, len(letters) while l < r: mid = l + (r - l) // 2 if letters[mid] > target: r = mid else: l = mid + 1 if r == len(letters): return letters[0] return letters[r]

    69. x 的平方根

    https://leetcode-cn.com/problems/sqrtx/

    实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

    示例 1:输入: 4,输出: 2 示例 2:输入: 8,输出: 2,说明: 8 的平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

    题解

    一:

    class Solution(object): def mySqrt(self, x): """ :type x: int :rtype: int """ l, r, ans = 0, x, -1 while l <= r: mid = l + (r - l) // 2 target = mid * mid if target > x: r = mid - 1 else: ans = mid l = mid + 1 return ans

    二:这边解释一下特例0,若x=0,则不进入循环,直接返回l(l初始化为0)。这边是在(l,r]中二分查找,,也是确保了mid取不到l且能取到r。

    class Solution(object): def mySqrt(self, x): l, r = 0, x while l < r: mid = l + (r - l + 1) // 2 target = mid * mid if target == x: return mid elif target > x : r = mid - 1 else: l = mid return l

    475. 供暖器

    https://leetcode-cn.com/problems/heaters/

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

    说明:给出的房屋和供暖器的数目是非负数且不会超过 25000。给出的房屋和供暖器的位置均是非负数且不会超过10^9。只要房屋位于供暖器的半径内(包括在边缘上),它就可以得到供暖。所有供暖器都遵循你的半径标准,加热的半径也一样。 示例 1:输入: [1,2,3],[2],输出: 1。解释: 仅在位置2上有一个供暖器。如果我们将加热半径设为1,那么所有房屋就都能得到供暖。 示例 2:输入: [1,2,3,4],[1,4],输出: 1。解释: 在位置1, 4上有两个供暖器。我们需要将加热半径设为1,这样所有房屋就都能得到供暖。

    题解

    一:先排序,然后对每一个house遍历离他最近的热水器。排序时间复杂度,其中m是houses的规模,n是heaters的规模。由于排过序,后面的房屋的最近的热水器必定是当前房屋最近的热水器或者他后面的热水器,故这一部分的时间复杂度是,因为内层循环的cur要么向右移动要么不变。

    """ :type houses: List[int] :type heaters: List[int] :rtype: int """ houses, heaters = sorted(houses), sorted(heaters) if not houses or not heaters: return 0 cur = 0 res, n = 0, len(heaters) for house in houses: rec = abs(house - heaters[cur]) while cur < n: follow = cur + 1 if follow >= n: break tmp = abs(house - heaters[follow]) if tmp > rec: break else: rec = tmp cur = follow res = max(res, rec) return res

    二:时间复杂度,m和n的定义同法一。房屋与热水器的距离差,要么单调减,要么单调增,要么先减后增,即最小值要么在两端取到,要么则中间某处取到(极小点)。

    class Solution(object): def findRadius(self, houses, heaters): heaters = sorted(set(heaters)) if not houses or not heaters: return 0 res, n = 0, len(heaters) for house in houses: l, r, local_res = 1, n - 1, abs(house - heaters[0]) while l < r: mid = l + (r - l) // 2 a, am1, ap1 = (abs(house - heaters[mid]),abs(house - heaters[mid - 1]) ,abs(house - heaters[mid + 1])) if a <= ap1 and a <= am1: local_res = a break elif a > am1: r = mid else: l = mid + 1 res = max(res, min(local_res, min(abs(house - heaters[0]), abs(house - heaters[-1])))) return res

    三: 时间复杂度,m和n的定义同法一。找到距离房屋最近的热水器,由于l,r是往房屋逼近,故当不重合时,最近的要么在r,要么在l,要么在l-1。

    class Solution(object): def findRadius(self, houses, heaters): heaters = sorted(heaters) if not houses or not heaters: return 0 res, n = 0, len(heaters) for house in houses: l, r, local_res = 0, n, abs(house - heaters[0]) while l < r: mid = l + (r - l) // 2 if heaters[mid] == house: local_res = 0 break elif heaters[mid] < house: l = mid + 1 else: r = mid if local_res == 0: continue if l < n: local_res = min(local_res, abs(house - heaters[l])) if l > 0: local_res = min(local_res, abs(house - heaters[l - 1])) if r < n: local_res = min(local_res, abs(house - heaters[r])) res = max(res, local_res) return res

    287. 寻找重复数

    https://leetcode-cn.com/problems/find-the-duplicate-number/

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

    题解

    一:暴力解法,双重循环,时间复杂度,空间复杂度。

    二:哈希表,时间复杂度,空间复杂度。

    三:转自官方题解,https://leetcode-cn.com/problems/find-the-duplicate-number/solution/xun-zhao-zhong-fu-shu-by-leetcode-solution/,我们定义 cnt[i] 表示 ,nums[] 数组中小于等于 i 的数有多少个,假设我们重复的数是 target,那么 [1,target−1]里的所有数满足 cnt[i]≤i(没有重复值取等号,有重复值,必存在缺失值,若缺失值在前面则取小于,若不在前面,依旧等于),[target,n] 里的所有数满足 cnt[i]>i(因为只有一个重复的),且具有单调性。我们只要找出第一个满足 cnt[i]>i的i。

    时间复杂度:O(nlogn),其中 n 为 nums[] 数组的长度。二分查找最多需要二分 O(logn) 次,每次判断的时候需要O(n) 遍历 nums[] 数组求解小于等于 mid 的数的个数,因此总时间复杂度为 O(nlogn)。 空间复杂度:O(1)。我们只需要常数空间存放若干变量。

    class Solution(object): def findDuplicate(self, nums): n = len(nums) l, r = 1, n while l < r: # 每次用到的时候跑一遍cnt。cnt表示小于等于i的数字的个数 # 类似于cnt[i],只不过没有提前跑,而是用到的时候跑。 cnt, i = 0, l + (r - l) // 2 for num in nums: if num <= i: cnt += 1 if cnt <= i: l = i + 1 else: r = i return r if r != n else -1

    四:类似于有环链表的入口。

    class Solution(object): def findDuplicate(self, nums): slow, fast = 0, 0 while True: slow = nums[slow] fast = nums[nums[fast]] if fast == slow: break slow = 0 while True: slow = nums[slow] fast = nums[fast] if slow == fast: return slow

    1539. 第 k 个缺失的正整数

    https://leetcode-cn.com/problems/kth-missing-positive-number/

    给你一个严格升序排列 的正整数数组 arr 和一个整数 k 。请你找到这个数组里第 k 个缺失的正整数。

    示例 1:

    输入:arr = [2,3,4,7,11], k = 5,输出:9 解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,...] 。第 5 个缺失的正整数为 9 。 示例 2:输入:arr = [1,2,3,4], k = 2,输出:6 解释:缺失的正整数包括 [5,6,7,...] 。第 2 个缺失的正整数为 6 。 提示:1 <= arr.length <= 1000,1 <= arr[i] <= 1000,1 <= k <= 1000,对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j] 。

    题解

    一:直接遍历O(n)的时间复杂度。prev代表前一个数字,我们看arr[i]是否是前一个数字加1,是,则没有缺失,直接更新prev,不是有缺失数字,做处理。count代表arr[i-1]到arr[i]之间缺失的数字个数。

    class Solution(object): def findKthPositive(self, arr, k): """ :type arr: List[int] :type k: int :rtype: int """ prev, idx = 0, 0 for i, num in enumerate(arr): if arr[i] != prev + 1: count = arr[i] - prev - 1 if idx + count >= k: ans = prev + k - idx return ans else: idx += count prev = arr[i] else: prev = arr[i] if idx < k: ans = prev + k - idx return ans

    二:二分法,时间复杂度O(lgn),通常看见升序或降序等有序数组,都可以想想能否二分。这边二分我们会用arr[i] - (i + 1)是否<k,来看,因为arr[i] - (i + 1)是arr[i]之前确实的正数的个数。要单独考虑缺失值大于arr[-1]的情况。

    class Solution(object): def findKthPositive(self, arr, k): n = len(arr) if arr[-1] - n < k: # k - (arr[i] - n) + arr[-1] # arr[i] - n 缺失的个数 [1, arr[i]] # 需要补的个数k - (arr[i] - n) return k + n l, r = 0, n while l < r: mid = l + (r - l) // 2 if arr[mid] - (mid + 1) < k: l = mid + 1 else: r = mid l -= 1 # k - (arr[l] - (l + 1)) + arr[l] # arr[l] - (l + 1) 缺失的个数 [1, arr[l]] # 需要补的个数k - (arr[l] - (l + 1) return (k + l + 1)

    统一处理,这边无需考虑缺失值大于arr[-1]的情况(通过在 arr 序列的最后加入一个虚拟的值,这个值的大小为一个不会出现的非常大的数,这样就可以保证一定能找到一个大于等于 k 的 p_i。

    class Solution(object): def findKthPositive(self, arr, k): l, r = 0, len(arr) while l < r: mid = l + (r - l) // 2 x = arr[mid] if mid < len(arr) else float("inf") if x - (mid + 1) < k: l = mid + 1 else: r = mid # l 是大于等于k的第一个,往前退一个 l -= 1 # k - (arr[l] - (l + 1)) + arr[l] # arr[l] - (l + 1) 缺失的个数 [1, arr[l]] # 需要补的个数k - (arr[l] - (l + 1) return (k + l + 1)

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    Processed: 0.013, SQL: 9