代码随想录算法训练营第二天|长度最小的子数组、螺旋矩阵II、 区间和、开发商购买土地

一、长度最小的子数组

题目:209. 长度最小的子数组 - 力扣(LeetCode)209. 长度最小的子数组 - 力扣(LeetCode)题目:209. 长度最小的子数组 - 力扣(LeetCode)

题目:给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

  • 输入:s = 7, nums = [2,3,1,2,4,3]
  • 输出:2
  • 解释:子数组 [4,3] 是该条件下的长度最小的子数组
1.我的代码:

    官方叫做滑动窗口,不断的调节子序列的起始位置和终止位置,但是我是以双指针移动作为思路完成的代码,其实是一样哒~

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        int sum = 0;//和
        int start = 0;//起始位置
        int result = nums.length + 1;  // 初始化一个不可能达到的长度

        for (int i = 0; i < nums.length; i++) {//终止位置指针
            sum += nums[i];
            while (sum>=s){
                //写起始位置指针
                int len = i-start+1;//len是起始位置和终止位置之间长度
                result = Math.min(result, len);
                sum -=nums[start];
                start++;
            }
        }
        // 如果result未被更新,说明没有符合条件的子数组
        return result == nums.length + 1 ? 0 : result;
    }
}
2.小思路:

二、螺旋矩阵II

59. 螺旋矩阵 II - 力扣(LeetCode)

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

示例 1:

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

示例 2:输入:n = 1 输出:[[1]]

1.我的代码
class Solution {
    public int[][] generateMatrix(int n) {
        int[][] nums = new int[n][n];
        int startx = 0;//每一圈起点
        int starty = 0;
        int a = 1;//每完成一圈加 1。
        int count = 1;  // 矩阵中需要填写的数字
        int loop = 1; // 记录当前的圈数
        int k, i; // k 代表列, i 代表行;

        while (loop<=n/2){
            //从左向右(顶部)
            for (k = starty; k < n-a; k++) {
                nums[startx][k] = count++;
            }
            //从右上角到下面(右边列)
            for (i = startx; i < n-a ; i++) {
                nums[i][k] = count++;
            }
            //从右下到左(底部)
            for (; k >starty ; k--) {
                nums[i][k] = count++;
            }
            //从左下到上(左边列)
            for (;i>startx;i--) {
                nums[i][k] = count++;

            }
            startx++;
            starty++;
            a++;
            loop++;
        }
        if(n%2==1){//n为奇数,最中间的数字
            nums[startx][starty] = count;

        }
        return nums;

    }
}
2.核心逻辑:

按「圈」填充,每圈分 4 步(右→下→左→上)

螺旋矩阵的填充规律是:从外层到内层,每一圈都按顺时针方向分 4 步填充

3.心得

在知道题意时候很无措,不知道怎么完成这个顺时针的输出,大概看了一下哔哩哔哩视频讲解一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili,一下子恍然大悟,就是四次循环主要,二维数组的感觉++输出,还有一个点就是n是奇数时候,怎木办?相当于最中间的数字没有打印出来,那就可以将nums[startx][starty]在输出一次count就好啦,很开心!!!

三、 区间和

58. 区间和(第九期模拟笔试)

1.题目描述

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

输入描述

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

输出描述

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

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

数据范围:
0 < n <= 100000

2.暴力破解(会超时)

  在我看到题目感觉非常简单,属于暴力了,完成录入代码就很简单算和,但是提交后会发现超时。

import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] nums = new int[n];
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
        }
        while (sc.hasNext()){
            int start = sc.nextInt();
            int end = sc.nextInt();
            int sum = 0;
            for (int i = start; i <=end; i++) {
                sum+=nums[i];
            }
            System.out.println(sum);
        }
    }
}
3.官方解法(前缀和

第一次接触前缀和,一下子省去很多遍历

(1)代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] nums = new int[n];
        int[] p = new int[n];
        int sum=0;
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
            sum+=nums[i];
            p[i] = sum;

        }
        int output;
        while (sc.hasNext()){
            int start = sc.nextInt();
            int end = sc.nextInt();
           if(start==0){
               output = p[end];
           }else{
               output = p[end]-p[start-1];
           }
            System.out.println(output);
        }
    }
}

采用前缀和(Prefix Sum) 技术,核心原理是:

  1. 先预处理原数组,生成一个前缀和数组,存储从数组起始位置到每个下标处的累加和。
  2. 对于任意查询区间 [start, end],利用前缀和数组快速计算总和,无需重复遍历区间内元素。
(2)代码流程详解
1> 输入数组并构建前缀和数组
// 读取数组长度
int n = sc.nextInt();
int[] nums = new int[n];  // 存储原数组
int[] p = new int[n];     // 存储前缀和数组
int sum = 0;              // 临时累加变量

