704.二分查找
二分查找的思路前几天还复习过一遍,没想到还是一写就废。看了卡哥的视频讲解思路清晰了不少。
思路重点:区间的定义——是左闭右闭还是左闭右开?
· 右开还是右闭决定了right指向的值能否被访问
区间的定义回答了以下问题:
1、right的初始值是nums.size()还是nums.size()-1?
2、循环条件是left < right还是left <= right?
3、right的更新是right = middle还是right = middle - 1?
最开始的写法,最近一直在看C++ Primer,所以风格受影响比较多(比如条件的!=和decltype),没想到实际写起来一写就废 /(T_T)/
// 最开始的写法,更接近左闭右开(但实际是左也开了)
int search(vector<int>& nums, int target) {
// 为了追求规范使用了decltype,但实际right可能会向下越界(size_t是无符号)
decltype(nums.size()) ind_f = 0;
// 因为右区间无法访问到,所以初始值应改成num.size();
decltype(nums.size()) ind_b = nums.size() - 1;
while (ind_f != ind_b) {
decltype(nums.size()) ind_m = (ind_f + ind_b) / 2;
if (nums[ind_m] == target)
return ind_m;
else if (nums[ind_m] > target) {
ind_b = ind_m;
}
else {
ind_f = ind_m; // 左应该是闭的,所以改成=middle+1
}
}
return -1;
}
修正后的左闭右开写法
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size(); // 初始值等于size
while (left < right) {
int middle = (left + right) / 2;
if (nums[middle] == target)
return middle;
else if (nums[middle] > target) {
right = middle; // 右开,所以直接等于middle
}
else {
left = middle + 1;
}
}
return -1;
}
左闭右闭写法
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
// 右闭时条件为<=
while (left <= right) {
//int middle = (left + right) / 2; // 这样的写法可能int越界
int middle = left + (right - left) / 2;
if (nums[middle] == target)
return middle;
else if (nums[middle] > target) {
right = middle - 1; // 右闭时,middle不应该在搜索区间内
}
else {
left = middle + 1;
}
}
return -1;
}
27.移除元素
知识点:数组中的元素不能删除,只能覆盖
这题倒是一写就会了
快慢指针:
· 快指针——负责遍历数组
· 慢指针——负责更新数组
双指针法:
// 双指针解法,时间复杂度O(n),空间复杂度O(1)
int removeElement(vector<int>& nums, int val) {
int fast = 0; // 快指针负责向前遍历
int slow = 0; // 慢指针负责维护更新
while (fast < nums.size()) {
// 不需要“删除”的值使用slow进行更新
if (nums[fast] != val) {
nums[slow] = nums[fast];
++fast;
++slow;
}
// 需要“删除”的值直接忽略(没看到就是不存在:D)
else {
++fast;
}
}
return slow;
}
暴力解——双层遍历,一层遍历一层更新
// 暴力解法,时间复杂度O(n^2),空间复杂度O(1)
int removeElement(vector<int>& nums, int val) {
int size = nums.size();
int i = 0;
while (i < size) {
if (nums[i] == val) {
for (int j = i; j < nums.size() - 1; ++j)
nums[j] = nums[j + 1];
--size;
}
else
++i;
}
return size;
}
拓展:35.搜索插入位置
也是利用了二分搜索思维的一道题,开始想的是如何返回正确的middle值,看了题解后发现是与left和right的性质有关
left:左闭的情况下left所指位置的左侧一定小于target
rigjt:右闭的情况下right所指位置的右侧一定大于target
解题思路的转换:寻找插入位置->寻找第一个大于target值的位置->寻找一个位置,它的左侧值全都小于target
int searchInsert(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 左闭右闭
int middle = 0;
while (left <= right) {
middle = left + (right - left) / 2;
if (nums[middle] == target)
return middle;
else if (nums[middle] < target)
left = middle + 1;
else
right = middle - 1;
}
return left; // 此时left为第一个大于target的下标(left左侧的值始终小于target)
}
还看了下34. 在排序数组中查找元素的第一个和最后一个位置,没看懂,之后再抽时间看看吧。