📝704(Easy)
题目链接: - LeetCode
文章讲解:代码随想录
视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili
👩💻思路:binary search
用二分查找的方法可以巧妙地加快寻找到target的时间因为我们不再需要遍历整个数组去查找。方法分两种:左右闭合和左闭右开,但是大致思路是一致的。首先,我们要初始化的左端和右端的位置(index)计算出mid;随着范围的逐步缩小,确定target在哪,如果没找到则返回-1。
- 左右闭合:"while left<=right" 这是因为区间闭合,left可以等于right。因此,right的初始值设置为数组中最后一位 "right = len(nums) - 1"。将mid与target进行比较,如果target大,那么左端更新为当前的mid向后顺延一位的value,反之,右端更新为mid向前顺延一位的value。
- 左闭右开:"while left < right" 为了使其边界合法,右边打开不包含,所以left不可以等于right。right的初始值设置为数组中最后一位的后面 "right = len(nums)"。也就是说,左端不可能等于一个根本不存在与数组里的数。将mid与target进行比较,如果target比mid小,那么新的right变为当前的mid,反之,将左端更新为当前的mid向后顺延一位的value。
❌易错点/难点总结:
1.避免整数溢出,mid = left + (right - left) // 2 很重要。不可以写成mid = right//2,他只适用于left=0的情况。因为我们要考虑到left的更新。此外,要记住//的优先级大于+。我找到一个例子,如下:
3.分不太清什么时候该用if,elif,else等等
- `if` 用于第一次检查的条件。
- `elif` 用于后续检查的条件,每个 `elif` 都是一个新的独立条件。
- `else` 用于捕获所有前面条件都不满足的情况,作为一种默认处理机制。
- tips:如果你有多个条件需要检查,你可以使用多个连续的
elif
语句来处理所有可能的情况。每个elif
语句都会在前面的if
或elif
条件不满足时进行检查。如果最后还有未处理的情况,可以使用else
来捕获所有剩余的可能性。请看以下的小例子:4.更新left和right的值的时候是based on mid+1或-1而不是根据他们本身值的变动,在这一点上可以谨记抛弃原来有关左右端的value。
📊视频讲解要点总结:
- 大家面临选择困难的两个方面:1.该用while(left <= right)作为判断条件还是while(left < right)呢?;2.当我们的mid>target时,右端应该怎么更新,是设置为middle还是middle - 1呢?
- 如何定义区间:这个问题和上一个紧密相连。也就是说,只有确定了区间定义,才能解答我们针对上个问题发出的疑问。一般,我们有两种选择,分别是左右闭合和左合右开。我们要先选择用哪个区间解题,区间的选择会影响:1.right初始化的value;2.边界的处理;3.while后的判断条件。
- 左右闭合:在这种情况下,左右顶端的值都是包含在里面所以while(left <= right)。举个例子,当m大于target的时候,我们需要缩小右端,应该选择right=m-1。如果设置right = m的话,已经判断当前这个m所在的位置大于target,所以他肯定不是我们要找的位置,因此,要缩小一位形成新的区间,应该把这个旧的过滤掉了。
- 左合右开:右端不包含所以左右相等没有任何意义,因此while(left < right)。假设m大于target,我们需要缩小右端的范围,选择right = m 。因为right本来就不包含原本的m(开区间过滤掉了),所以r=m就能实现一个新的区间范围。注意,如果错误用了m-1,我们也许会错漏一个element。
📸解题代码:
1.左合右开
class Solution(object): def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """ left = 0 right = len(nums) while left < right: mid = left + (right-left)//2 if nums[mid] == target: return mid elif nums[mid] > target: right = mid elif nums[mid] < target: left = mid + 1 return -1
2.左右闭合
class Solution(object): def search(self, nums, target): """ :type nums: List[int] :type target: int :rtype: int """ left = 0 right = len(nums)-1 #这里用<=是因为这是一个闭合区间(由于right = len(nums)-1决定的 while left <= right: mid = left + (right - left)//2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid - 1 return -1
📝27(Easy)
题目建议: 暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。 双指针法 是本题的精髓,今日需要掌握,至于拓展题目可以先不看。
题目链接:. - 力扣(LeetCode)
文章讲解:代码随想录
👩💻思路:
1.暴力解法:
在 `while` 循环中,不断扫描列表元素,当发现目标值时执行移除操作。移除操作包括将目标值元素后面的元素向前平移一位,并更新列表长度 `l`。在移除操作后,为了处理连续目标值的情况,需要手动将 `i` 减去 1,以确保下一次循环在当前位置进行扫描。最后返回更新后的列表长度 `l`,即为移除目标值后的列表长度。2.双指针:
左指针 slow 指向下一个非目标值元素的位置,右指针 fast 遍历整个列表,当发现非目标值时,将其放到 slow 指向的位置,并递增 slow。如果没有目标值,那么继续fast遍历后面的元素,slow保持在原位。最后返回 slow,即为移除目标值后列表的长度。
❌易错点/难点总结:
1. 在写暴力方法时,忘记手动给i减1。这一步不能省略因为在移除操作后旧的i对应新的元素,但是最后一步会给i加1,如果忘记提前剪去1的话,在遇到连续重复元素时会出错。
2. 双指针,nums[slow]=nums[fast]。感觉赋值的这一步一开始很难理解,不过可以想象成slow是坑,fast是萝卜,遇到合适的坑,就把fast放进去,没有合适的就继续寻找更合适的fast萝卜。
📊视频讲解要点总结:
暴力法是需要double for loops;双指针法将double for loops缩减为single,先遍历再覆盖。
📸解题代码:
1.暴力:
#暴力法 #时间复杂度:O(n^2) #空间复杂度:O(1) class Solution(object): def removeElement(self, nums, val): """ :type nums: List[int] :type val: int :rtype: int """ i = 0 end = len(nums)-1 while i < end: if nums[i] == val: for j in range(i+1,end): nums[j-1]=nums[j] end -= 1 #向前移一位后数组少了一位数 i -= 1 #i需要更新因为避免连续重复的情况 i += 1 return end #返回返回 nums 中与 val 不同的元素的数量
2.双指针:
#双指针法 #时间复杂度:O(n) #空间复杂度:O(1) class Solution(object): def removeElement(self, nums, val): """ :type nums: List[int] :type val: int :rtype: int """ slow = 0 for fast in range(len(nums)): if nums[fast]!= val: nums[slow]=nums[fast] slow += 1 return slow