基础算法-数组篇

1.二分查找

力扣链接

题目描述

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

例子1:

输入 nums = [-1,1,3,5,8,12], target = 5     
输出 3       

例子2:

输入 nums = [-1,1,3,5,8,12], target = -2     
输出 -1   

思路

这道题目的条件一是数组为有序数组,题目条件二数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,满足以上两个条件,就可以考虑是否使用二分法了。

这道题目的重点是:二分查找涉及的边界条件,逻辑比较简单,但就是写不好。例如写循环条件的时候,到底是 while(left < right) 还是 while(left <= right),和写循环体的时候,到底是right = middle呢,还是要right = middle - 1呢?

边界条件写不对,究根结底是没有遵循循环不变量规则。而这道题目中的循环不变量,就是你对区间的定义

写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

下面我用左闭右闭即[left, right]的定义来写二分法,左闭右开留给大家写。


二分法(左闭右闭[left.right])

区间的定义决定了如何书写代码。定义target在[left,rigjt]之间,有如下两点很重要

1.循环条件的判断:while(left <= right) 此时为什么可以等于呢,因为区间是[left,right],所以left = right 是有意义的,使用 <= 。

2.if(nums[middle] > target) 此时目标值小于中间值,在左区间需更新right的值。怎么给right赋值呢?因为已经判断过nums[middle] > target,所以当前nums[middle]不可能是target,则right = middle - 1。


代码如下(以java为例)

时间复杂度:O(logn)

空间复杂度:O(1)

int left = 0;
int rigjt = nums.length - 1;

while(left <= right){ //区间[left,right]
     int middle = (left + right) / 2;
     if(nums[middle] > target)

        right = middle - 1 ;// nums[middle] 不可能是目标值

     else if(nums[middle] < target)

        left =  middle + 1;

     else 
        return middle;        
}

return -1;//找不到返回-1

总结

二分法这种非常重要的基础算法,为什么有的同学对边界的鉴定模糊不清呢?

其实最主要的是同学对区间的定义没完全理解,在循环中没有坚持使用区间的定义去做边界的处理。此题中区间的定义就是不变量,在循环中坚持根据区间的定义做边界的定理,就是循环不变原则。


2.移除元素

力扣题目链接

题目描述

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量(返回新数组的长度).假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:

  • 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
  • 返回 k
  • 示例 1: 给定 nums = [4,2,3,3], val = 3, 函数应该返回新的长度 2。并且 nums 中的前两个个元素为 4,2。

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


思路

有同学可能会想,遇到等于val的元素直接删掉不就可以了嘛。可是你不要忘记了,数组存放的元素在内存中是连续的,如果删除某个元素,后面的元素就要往前覆盖。既然如此,我们就采用覆盖的方法。下面分享两种方法的思路:


暴力求解法(双层for 循环)

第一层for循环去遍历数组nums,第二层循环去更新数组。

代码如下:

部分主体代码(重点不是这个)

for (int i = 0; i < nums.size; i++) {
            if (nums[i] == val) { // 移除目标元素,将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }

                i--; // 数组集体向前移动一位,i下标-1
                size--; // 此时数组的大小-1

            }

如果数组很大的话,在力扣上可能跑不过(具体看后台给的数据)

上面不难看出

时间复杂度:O(n^2)

空间复杂度:O(1)


快慢指针

其本质就是用快慢指针在一个for循环下完成两个for循环的工作

明确快慢指针的定义

1.快指针:指向新数组所需的元素

2.满指针:指向新数组中需要更新的位置

明确快慢指针的定义,掌握后面的过程就易如反掌了。

过程如下:

代码如下(以Java为例)
class Solution {
    public int removeElement(int[] nums, int val) {
        int slow = 0; //指向新数组所需的元素(不包括val)
        int fast = 0; //指向新数组中所需要更新的位置

        for(fast = 0;fast < nums.length; fast++){
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }
        }
        return slow;
    }
}

时间复杂度 O(n)

空间复杂度 O(1)

总结

快慢指针,也就是双指针在数组和链表中的操作十分常见,也是我们必须掌握的一种常规解题思想。有的同学遇到这道题,就去背代码,下次遇到还是会缺这缺那的。最为重要的是在不同的题中,明确快慢指针的定义。这才是我们制胜的法宝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值