面试题57. 和为s的两个数字
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:
输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
解答:
最开始的思路 从头开始做两层遍历,一旦发现即return。
时间复杂度为O(n^2) TLE
优化:
观察数组,如果当数字小于最小值就不存在了;当数字大于最大时依然有可能存在。
1 设置左右边界点,相加和看是否大于target。
2 大于时 r-=1, 小于时l+=1,等于时返回数组即可。
时间复杂度:O(n)
可能存在的优化:针对中间数字,可以设置二分查找确定右边界。
时间复杂度O(n)+O(log n) 还是O(n),但是针对特殊情况会有更好的效果。
代码:
import bisect
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
if nums[0]>=target:
return []
r = bisect.bisect_left(nums,target)-1
l = 0
while l<r:
if nums[l]+nums[r] == target:
return [nums[l],nums[r]]
elif nums[l]+nums[r]>target:
r-=1
else:
l+=1
return []
关注点:
滑动窗口的滑动条件变化。根据现在值的大小变化确定滑动的节奏。
面试题57 - II. 和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
示例 1:
输入:target = 9
输出:[[2,3,4],[4,5]]
示例 2:
输入:target = 15
输出:[[1,2,3,4,5],[4,5,6],[7,8]]
限制:
1 <= target <= 10^5
解答:
1 观察发现数组出现的方式 一般是中间值左右分布。
2 右分布只有一种情况 因为再往右肯定更大。
3 左分布存在多种情况,可以在左边做滑动窗口,起点均为中值,如果小于target,则l-=1,如果大于target,则r-=1。满足条件l<=r, l>=1。
4 最后结果没问题,但是题中需要返回从小到大的分布。可以在sorted一次或者生成时即进行排列。
优化1:
滑动窗口均从1开始,小于时则 r+=1 , 大于时则 l+=1.
边界l <=mid 即可, 满足从小到大排列。
优化2:
以上每一步需要从新生成数组进行计算,使用了额外的空间(事实上都需要添加数组到res,对比也算不上额外空间)以及算力(每次sum数组)。
可以改为cur_sum,r+=1时,加上新的值; l+=1时,提前减去旧的值。减少了sum的时间。
代码:
class Solution:###improved!!!!!!!!
def findContinuousSequence(self, target: int):
mid = target // 2
res = []
l = 1
r = 1
cur_sum = 1
while l <= r and l <= mid:
#print(l,r,cur_sum)
if cur_sum == target:
res.append([i for i in range(l,r+1)])
cur_sum -= l
l += 1
elif cur_sum < target:
r += 1
cur_sum += r
else:
cur_sum -= l
l += 1
return res
总结:
滑动窗口的使用:
1 明确需求: 一般对 排序或者存在规律的数组使用(因为在滑动时 存在规律)
2 明确滑动时的变化:针对滑动的变化,作出不同的判定。
3 终止点:一般是窗口左右相遇 或者达到题意条件