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]之间。
好久没有写数据结构相关代码了, 今天开始,决定坚持60天。
- 自己预习写二分法代码,写得有些凌乱,忘记了区间[ ], [ )的区别, 导致写出来的[ ]中,right = mid, 忽略了nums[mid] > target时, mid索引的值一定不会等于target, 且right是闭区间, 所以,如果right = mid, 则mid出现重复计算的情况, 需要修正为right = mid - 1.
- 看了carl哥的视频,理解整理如下:
2.1 在使用二分法时,注意二分法的适用情况: nums数组有序 + 数值不重复。
2.2 注意区间选择一定要保持不变, 即选择 [left, right] , 则在while 循环中, 一定要保证区间合法, 对于[ ], 则left=right是成立的; 但是 [left, right)情况下, left = right会导致集合区间不合法, 因为不存在一个数i, 使得i 包含在区间, 同时i又不包含在区间的情况。
2.3 保证循环的不变性, 在循环中,对于left, right的更新要保证和定义区间 [], [)一致。
根据[left, right] 实现二分法
# 条件: nums 有序;
left = 0
right = len(nums) -1
while left <= right:
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1 #表明target在右半区间, 需要更新左区间, 且左区间是闭区间, mid已经取过,且nums[mid]!=target, 所以, left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
return mid
return -1
根据[left, right) 实现二分法
left = 0
right = len(nums) # 因为右边区间无法取到
while left < right: # 区间定义为 [ ), left = right不合法
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1 #表明target在右半区间, 需要更新左区间, 且左区间是闭区间, mid已经取过,且nums[mid]!=target, 所以, left = mid + 1
elif nums[mid] > target:
right = mid # 因为右区间为开区间, 无法取到
else:
return mid
return -1
code python 1 []
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
"""
条件: nums 有序;
# [], [)
"""
left, right = 0, len(nums)-1
while left <= right: # 区间 与限定一致
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
code python 2 [)
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
"""
条件: nums 有序;
# [)
"""
left, right = 0, len(nums)
while left < right: # 区间 与限定一致
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid
return -1
小结,
5. [], [), 要注意右区间的取值, []: right = len(nums) -1, [): right = len(nums)
6. 为防止溢出, mid = (left + right) // 2 可以修正为 mid = left + (right -left) // 2
27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。
然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
返回 k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。
int k = removeElement(nums, val); // 调用你的实现
assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:你的函数函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
解释:你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
提示:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
我的理解:删除数组nums在值为val1的数据。
- 无脑操作,直接暴力求解。需要注意的是,删除当前nums[i]=val之后,所有索引>i的数据都需要前移动1位。数组的删除只可以覆盖实现, 因为数组的地址是连续的的。
代码提交leetcode如下:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 暴力方法
# 删除某一个元素, 其余所有元素都要前移
if not nums: return 0
rest = len(nums)
i = 0
while i < rest:
if nums[i] != val:
i += 1
else:
# for j in range(i, rest-1):
# nums[j] = nums[j+1]
# nums[rest-1]='_'
nums[i:rest-1]=nums[i+1:rest]
rest -= 1
return rest
2. 动点脑子,使用双指针。
题目的要求: 使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
只需要把nums[i]!=val的数据移动到数组nums的前面, ,你不需要考虑数组中超出新长度后面的元素。
需要一个东西记录k的位置, 另一个东西表示数组nums已经遍历完成。
left,right指针
right表示当前遍历到数组的最新位置。
left 遍历到right位置时,nums中不为val的索引位置。
> 双指针法
双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
定义快慢指针
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
code python 1
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 双指针
left = 0
right = len(nums)
while left < right:
if nums[left] != val:
left += 1
else:
nums[left], nums[right-1] = nums[right-1], nums[left]
right -= 1
return left
code python 2
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left, right = 0, n - 1
while left <= right:
while left <= right and nums[left] != val: ## val = 7, nums=[7,7,7,7,3,53,5, 7,7,7,7]
left += 1
while left <= right and nums[right] == val: ## val = 7, nums=[7,7,7,7,3,53,5, 7,7,7,7]
right -= 1
if left < right: ## 此时nums[left]=3, nums[right]=5,一定不等于val=7, 且 left==right结束
nums[left] = nums[right]
left += 1
right -= 1
return left
- 小结:
- 题目的要求决定了code
- 双指针的初始化要仔细考虑, 区间一致性, 循环的一致性
- 下面的code来自代码随想录
(版本一)快慢指针法
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
fast = 0 # 快指针
slow = 0 # 慢指针
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow