寒假刷代码随想录

二分

在循环中 始终坚持根据查找区间的定义做边界处理
第一种写法,我们定义 target 是在一个在左闭右闭的区间里,也就是[left, right] (这个很重要非常重要)。

区间的定义这就决定了二分法的代码应该如何写,因为定义target在[left, right]区间,所以有如下两点:

while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};

双指针

27. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素

数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。(平时是封装了,调用了一层接口:.earse()是O(n)的,后面的元素逐个前移)

26.删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。要求删除重复元素,实际上就是将不重复的元素移到数组的左侧。

比较 p 和 q 位置的元素是否相等。
如果相等,q 后移 1 位
如果不相等,将 q 位置的元素复制到 p+1 位置上,p 后移一位,q 后移 1 位
重复上述过程,直到 q 等于数组长度。

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int p=0,q=0;
        while(q<nums.size()){
            if(nums[p]==nums[q]) q++;
            else{
                nums[p+1]=nums[q];
                p++;q++;
            }
        }
        return p+1;

    }
};

283.移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int p=0,q=0;
        while(q<nums.size()){
            if(nums[p]==0 && nums[q]!=0) swap(nums[p],nums[q]),p++,q++;
            else if(nums[p]==0 && nums[q]==0) q++;
            else p++,q++;
        }

    }
};

977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
分界是 第一个非零的数。双指针实现归并排序

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int reg = 0;
        while(reg < nums.size()){
            if(nums[reg]>=0) break;
            else{
                reg ++;
            }
        }
        vector<int> res;
        //[0,reg-1]减 [reg,size]增
        int p = reg-1,q = reg;
        while(p>=0 && q<nums.size()){
            if(pow(nums[p],2) <= pow(nums[q],2)) res.push_back(pow(nums[p],2)),p--;
            else res.push_back(pow(nums[q],2)),q++;
        }
        while(p>=0) res.push_back(pow(nums[p--],2));
        while(q<nums.size()) res.push_back(pow(nums[q++],2));
        return res;
    }
};

209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [ n u m s l , n u m s l + 1 , . . . , n u m s r − 1 , n u m s r ] [nums_l, nums_{l+1}, ..., nums_{r-1}, nums_r] [numsl,numsl+1,...,numsr1,numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

  • 超时了,前缀和+二分
    class Solution {
    public:
        int midsearch(vector<int> a,int l,int r,int target){
            if (a[r] < target) return -1;
            while(l<r){
                int mid = (l+r)>>1;
                if(a[mid]>=target) r=mid;
                else if(a[mid]<target) l=mid+1;
            }
            return l;
        }
        int minSubArrayLen(int s, vector<int>& nums) {
            int n = nums.size();
            if(n==0) return 0;
            vector<int> sums(n+1,0);
            for(int i=1;i<=n;i++)
                sums[i]=sums[i-1]+nums[i-1];
            int ans = INT_MAX;
            for(int i=1;i<=n;i++){
                int target = s + sums[i-1];
                int bound = midsearch(sums,1,n,target);
                if(bound !=-1)
                    ans = min(ans,bound-(i-1));
            }
            return ans == INT_MAX ? 0 : ans;
        }
    };
    
  • 滑动窗口:滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)
    循环的索引,一定是表示 滑动窗口的终止位置
    class Solution {
    public:
        int minSubArrayLen(int s, vector<int>& nums) {
            int l=0,r=0;
            int tmp=0;
            int res = INT_MAX;
            while(r<nums.size()){//循环是r
                tmp += nums[r];//窗口都是以r加的
                while(tmp>=s){
                    res=min(res,r-l+1);//更新结果
                    tmp-=nums[l];//退l,l++
                    l++;
                } 
                r++;
            }
            return res==INT_MAX?0:res;
        }
    };
    

不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

904. 水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int l=0,r=0;
        int ans=0;
        unordered_map<int,int> box; 
        while(r<fruits.size()){
            box[fruits[r]]++;//窗口都是以r加的
            while(box.size()>2){
                auto it=box.find(fruits[l]);
                it->second--;
                if(it->second==0) box.erase(it);
                l++;
            }
            ans=max(ans,r-l+1);
            r++;
        }
        return ans;
    }
};

202.快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

class Solution {
public:
    bool isHappy(int n) {
        long long int tmp=0;
        unordered_set<int> h;
        h.insert(n);
        while(n!=1){
            tmp=0;
            while(n){
                tmp+=pow(n%10,2);
                n/=10;
            }
            if(h.find(tmp) != h.end()) return false;
            h.insert(tmp);
            n=tmp;
        }
        return true;
    }
};

栈和队列

在这里插入图片描述
忘记判断栈是否为空了;特例:']'
if(!st.empty() && st.top()=='(' && s[i]==')') st.pop();

class Solution {
public:
    bool isValid(string s) {
        stack<char> st;
        for(int i=0;i<s.size();i++){
            if(s[i]=='(' || s[i]=='[' || s[i]=='{') st.push(s[i]);
            else{
                if(!st.empty() && st.top()=='(' && s[i]==')') st.pop();
                else if(!st.empty() && st.top()=='[' && s[i]==']') st.pop();
                else if(!st.empty() && st.top()=='{' && s[i]=='}') st.pop();
                else return false;
            }
        }
        if(st.empty()) return true;
        else return false;
    }
};

在这里插入图片描述

不知道std::string 类本身就提供了类似「入栈」和「出栈」的接口
if那写错了:一样的就pop,不一样的push进栈

class Solution {
public:
    string removeDuplicates(string s) {
        string stk;
        for(int i=0;i<s.size();i++){
            if(!stk.empty() && s[i]==stk.back()) stk.pop_back();
            else   stk.push_back(s[i]);
        }
        return stk;
    }
};

单调栈

239.滑动窗口最大值
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值


[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7

class Solution {
private:
    class MyQueue{
        public:
            deque<int> que;
            // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
            // 同时pop之前判断队列当前是否为空。
            void pop(int value) {
                if (!que.empty() && value == que.front()) {
                    que.pop_front();
                }
            }
            // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
            void push(int value){
                while(!que.empty() && value > que.back()){
                    que.pop_back();
                }
                que.push_back(value);
            }
            int front(){
                return que.front();
            }
    };

public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue q;
        vector<int> res;
        for(int i=0;i<nums.size();i++){
            q.push(nums[i]);
            if(i>=(k-1)) {
                // 滑动窗口移除最前面元素,往后移一位,前面要移除nums[0]
                res.push_back(q.front());
                q.pop(nums[i-(k-1)]);
                
            }
        }
        return res;
    }
};

时间复杂度 O ( n ) O(n) O(n)
nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,所以整体的复杂度还是 O(n)。空间复杂度因为我们定义一个辅助队列,所以是O(k)。

  1. 题解中单调队列里的pop和push接口,仅适用于本题哈。单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列
  2. deque是可以两边扩展的,双端队列,C++中deque是stack和queue默认的底层实现容器
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值