for (int i = 0; i < n; i++) {
    nums[i] = sc.nextInt();  // 读取数组元素
    sum += nums[i];          // 累加计算前缀和
    p[i] = sum;              // 存储前缀和(p[i] = nums[0] + nums[1] + ... + nums[i])
}
  • 前缀和数组 p 的定义p[i] 表示原数组 nums 中从下标 0 到 i 的所有元素之和。
    例如:若 nums = [1,2,3,4,5],则 p = [1,3,6,10,15]
2.>处理区间查询并输出结果
while (sc.hasNext()) {  // 循环读取区间,直至输入结束
    int start = sc.nextInt();  // 区间起始下标
    int end = sc.nextInt();    // 区间结束下标
    
    int output;
    if (start == 0) {
        // 区间从0开始时,直接使用p[end](0到end的累加和)
        output = p[end];
    } else {
        // 区间从start开始时,用p[end] - p[start-1](抵消0到start-1的和)
        output = p[end] - p[start - 1];
    }
    System.out.println(output);  // 输出区间和
}

四、开发商购买土地

44. 开发商购买土地(第五期模拟笔试)

  • 计算逻辑示例
    对于数组 [1,2,3,4,5],查询区间 [1,3] 时:
    p[3] = 1+2+3+4 = 10p[0] = 1,因此区间和为 10 - 1 = 9(即 2+3+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。

2.思路 1:暴力方法(基础思路,适合理解)

核心逻辑:遍历所有可能的 “横向划分” 和 “纵向划分” 位置,计算每个位置的差值,取最小值。

(1)步骤:
  1. 计算总价值:先遍历整个 n×m 矩阵,求出所有区块的总价值 total(用于快速计算两个子区域的价值:若一个子区域价值为 sum,另一个则为 total - sum,差值为 sum - (total - sum)=2×sum - total

  2. 遍历横向划分(按行切)
    横向划分的可能位置有 n-1 个(比如 3 行区域,可在第 1 行下、第 2 行下切,分成 “1 行 + 2 行”“2 行 + 1 行”)。
    对每个位置 i(表示 “前 i 行作为一个子区域”,1≤i≤n-1):

    • 遍历前 i 行的所有区块,累加得到子区域价值 sum_row
    • 计算差值 2×sum_row - total,记录当前最小值。
  3. 遍历纵向划分(按列切)
    纵向划分的可能位置有 m-1 个(比如 3 列区域,可在第 1 列右、第 2 列右切)。
    对每个位置 j(表示 “前 j 列作为一个子区域”,1≤j≤m-1):

    • 遍历前 j 列的所有区块,累加得到子区域价值 sum_col
    • 计算差值 |2×sum_col - total|,更新当前最小值。
  4. 输出结果:所有划分位置的差值中,最小的那个即为答案。

(2)代码
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int [][] nums = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                nums[i][j] = sc.nextInt();
            }
        }
        int total = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                total += nums[i][j];
            }
        }
        // 初始化最小差值(用一个较大的数,确保后续能被更新)
        int minDiff = Integer.MAX_VALUE;
        for (int i = 1; i <= n - 1; i++) {
            int sumRow = 0;
            // 累加前i行的所有元素
            for (int row = 0; row < i; row++) {
                for (int col = 0; col < m; col++) {
                    sumRow += nums[row][col];
                }
            }
            // 计算当前划分的差值:|2*sumRow - total|
            int diff = Math.abs(2 * sumRow - total);
            // 更新最小差值
            if (diff < minDiff) {
                minDiff = diff;
            }
        }

        // 步骤3:遍历所有纵向划分位置(按列切)
        // 纵向划分有m-1种可能:前1列、前2列、...、前m-1列
        for (int j = 1; j <= m - 1; j++) {
            int sumCol = 0;
            // 累加前j列的所有元素
            for (int col = 0; col < j; col++) {
                for (int row = 0; row < n; row++) {
                    sumCol += nums[row][col];
                }
            }
            // 计算当前划分的差值:|2*sumCol - total|
            int diff = Math.abs(2 * sumCol - total);
            // 更新最小差值
            if (diff < minDiff) {
                minDiff = diff;
            }
        }

        // 输出最小差值
        System.out.println(minDiff);
    }
}


3.思路 2:前缀和方法(优化效率,适合大规模数据)

   暴力方法的问题:若 n 和 m 较大(比如 n=m=1000),遍历每个划分位置时需重复计算 “前 i 行”“前 j 列” 的总和(时间复杂度 O(n×m×(n+m))),效率极低。

(1)步骤

