今日碎碎念:从今天开始,正式开启连续刷题的日子啦!!坚持就能胜利!
开始今日的刷题内容:
704. 二分查找
题目链接:704.二分查找
文章讲解:代码随想录数组2.二分查找
视频链接:手把手教你手撕正确的二分法|二分查找法|二分搜索法
状态:没有思路,做不出来
题目:
给定一个 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
思路:
根据题意,数组为有序的升序整型数组,并且没有重复的元素,因此可以考虑使用二分法来进行求解。
算法:二分法
1. 定义:
在一组有序数组中查找target目标值,如果target在这组有序数组中,则返回target在数组中的下标。
算法思路如其名,就是将一组数组依次一分为两个区间,然后逐个比较其边界条件来最终确定目标值的所在区间。
2. 注意事项
-
二分法的使用前提是数组有序并且没有重复的元素。只有这样,使用二分法返回的下标才具有唯一性。
-
尝试用左闭右闭、左闭右开两种方式来定义查询区间。
-
一定要先确定好查找区间,避免在代码编写过程中随意改变(循环不变量原则)
-
关键:根据区间的定义来写代码
题解:
-
方法1:使用左闭右闭的方式来定义查找区间
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums)-1 # 确定好搜索的边界,这里是一个左闭右闭的搜索区间。确定好target是在这个区间内的
while left <= right:
middle = (left + right) // 2
if nums[middle] > target:
right = middle - 1 #target在左边的区间,所以要更新右边界。由于是左闭右闭的区间,middle已经属于上一个区间内了,所以减一后才是新的左闭右闭区间
elif nums[middle] < target:
left = middle + 1
else:
return middle
return -1 # 在有序数组中没有这样的值,返回-1
- 方法2:使用左闭右开的方式来定义查找区间(运行速度很慢,不推荐)
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) # 确定好搜索的边界,这里是一个左闭右开的搜索区间,确定好target在搜索区间范围内。因为不包含右边的值,所以不需要减一来获取实际的数值位置
while left < right: # 这里的定义要符合左闭右开区间的定义。[1,1),如果取等号,则不符合区间的定义。
middle = (left + right) // 2
if nums[middle] > target:
right = middle #target在左边的区间,所以要更新右边界。由于是左闭右闭的区间,middle已经属于上一个区间内了,所以减一后才是新的左闭右闭区间
elif nums[middle] < target:
left = middle + 1 # 更新左边界,由于左边是闭区间,则需要加1才能不包含上一区间
else:
return middle
return -1 # 在有序数组中没有这样的值,返回-1
27. 移除元素
题目链接:27.移除元素
文章讲解:代码随想录数组3.移除元素
视频链接:数组中移除元素
状态:没有思路,做不出来
题目:
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1)
额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例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。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
思路:
根据数据结构的知识,数组这个数据类型在系统中的存储地址是连续的,不能单独删除只能进行覆盖。同时根据题意,需要在原地移除所有数值,并不能使用额外的数组空间,即仅使用O(1)的空间来使用数组。因此可以有两种方法来进行求解:①暴力解法,时间复杂度O(n^2);②双指针法(快慢指针法)
那什么是快慢指针法?
算法:双指针法
(1)简介:
在遍历元素的过程中,不是使用单个指针进行访问(暴力解法),而是使用两个指针进行访问。定义一个快指针和一个慢指针分别指向不同的元素,通过对比指向的元素来完成任务。
-
指针名称定义:
-
对撞指针:两个指针的方向相反
-
快慢指针:两个指针方向相同
-
分离双指针:两个指针分别属于不同的数组/链表
-
-
不同问题选用不同的指针:
-
区间搜索问题:滑动窗口。两个指针指向同一数组,遍历方向相同且不会相交,则称为滑动窗口。
-
搜索有序数组(需要提前进行排序):对撞指针。两个指针指向同一数组,遍历方向相反。
-
(2)对撞指针
两个指针left、right分别指向序列第一个元素和最后一个元素,left指针不断递增,right指针不断递减,直到两个指针的值相等(left == right),或者满足其他要求的特殊条件为止。
1)对撞指针的求解步骤
-
使用两个指针left、right。left指向序列的第一个元素,即:left = 0, right指向序列的最后一个元素,即:right = len(nums) - 1。
-
在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,left += 1。当满足另外一定条件时,将右指针左移,right -= 1。
-
直到两指针相撞(即left == right),或者满足其他要求的特殊条件时,跳出循环
2) 对撞指针适用范围
对撞指针一般用来解决有序数组或者字符串问题:
-
查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题。
-
字符串反转问题:反转字符串、回文数、颠倒二进制等问题。
(3)快慢指针
指两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为快指针(fast),移动慢的指针被称为慢指针(slow)。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。
1) 快慢指针的求解步骤
-
使用两个指针slow、fast。slow一般指向序列第一个元素,即:slow = 0,fast一般指向序列的第二个元素,即:fast = 1
-
在循环体中将左右指针向左移动。当满足一定条件时,将慢指针右移,即 slow += 1。当满足另外一定条件时(也可能不需要满足条件),将快慢指针右移,即fast += 1。
-
到快指针移动到数组尾端(即fast == len(nums) - 1),或者两指针相交,或者满足其他的特殊条件时跳出循环体
2)快慢指针的适用范围
快慢指针一般用于处理数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。
(4)分离双指针
两个指针分别属于不同的数组/链表,两个指针分别在两个数组/链表中移动
1)分离双指针的求解步骤
-
使用两个指针left_1、left_2。left_1指向第一个数组/链表的第一个元素,即:left_1 = 0,left_2指向第二个数组/链表的第一个元素,即:left_2 = 0。
-
当满足一定条件时,两个指针同时右移,即left_1 += 1、left_2 += 1。
-
当满足另外一定条件时,将left_1指针右移,即left_1 += 1。
-
当满足其他一定条件时,将left_2指针右移,即left_2 += 1。
-
当其中一个数组/链表遍历完时,或者满足其他特殊条件时,跳出循环体。
2) 分离双指针适用范围
一般用于处理有序数组合并、求交集、并集问题。
题解:
-
双指针法(快慢指针)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 定义快慢指针
fast, slow = 0, 0 #两个指针都从零开始进行遍历
size = len(nums) #在这个范围内是这一组数组的长度,不包含边界
while fast < size: #不加等于是预防数据越界
# slow 用来收集不等于val的值,如果fast对应值不等于val,则把他与slow替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
-
暴力解法:(速度比上一个方法快很多)
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i, l = 0, len(nums) #从第一个位置开始遍历,遍历的长度是l=数组的长度
while i < l:
if nums[i] == val: #如果找到了目标值的节点
for j in range(i+1, l): # 移除该元素,并将后面元素向前平移
nums[j - 1] = nums[j]
l -= 1
i -= 1
i += 1
return l