算法刷题 | 数组

二分查找

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

输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4

思路:将数组的中间值与 target 进行比较,如果 target 大于中间值,就去右半部分寻找,如果 target 小于中间值,就去左半部分寻找。

边界处理问题
整个数组的空间可以有左闭右开、左闭右闭的形式,首先以左闭右开为例

int left = 0;
int right = nums.size(); // 这里要指向最后一个元素的下一个位置,因为右边开区间无意义
左闭右开区间,while 判断条件为 left < right ,因为 left = right 无意义

区间的更新
当 target 在 mid 左侧时,nums[mid] 不等于 target,因为是左闭右开,要保证 mid - 1 在区间中,所以 right = mid

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size();

        while(left < right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid;
            }
            else if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

同理,左闭右闭区间,right = nums.size() - 1(区间右端点有意义)
更新区间时,当 target 在 mid 左侧,nums[mid] 不等于 target,right = mid - 1即可,因为mid - 1在此区间中。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;

        while(left <= right){
            int mid = left + (right - left) / 2;
            if(nums[mid] > target){
                right = mid - 1;
            }
            else if(nums[mid] < target){
                left = mid + 1;
            }
            else{
                return mid;
            }
        }
        return -1;
    }
};

移除元素

题目描述 : 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]

若不考虑原地移动,暴力解法:遍历数组,遇到值等于 val 的,将 val 后面的元素依次向前移动一个位置(两层 for 循环)每次移动完,数组大小减一

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {

        int size = nums.size();  // 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];
                }
                size--;
                i--;   // i 下标以后的元素都向前移动了一位,i 也要向前移动
            }
        }
        return size;
    }
};

双指针法
通过一个快指针和一个慢指针在一个 for 循环中完成两个 for 循环做的事情

  • 快指针遍历数组,寻找新数组的元素
  • 慢指针决定将谁保留在新数组中

fast 指针遍历数组,如果找到的值 nums[fast] == val,只让 fast 向前移动,否则两者都会移动
而且要在移动过程中完成赋值,将 fast 遍历的旧数组的值赋给 slow 所指的新数组
删除过程

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;
    }
};

有序数组的平方

题目描述:给你一个按非递减顺序排序的整数数组 nums,返回 每个数字的平方组成的新数组,要求也按非递减顺序排序

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]

暴力解法:遍历数组,求每个元素的平方,最后再进行排序
双指针法(反向双指针):因为数组是有序的,所以对数组中元素平方后,负数可能变为最大值,最大值会出现在数组两端

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        vector<int> result(nums.size(), 0);  // 保存最终的结果
        int k = nums.size() - 1;
        while(left <= right)  // 含等号,因为要处理最后两个元素
        {
            if(nums[left] * nums[left] < nums[right] * nums[right])
            {
                result[k--] = nums[right] * nums[right];
                right--;
            }
            else
            {   // 大于等于的情况,如果 left = right 情况,会走到这里
                result[k--] = nums[left] * nums[left];
                left++;
            }
        }
        return result;
    }
};

长度最小的子数组

题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组
[numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

暴力解法:两个for循环,然后不断的寻找符合条件的子序列。
第一个 for 循环即遍历子数组的起点位置,第二个 for 循环确定的是子数组的终止位置
每次遍历,记录下子序列的长度并求和,最终判断最小的子序列,每轮新的子序列都要将子序列和 sum 清零

// leetcode 会超时
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT_MAX;
        int sublength = 0;  // 子序列的长度
        int sum = 0;  // 子序列和

        for(int i = 0; i < nums.size(); i++)  // 遍历,寻找符合条件的子序列
        {
            sum = 0;
            for(int j = i; j < nums.size(); j++)
            {
                sum += nums[j];
                if(sum >= target)
                {
                    sublength = j - i + 1;
                    result = result < sublength ? result : sublength;
                    break;
                }
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

滑动窗口
滑动窗口采用一个 for 循环去表示子序列的起始和终止位置
在这里插入图片描述
通过上面的动态图可以知道,窗口内是满足 sum 大于等于 target 的最小连续子数组,当总和大于等于 target 了,记录下此长度,起始位置就可以向前移动了,窗口的结束位置就是遍历数组的指针,就是for循环里的索引

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT_MAX;
        int i = 0;  // 滑动窗口的起始位置
        int subLength = 0; // 子数组的长度
        int sum = 0;   // 子数组的大小

        for(int j = 0; j < nums.size(); j++){
            sum += nums[j];
            
            while(sum >= target)
            {
                subLength = j - i + 1;
                result = result < subLength ? result : subLength;  // 更新结果

                // 更新滑动窗口的起点  (滑动窗口的精髓)
                sum -= nums[i++];
            }
        }
        return result == INT_MAX ? 0 : result;
    }
};

滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值