代码随想录数组部分笔记

文章介绍了数组操作中的二分查找、移动元素(使用快慢指针和相向双指针方法)、以及滑动窗口的应用,如长度最小的子数组和水果成篮问题的解决方案。
摘要由CSDN通过智能技术生成

代码随想录数组部分

一、二分查找

二、移动元素


27. 移动元素

  • 题目描述

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

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

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

  • 思路及代码

1. 快慢双指针

思路:
使用一快一慢两个指针。慢指针在后面用于确定需要重新赋值的位置。快指针在前面用于寻找需要往合适的位置里赋的值。此时分为两种情况:

  • 快指针指向的值等于val:说明该值肯定不是需要输出的元素。快指针右移一位。
  • 快指针指向的值不等于val:说明该值是需要输出的元素。把该值赋给满指针指向的位置。快慢指针同时右移一位。

Q&A:
Q: 为什么不需要管慢指针指向的值是否不等于val导致应有的值被覆盖?
A: 因为快慢指针是同时出发的。初始当快指针的值不等于val时,慢指针和他指向同一个值,赋值是原地赋值。此后快慢指针一同后移。只有当两指针指向数值为val的时候。慢指针原地等待,快指针后移寻值。

代码:

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

2. 相向双指针

思路:
使用一左一右两个指针。头部指针用于找到第一个值等于val的元素。尾部指针用于找到第一个值不等于val的元素。这是最基础的思路。
使用快慢指针方法时,例如序列 [1,2,3,4,5],当 val为 1 时,我们需要把每一个元素都左移一位。注意到题目中说:「元素的顺序可以改变」。实际上我们可以直接将最后一个元素 5 移动到序列开头,取代元素 1,得到序列 [5,2,3,4],同样满足题目要求。这个优化在序列中 val元素的数量较少时非常有效。
具体思路是,左右指针相遇前,两种情况:

  • 如果左指针指向的值等于val,就把右指针指向的值赋值过来。同时右指针左移。
  • 如果左指针指向的值不等于val,该值保留,左指针右移。

这种方法避免了需要保留元素的重复赋值。并且在最坏情况下,相比较于第一种方法快慢指针都需要遍历整个数组,该方法左右指针合一起只遍历了一次整个数组。

代码:

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

26. 删除有序数组中的重复项

  • 题目描述

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

  • 思路及代码

使用快慢指针方法,因为只需要前k个元素包含唯一元素,慢指针留在后方占位,快指针走在前面找不重复的值。因为原数组是升序排列。一个一个找即可。快慢指针,的复杂度是O(n);

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int slow = 0;
        for(int fast =0;fast<nums.size();fast++){
            if(nums[slow]!=nums[fast]){
                nums[++slow] = nums[fast];
            }
        }
        return slow+1;
    }
};

三、滑动窗口


209. 长度最小的子数组

  • 题目描述

给定一个含有 n 个正整数的数组和一个正整数 target 。

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

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

  • 思路及代码
  • 暴力解法
    最先能想到的应该是暴力解法,遍历所有的子数组,判断是否符合条件,更新答案。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int res = INT32_MAX;
        int i = 0;//起点位置
        int sublen = 0;
        int tempsum = 0;
        for(int j=0;j<nums.size();j++){
            tempsum+=nums[j];
            while(tempsum >= target){
                sublen = j-i+1;
                res = res < sublen?res:sublen;
                tempsum -=nums[i++];
            }
        }
        return res==INT32_MAX?0:res;
    }
};

暴力解法的时间复杂度明显是O(n²)。可以使用滑动窗口的思想降低时间复杂度。

  • 滑动窗口

滑动窗口的意义是,不断地调整窗口的初始位置和终止位置。暴力解法中使用两个for循环来遍历,为了降低时间复杂度,需要只使用一个for循环完成不断搜索区间的操作。

Q&A:
Q:循环的索引应表示初始位置还是终止位置?
A:若索引表示初始位置,如何确定终止位置,仍会陷入循环遍历。和暴力算法一样。所以循环索引表示终止位置。

Q:窗口内是什么?
A:窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

Q:如何移动窗口的起始位置?
A:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

Q:如何移动窗口的结束位置?
A:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

for中套while。while循环每次看当前窗口内和是否大于target,如果大于target,试图缩小窗口,看能否得到更小的子数组。一个while结束,说明当前终止位置的长度最小的子数组已经固定。for循环继续寻找下一个终止位置。
一次for循环,每次循环终止位置之前的个数有可能进出一次窗口,复杂度O(2n)。

904.水果成篮

  • 题目描述

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

示例 1:
输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

示例 2:
输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

  • 思路及代码

可以将题目的意思转化为:寻找一个长度最长的子数组(子串),使得子串里只包含两种数字。看见子数组问题,可以优先考虑滑动窗口法。
同样for循环的索引表示窗口终止位置right。使用一个变量维护窗口起始位置left。使用一个哈希表记录遇见每个数字的次数。每次把窗口终止位置fruits[right]加入哈希表。并判断哈希表内存的键值对是否大于2,如果大于2,把fruits[left]从哈希表中删除,移动left。将 fruits[left]哈希表中移除后,如果 fruits[left]在哈希表中的出现次数减少为 000,需要将对应的键值对从哈希表中移除。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int num = fruits.size();
        unordered_map<int,int> cnt;
        int i =0;
        int ans =0;
        for(int j=0;j<num;j++){
            ++cnt[fruits[j]];
            while(cnt.size()>2){
                auto it = cnt.find(fruits[i]);
                --it->second;
                if(it->second == 0){
                    cnt.erase(it);
                }
                i++;
            }
            ans = max(ans,j-i+1);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值