LeetCode 704.二分查找
题目链接:
看到这道题的题目就想到了在之前学习中编过的一个“猜数游戏”。其中就运用到了二分法的思想。后来再看了代码随想录上的思路解析觉得心里就有谱了。
文章链接:
https://programmercarl.com/0704.%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE.html
这道题主要需要的就是坚定一个区间原则,即为左闭右闭,左闭右开……,了解了这两个其他的区间也会理解。从一开始就要选定一种去写代码,不能再改变;
左闭右闭
C++代码如下
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right = 0, middle;
right = nums.size() - 1;
while (left <= right) {
middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1;
}
if (nums[middle] < target) {
left = middle +1;
}
if (nums[middle] == target) return middle;
}
return -1;
}
};
此种情况下right定义时应该等于 nums.size() - 1。因为右边是闭区间,所以要让nums[right]是有意义的值。while结束条件是left <= right,也是左闭右闭的一个重要特征。然后因为找的是相对的中间值且防止溢出,middle就等于left + ((right - left)/2)。后面的思想就和前面类似了,因为left和right都是有意义的,所以赋值时是middle + 1或者middle - 1。最后就是找到返回middle,没找到返回-1。
左闭右开
代码如下
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0, right, middle;
right = nums.size();
while (right > left) {
middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle;
}
if (nums[middle] < target) {
left = middle + 1;
}
if(nums[middle] == target) {
return middle;
}
}
return -1;
}
};
此种情况下right就直接等于nums.size()。因为右边是开区间,所以right可以指向没意义的位置。while的结束条件变为right < left即为左闭右开的特征。因为右边为开区间,所以给right赋值时就直接将middle赋给right。其他思路和左闭右闭类似。
还有两道拓展题目,有一道较简单,另一道
题目链接:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int middle, right, left = 0;
right = nums.size ();
if (nums[0] >= target) return 0;
if (nums[right - 1] < target) return right;
while (left + 1 < right) {
middle = left + ((right - left) / 2);
if (nums[middle] < target) {
left = middle;
}
if (nums[middle] > target) {
right = middle;
}
if (nums[middle] == target) {
return middle;
}
}
if (nums[left] < target) return right;
if (nums[right] > target) return left;
return -1;
}
};
代码比较厚,写的时候可能多了很多不必要的if语句判断。
这道题我想到用二分法,但是有一个需要注意的条件就是最后循环结束后的两个if判断,这是必要的,否则不一定能返回正确的值,因为是有序数组,所以会有这样的判断。
LeetCode 27. 移除元素
题目链接:
看完这道题第一时间想到的就是遍历到目标值的位置,然后一一将后面的元素覆盖过来。也就是暴力解法。但是看了代码随想录的思路后就悟了。
文章链接:
这道题的精髓就是双指针的用法,一前一后指针分别承载不同的功能。慢指针(slow):记录删除目标值后的新数组成员,快指针(fast):寻找目标值,并将其覆盖。
暴力解法
代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0, right = 0, size, j = 0;
size = nums.size();
for(left = 0; left < size; left ++) {
if (nums[left] == val) {
for(right = left; right < size - 1; right++) {
nums[right] = nums[right + 1];
}
left--;
size--;
}
}
return size;
}
};
简单粗暴,两个for循环结束。
双指针法
代码如下:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0, fast = 0;
for(fast = 0; fast < nums.size(); fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}
};
巧妙运用两个指针来进行操作,减小了时间复杂度。
今日学习5小时收获
第一天真正意义上的进行刷题,拓宽了我再算法题上近乎空白的思维,脑中多了很多想法。熟练的掌握了有序数组二分法的使用。对于指针的双指针的用法更精进了一步。后面再遇到数组的题可能会优先考虑双指针。多多重复,百炼成钢。