O(m + n)
的算法,不满足题目的要求。
拿到题目首先想到的是归并排序“并”的过程,于是就直接写了。
#
# @lc app=leetcode.cn id=4 lang=python
#
# [4] 寻找两个有序数组的中位数
#
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
nums = list()
length1 = len(nums1)
length2 = len(nums2)
i = 0
j = 0
while i < length1 and j < length2:
n1 = nums1[i]
n2 = nums2[j]
if n1 < n2:
nums.append(n1)
i += 1
else:
nums.append(n2)
j += 1
while i < length1:
nums.append(nums1[i])
i += 1
while j < length2:
nums.append(nums2[j])
j += 1
length = len(nums)
if length % 2 == 0:
b = length / 2
a = b - 1
res = (nums[a] + nums[b]) / 2.0
else:
index = (length - 1) / 2
res = nums[index]
return res
O(log(m + n))
二分法。
class Solution(object):
def findMedianSortedArrays(self, nums1, nums2):
"""
:type nums1: List[int]
:type nums2: List[int]
:rtype: float
"""
length1 = len(nums1)
length2 = len(nums2)
if length1 > length2:
return self.findMedianSortedArrays(nums2, nums1)
left = 0
right = length1
k = (length1 + length2 + 1) // 2
while left < right:
m1 = left + (right - left) // 2
m2 = k - m1
if nums1[m1] < nums2[m2 - 1]:
left = m1 + 1
else:
right = m1
m1 = left
m2 = k - m1
if m1 > 0 and m2 > 0:
c1 = max(nums1[m1 - 1], nums2[m2 - 1])
else:
if m1 > 0:
c1 = nums1[m1 - 1]
else:
c1 = nums2[m2 - 1]
if (length1 + length2) % 2 == 1:
return c1
if m1 < length1 and m2 < length2:
c2 = min(nums1[m1], nums2[m2])
else:
if m1 < length1:
c2 = nums1[m1]
else:
c2 = nums2[m2]
return (c1 + c2) / 2.0
二分法递归写法:
思想:转变为求第 K 小数。
Tips:因为长度要分奇偶讨论,因此直接取 (m + n + 1) / 2
与 (m + n + 2) / 2
的平均值,奇偶就一样了。
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def findKthElement(arr1, arr2, k):
# 默认 2 比 1 长
length1, length2 = len(arr1), len(arr2)
if length1 > length2:
return findKthElement(arr2, arr1, k)
# 如果 arr1 没有元素,直接返回 arr2 的第 k 小
if length1 == 0:
return arr2[k - 1]
# 等于 1 时单独处理
if k == 1:
return min(arr1[0], arr2[0])
# 两者都有元素,对比 k/2 位置,但不超过数组长度
mid1 = min(k//2, length1) - 1
mid2 = min(k//2, length2) - 1
if arr1[mid1] > arr2[mid2]:
# 删除 arr 的部分
return findKthElement(arr1, arr2[mid2+1:], k - mid2 - 1)
else:
return findKthElement(arr1[mid1+1:], arr2, k - mid1 - 1)
l1, l2 = len(nums1), len(nums2)
left, right = (l1 + l2 + 1)//2, (l1 + l2 + 2)//2
return (findKthElement(nums1, nums2, left) + findKthElement(nums1, nums2, right)) / 2
先使用二分查找法找到 target 值,然后从找到 target 值的位置往左右两侧延伸,直到寻找到两侧的边界值。
class Solution(object):
def searchRange(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
length = len(nums)
left = 0
right = length - 1
res = [-1, -1]
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
left = mid
right = mid
# 左侧边界往左延伸
while left > 0 and nums[left - 1] == nums[left]:
left -= 1
# 右侧边界往右延伸
while right < length - 1 and nums[right + 1] == nums[right]:
right += 1
res[0] = left
res[1] = right
break
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return res
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
res = [-1, -1]
if len(nums) == 0:
return res
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:
right = mid
if nums[left] == target:
res[0] = left
left = 0
right = len(nums) # 只有一个数的时候需要取到右边界
while left < right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid
else:
left = mid + 1
if nums[left - 1] == target:
res[1] = left - 1
return res
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
i = 0
while i < len(nums):
if nums[i] < target:
i = i + 1
else:
break
return i
class Solution(object):
def searchInsert(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
length = len(nums)
left = 0
right = length - 1
while left <= right:
mid = (left + right) / 2
if nums[mid] > target:
right = mid - 1
elif nums[mid] < target:
left = mid + 1
else:
return mid
return left
利用分治的思想,$x^2n$ 均可以写作
递归:
class Solution:
def myPow(self, x: float, n: int) -> float:
def fast_pow(x, n):
if n == 0:
return 1
fast_res = fast_pow(x, n//2)
if n % 2 == 0:
return fast_res * fast_res
else:
return fast_res * fast_res * x
res = fast_pow(x, abs(n))
return res if n > 0 else 1/res
func myPow(x float64, n int) float64 {
if n >= 0 {
return quickMul(x, n)
}
return 1 / quickMul(x, -n)
}
func quickMul(x float64, n int) float64 {
if n == 0 {
return 1
}
if n == 1 {
return x
}
y := quickMul(x, n / 2)
if n % 2 == 0 {
return y * y
} else {
return y * y * x
}
}
二分查找,注意边界值的处理。
class Solution(object):
def mySqrt(self, x):
"""
:type x: int
:rtype: int
"""
i = 0
j = x
while i <= j:
mid = (i + j) // 2
num = mid * mid
if num > x:
if (mid-1)*(mid-1) < x:
return mid - 1
j = mid - 1
elif num < x:
if (mid + 1) * (mid + 1) > x:
return mid
i = mid + 1
else:
return mid
func mySqrt(x int) int {
left := 0
right := x
ans := 0
for left <= right {
mid := (left + right) / 2
// fmt.Println(mid)
if mid * mid <= x {
// 可能出现结果
ans = mid
left = mid + 1
} else {
right = mid - 1
}
}
return ans
}
ps:看评论有很秀的牛顿迭代法,有空研究下。
逐行进行二分查找。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
m = len(matrix)
if m == 0:
return False
n = len(matrix[0])
if n == 0:
return False
for i in range(m):
left = 0
right = n - 1
if matrix[i][right] < target:
continue
if matrix[i][left] > target:
return False
if matrix[i][right] == target or matrix[i][left] == target:
return True
while left <= right:
middle = left + (right - left) // 2
if matrix[i][middle] > target:
right = middle - 1
elif matrix[i][middle] < target:
left = middle + 1
else:
return True
return False
将矩阵拉成直线后进行二分查找。
func searchMatrix(matrix [][]int, target int) bool {
m := len(matrix)
if m == 0 {
return false
}
n := len(matrix[0])
if n == 0 {
return false
}
length := m * n
left := 0
right := length - 1
for left <= right {
middle := left + (right - left) / 2
// fmt.Printf("%d", middle)
row := middle / n
column := middle % n
num := matrix[row][column]
if num > target {
right = middle - 1
} else if num < target {
left = middle + 1
} else {
return true
}
}
return false
}
划分出有序数组进行二分查找。
当出现重复元素时,即 nums[left] == nums[mid] && nums[right] == nums[mid]
,无法判断数组哪侧有序,应从两端往中间缩短查找范围。
func search(nums []int, target int) bool {
length := len(nums)
left := 0
right := length - 1
for left <= right {
mid := (left + right) / 2
if nums[mid] == target {
return true
}
if nums[left] == nums[mid] && nums[right] == nums[mid] {
left++
right--
} else if nums[left] <= nums[mid] {
// 左侧更小
// 左侧区间是否连续
if target >= nums[left] && target < nums[mid] {
right = mid - 1
} else {
left = mid + 1
}
} else {
// 左侧更大
// 右侧区间是否连续
if target > nums[mid] && target <= nums[length - 1] {
left = mid + 1
} else {
right = mid - 1
}
}
}
return false
}
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]:
# mid 可能是最小值
right = mid
else:
# 最小值在 mid 右侧
left = mid + 1
return nums[left]
同 153,重点在于如何处理相等情况。
class Solution:
def findMin(self, nums: List[int]) -> int:
length = len(nums)
left = 0
right = length - 1
while left < right:
mid = (left + right) // 2
if nums[mid] < nums[right]:
# 在左侧
right = mid
elif nums[mid] > nums[right]:
# 在右侧
left = mid + 1
else:
right -= 1
return nums[left]
二分查找。
class Solution(object):
def findPeakElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
left = 0
right = len(nums) - 1
while left + 1 < right:
mid = (left + right) / 2
if nums[mid - 1] < nums[mid]:
# 峰值在 mid 右侧
left = mid
else:
# 峰值在 mid 左侧
right = mid
if nums[left] > nums[right]:
return left
else:
return right
一开始理解错题目了,直接查找最大值居然过了。。。
class Solution(object):
def findPeakElement(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
m = -float('inf')
m_index = -1
for i in range(len(nums)):
t = nums[i]
if t > m:
m = t
m_index = i
return m_index
顺序查找,导致 MemoryError
# The isBadVersion API is already defined for you.
# @param version, an integer
# @return a bool
# def isBadVersion(version):
class Solution(object):
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
for i in range(1, n + 1):
if isBadVersion(i) == True:
return i
# The isBadVersion API is already defined for you.
# @param version, an integer
# @return a bool
# def isBadVersion(version):
class Solution(object):
def firstBadVersion(self, n):
"""
:type n: int
:rtype: int
"""
left = 0
right = n - 1
while left <= right:
mid = (left + right) // 2
if isBadVersion(mid) == True:
right = mid - 1
else:
left = mid + 1
return left
class Solution:
def isPerfectSquare(self, num: int) -> bool:
i = 1
while i * i <= num:
if i * i == num:
return True
i += 1
return False
func isPerfectSquare(num int) bool {
i := 1
for i * i <= num {
if i * i == num {
return true
}
i++
}
return false
}
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left = 1
right = num
while left < right:
mid = (left + right) >> 1
tmp = mid * mid
if tmp < num:
left = mid + 1
elif tmp > num:
right = mid - 1
else:
return True
return left * left == num
缩小查找范围:right = num / 2
class Solution:
def isPerfectSquare(self, num: int) -> bool:
if num < 2:
return True
left = 1
right = num // 2
while left <= right:
mid = (left + right) // 2
tmp = mid * mid
if tmp == num:
return True
elif tmp < num:
left = mid + 1
else:
right = mid - 1
return False
func isPerfectSquare(num int) bool {
left := 0
right := num
for left < right {
mid := (left + right) >> 1
tmp := mid * mid
if tmp < num {
left = mid + 1
} else if tmp > num {
right = mid - 1
} else {
return true
}
}
return left * left == num
}
说是一道算法题,但感觉是道阅读理解题。
初看题目比较懵逼,题目的意思是:
- 提供了一个预先定义好的接口
guess(int num)
- 这个接口回返回 3 个结果:
-1
:代表你猜测的数字大了1
:代表你猜测的数字小了0
:代表你猜对了( •̀ᄇ• ́)ﻭ✧
本质就是一个在 1 ~ n
范围内查找某个特定数的过程,用二分来做。
# The guess API is already defined for you.
# @param num, your guess
# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0
# def guess(num):
class Solution(object):
def guessNumber(self, n):
"""
:type n: int
:rtype: int
"""
left = 1
right = n
while left <= right:
mid = left + (right - left) // 2
# 猜测结果
flag = guess(mid)
if flag == -1:
# 猜大了
right = mid - 1
elif flag == 1:
# 猜小了
left = mid + 1
else:
# 猜对了
return mid
/* The guess API is defined in the parent class GuessGame.
@param num, your guess
@return -1 if my number is lower, 1 if my number is higher, otherwise return 0
int guess(int num); */
public class Solution extends GuessGame {
public int guessNumber(int n) {
int start = 1;
int end = n;
while(start < end) {
int mid = start + (end - start) / 2;
int result = guess(mid);
if (result == 0) {
return mid;
} else if (result == -1) {
end = mid - 1;
} else if (result == 1) {
start = mid + 1;
}
}
return end;
}
}
想不到的神思路:对比分割次数,不断缩小最终答案范围。
在不考虑 m
的情况下,我们可知道最终的答案一定落在 [max(nums), sum(nums)]
区间内。因此,我们可以使用二分查找,在该范围内不断缩小最终答案的范围。
设 left = max(nums)
,right = sum(nums)
,mid = (left + right) // 2
,此时假设 mid
是已求出的答案,即所谓「最大值」。我们可以用 mid
反推数组需要分割的数量 cnt
:
cnt <= m
:分割的数量过少,说明mid
太大,right = mid
cnt > m
:分割数量过多,说明mid
太小,left = mid + 1
class Solution:
def splitArray(self, nums: List[int], m: int) -> int:
# 最终结果范围:[max(nums), sum(nums)]
left = max(nums)
right = sum(nums)
while left < right:
mid = (left + right) // 2
# 根据此 mid 反求划分数量
cnt, total = 1, 0
for num in nums:
total += num
if total > mid:
# 划分
cnt += 1
total = num
if cnt < m:
# 划分少了,mid 太大,往左边找
right = mid
elif cnt == m:
right = mid
else:
# 划分多了,mid 太小,往右边找
left = mid + 1
return left
题目要求计算供暖器的最小加热半径,即需要计算:
- 距离每间房屋最近的供暖器距离
- 这些距离中的最长距离即为最小加热半径
供暖器和房屋都在一条水平线上,因此我们可以对两者进行排序,然后使用二分查找来搜索离房屋最近的供暖器。
由二分查找得出的供暖器位置可能存在三种情况:
- 和房子的位置相同:说明可以直接供暖,半径 = 0
- 小于房子的位置:该供暖器在房子的左侧,此时求出的供暖器是距离房子最近的供暖器
- 大于房子的位置:该供暖器在房子的右侧,需要计算房子左侧的供暖器和房子的距离,两者取较小值
class Solution:
def findRadius(self, houses: List[int], heaters: List[int]) -> int:
houses.sort()
heaters.sort()
res = 0
# 每个房子被供暖需要的最小半径
for house in houses:
left = 0
right = len(heaters) - 1
while left < right:
middle = left + (right - left) // 2
if heaters[middle] < house:
left = middle + 1
else:
right = middle
# 供暖器和房子的位置相等
if heaters[left] == house:
house_res = 0
# 此时是最接近房子的供暖器
elif heaters[left] < house:
house_res = house - heaters[left]
# 供暖器不一定是最接近房子的
else:
# 第一个供暖器
if left == 0:
house_res = heaters[left] - house
else:
# 这个供暖器和前一个供暖期哪个更接近
house_res = min(heaters[left] - house, house - heaters[left - 1])
res = max(house_res, res)
return res
func findRadius(houses []int, heaters []int) int {
sort.Ints(houses)
sort.Ints(heaters)
res := 0
for _, house := range houses {
left := 0
right := len(heaters) - 1
houseRes := 0
// 二分查找
for left < right {
middle := left + (right - left) / 2
if heaters[middle] < house {
left = middle + 1
} else {
right = middle
}
}
if heaters[left] == house {
houseRes = 0
} else if heaters[left] < house { // 供暖器在左侧
houseRes = house - heaters[left]
} else { // 供暖器在右侧
if left == 0 {
houseRes = heaters[left] - house
} else {
houseRes = getMin(heaters[left] - house, house - heaters[left - 1])
}
}
res = getMax(res, houseRes)
}
return res
}
func getMin(a int, b int) int {
if a < b {
return a
} else {
return b
}
}
func getMax(a int, b int) int {
if a > b {
return a
} else {
return b
}
}
在 [0, length - k]
区间查找最终答案的最左边界,对比方法是比较 arr[mid] - x
(记作 diff_left
) 和 arr[mid + k] - x
(记作 diff_right
)的大小:
diff_left < diff_right
:说明此时arr[mid]
距离x
更近些,可以继续向左边扩展(但保留此mid
位置),right = mid
diff_left == diff_right
:此时两处一样近,可以继续向左边扩展(但保留此mid
位置),right = mid
diff_left > diff_right
:说明arr[mid + k]
距离x
更近些,向右边扩展,left = mid + 1
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
length = len(arr)
left = 0
right = length - k
# 找到距离 k 最近的左边界
while left < right:
mid = (left + right) // 2
left_diff = x - arr[mid]
right_diff = arr[mid + k] - x
print(left, right, left_diff, right_diff)
if left_diff < right_diff:
# 左边离 x 比较近,区间可以继续向左扩张
right = mid
# pass
elif left_diff == right_diff:
# 两边一样近,可以往左(取小值)
right = mid
else:
# 右边离 x 比较近,区间向右扩张
left = mid + 1
return arr[left:left + k]
- k 值落在
[0, max(nums) - min(nums)]
中,此为二分查找范围 - 以此范围内的值查找小于该值的距离对数量
class Solution:
def smallestDistancePair(self, nums: List[int], k: int) -> int:
nums.sort()
def get_mid_count(target):
# 双指针
i, count = 0, 0
for j in range(1, len(nums)):
while nums[j] - nums[i] > target:
i += 1
count += j - i
return count
# 第 k 小距离在 [left, right] 之间
left = 0
right = nums[-1] - nums[0]
while left < right:
mid = (left + right) // 2
# 计算小于等于 mid 的数量
count = get_mid_count(mid)
if count >= k:
# 如果数量大于 k,说明 mid 大了或就是结果
right = mid
else:
# 如果数量小于 k,说明 mid 小了
left = mid + 1
return left
给你一个排序后的字符列表,和一个目标字母,要查找比这个字母大的最小字母,很容易就想到二分查找。
因为「在比较时,字母是依序循环出现的」,所以我们可以先处理特殊情况:当我们要查找的字母大于等于列表的最后一个字母时,直接返回列表第一字母。
下面说说二分查找。
首先定义好模板:
left = 0
right = length
while left < right:
mid = (left + rigt) // 2
letter = letters[mid]
# 其他逻辑……
此处我们取到的中间值 letter
与 target
存在三种大小关系:
letter == target
:两者相等。此时说明我们已经找到了目标字母,但我们要找的是比目标字母大的最小字母,所以我们需要把查找范围变成mid
右侧,继续向更大的字母范围处查找,即left = mid + 1
letter < target
:当前字母小于目标字母,说明目标值在mid
右侧,同上left = mid + 1
letter > target
:当前字母大于目标字母。此时当前字母可能就是我们要找的最终答案了,但也有可能答案在左侧范围,所以right = mid
,保留当前字母位置,且继续向左侧查找
class Solution:
def nextGreatestLetter(self, letters: List[str], target: str) -> str:
length = len(letters)
# 循环的特殊情况
if letters[length - 1] <= target:
return letters[0]
left = 0
right = length - 1
while left < right:
mid = (left + right) // 2
letter = letters[mid]
if letter == target:
# 等于目标值,往右找
left = mid + 1
elif letter < target:
# 比目标值小,往右找
left = mid + 1
else:
# 比目标值大,可能就是要找的数!
right = mid
return letters[left]
核心思想二分法:
- 通过二分法寻找峰值
- 二分法在峰值左侧寻找目标
- 如果目标不在左侧,使用二分法在峰值右侧寻找目标
# """
# This is MountainArray's API interface.
# You should not implement it, or speculate about its implementation
# """
#class MountainArray:
# def get(self, index: int) -> int:
# def length(self) -> int:
class Solution:
def findInMountainArray(self, target: int, mountain_arr: 'MountainArray') -> int:
length = mountain_arr.length()
# print(length)
# 找到峰值
left = 0
right = length - 1
while left < right:
mid = (left + right) // 2
if mountain_arr.get(mid + 1) > mountain_arr.get(mid):
# 峰值在右侧
left = mid + 1
else:
right = mid
# 峰值
peak = left
# 左侧二分查找
left = 0
right = peak
while left <= right:
mid = (left + right) // 2
cur = mountain_arr.get(mid)
if cur == target:
return mid
elif cur > target:
right = mid - 1
else:
left = mid + 1
# 右边二分查找:递减数组
left = peak + 1
right = length - 1
while left <= right:
mid = (left + right) // 2
cur = mountain_arr.get(mid)
if cur == target:
return mid
elif cur > target:
left = mid + 1
else:
right = mid - 1
return -1
简单得说,就是找到一个正整数 x
,把 nums
中的数依次除以该数,相加之和 total
需要小于阈值 threshold
,且要求 x
最小。可知当 total
越接近 threshold
时,x
越小。
因为数据量的原因(1 <= nums.length <= 5 * 10^4),我们不能通过暴力破解枚举所有可能的 x
,可以想到应当使用二分查找,那么二分查找的区间如何定义呢?
左侧值 left
选取最小正整数 1,而右侧值 right
可以选择数组 nums
中的最大数 max(nums)
。当 x
取值为 max(nums)
时,结果和 total
等于数组长度,若此时 x
再继续增加,total
则始终保持不变。因此右侧区间取值 max(nums)
,再取更大的值则没有意义。
func smallestDivisor(nums []int, threshold int) int {
// 寻找最大数
maxNum := 1
for _, num := range nums {
if num > maxNum {
maxNum = num
}
}
left := 1
right := maxNum
ans := 0
for left <= right {
mid := (left + right) / 2
// 计算总数
total := 0
for _, num := range nums {
total += num / mid
if num % mid > 0 {
total += 1
}
}
if total <= threshold {
// 找到满足条件的 mid 了,但还希望能找到更小的:往左边找
right = mid - 1
ans = mid
} else {
// total 太大了,应该要提高 mid:往右边找
left = mid + 1
}
}
return ans
}