LeetCode 704. 二分查找
题目链接:力扣704. 二分查找
题目思路
这道题的难点主要在于如何确定边界,区间的开闭决定了二分法的代码如何去写。
解题方法
方法一:
假如 target 是在一个在左闭右闭的区间里,即[left, right]。
- while (left <= right) 要使用 <= ,因为 left == right 是有意义的,所以使用 <=
- if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个 nums[middle] 一定不是 target,那么接下来要查找的左区间结束下标位置就是 middle - 1
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0) return NULL;
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 是在一个在左闭右开的区间里,即 [left, right)。
- while (left < right),这里使用 < ,因为 left == right 在区间 [left, right) 是没有意义的
- if (nums[middle] > target) right 更新为 middle,因为当前 nums[middle] 不等于 target,去左区间继续寻找,而寻找区间是左闭右开区间,所以 right 更新为 middle,即:下一个查询区间不会去比较 nums[middle]
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.size() == 0) return NULL;
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; //未找到目标值
}
};
(right - left) / 2 可能会造成越界,例如 left + right = 33亿 > int 上限值,已经超上限值了,除 2 就没意义了,而用 left 加 right - left 差值这种形式就相当于 15 亿加 3 亿,结果才 18 亿,没有超,所以可以得到正确结果
left + (right - left) / 2 和 left + ((right - left) >> 1) 的区别:
总结
这道题在一开始的时候我也是因为没有确定好边界,导致时间一直超出限制,后来看了讲解之后,才成功解决了这个边界的设定的问题。还有就是运算符的优先级的问题,“+” 的优先级高于 “>>”,所以在移位运算的时候要加括号。
LeetCode 27. 移除元素
题目链接:力扣27. 移除元素
题目思路
可以考虑到的就是暴力,找到要删除的元素,将其后面的元素向前覆盖,还有就是利用双指针。
解题方法
方法一:(暴力)
暴力解法,首先循环遍历待操作的数组,找到要删除的元素,然后再遍历将该元素后面的元素都向前移动一位。时间复杂度:O(n^2),空间复杂度:O(1)
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size();
if(n == 0) return 0;
for(int i = 0; i < n; i++)
{
if(nums[i] == val) // 发现需要移除的元素,就将数组集体向前移动一位
{
for(int j = i + 1; j < n; j++)
{
nums[j - 1] = nums[j];
}
i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
n--; // 此时数组的大小-1
}
}
return n;
}
};
方法二:(快慢双指针)
定义快慢指针
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新新数组下标的位置
快指针寻找不是要删除的元素赋值给 nums[slow],就相当于是将删除后的数组元素存放进一个新的 nums[slow] 数组中,只不过没有额外定义一个数组。时间复杂度:O(n),空间复杂度:O(1)
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.size(); ++fast){
if(nums[fast] != val){
nums[slow++] = nums[fast];
}
}
return slow;
}
};
元素的相对位置没有改变
方法三:(双向双指针)
把左边需要剔除的值和右边不需要剔除的值颠倒一下,这样右边的就都是数组中需要剔除的值, left 存的就是新数组的长度。时间复杂度:O(n),空间复杂度:O(1)
// 时间复杂度:O(n)
// 空间复杂度:O(1)
// 双向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int left = 0;
int right = nums.size() - 1;
while(left <= right){
// 找左边等于 val 的元素
while(left <= right && nums[left] != val) ++left;
// 找右边不等于 val 的元素
while(left <= right && nums[right] == val) --right;
// 将右边不等于 val 的元素覆盖左边等于 val 的元素
if(left <= right){
nums[left++] = nums[right--];
}
}
return left; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
元素的相对位置改变了
总结
这道题目让我对双指针解题有了新的认识,双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。