步骤 1:输入数据与初始化

  1. 读取输入的行数n和列数m,定义二维数组nums用于存储每个区块的土地价值。

  2. 定义两个前缀和数组:

    • isum(长度n+1):用于存储 “前i行的总价值”(i从 0 到nisum[0]初始为 0,方便后续累加);

    • jsum(长度m+1):用于存储 “前j列的总价值”(j从 0 到mjsum[0]初始为 0)。

步骤 2:构建行前缀和数组isum

  1. 遍历矩阵的每一行(i从 0 到n-1):

    • 定义临时变量currentisum,用于累加当前行(第i行)所有区块的价值:遍历该行的每一列(j从 0 到m-1),将nums[i][j]累加到currentisum中。

    • 完成当前行的累加后,更新isum数组:isum[i+1] = isum[i] + currentisum
      (例如:isum[1]为第 0 行的总价值,isum[2]为第 0 行 + 第 1 行的总价值,以此类推,最终isum[n]即为所有区块的总价值)。

步骤 3:构建列前缀和数组jsum

  1. 遍历矩阵的每一列(j从 0 到m-1):

    • 定义临时变量currentjsum,用于累加当前列(第j列)所有区块的价值:遍历该列的每一行(i从 0 到n-1),将nums[i][j]累加到currentjsum中。

    • 完成当前列的累加后,更新jsum数组:jsum[j+1] = jsum[j] + currentjsum
      (例如:jsum[1]为第 0 列的总价值,jsum[2]为第 0 列 + 第 1 列的总价值,与isum逻辑一致)。

步骤 4:计算总价值与最小差值

  1. 确定所有区块的总价值total:直接取isum[n](因isum[n]为前n行的总价值,即整个矩阵的价值)。

  2. 初始化最小差值minDiffInteger.MAX_VALUE(用于后续比较更新)。

  3. 遍历横向划分位置(按行切):

    • 横向划分有n-1种可能(划分位置为 “前 1 行” 到 “前n-1行”),遍历i从 1 到n-1

      • i行的总价值直接通过isum[i]获取,计算当前划分的差值:|2 * isum[i] - total|(因另一个子区域价值为total - isum[i],差值为两者差值的绝对值)。

      • 用当前差值更新minDiffminDiff = Math.min(minDiff, 计算出的差值)

  4. 遍历纵向划分位置(按列切):

    • 纵向划分有m-1种可能(划分位置为 “前 1 列” 到 “前m-1列”),遍历j从 1 到m-1

      • j列的总价值直接通过jsum[j]获取,计算当前划分的差值:|2 * jsum[j] - total|

      • 用当前差值更新minDiffminDiff = Math.min(minDiff, 计算出的差值)

步骤 5:输出结果

最终minDiff即为所有划分方式中,两个子区域土地总价值的最小差距,将其输出即可。

(2)代码
import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[][] nums = new int[n][m];
        int[] isum = new int[n+1];
        int[] jsum = new int[m+1];
        for (int i = 0; i < n; i++) {
            int currentisum = 0;;
            for (int j = 0; j < m; j++) {
                nums[i][j] = sc.nextInt();
                currentisum +=nums[i][j];
            }
            // 构建行前缀和:前i+1行的总和 = 前i行的总和 + 当前行的总和
            isum[i + 1] = isum[i] + currentisum;
        }
        for (int j = 0; j < m; j++) {
            int currentjsum = 0;  // 临时存储第j列的总和
            for (int i = 0; i < n; i++) {
                currentjsum += nums[i][j];  // 累加当前列的元素
            }
            // 构建列前缀和:前j+1列的总和 = 前j列的总和 + 当前列的总和
            jsum[j + 1] = jsum[j] + currentjsum;
        }
        // 总价值 = 前n行的总价值(即rowPrefix[n])
        int total = isum[n];
        int minDiff = Integer.MAX_VALUE;
        // 横向划分(按行切)
        for (int i = 1; i <= n - 1; i++) {
            minDiff = Math.min(minDiff, Math.abs(2 * isum[i] - total));
        }

        // 纵向划分(按列切)
        for (int j = 1; j <= m - 1; j++) {
            minDiff = Math.min(minDiff, Math.abs(2 * jsum[j] - total));
        }
        System.out.println(minDiff);



    }
}
        

五、心得

  学习了俩天,将第一章数组学习完毕从二分法到双指针,从滑动窗口到螺旋矩阵,再到区间和以及买土地,我是Java菜鸟一枚,原来看到题目就是暴力破解,但时间复杂度确实不行,效率低,在接触算法后,也是学习到很多,像今天学习时间较长,脑子还是不灵活,特别是理解螺旋矩阵时候有点转不过来,但是是最后还是掌握了,希望为其67天我可以坚持下去,并且二刷,加油!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值