代码随想录算法训练营第2天

代码随想录算法训练营第2天 | 209.长度最小的子数组,螺旋矩阵 Ⅱ,区间和,开发商购买土地,总结

一、刷题部分

1.1 209.长度最小的子数组

1.1.1 题目描述

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

找出该数组中满足其总和大于等于 target 的长度最小的

子数组

[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度如果不存在符合条件的子数组,返回 0

示例 1:

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

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

1.1.2 初见想法

这题用两个指针框住一个一个子数组,同时用一个变量维持子数组里的和,如果和太小就向右指针向右扩大数组,和太大就左指针向右缩小数组。子数组右指针打到总体的右端时就不会继续扩大了,最后就是左指针右移来缩小数组到数组和小于目标值。代码如下:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0, right = 0;
        int sum = nums[0];
        int ans = nums.size() + 1;
        while(right < nums.size()){
            if(sum < target){
                right ++;
                if(right == nums.size()) break;
                sum += nums[right];
            }else{
                if(ans > (right - left + 1)){
                    ans = right - left + 1;
                }
                sum -= nums[left];
                left ++;
            }
        }
        return ans == nums.size() + 1 ? 0 : ans;
    }
};

1.1.3 看录后想法

录采用了滑动窗口的思想来做这道题,我认为这个和我前面的想法底层思想是一样的,不过确实使用录的做法会更加省心,抽象出一个窗口实体更有利于理解整个过程。因此采用滑动窗口的想法再做一次:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int sum = 0;//窗口内的和
        int bgn = 0;//窗口起始位置
        int wln = 0;//窗口内元素个数
        int minLength = 100001;

        for (int i = 0; i < nums.size(); i++) {
            //窗口持续向右扩大
            sum += nums[i];
            wln ++;
            //够大的时候就缩小,然后不断更新minlength值
            while (sum >= target) {
                if (minLength > wln) {
                    minLength = wln;
                }
                sum -= nums[bgn++];
                wln --;
            }
        }

        return minLength == 100001 ? 0 : minLength;
    }
};

1.1.4 遇到的困难

在自己写的过程中,总是要考虑开始和结束时的各种特殊情况,后来发现如果抽象出一个滑动窗口的实体就会变得比较清晰。其他也不多说了,本题相当是双指针的一个应用。

1.2 螺旋矩阵 Ⅱ

1.2.1 题目描述

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

示例 1:

输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]

示例 2:

输入:n = 1
输出:[[1]]

提示:

  • 1 <= n <= 20

1.2.2 初见想法

本题感觉没有太好的思路,直接暴力模拟吧,可以把问题拆解成一圈一圈的工作。以 3*3 的矩阵为例,第一圈就是最外面,边长为 3,第2圈就是中间,边长为1。可以看到边长初值是 n,然后每次递减2,可能有 2 个结果:递减到 1 结束,递减到 0 结束。因此这里的条件判断要写好。

再来就是每一圈的起始坐标,会发现一般是 (0,0) (1,1) (2,2) 这样,每次递增 1 个数,这个数与 边长 有关,所以给 边长 添加一个 1/2 的系数,又 边长 是递减,这个是递增,所以它应该是 -1/2系数 再添加一个常数修正一下就得到起始坐标和每圈边长的关系 x = y = (n - 边长) / 2。

主体逻辑实现完毕,接下来去设计一下填写每圈的函数:

首先考虑需要什么参数,应该是需要 矩阵本身vector<vector<int>> & matrix,初始值、初始坐标值、本圈的边长。

对于填写数字的步骤,又可以分为四条边来做4步,界定好边界就可以做了。

