代码随想录训练营第1天|LeetCode 704.二分查找、LeetCode 27.移除元素

题目一:LeetCode 704.二分查找

一、按照自己的思路求解

这个题目比较简单,直接遍历就可以了(虽然题目就是二分查找,但是最容易的还是直接遍历哈哈),因为元素不会重复,所以找到目标值target之后直接返回其下标即可。代码实现如下:

//cpp
class Solution {
public:
    int search(vector<int>& nums, int target) {
        size_t size = nums.size();
        for(int i=0;i<size;i++)
        {
            if(nums[i] == target)
                return i;
        }
        return -1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

二、参考代码随想录的求解方法

题目中有一个很重要的信息:有序数组,也是本题能够用二分查找的重要前提。另外还有一点是无重复,如果有重复的元素但有序的话二分查找得到的结果可能不是唯一的。

(一)二分查找写法一

定义一个左闭右闭的区间[left,right],这种情况下因为左右边界都是在查找范围内,因此循环条件是left <= right,包含等号,当left == right是,查找区间就剩下一个数。代码实现如下:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        int ret = -1;
        while(left <= right)
        {
            //int middle = (left + right) / 2;
            int middle = left + (right - left) / 2;
            if(nums[middle] > target)
                right = middle - 1;
            else if(nums[middle] < target)
                left = middle + 1;
            else 
            {
                ret = middle;
                break;
            }
        }
        return ret;
    }
};
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)
    参照代码随想录给出的代码,在计算 middle 的值时用middle = left + (right - left) / 2;代替middle = (left + right) / 2来防止溢出,但就本题而言不用这么做也是能通过的。
    代码随想录给出的代码如下:
// 版本一
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;
    }
};
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

(二)二分查找写法二

定义左闭右开的区间[left,right),因为当left == right的时候区间中已经没有数据了,所以循环的条件是left < right。代码实现如下:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size();
        while(left < right)
        {
            int middle = left + (right - left) / 2;
            if(nums[middle] > target)
                right = middle;
            else if(nums[middle] < target)
                left = middle + 1;
            else
                return middle;
        }
        return -1;
    }
};
  • 时间复杂度:O(logn)
  • 空间复杂度:O(1)

小结

  • 注意使用二分法的条件:有序,另外需要注意元素是否重复,如果重复得到的结果可能不唯一。
  • 注意查找区间的定义,区间的定义会影响到查找时的循环条件,个人更喜欢左闭右闭,当然不必死记硬背,主要理解什么时候查找的区间内没有数据,比如左闭右闭的区间内,当left > right时,区间内就没有数据了,因此循环的条件是 left <= right,同理,左闭右开也是一样的道理。
  • 另外需要注意每次如何更新区间,在左闭右闭情况下, left = mid + 1或者right = mid - 1,如果少了减1或者加1则会导致边界值重复判断,更主要的是会导致死循环。

题目二:LeetCode 27.移除元素

一、按照自己的思路求解

可以用最简单粗暴的方法求解,即暴力求解,两层循环,外层循环遍历数组,内层循环更新数组。注意,不能使用额外的空间,可以打乱原来的顺序。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        size_t size = nums.size();
        for(int i=0;i<size;i++)    //注意这里不能用nums.size(),必须用size,size是会变化的
        {
            if(nums[i] == val)
            {
                //移动(更新)数组
                for(int j=i;j<size-1;j++)
                    nums[j] = nums[j+1];
                --size;  //更新数组长度
                --i;  //原来i指向的位置存入了新的数据,所以要重新判断
            }
        }
        return size;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
    自己在写的时候第一次只通过了部分测试用例,问题是缺少了--i这条语句,因为如果移动数组之后原来i指向的位置放入了新的数据,因此要对这个位置的数据进行判断,所以要加入--i这条语句,再在for语句中加1,最终的结果就是i的值不变。另外,移除(准确地说覆盖)了一个数据之后要更新数组的长度。

二、参考代码随想录的思路求解

代码随想录中给出两种方法:暴力求解和双指针法,暴力求解已经实现,这里补充双指针法。
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环内完成两个for循环的工作。个人理解,就本题而言,一个指针遍历数组,另一个指针指向下一个存储位置。代码实现如下:

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        size_t size = nums.size();
        int j = 0;
        for(int i=0;i<size;i++)
        {
            if(nums[i] != val)  
                nums[j++] = nums[i];
        }
        return j;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
    相比暴力求解,这种方法时间复杂度更低,更简洁。
    代码随想录给出的代码如下:
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

小结

本题用暴力求解也能通过,但暴力求解的一大缺点是时间复杂度高,双指针法是应该掌握的一种方法,相比于快慢指针的说法,就本题而言,个人觉得一个指针遍历数组,另一个指针指向下一个i存储位置的说法更容易理解。

参考

代码随想录

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忆昔z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值