代码随想录算法训练营第二天继续(实际上补了上一天),不要掉队!
209.长度最小的子数组
解题过程
- 想到用滑动窗口做,初始化左右指针为0,当
sum
小于等于target
时,持续向右移动右指针,并累加sum
,直到sum
大于target
,这时持续移动左指针直到sum
小于等于target
或左右指针重合,并更新记录最小子数组的长度。
知识点
- 滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
滑动窗口法
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT_MAX;
int left = 0;
int right = 0;
int sum = 0;
for (; right < nums.size(); right++) {
sum += nums[right];
while (sum >= target) {
result = min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == INT_MAX ? 0 : result;
}
};
59.螺旋矩阵II
解题过程
- 按顺时针顺序模拟填一圈数字的过程,圈数为
int loop = n / 2;
知识点
- 注意循环过程中的不变量,即区间的开闭,本题统一使用左闭右开比较方便。
模拟过程
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>>result(n, vector<int>(n));
int count = 1;
int index = 0;
int loop = n / 2;
int i, j;
while (loop--) {
for (j = index; j < n - index - 1; j++) result[index][j] = count++;
for (i = index; i < n - index - 1; i++) result[i][j] = count++;
for (; j > index; j--) result[i][j] = count++;
for (; i > index; i--) result[i][j] = count++;
index++;
}
if (n % 2 == 1) result[n / 2][n / 2] = n * n;
return result;
}
};
区间和
解题过程
- 使用暴力法,直接超时。
- 看答案使用前缀和思路,构造一个前缀和数组p,
p[i]
记录vec
在[0, i]
之间的和,如要求vec
在[i, j]
之间的和,就可以由p[j] - p[i - 1]
直接求得。
知识点
- 前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。
- 前缀和 在涉及计算区间和的问题时非常有用!
- C++ 代码 面对大量数据 读取 输出操作,最好用
scanf
和printf
,耗时会小很多。
暴力法(超时)
#include <iostream>
#include <vector>
using namespace std;
int main() {
int len;
cin >> len;
vector<int>vec(len);
for (int i = 0; i < len; i++) {
cin >> vec[i];
}
int left, right;
while (cin >> left >> right) {
int sum = 0;
while (left <= right) {
sum += vec[left++];
}
cout << sum << endl;
}
return 0;
}
前缀和
#include <iostream>
#include <vector>
using namespace std;
int main() {
int len;
cin >> len;
vector<int>vec(len);
vector<int>p(len);
int preSum = 0;
for (int i = 0; i < len; i++) {
scanf("%d", &vec[i]);
preSum += vec[i];
p[i] = preSum;
}
int left, right;
while (~scanf("%d%d", &left, &right)) {
int sum = 0;
if (left == 0) sum = p[right];
else sum = p[right] - p[left - 1];
printf("%d\n", sum);
}
return 0;
}
知道前缀和思路后,一开始用cin
和cout
进行输入输出打印,用时645ms,然后用scanf
和printf
优化,用时38ms,耗时少了很多。
开发商购买土地
解题思路
- 利用上一题思路做出来了,使用前缀和,先统计好每一行的前所有元素的和
rowSum[n]
,如果要求矩阵 a行 到 b行 之间的总和,那么就rowSum[b] - rowSum[a - 1]
就好,result = abs(rowSum[b] - 2 * rowSum[a - 1])
,取最小的result。列同理。
知识点
- 使用前缀和,解决区间和的问题,减少计算量!
前缀和解法
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>>grid(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> grid[i][j];
}
}
vector<int>rowSum(n, 0);
vector<int>colSum(m, 0);
int result = INT_MAX;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) rowSum[i] += grid[i][j];
if (i > 0) rowSum[i] += rowSum[i - 1];
}
for (int i = 0; i < n - 1; i++) {
result = min(result, abs(rowSum.back() - 2 * rowSum[i]));
}
for (int j = 0; j < m; j++) {
for (int i = 0; i < n; i++) colSum[j] += grid[i][j];
if (j > 0) colSum[j] += colSum[j - 1];
}
for (int j = 0; j < m - 1; j++) {
result = min(result, abs(colSum.back() - 2 * colSum[j]));
}
cout << result << endl;
return 0;
}
数组总结
数组理论基础
- 数组是存放在连续内存空间上的相同类型数据的集合。
- 数组可以方便的通过下标索引的方式获取到下标对应的数据。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
- 在删除或者增添元素的时候,要移动其他元素的地址。
- 数组的元素是不能删的,只能覆盖。
数组的经典题目
- 二分法
- 704.二分查找
- 暴力解法时间复杂度:O(n)
- 二分法时间复杂度:O(logn)
- 注意循环不变量原则,只有在循环中坚持对区间的定义,才能清楚的把握循环中的各种细节。
- 双指针法
- 27.移除元素
- 双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
- 暴力解法时间复杂度:O(n^2)
- 双指针时间复杂度:O(n)
- 注意数组中的元素不能真的删除 ,因为数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。
- 滑动窗口
- 209.长度最小的子数组
- 暴力解法时间复杂度:O(n^2)
- 滑动窗口时间复杂度:O(n)
- 滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。
- 模拟行为
- 59.螺旋矩阵Ⅱ
- 注意循环不变量原则,循环中边界判断要一致。
- 前缀和
- 58.区间和
- 前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。
- 前缀和在涉及计算区间和的问题时非常有用!