代码如下:

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> matrix(n,vector<int>(n));
        int num = 1;
        for(int i = n; i > 0; i -= 2){
            num = OneCircle(matrix, (n - i)/2, (n - i)/2, num, i);
          }
        return matrix;
    }
    int OneCircle(vector<vector<int>> & matrix, int i, int j, int num, int n){
        //cout << "i = " << i << " j = " << j << " num = " << num << " n = " << n << endl;
        //定义一个基本操作,对一圈遍历一次
        //i,j是初始坐标,num是初始应填的元素,n是这个矩形的边长
        //返回值是填的最后一个元素填完之后下一个元素。

        //对边长为1的特殊情况处理
        if(n == 1){
            matrix[i][j] = num;
            return ++num;
        }
        //对边长为2的特殊情况处理
        if(n == 2){
            matrix[i][j] = num;
            matrix[i][j + 1] = num + 1;
            matrix[i + 1][j + 1] = num + 2;
            matrix[i + 1][j] = num + 3;
            return num + 4;
        }
        //用x,y记录初始位置
        int x = i, y = j;
        //cout << "x = " << x << " y = " << y << endl;
        //从左上角画到右上角
        for(; j <= (y + n - 1); j++)
            matrix[i][j] = num++;
        j--;
        //从右上角画到右下角
        for(i = i + 1; i <= x + n - 1; i++)
            matrix[i][j] = num++;
        //从右下角画到左下角
        i--;
        for(j = j - 1; j >= y; j--)
            matrix[i][j] = num++;
        j++;
        //从右下角画到最终位置
        for(i = i - 1; i >= x + 1; i--)
            matrix[i][j] = num++;
        i++;
        return num;
    }
};

1.2.3 看录后想法

录里在强调这个边界定义问题,我觉得挺好的。不过我在自己写的时候没有遇到这种问题,可能是由于本科学习写代码的时候所有代码都是统一使用左闭右闭的定义因此形成了习惯。

录的解法和我的解法基本一致,都是暴力模拟,我多做了一点计算因而代码逻辑更简单,录的解法整体写下来我觉得第一遍写会很困难。

1.2.4 遇到的困难

vector的二维表示不会写,于是上网找了一下资料学习了一番。

1.3 区间和

1.3.1 题目描述

给定一个整数数组 Array,请计算该数组在每个指定区间内元素的总和。

输入描述

第一行输入为整数数组 Array 的长度 n,接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间下标:a,b (b > = a),直至文件结束。

输出描述

输出每个指定区间内元素的总和。

输入示例
5
1
2
3
4
5
0 1
1 3
输出示例
3
9
提示信息

数据范围:
0 < n <= 100000

1.3.2 初见想法

确实感觉 ACM 模式有些生疏,本题虽然简单但是要好好练一下:

#include <iostream>
#include <vector>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<int> nums(n, -1);
    for (int i = 0; i < n; i++) {
        cin >> nums[i];
    }
    //输入数组完毕,开始计算区间和
    int left, right;
    while (cin >> left >> right) {
        //合法性检查
        if (left > right || left < 0 || right >= nums.size()) {
            return -1;
        }
        //开始加和
        int sum = 0;
        for (int i = left; i <= right; i++) {
            sum += nums[i];
        }
        cout << sum << endl;
    }
    return 0;
}

1.3.3 看录后想法

原来可以用前缀和来节省时间,确实之前学习数组的时候学的前 n 项和相减就可以得到部分列的和。这个想法很好,以后遇到类似的题目我应该想到。

#include<iostream>
#include<vector>
using namespace std;

int main(){
    int n;
    cin >> n;
    vector<int> array(n, 0);
    for(int i = 0; i < n; i++){
        cin>>array[i];
    }
    //计算前缀和
    vector<int> s(n, 0);
    s[0] = array[0];
    for(int i = 1; i < n; i++){
        s[i] = s[i - 1] + array[i];
    }
    int left, right;
    while(cin>>left>>right){
        cout<< s[right] - s[left] + array[left] << endl;
    }
    return 0;
}

1.3.4 遇到的困难

对于本题的不知道有多少行左右区间下标的输入方式,我发现我不知道该怎么写这一块的代码,看了一下题解学习了一下输入方式。

1.4 开发商购买土地

1.4.1 题目描述

在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。

现在,需要将这个城市区域的所有区块分配给 A 公司和 B 公司。

然而,由于城市规划的限制,只允许将区域按横向或纵向划分成两个子区域,而且每个子区域都必须包含一个或多个区块。 为了确保公平竞争,你需要找到一种分配方式,使得 A 公司和 B 公司各自的子区域内的土地总价值之差最小。

