一、长度最小的子数组
题目: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
题目:给你一个正整数 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就好啦,很开心!!!
三、 区间和
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) 技术,核心原理是:
- 先预处理原数组,生成一个前缀和数组,存储从数组起始位置到每个下标处的累加和。
- 对于任意查询区间
[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); // 输出区间和
}
四、开发商购买土地
- 计算逻辑示例:
对于数组[1,2,3,4,5]
,查询区间[1,3]
时:
p[3] = 1+2+3+4 = 10
,p[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)步骤:
-
计算总价值:先遍历整个
n×m
矩阵,求出所有区块的总价值total
(用于快速计算两个子区域的价值:若一个子区域价值为sum
,另一个则为total - sum
,差值为sum - (total - sum)=2×sum - total
。 -
遍历横向划分(按行切):
横向划分的可能位置有n-1
个(比如 3 行区域,可在第 1 行下、第 2 行下切,分成 “1 行 + 2 行”“2 行 + 1 行”)。
对每个位置i
(表示 “前i
行作为一个子区域”,1≤i≤n-1
):- 遍历前
i
行的所有区块,累加得到子区域价值sum_row
; - 计算差值
2×sum_row - total
,记录当前最小值。
- 遍历前
-
遍历纵向划分(按列切):
纵向划分的可能位置有m-1
个(比如 3 列区域,可在第 1 列右、第 2 列右切)。
对每个位置j
(表示 “前j
列作为一个子区域”,1≤j≤m-1
):- 遍历前
j
列的所有区块,累加得到子区域价值sum_col
; - 计算差值
|2×sum_col - total|
,更新当前最小值。
- 遍历前
-
输出结果:所有划分位置的差值中,最小的那个即为答案。
(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:输入数据与初始化
-
读取输入的行数
n
和列数m
,定义二维数组nums
用于存储每个区块的土地价值。 -
定义两个前缀和数组:
-
isum
(长度n+1
):用于存储 “前i
行的总价值”(i
从 0 到n
,isum[0]
初始为 0,方便后续累加); -
jsum
(长度m+1
):用于存储 “前j
列的总价值”(j
从 0 到m
,jsum[0]
初始为 0)。
-
步骤 2:构建行前缀和数组isum
-
遍历矩阵的每一行(
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
-
遍历矩阵的每一列(
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:计算总价值与最小差值
-
确定所有区块的总价值
total
:直接取isum[n]
(因isum[n]
为前n
行的总价值,即整个矩阵的价值)。 -
初始化最小差值
minDiff
为Integer.MAX_VALUE
(用于后续比较更新)。 -
遍历横向划分位置(按行切):
-
横向划分有
n-1
种可能(划分位置为 “前 1 行” 到 “前n-1
行”),遍历i
从 1 到n-1
:-
前
i
行的总价值直接通过isum[i]
获取,计算当前划分的差值:|2 * isum[i] - total|
(因另一个子区域价值为total - isum[i]
,差值为两者差值的绝对值)。 -
用当前差值更新
minDiff
:minDiff = Math.min(minDiff, 计算出的差值)
。
-
-
-
遍历纵向划分位置(按列切):
-
纵向划分有
m-1
种可能(划分位置为 “前 1 列” 到 “前m-1
列”),遍历j
从 1 到m-1
:-
前
j
列的总价值直接通过jsum[j]
获取,计算当前划分的差值:|2 * jsum[j] - total|
。 -
用当前差值更新
minDiff
:minDiff = 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天我可以坚持下去,并且二刷,加油!!!