Leetcode704二分查找
给定一个 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]
之间。
看到题目后根据对二分查找的记忆写了如下代码,结果超出时间限制
class Solution {
public:
int search(vector<int>& nums, int target) {
int mid, low = 0, high = nums.size()-1;
while(true)
{
mid = (low+high)/2;
if(target == nums[mid])
{
return mid;
}
else if(target < nums[mid])
{
high = mid - 1;
}
else
low = mid + 1;
}
return -1;
}
};
想了想,发现while的判断有问题,应该要保证low<=high,修改后运行通过。
但是看官方题解中,关于mid的值,一开始是这样求的:
int mid = (right - left) / 2 + left;
看评论区知道是为了防止溢出:在计算机里可能两个int相加会溢出,先减除后加确实可以用于更大的数组
之后我返回去看了一下卡尔哥的讲解,发现卡尔哥讲的很详细,下次我可以直接写完看卡尔哥的讲解。然后,卡尔哥讲到了二分法的两种写法
target在左闭右闭的区间内
也就是我一开始的思路,这里附上卡尔哥的代码,也就是一开始left=0,right=nums.size()-1,这里的right是数组最后一个元素的下标
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
target在左闭右开的区间内
这里对边界的处理方式产生了变化:
- while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
left还是mid+1
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
right = middle; // target 在左区间,在[left, middle)中
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,在[middle + 1, right)中
} else { // nums[middle] == target
return middle; // 数组中找到目标值,直接返回下标
}
}
// 未找到目标值
return -1;
}
};
leetcode35插入位置
(这里其实我还有一些疑问,今天有点来不及解决了,先存一下疑,之后去看一下卡尔哥的解析)
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n)
的算法。
自己写完一遍后,因为nums.size()没有减一,报错,修改乘high=nums.size()-1后,运行没有问题,但是解答错误
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int low = 0, high = nums.size()-1;
int mid;
while(low <= high)
{
mid = (high-low)/2 + low;
int num = nums[mid];
if(num == target)
return mid;
else if(target < num)
high = mid - 1;
else low = mid + 1;
}
if(target > nums[mid]) return mid+1;
else return mid-1;
}
};
当插入位置为0,也就是,要查找的数字小于数组里的所有数时,我的运行结果为插入位置为-1
我想了想,当target<nums[mid]时,插入位置应该就是mid
else return mid;
结合官方题解和评论区,原来二分查找最后返回的mid实际上就是left
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]<target)
l=mid+1;
else r=mid-1;
}
return l;
}
};
leetcode27移除元素
我的思路是,每次发现一个等于val的值,从这个位置开始,把后面的数整体前移,覆盖掉val,数组长度减1即newn减1。第一次我是用的两个for循环,第一层是数组从0开始循环到newn,这样的问题是如果后面移过来的值也等于val,就会被忽略掉。第二次我该用了while,然后每次循环都判断一遍原来的位置是不是不等于val了。
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size()-1;
int newn = n;
int i = 0, pos = 0;
while(i <= newn)
{
if(nums[i] == val)
{
--newn;
for(int j = i; j <= newn; ++j)
{
nums[j] = nums[j+1];
}
}
else ++i;
}
return newn+1;
}
};
后面我看了卡尔哥暴力求解的代码,如果采用两层for循环,可以在第二层for循环中,将i也前移一位,解决了我采用两层循环遇到的问题。
另外,这道题我刚开始想过用如erase这样的函数不是直接就解决了吗,根据视频讲解,erase的时间复杂度实际上是O(n),最后size能返回删除后大小,是因为内部有一个计数用的count。这道题就是要我们实现erase的功能。
卡尔哥具体讲解了双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
- 快指针:寻找新数组的元素,新数组就是不含有目标元素的数组
- 慢指针:指向更新新数组下标的位置
具体代码就不附上了,在随想录里可以看,感觉有点复杂,还需要再理解理解