注意:区块不可再分。

输入描述

第一行输入两个正整数,代表 n 和 m。

接下来的 n 行,每行输出 m 个正整数。

输出描述

请输出一个整数,代表两个子区域内土地总价值之间的最小差距。

输入示例
3 3
1 2 3
2 1 3
1 2 3
输出示例
0
提示信息

如果将区域按照如下方式划分:

1 2 | 3
2 1 | 3
1 2 | 3

两个子区域内土地总价值之间的最小差距可以达到 0。

数据范围:

1 <= n, m <= 100;
n 和 m 不同时为 1。

1.4.2 初见想法

有了上一题的经验,很容易想到借助前缀和来完成这种多次的比较。就是对于二维数组需要做两个方向的前缀和,然后还要在两个方向求价值差,逻辑一下子就变得非常复杂。写一下锻炼一下自己的代码能力吧:

#include<iostream>
#include<vector>
using namespace std;
#define MAX_INT 2147483647
int main(){
    //定义,初始化,输入 部分
    int n,m;
    cin >> n >> m;
    vector<vector<int>> matrix(n,vector<int>(m));
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cin>>matrix[i][j];
        }
    }
    int min_dif = MAX_INT;
    
    //求竖向前缀和矩阵
    vector<vector<int>> S_shu_mx(n,vector<int>(m));
    for(int j = 0; j < m; j++){
        S_shu_mx[0][j] = matrix[0][j];
    }
    for(int i = 1; i < n; i++){
        for(int j = 0; j < m; j++){
            S_shu_mx[i][j] = S_shu_mx[i - 1][j] + matrix[i][j];
        }
    }
    
    //求横向前缀和矩阵
    vector<vector<int>> S_hng_mx(n,vector<int>(m));
    for(int i = 0; i < n; i++){
        S_hng_mx[i][0] = matrix[i][0];
    }
    for(int j = 1; j < m; j++){
        for(int i = 0; i < n; i++){
            S_hng_mx[i][j] = S_hng_mx[i][j - 1] + matrix[i][j];
        }
    }
    
    //寻找横向划分的最小差值
    for(int i = 0; i < n; i++){
        int dif = 0;
        for(int j = 0; j < m; j++){
            dif += (S_shu_mx[n - 1][j] - 2 * S_shu_mx[i][j]);
        }
        dif = dif < 0 ? -dif : dif;
        min_dif = min_dif > dif ? dif : min_dif;
    }
    
    //寻找竖向划分的最小差值
    for(int j = 0; j < m; j++){
        int dif = 0;
        for(int i = 0; i < n; i++){
            dif += (S_hng_mx[i][m-1] - 2 * S_hng_mx[i][j]);
        }
        dif = dif < 0 ? -dif : dif;
        min_dif = min_dif > dif ? dif : min_dif;
    }
    
    cout << min_dif <<endl;
}

1.4.3 看录后想法

果然和我想的是一样的。不过录里还提供了一个不使用前缀和的算法,我研究一下先。

也是一种思路吧,不过我觉得不会像前缀和那样清晰。感觉没有写一遍的必要了。今天的时间不是很少充裕。

1.4.4 遇到的困难

逻辑稍微有点复杂,但是在草稿纸上一画就清楚了很多,因此没要太多困难。

1.5 数组总结篇

数组需要注意的点在于:

  1. 区间定义要清晰(左闭右开、左闭右闭)
  2. 数组有一些典型题目:二分法、双指针、滑动窗口、前缀和
  3. 模拟写法多用到数组,因此熟练掌握是必要的
  4. vector的一些用法需要掌握,这是在写 C++ 代码必不可少的东西

二、总结与回顾

今天是把数组部分给结束了,虽然觉得意犹未尽但是我的时间确实不够再去多刷太多题目了,还有许多其他未完成的事情等着我。

数组这里的一些总结性的想法在前面的 1.5 部分已经有所阐述了。就不多说了。

到了第二天这个题量感觉马上就上来了,希望后面难度增大的前提下可以顺利坚持下去。

今日用时:约5h

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