这里写目录标题
-
- 704-二分查找
- 4-寻找两个正序数组的中位数
- 300-最长递增子序列
- 69-x 的平方根
- 74-搜索二维矩阵
- 34-在排序数组中查找元素的第一个和最后一个位置【剑指 Offer 53 - I. 在排序数组中查找数字 I】
- 354-俄罗斯套娃信封问题
- 35-搜索插入位置【剑指 Offer II 068. 查找插入位置】
- 454-四数相加 II
- 887-鸡蛋掉落
- 剑指 Offer 04. 二维数组中的查找【240-搜索二维矩阵 II 】
- 222-完全二叉树的节点个数
- 718-最长重复子数组
- 剑指 Offer 53 - II-0~n-1中缺失的数字
- 50-Pow(x, n)
- 162. 寻找峰值
- 爱吃香蕉的珂珂
- 旋转数组
704-二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
提示:
- 你可以假设 nums 中的所有元素是不重复的。
- n 将在 [1, 10000]之间。
- nums 的每个元素都将在 [-9999, 9999]之间。
二分查找是一种基于比较目标值和数组中间元素的教科书式算法。
- 如果目标值等于中间元素,则找到目标值。
- 如果目标值较小,继续在左侧搜索。
- 如果目标值较大,则继续在右侧搜索。
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] > target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
};
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
pivot = left + (right - left) // 2
if nums[pivot] == target:
return pivot
if target < nums[pivot]:
right = pivot - 1
else:
left = pivot + 1
return -1
4-寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
提示:
- nums1.length == m
- nums2.length == n
- 0 <= m <= 1000
- 0 <= n <= 1000
- 1 <= m + n <= 2000
- -106 <= nums1[i], nums2[i] <= 106
方法一:二分查找【时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))】
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
def getKthElement(k):
"""
- 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
- 这里的 "/" 表示整除
- nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
- nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
- 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
- 这样 pivot 本身最大也只能是第 k-1 小的元素
- 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
- 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
- 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
"""
index1, index2 = 0, 0
while True:
# 特殊情况
if index1 == m:
return nums2[index2 + k - 1]
if index2 == n:
return nums1[index1 + k - 1]
if k == 1:
return min(nums1[index1], nums2[index2])
# 正常情况
newIndex1 = min(index1 + k // 2 - 1, m - 1)
newIndex2 = min(index2 + k // 2 - 1, n - 1)
pivot1, pivot2 = nums1[newIndex1], nums2[newIndex2]
if pivot1 <= pivot2:
k -= newIndex1 - index1 + 1
index1 = newIndex1 + 1
else:
k -= newIndex2 - index2 + 1
index2 = newIndex2 + 1
m, n = len(nums1), len(nums2)
totalLength = m + n
if totalLength % 2 == 1:
return getKthElement((totalLength + 1) // 2)
else:
return (getKthElement(totalLength // 2) + getKthElement(totalLength // 2 + 1)) / 2
方法二:
一般看到O(log())级别的,就先想二分,分而治之的那些思想。比如归并排序,快排……
本题主要处理好边界问题,相对于思想,边界处理和代码技巧更重要
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
n1, n2 = len(nums1), len(nums2)
def get_kth_element(k: int) -> int:
i1, i2 = 0, 0
while k != 0:
if i1 == n1:
return nums2[i2 + k - 1]
if i2 == n2:
return nums1[i1 + k - 1]
if k == 1: #1//2 = 0 所有也要判断一下
return min(nums1[i1], nums2[i2])
new_i1 = min(i1 + k//2 - 1, n1 - 1) #每个数组贡献 k//2
new_i2 = min(i2 + k//2 - 1, n2 - 1)
pivot_1, pivot_2 = nums1[new_i1], nums2[new_i2]
if pivot_1 <= pivot_2: #把小的那段扔掉
k -= (new_i1 - i1 + 1) #做好index的更新
i1 = new_i1 + 1
else:
k -= (new_i2 - i2 + 1)
i2 = new_i2 + 1
n = n1 + n2
if n % 2 == 1:
return get_kth_element( (n+1) // 2 ) # 0 1 2 3 4 n=5 取第3个
else:
return ( get_kth_element( n//2 ) + get_kth_element( (n+2)//2 ) ) / 2.0 # 0 1 2 3 n=4 取第2个,第3个的aver
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
len1, len2 = len(nums1), len(nums2)
left, right, half_len = 0, len1, (len1 + len2 + 1) // 2
mid1 = (left + right) // 2
mid2 = half_len - mid1
while left < right:
if mid1 < len1 and nums2[mid2-1] > nums1[mid1]:
left = mid1 + 1
else:
right = mid1
mid1 = (left + right) // 2
mid2 = half_len - mid1
if mid1 == 0:
max_of_left = nums2[mid2-1]
elif mid2 == 0:
max_of_left = nums1[mid1-1]
else:
max_of_left = max(nums1[mid1-1], nums2[mid2-1])
if (len1 + len2) % 2 == 1:
return max_of_left
if mid1 == len1:
min_of_right = nums2[mid2]
elif mid2 == len2:
min_of_right = nums1[mid1]
else:
min_of_right = min(nums1[mid1], nums2[mid2])
return (max_of_left + min_of_right) / 2
方法四:
- 将两个列表整合在一起
- 对新列表进行排序
- 判断列表长度,如果长度是奇数,取中间数值,如果是偶数,取中间两个数值平均数
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
list_new = nums1 + nums2
list_new.sort()
if len(list_new) % 2 == 0:
return (list_new[len(list_new)//2]+list_new[len(list_new)//2 - 1])/2
else:
return list_new[len(list_new)//2]
方法五:
class Solution:
#这题很自然地想到归并排序,再取中间数,但是是nlogn的复杂度,题目要求logn
#所以要用二分法来巧妙地进一步降低时间复杂度
#思想就是利用总体中位数的性质和左右中位数之间的关系来把所有的数先分成两堆,然后再在两堆的边界返回答案
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
m = len(nums1)
n = len(nums2)
# 让nums2成为更长的那一个数组
if m>n:
nums1,nums2,m,n = nums2,nums1,n,m
# 如果两个都为空的异常处理
if n == 0:
raise ValueError
# nums1中index在lmid左边的都被分到左堆,nums2中rmid左边的都被分到左堆
left,right = 0,m
# 二分答案
while(left<=right):
lmid = left + (right-left)//2
# 左堆最大的只有可能是nums1[lmid-1],nums2[rmid-1]
# 右堆最小只有可能是nums1[lmid],nums2[rmid]
# 让左右堆大致相等需要满足的条件是lmid+rmid = m-lmid+n-rmid 即 rmid = (m+n-2lmid)//2
# 为什么是大致呢?因为有总数为奇数的情况,这里用向下取整数操作,所以如果是奇数,右堆会多1
rmid = (m+n-2*lmid)//2
# 前面的判断条件只是为了保证不会index out of range
if(lmid>0 and nums1[lmid-1] > nums2[rmid]):
# lmid太大了,这是里精确查找,不是左闭右开,而是双闭区间,所以直接移动一位
right = lmid-1
elif(lmid<m and nums2[rmid-1] > nums1[lmid]):
left = lmid+1
# 满足条件
else:
# 边界情况处理,都是为了不out of index
# 依次得到左堆最大和右堆最小
if(lmid == m):
minright = nums2[rmid]
elif(rmid == n):
minright = nums1[lmid]
else:
minright = min(nums1[lmid],nums2[rmid])
if(lmid == 0):
maxleft = nums2[rmid-1]
elif(rmid == 0):
maxleft = nums1[lmid-1]
else:
maxleft = max(nums1[lmid-1],nums2[rmid-1])
# 前面也提过,因为取中间的时候用的是向下取整,所以如果总数是奇数的话,
# 应该是右边个数多一些,边界的minright就是中位数
if((m+n)%2) == 1:
return minright
# 否则我们在两个值中间做个平均
return (maxleft + minright)/2
300-最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
- 1 <= nums.length <= 2500
- − 1 0 4 < = n u m s [ i ] < = 1 0 4 -10^4 <= nums[i] <= 10^4 −104<=nums[i]<=104
进阶:
- 你可以设计时间复杂度为 O(n2) 的解决方案吗?
- 你能将算法的时间复杂度降低到 O(n log(n)) 吗?
方法一:动态规划【 O ( n 2 ) O(n^2) O(n2),其中 n 为数组 nums 的长度】
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = []
for i in range(len(nums)):
dp.append(1)
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
方法二:贪心 + 二分查找【 O ( n l o g n ) O(nlogn) O(nlogn),其中 n 为数组 nums 的长度】
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
d = []
for n in nums:
if not d or n > d[-1]:
d.append(n)
else:
l, r = 0, len(d) - 1
loc = r
while l <= r:
mid = (l + r) // 2
if d[mid] >= n:
loc = mid
r = mid - 1
else:
l = mid + 1
d[loc] = n
return len(d)
69-x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
由于返回类型是整数,小数部分将被舍去。
本题是一道常见的面试题,面试官一般会要求面试者在不使用 x \sqrt{x} x 函数的情况下,得到 x x x 的平方根的整数部分。一般的思路会有以下几种:
- 通过其它的数学函数代替平方根函数得到精确结果,取整数部分作为答案;
- 通过数学方法得到近似结果,直接作为答案。
https://leetcode-cn.com/problems/sqrtx/solution/x-de-ping-fang-gen-by-leetcode-solution/
方法一:袖珍计算器算法【 O ( 1 ) O(1) O(1)】
class Solution:
def mySqrt(self, x: int) -> int:
if x == 0:
return 0
ans = int(math.exp(0.5 * math.log(x)))
return ans + 1 if (ans + 1) ** 2 <= x else ans
方法二:二分查找【时间复杂度: O ( l o g x ) O(logx) O(logx),即为二分查找需要的次数。】
class Solution {
public:
int mySqrt(int x) {
int left = 0;
int right = x;
int result = -1;
while(left <= right){
long mid = left + (right - left) / 2;
if(mid * mid == x){
return mid;
}else if(mid * mid < x){
result = mid;
left = mid + 1;
}else{
right = mid - 1;
}
}
return result;
}
};
class Solution:
def mySqrt(self, x: int) -> int:
left, right = 0, x
result = -1
while left <= right:
mid = left + (right - left) // 2
if mid*mid == x:
return mid
elif mid*mid < x:
left = mid + 1
result = mid
else:
right = mid - 1
return result
class Solution:
def mySqrt(self, x: int) -> int:
l, r, ans = 0, x, -1
while l <= r:
mid = (l + r) // 2
if mid * mid <= x:
ans = mid
l = mid + 1
else:
r = mid - 1
return ans
74-搜索二维矩阵
编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
- 每行中的整数从左到右按升序排列。
- 每行的第一个整数大于前一行的最后一个整数。
示例 1:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
示例 2:
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false
提示:
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 100
- − 1 0 4 < = m a t r i x [ i ] [ j ] , t a r g e t < = 1 0 4 -10^4 <= matrix[i][j], target <= 10^4 −104<=matrix[i][j],target<=104
凡是能通过暴力AC的题都不能算中等题…这道题分别用暴力、贪心、二分完成
解题1.双层for循环:
class Solution:
def searchMatrix(self, matrix, target):
for i in matrix:
for j in i: