二分搜索
在有序数组中查找指定值。一般二分搜索需要注意两个点:
- 循环条件是l<r 还是 l<= r;
- 更新r值时,是r=mid还是r= mid-1;
- 具体问题具体分析。
#二分搜索源代码
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
- 寻找峰值
- 类似于[0,1,2,3,2,1]这种单峰数组,返回峰值对应index;
- 类似于[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:
- 若num[l] + num[r] == target,则返回[l,r];
- 若num[l] + num[r] < target,则r-=1;
- 否则, 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遍历。注意为了提升速度:
- 先对数组排序,然后按照双指针有序2sum来做;
- 为了防止重复元素,循环时需要判断跳过重复元素。(不能为了简单直接在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一样,只不过:
- 时刻更新最接近target的数;
- 不需要判断重复元素。
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,这样所有房屋就都能得到供暖。
思路:
- 对于每个房屋,要么用前面的暖气,要么用后面的,二者取近的,得到距离;
- 对于所有的房屋,选择最大的上述距离。
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