代码随想录算法训练营day2 | 209.长度最小的子数组、59.螺旋矩阵Ⅱ、58.区间和、44.开发商购买土地

209.长度最小的子数组
(滑动窗口)

解法一:自创暴力解,先遍历完所有长度为1的子数组,再遍历所有长度为2的子数组,以此类推

点击查看代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        //winlen为窗口大小,从小到大遍历
        for(int winlen = 1; winlen <= nums.size(); ++winlen) {
            //求出当前窗口下的各子数组的和
            for(int i = 0; i <= nums.size() - winlen; ++i) {
                int sum = 0;
                for(int j = i; j < i + winlen; ++j) {
                    sum += nums[j];
                }
                if(sum >= target) return winlen; //找到符合条件的子数组后直接返回长度
            }
        }
        return 0;  //若前面都未返回则说明不存在符合条件的子数组
    }
};

上述解法会超出时间限制

解法二:代码随想录暴力解,固定左窗口,递增右窗口,找到符合条件的子数组后,再右移左窗口重新寻找符合条件的子数组

点击查看代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minlen = INT_MAX; //INT_MAX为int型数据可表示的最大数
        for(int left = 0;left < nums.size(); ++left) { //left控制左窗口边界
            int sum = 0;
            //right控制右窗口边界
            for(int right = left; right < nums.size(); ++right) {
                sum += nums[right];
                if(sum >= target) {
                    minlen = min(minlen, right - left + 1);
                    break;
                 //已经找到的left起始的符合条件的最小子数组,递增左窗口进行下一轮寻找
                }
            }
        }
        if(minlen == INT_MAX) return 0;
        else return minlen;
    }
};

双重循环,外层循环控制左窗口,内层循环控制右窗口
该方法的问题在于,每次递增左窗口后,右窗口均从左窗口开始递增,从而会对不符合条件的子数组进行重复检验,导致时间复杂度较高,例如:
[1,1,1,1,1,1] target = 4
当左窗口left = 0时,通过该轮循环证明了nums[0] + nums[1] +nums[2]不符合条件
当左窗口更新到left = 1时,该轮循环仍然冗余计算并判断nums[1]、nums[1] + nums[2]子数组,没有充分利用前述的计算证明结果

解法三:单层循环控制右窗口O(n)

点击查看代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int minlen = INT_MAX;
        int left = 0; //左窗口从0开始
        int sum = 0; 
        for(int right = 0; right < nums.size(); ++right) {
            sum += nums[right]; //右窗口右滑sum值增加
            while(sum >= target) {
                minlen = min(minlen, right - left + 1);//更新符合条件的最小子数组长度
                //sum值足够时,尝试右滑左窗口,看看是否有长度更小的符合条件的子数组
                sum -= nums[left++]; 
            }
            //子数组sum值不够时,右窗口右滑
        }
        if(minlen == INT_MAX) return 0;
        else return minlen;
    }
};

本方法解决了方法二的缺点,充分利用了已计算证明不符合条件的子数组,不会进行冗余计算,左右窗口均从左至右滑动一轮,时间复杂度为O(n)
核心点:
1、固定左窗口left,当前子数组sum值不够时,右窗口right才会右移,右移到第一次sum足够为止,即找到了以left起始的符合条件的最小子数组,后续right没有左移的必要,因为sum不够才会右移,再左移必然导致sum不够,故充分利用了前述计算结果
2、当前子数组sum值足够时,说明找到了以left为起点的符合条件的最小子数组,此时可以右滑左窗口,查看长度变小后sum是否仍然足够,若仍然足够则更新minlen,再继续右移left,后续left窗口没有左移的必要,因为当前left以前的元素均找到了以他们为起点的最小子数组,否则left窗口不会滑到当前位置,left右移至sum值不够时,再回到1

59.螺旋矩阵Ⅱ
模拟

点击查看代码
class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> result(n, vector<int>(n, 0));
        int num = 1;
        for(int l = 0, r = n-1, u = 0, d = n -1; num <= n * n; ++l, --r, ++u, --d) {
            for(int i = l; i < r; ++i) { //填充最上边一行
                result[u][i] = num++;
                //cout << result[u][i];
            }
            
            for(int i = u; i < d; ++i) { //填充最右边一列
                result[i][r] = num++;
                //cout << result[i][r];
            }

            for(int i = r; i > l; --i) { //填充最下边一行
                result[d][i] = num++;
                //cout << result[d][i];
            }

            for(int i = d; i > u; --i) { //填充最左边一列
                result[i][l] = num++;
                //cout << result[i][l];
            }

            if(n % 2 == 1 && num == n * n) break;
        }
        if(n % 2 == 1) result[n/2][n/2] = n * n;
        return result;
    }
};

关键点:设立左边界l 右边界r 上边界u 下边界d,每次循环填充一圈,填充完一圈后所有边界往内部收缩一个单位
每个内部for循环填充的均为左闭右开区间,即每次填充一行/列,不填充最后一个元素
另外,应该区分n为奇数和偶数的情况,当n为奇数时,num遍历到n*n时,此时l = r, u = d,故内部4个for循环均不会执行,故会死循环,需要通过break跳出循环,然后在循环外额外填充该元素,将该元素填充在整个矩阵最中间的位置,即result[n/2][n/2] = n * n
当n为偶数时,循环结构可以填充完所有的格子,无需再额外填充

58.区间和
前缀和技巧
注意本题采用ACM模式,即自行处理输入输出,需要main函数
题目链接:https://kamacoder.com/problempage.php?pid=1070

解法一:暴力求解,通过循环求区间和

点击查看代码
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> Array(n);
    for(int i = 0; i < n; ++i) {
        cin >> Array[i];
    }
    int a = 0, b = 0;
    while(cin >> a >>b) {
        int sum = 0;
        for(int i = a; i <= b; ++i)
            sum += Array[i];
        cout << sum << endl;
    }
}

本解法会超出时间限制
缺点:当查询量大即请求区间和的次数非常多时,每次求解都需要利用循环从头开始累加计算,非常低效。

解法二:
利用前缀和思想

点击查看代码
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n;
    cin >> n;
    vector<int> Array(n);
    vector<int> prefix_sum(n);
    int psum = 0;
    for(int i = 0; i < n; ++i) {
        cin >> Array[i];
        prefix_sum[i] = psum += Array[i];
    }
    int a = 0, b = 0;
    while(cin >> a >>b) {
        int sum = 0;
        if(a == 0) sum = prefix_sum[b];
        else sum = prefix_sum[b] - prefix_sum[a - 1];
        cout << sum << endl;
    }
}

通过计算前缀和(前1个元素的总和、前2个元素的总和、...、前n个元素的总和),存储再前缀和表prefix_sum中,后续每次查询区间和时,无需再通过循环计算,而只需通过查询前缀和表进行一次简单运算即可,当查询量超大时,这种方法更为高效。

44.开发商购买土地
待后续解决

2025/02/13

posted @   coder小杰  阅读(497)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示