leetcode:二分查找、移除元素

1、二分查找

很经典的一道题,直接用二分模板即可,但有几点需要注意:

  • 不是传统的二分模板,l 和 r 是永远不会交错的,只会到分界线处相邻,即 r - l == 1
  • 心中要清楚,l 分区和 r 分区在题目中分别代表的性质
  • 循环结束后,如果需要使用 l、r 判断某条件时,要考虑到可能分区根本没有移动的情况,此时 l / r 是在数组合法范围外的!

二分模板:

详细说明,往期博客:二分专题----如何优雅的写出二分

cpp 复制代码
l=-1, r=N  //在(l,r)的数组索引开区间内,数组元素都是灰色的
 
while l+1≠r  //l+1=r时,蓝红区域接触
 
	m = ⌊(l+r)/2⌋  //开区间中间元素
 
	if IsBlue(m)
 
		l=m  //蓝色区域向右拓展到中间元素
 
	else
 
		r=m  //红色区域向左拓展到中间元素
 
//根据实际情况返回l或r,但要注意:返回谁就要对谁做越界判断!
return l or r  

本题代码:

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) 
    {
        int n = nums.size();
        int l = -1, r = n;
        while(r - l > 1) // l + 1 != r
        {
            int mid = (l + r) >> 1;
            if(nums[mid] < target)
                l = mid;
            else
                r = mid;
        }
        int ret = -1;
        if(r <n && nums[r] == target) // 先判断r是否合法
            ret = r;
        
        return ret;
    }
};

2、移除元素

本题类似于实现vector中的erase方法,只不过标准库的erase是只能用迭代器删除元素的:

而本题要实现的是,指定vector中的一个值val,将数组中所有val值都删除,并且返回删除后的有效元素个数,且可以打乱原有的顺序。

本题的数据范围很小,最多只有100个元素,很直接的我们可以想到暴力做法:

1、双重循环:外层找出val,内层移动后续元素覆盖时间复杂度是O(n),空间复杂度也是O(1)。

2、空间换时间:新开一个vector,遍历原vector,将不是val的值插入新的vector中,再赋值给原vector,这样时间复杂度是O(n),空间复杂度也是O(n)。


第二种方法并不符合题意,题目要求原地修改数组 ,那么除了第一种暴力,还有更优化的做法吗?-------双指针,数组操作中很常见的优化方法

1、同向双指针:定义快慢指针,slow在原数组中更新新数组,fast遍历数组元素,将不是val的元素更新到slow

2、对撞指针:定义左右指针,左指针找出等于val的位置,右指针找出不等于val的指针,然后将右指针指向的值赋值给左指针处,直到左右指针错过。

对撞指针中有很多细节,循环中取等问题、赋值时的条件判断

代码如下:

暴力代码1:双重循环:

cpp 复制代码
// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

暴力代码2:空间换时间(不满足原地修改数组的题意!)

cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) 
    {
        int n = nums.size();
        vector<int> arr;
        for(int i = 0; i < n; i++)
        {
            if(nums[i] != val)
                arr.push_back(nums[i]);
        }
        nums = arr;

        return arr.size();
    }
};

双指针代码1:同向双指针

cpp 复制代码
class Solution {
public:
    int removeElement(vector<int>& nums, int val) 
    {
        int n = nums.size();
        int fast, slow = 0;

        //fast遍历所有元素,忽略val,其余赋值到slow处
        //slow指向待更新的新数组处,slow之前的全部是合法元素
        for(fast = 0; fast < n; fast++)
        {
            if(nums[fast] != val)
            {
                nums[slow++] = nums[fast];
            }
        }

        return slow;
    }
};

双指针代码2:对撞指针

cpp 复制代码
class Solution {
public:

    int removeElement(vector<int>& nums, int val) 
    {
        int n = nums.size();
        int l = 0, r = n - 1;

        //必须取等,如果不取等,l、r相遇的位置还没有判断
        //如果是合法值,不取等的话,l不会右移,最终结果会遗漏
        while(l <= r)
        {
            // l找出等于val的值
            while(l <= r && nums[l] != val) l++;
            // r找出不等于val的值
            while(l <= r && nums[r] == val) r--;
            //上面的寻找中,l、r可能已经错过了,需要判断
            if(l < r) nums[l++] = nums[r--];
        }

        //l代表:l之前的全是合法元素(全都是经过l判断过的)
        return l;
    }
};
相关推荐
passer__jw7671 小时前
【LeetCode】【算法】3. 无重复字符的最长子串
算法·leetcode
passer__jw7671 小时前
【LeetCode】【算法】21. 合并两个有序链表
算法·leetcode·链表
sweetheart7-71 小时前
LeetCode22. 括号生成(2024冬季每日一题 2)
算法·深度优先·力扣·dfs·左右括号匹配
景鹤4 小时前
【算法】递归+回溯+剪枝:78.子集
算法·机器学习·剪枝
_OLi_4 小时前
力扣 LeetCode 704. 二分查找(Day1:数组)
算法·leetcode·职场和发展
丶Darling.4 小时前
Day40 | 动态规划 :完全背包应用 组合总和IV(类比爬楼梯)
c++·算法·动态规划·记忆化搜索·回溯
风影小子5 小时前
IO作业5
算法
奶味少女酱~5 小时前
常用的c++特性-->day02
开发语言·c++·算法
passer__jw7675 小时前
【LeetCode】【算法】11. 盛最多水的容器
算法·leetcode
诸葛悠闲5 小时前
C++ 面试问题集合
算法