二分法查找
35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
l,r=0,len(nums)-1
while l<=r:
mid=(l+r)>>1
if nums[mid]>target:
r=mid-1
elif nums[mid]<target:
l=mid+1
else:
return mid
//分别处理如下四种情况
// 目标值在数组所有元素之前 [0,0)
// 目标值等于数组中某一个元素 return middle
// 目标值插入数组中的位置 [left, right) ,return right 即可
// 目标值在数组所有元素之后的情况 [left, right),return right 即可
return r+1
思路:我们从数组的最左端和最右端开始遍历,每次取区间为[left,right]的中间值nums[mid],然后将mid位置的数字与目标数字比较,不断缩减区间[left,right]的大小,最后返回结果。
时间复杂度:O(logn)
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
class Solution:
def search(self, nums: List[int], target: int) -> int:
if len(nums)==1:
return 0 if nums[0]==target else -1
l,r=0,len(nums)-1
while l<=r:
mid=(l+r)>>1
if nums[mid]>target:
r=mid-1
elif nums[mid]<target:
l=mid+1
else:
return mid
return -1
这道题相对来说就是搜索插入位置的简化表,其实就是搜索数组中是否有我们需要找到的目标数字target,没有就返回-1,有就返回对应的索引index
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
slow=0
for fast in nums:
if fast!=val:
nums[slow]=fast
slow+=1
return slow
这道题我们可以用一个巧妙的双指针法来解决,我们使用fast’指针来遍历数组,slow指针用来指向数组中与val值相等的数字的位置,当fast指针遍历的数字不等于val值的时候,就将slow指针处的数字替换成fast指针处的值,相当于我们把数组中数字为val的数字覆盖了,遍历完数组时,slow指针指向的就是最后一个值不等于val的数字,我们直接返回即可。
比较含退格的字符串
给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
示例 1:
输入:S = “ab#c”, T = “ad#c”
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:S = “ab##”, T = “c#d#”
输出:true
解释:S 和 T 都会变成 “”。
示例 3:
输入:S = “a##c”, T = “#a#c”
输出:true
解释:S 和 T 都会变成 “c”。
示例 4:
输入:S = “a#c”, T = “b”
输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。
提示:
1 <= S.length <= 200
1 <= T.length <= 200
S 和 T 只含有小写字母以及字符 ‘#’。
class Solution:
def backspaceCompare(self, s: str, t: str) -> bool:
i,j=len(s)-1,len(t)-1
count_s,count_t=0,0
while i >= 0 or j >= 0:
# s字符串的第一次遍历
while i >= 0:
if s[i] == "#":
count_s += 1
# 指针往前推一个
i -= 1
elif count_s>0:
i -= 1
count_s -= 1
else:
break
# t同上
while j >= 0:
if t[j] == "#":
count_t += 1
# 指针往前推一个
j -= 1
elif count_t>0:
# 代表有退格符#
j -= 1
count_t -= 1
else:
break
if i >= 0 and j >= 0:
# 不相等返回False
if s[i] != t[j]:
return False
elif i>=0 or j>=0:
return False
# 相等就不管,看下一个字符
j -= 1
i -= 1
return True
思路大概为:首先我们要从后往前遍历,因为退格符删除的是前面的字符,反序遍历才能确保我们遍历过的字符都是相等的,那么在我们遍历字符的时候又分为三种情况:
1.当遍历的字符是#,代表我们可以跳过一个字符,给我们的跳跃量count加1
2.当遍历的字符不是#,我们首先看count是否>0,大于0就当作跳过这个字符,不比较
3.当遍历的字符不是#而且count=0,此时退出循环,对另一个字符串也进行一次遍历,当另一个数组也退出循环后,比较两个字符串当前位置的字符是否相等。
有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
i,j=0,len(nums)-1
n=len(nums)
res=[0]*n
while i<=j:
if nums[i]*nums[i]>=nums[j]*nums[j]:
n-=1
res[n]=nums[i]*nums[i]
i+=1
else:
n-=1
res[n]=nums[j]*nums[j]
j-=1
return res
这道题的思路其实很容易想到,就是对数组进行排序,不过排序的标准是平方值的大小,也就是说负数也可能比正数大,那么题目首先给出了一个升序数组,我们就初始化一个结果集数组存放结果,然后使用双指针法,从最左和最右端同时开始比较
1.当左边的平方值大于等于右边的平方时,应该将左边的平方加入res
2.否则将右端的平方加入res
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105
进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
res=float('inf')
sublength=0
for i in range(len(nums)):
sum1=0
for j in range(i,len(nums)):
sum1+=nums[j]
if sum1>=target:
sublength=j-i+1
res=sublength if res>sublength else res
break
return 0 if res==float('inf') else res
暴力法双重for循环——时间复杂度:O(n**2)
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
res=float('inf')
sublength=0
sum1=0
i=0
for j in range(len(nums)):
sum1+=nums[j]
#注意我们这里的while相当于一个判定条件的意思,可以近似看作if
while sum1>=target:
sublength=j-i+1
res=sublength if sublength<res else res
sum1-=nums[i]
i+=1
return 0 if res==float('inf') else res
//这里要判断res是否有发生变化
//因为会出现一种情况就是整个数组的和都比target小,此时就应该返回0
//代表找不到和为target的子数组
滑动窗口法——时间复杂度O(n)
暴力法其实并不难,就是双重循环,找到目标和为target的子数组,然后更新最小长度即可,但是双重循环的时间复杂度较高,我们可不可以用一种更好的方法去解决呢?
其实就是用滑动窗口法
滑动窗口和双指针法非常类似,只需要遍历一次数组,**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)**那么具体的思路是如何呢?
我们还是用题目中的例子
我们设置i指针指向数组的开头,然后从头遍历数组,每次都累加遍历的数组值和target比较,那么这时候有两种情况:
1.累加和sum<target,继续遍历(相当于扩大窗口)
2.累加和sum>=target,这时候滑动窗口的精妙之处就体现出来了
1)我们首先要记录此时的窗口大小——j-i+1
2)然后将其与res比较,保存最小的值(题目找出最小长度数组在这一步完成)
3)然后我们要减去nums[i]的值,相当于把i向前滑动(滑动窗口的精妙之处)
3.最后返回res即可(注意这里要判断res是否有发生过改变)