209 长度最小的子数组
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 <= 10^91 <= nums.length <= 10^51 <= nums[i] <= 10^4
2.算法分析
从直觉上说,这道题可以用时间复杂度为的暴力算法解决,即用两层for循环,外层循环用于遍历数组中的元素,内层循环用于从某个元素开始进行累计求和,寻找满足条件的子数组。暴力算法的代码实现为:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int minLen = 0;
for(int i = 0; i < nums.length; i++) {
int sum = 0;
int j = i;
for(; j < nums.length; j++) {
sum += nums[j];
if(sum >= target) {
break;
}
}
if(sum >= target) {
if(minLen == 0 || (minLen > 0 && (j - i + 1) < minLen)) {
minLen = j - i + 1;
}
}
}
return minLen;
}
}
但是很遗憾,由于数组长度最大为,因此使用暴力算法会出现超时。要降低时间复杂度,可以考虑使用双指针法的变种——滑动窗口法。滑动窗口法本质上还是用两个指针在一层for循环内同时完成暴力算法中两层for循环的操作,在这里就是同时完成数组元素遍历和累计求和:
end指针表示窗口/子数组的终点;start指针表示窗口/子数组的起点。
在滑动窗口法中,首先不断向后移动end指针,在这个过程中同时完成数组元素遍历和累计求和操作,当窗口内的元素和大于等于目标值时,就将start当前位置的元素值从累计和中减去,并将start向后移,缩小窗口大小,以寻找最小窗口(因为题目要求寻找最小子数组)。注意到和暴力算法不同的是,此处不需要重新遍历一次窗口,只需要从累计和中减去start所处位置的元素值即可计算出新窗口内的元素和,这也是滑动窗口法降低时间复杂度的奥秘所在。
3.解题代码
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int start = 0;
int sum = 0;
int result = 0;
for(int end = 0; end < nums.length; end++) {
sum += nums[end];
// 由于是寻找最小子数组长度,所以此处用while而不是if
while(sum >= target) {
int subLen = end - start + 1;
if(result == 0) {
result = subLen;
}
else {
if(subLen < result) {
result = subLen;
}
}
sum -= nums[start];
start++;
}
}
return result;
}
}
59 螺旋矩阵 II
1.题目描述
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例 1:
输入: n = 3
输出: [[1,2,3],[8,9,4],[7,6,5]]
示例 2:
输入: n = 1
输出: [[1]]
提示:
1 <= n <= 20
2.算法分析
这是一道模拟题,没有什么特别的算法,主要是模拟螺旋的过程,关键在于:
- 如何用代码表示螺旋的过程;
- 如何处理边界条件。
第一,因为目标矩阵是一个n x n的方阵,因此每一次旋转都固定处理四条边且各条边的长度相同。又因为每一次旋转后,矩阵待填充的行数会减少2,待填充的列数也会减少2,因此每一次旋转都会使得边的长度减2。并且不难看出,每次旋转的起始位置都在矩阵的主对角线上:第一次旋转的起始位置是(0,0),第二次是(1,1),第三次是(2,2),以此类推。
第二,由于矩阵的四个顶点是被两条边所共享的,因此需要设计好各个顶点是被哪条边处理。我建议每条边都处理其起始位置,将终止位置交由下一条边处理,因为上一条边的终止位置就是下一条边的起始位置。这种对于边的表示,就类似于一种左闭右开区间。在这种表示下,每一条边要处理的元素个数就是边的长度减1。
最后需要注意一种特殊情况,当n为偶数时,经过若干次旋转,恰好可以填充完整个矩阵,而当n为奇数时,最后会剩下矩阵的中心位置,此时边长为1,也无所谓旋转,直接将值填入该位置即可。
基于上述分析,就可以写出模拟螺旋的代码了。
3.解题代码
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int val = 1;
int count = 0;// 记录旋转的次数,用于表示旋转的起始位置
// 初始边长为n,每一次旋转后边长减2
for(int len = n; len > 0; len -= 2) {
int i = count;// 旋转起始位置的行索引
int j = count;// 旋转起始位置的列索引
if(len == 1) {// 处理n为奇数时最后剩下的中心位置
matrix[i][j] = val;
}
else {
// 第一条边
for(; j < count + len - 1; j++) {
matrix[i][j] = val;
val++;
}
// 第二条边
for(; i < count + len - 1; i++) {
matrix[i][j] = val;
val++;
}
// 第三条边
// 循环开始时,j为count + len - 1,又因为要处理len - 1个元素,
// 所以循环条件是j > count,下同
for(; j > count; j--) {
matrix[i][j] = val;
val++;
}
// 第四条边
for(; i > count; i--) {
matrix[i][j] = val;
val++;
}
}
count++;
}
return matrix;
}
}
区间和
1.题目描述
给定一个整数数组Array,请计算该数组在每个指定区间内元素的总和。
输入描述
第一行输入为整数数组Array的长度n,接下来n行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间下标a,b(b >= a),直至文件结束。
输出描述
输出每个指定区间内元素的总和。
输入示例
5
1
2
3
4
5
0 1
1 3
输出示例
3
9
提示信息
0 < n <= 100000
题目链接:58.区间和|代码随想录
2.算法分析
对于区间求和,当然可以用循环进行累加求和,即每读入一个区间(a,b),就在(a,b)上做循环累加。显然,这种暴力算法的时间复杂度为,用于解此题会出现超时的情况。
通过分析不难看出,暴力算法之所以效率低下,是因为进行了多次重复的累计求和。如果能用一个数组rangeSum保存从数组开始到某个元素为止的累加,就可以直接计算出区间和。例如,rangeSum[b]表示从0到b的元素的累加,rangeSum[a]表示从0到a的元素的累加,那么用rangeSum[b] - rangeSum[a - 1](要注意处理a为0的情形)就得到了(a,b)上的区间和。这种方法被称为前缀和,若采用前缀和,则求区间和的时间复杂度降为,整体的时间复杂度降为。
3.解题代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] nums = new int[n];
int[] rangeSum = new int[n];// 前缀和数组
int sum = 0;
for(int i = 0; i < n; i++) {
int val = scanner.nextInt();
sum += val;// 读取元素的同时累加计算前缀和
nums[i] = val;
rangeSum[i] = sum;
}
while(scanner.hasNextInt()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
if(a > 0) {
System.out.println(rangeSum[b] - rangeSum[a - 1]);
}
else { // 注意处理a为0的特殊情形
System.out.println(rangeSum[b]);
}
}
scanner.close();
}
}
开发商购买土地
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.如果将区域按照如下方式划分:
1 2 | 3
2 1 | 3
1 2 | 3
两个子区域内土地总价值之间的最小差距可以达到0。
2.数据范围
1 <= n, m <= 100;
n和m不同时为 1。
题目链接:44.开发商购买土地|代码随想录
2.算法分析
由于矩阵只能按行或者列分为两部分,因此实际上我们要处理的是一维数组而非二维矩阵。以按行划分为例,用一个数组来保存各个行上元素的和,那么题目就转化为了将该数组划分为两个子区间,并使得这两个子区间上的元素和的差值最小,故本题实际上是一种区间和问题的变体。要计算两个子区间上元素和的差值,就可以借助前缀和。若A公司的区域范围是行0到行i,B公司的区域范围是行i+1到行n-1,那么A公司的土地价值总和就是行和数组区间(0,i)上的元素和,即行和前缀和数组的元素i的值,类似的,B公司的土地价值总和就是行和数组区间(i+1, n-1)上的元素和,即行和前缀和数组的元素n-1减去元素i(按行计算的总土地价值减去A公司的土地价值总和)。
综上所述,算法分为两步:
第一步,计算矩阵的行和前缀和数组和列和前缀和数组;
第二步,计算按行划分的最小价值差和计算按列划分的最小价值差,二者进行比较得到整体的最小价值差。
第一步的时间复杂度是,第二步的时间复杂度是,总体时间复杂度为。
3.解题代码
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[][] land = new int[n][m];
int[] rowRangeSum = new int[n];// 行和前缀和数组
int[] colRangeSum = new int[m];// 列和前缀和数组
// 读取元素并计算行和前缀和
for(int i = 0; i < n; i++) {
int r_sum = 0;// 行和
for(int j = 0; j < m; j++) {
int val = scanner.nextInt();
land[i][j] = val;
r_sum += val;
}
if(i == 0 ) {
rowRangeSum[i] = r_sum;
}
else {
rowRangeSum[i] = rowRangeSum[i - 1] + r_sum;// 行和前缀和
}
}
// 计算列和前缀和
for(int j = 0; j < m; j++) {
int c_sum = 0;// 列和
for(int i = 0; i < n; i++) {
c_sum += land[i][j];
}
if(j == 0) {
colRangeSum[j] = c_sum;
}
else {
colRangeSum[j] = colRangeSum[j - 1] + c_sum;// 列和前缀和
}
}
// 计算按行划分的最小价值差
int min_row_diff = Integer.MAX_VALUE;
for(int i = 0; i < n - 1; i++) {
int val_a = rowRangeSum[i];
int val_b = rowRangeSum[n - 1] - val_a;
int diff = Math.abs(val_b - val_a);
if(diff < min_row_diff) {
min_row_diff = diff;
}
}
// 计算按列划分的最小价值差
int min_col_diff = Integer.MAX_VALUE;
for(int j = 0; j < m - 1; j++) {
int val_a = colRangeSum[j];
int val_b = colRangeSum[m - 1] - val_a;
int diff = Math.abs(val_b - val_a);
if(diff < min_col_diff) {
min_col_diff = diff;
}
}
// 计算整体的最小价值差
int min_val_diff = Math.min(min_row_diff, min_col_diff);
System.out.println(min_val_diff);
scanner.close();
}
}