代码随想录算法训练营第一天 | 704 二分查找、27 移除元素

 day1 记录代码随想录第一天

第一题 力扣704 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

题目链接:. - 力扣(LeetCode)

本题思路为二分查找法。

定义两个边界left和right,取一个中间指针mid=(left+right)/2,如果考虑到数据越界,可以使用另一个公式:mid=left+(right-left)/2,结果都是一样的。

然后判断mid元素与target的大小,如果相等则直接输出,如果mid元素小于rarget,则left=mid+1,再次进循环。

考虑跳出循环的条件,主要考虑left=right的情况,如果存在这种情况,则left=right=mid,当mid=target时,直接输出mid,当不等时,则一定会在if以后出现left>right,则跳出循环,输出-1,由此,说明left=right是有意义的,需要在循环内加以讨论。

//二分查找
class Solution {
public:
    int search(vector& nums, int target) {
        int left = 0, mid = 0, right = 0;
        right = nums.size() - 1;
        while (left <= right) {
            mid = (left + right) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else
                return mid;
        }
        return -1;
    }
};

以上方法为闭区间方法,即定义target在[left,right]区间内,还有一种方法为左闭右开区间,即target在[left,right)区间内,此时需要考虑right 的初始值不需要再-1,且每次循环中right赋值也不需要再-1,代码如下:

//二分左闭右开
class Solution {
public:
    int search(vector& nums, int target) {
        int left = 0, mid = 0, right = 0;
        right = nums.size();
        while (left < right) {
            mid = (left + right) / 2;
            if (nums[mid] > target) {
                right = mid;
            } else if (nums[mid] < target) {
                left = mid + 1;
            } else
                return mid;
        }
        return -1;
    }
};

第二题 力扣27 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素

题目链接:. - 力扣(LeetCode)

本题有两种解法,暴力求解和双指针

首先尝试暴力求解:

所谓暴力求解,就是指遍历、枚举、穷举等等方法

第一次编写:

class Solution {
public:
    int removeElement(vector& nums, int val) {
        int length = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] = val) {
                for (int j = i; j = nums.size() - 1; j++) {
                    nums[j] = nums[j + 1];
                }
                length++;
            }
        }
        return nums.size() - length;
    }
};

编译出错,怀疑是内存溢出了。

于是修改代码,此处nums.size()重复使用了三次,考虑直接使用中间变量length来替代。

再思考使用length表示数组长度,于是每次循环只用遍历length长度的数组即可,当发现与val相同的元素时,将第i个元素后面的元素覆盖到前一位。此时后面所有的元素都前移一位,所以length应减一,同时由于第i个元素被覆盖了,相当于此刻的第i个元素没有遍历,但是循环还是在往下走,将i减一可以将循环重走这一步,至此应该可以达到效果。

代码如下:

//暴力求解
class Solution {
public:
    int removeElement(vector& nums, int val) {
        int length = nums.size();
        for (int i = 0; i < length; i++) {
            if (nums[i] == val) {
                for (int j = i; j < length - 1; j++) {
                    nums[j] = nums[j + 1];
                }
                length--;
                i--;
            }
        }
        return length;
    }
};

提交通过!

同时有个很有意思的地方是用时0ms,超过100%用户,虽然知道这可能是系统内部计算有问题,但是还是挺激励自己的,开心!

双指针法:

双指针法是指在遍历对象的过程中,不仅仅使用一个指针遍历,而使用两个指针同时遍历访问,依两个指针访问方向相同和相反分为快慢指针和对撞指针。

快慢指针解法:

定义一个快指针fast,一个慢指针slow,使用快指针遍历整个数组,慢指针遍历不含val元素的数组,每当快指针遍历一个非val元素,就将慢指针所指元素对应此时快指针所指元素,由于循环条件中只有快指针++,所以应设置慢指针++语句,而当快指针遍历到val元素时,不需要做任何操作,直接快指针遍历过去,此时慢指针没变,慢指针所指元素没变,即慢指针所指元素对应的数组忽略了此元素,达成要求!

代码如下:

//双指针法(快慢指针)
class Solution {
public:
    int removeElement(vector& nums, int val) {
        int slowIndex = 0, fastIndex = 0;
        for (fastIndex = 0; fastIndex < nums.size() ; fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
};

对撞指针解法:

定义一个左指针和一个右指针,分别从两边遍历,每次遍历中,当左指针先找到一个val,在从右指针反方向找一个非val,并将两个指针对应的元素互换,之后左指针右移,右指针左移。需要注意的是,在整个过程中,需要保证左指针在右指针的左边,否则为越界。

代码如下:

//双指针法(对撞指针)
class Solution {
public:
    int removeElement(vector& nums, int val) {
        int leftIndex = 0, rightIndex = 0;
        rightIndex = nums.size() - 1;
        while(leftIndex <= rightIndex) {
            //找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val) {
                ++leftIndex;
            }
            //找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                --rightIndex;
            }
            //互换元素
            if (leftIndex <= rightIndex) {
                nums[leftIndex] = nums[rightIndex];
                leftIndex++;
                rightIndex--;
            }
        }
        return leftIndex;
    }
};

最后一个判断语句中,曾考虑过<= 和<是否有不一样的结果,然后发现永远不会出现left=right的情况,所以,这里及时使用<=,其效果也跟<是一样的。