数组基础回顾
- 数组是内存空间连续的一组相同类型数据的集合,因此不能直接进行删改,只能进行覆盖操作。
- 在做数组题目的时候,注意变量循环定义不变量原则,在变量范围处理时,需要关注是否满足
[left,right]或[left,right)的初始定义。
今日遇到的一些做题技巧总结
- 除了「移除元素」中涉及的
快慢指针外,对于更新值总在左右两侧的问题,可以考虑使用双指针的方法,i,j分别指向数组首尾,如 977.有序数组的平方; - 对于仅在原始数组之上计算某类子串长度问题,则可以考虑使用
滑动窗口,滑动窗口变化的触发条件在于是否满足指定值target,通常是for内嵌套while的语句,如 209. 长度最小的子数组; - 关于规律性矩阵类问题,需要遵循变量循环定义不变量原则,模拟矩阵生成过程,如 59. 螺旋矩阵II。
- c++中
?条件运算符的使用:Exp1 ? Exp2 : Exp3;5.对于一个不一定存在的解,可以用一个同类型的占位符,最后如果存在解则返回解,否则返回0,如无限大INT32_MAX;
数组问题总结
- 在c++中需要注意array和vector的区别,vector的底层是array,vector的本质其实是容器
- 数组作为面试中必然会出现的一道题,常见的有四个高效的解法
- 二分法:在循环中坚持对区间的定义,需要会手撕。
- 双指针法(快慢指针法):通过一个快指针和一个慢指针在一个for循环下完成两个for循环的工作。
- 数组中的元素为什么不能删除的原因:
- 数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束后,回收内存栈空间)
- C++中的vector和array区别一定要弄清楚,vector的底层实现是array,封装后使用更友好。
- 快慢指针法在数组和链表操作中非常常见。
- 数组中的元素为什么不能删除的原因:
- 滑动窗口:需要理解究竟是怎么移动窗口起始位置的。
- 模拟行为:其实真正解决题目的代码都是简洁的,或者有原则的。
Leetcode相关题目及解法要义
此处埋下一个坑,之后补:需要补一下时间复杂度和空间复杂度分析的知识点。
977. 有序数组的平方
先上一个c++的暴力解法:
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
for (int i = 0; i < A.size(); i++) {
A[i] *= A[i];
}
sort(A.begin(), A.end()); // 快速排序
return A;
}
};
考虑到本就有序的数组,其平方最大值一定在首尾处,而不是在中间位置,所以采用双指针的思想,c++代码如下:
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
int k = A.size() - 1;
vector<int> result(A.size(), 0);
for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
if (A[i] * A[i] < A[j] * A[j]) {
result[k--] = A[j] * A[j]; //c++中的 k--,是先赋值之后再减,类似于=k,k--
j--;
}
else {
result[k--] = A[i] * A[i];
i++;
}
}
return result;
}
};
python实现的代码如下:
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
n = len(nums)
i,j,k = 0,n - 1,n - 1
ans = [-1] * n
while i <= j:
lm = nums[i] ** 2
rm = nums[j] ** 2
if lm > rm:
ans[k] = lm
i += 1
else:
ans[k] = rm
j -= 1
k -= 1
return ans
209. 长度最小的子数组
先上一个c++的暴力解法:
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX; // 最终的结果
int sum = 0; // 子序列的数值之和
int subLength = 0; // 子序列的长度
for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
sum = 0;
for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
sum += nums[j];
if (sum >= s) { // 一旦发现子序列和超过了s,更新result
subLength = j - i + 1; // 取子序列的长度
result = result < subLength ? result : subLength;
break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
}
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
寻找子串问题的滑动窗口,关键在于如何动态的调整子串的起始和终止位置,采用for+while的嵌套写法,for控制窗口的尾部;while只负责控制temp_sum-头部边界元素,实现窗口的滑动。
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};
python实现的代码如下:
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
# 定义一个无限大的数
res = float("inf")
Sum = 0
index = 0
for i in range(len(nums)):
Sum += nums[i]
while Sum >= s:
res = min(res, i-index+1)
Sum -= nums[index]
index += 1
return 0 if res==float("inf") else res
59. 螺旋矩阵II
此题类似于数学归纳法问题,代码中有很多细节。 解题思路是模拟顺时针画矩阵的过程,相当于循环中嵌套四个循环,所以一定要遵循变量循环定义不变量原则:
- 填充上行从左到右
- 填充右列从上到下
- 填充下行从右到左
- 填充左列从下到上
一些关键的变量:每次起始x,起始y
c++代码实现:
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2),注意索引是从0开始的
int count = 1; // 用来给矩阵中每一个空格赋值
int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
int i,j;
while (loop --) {
i = startx;
j = starty;
// 下面开始的四个for就是模拟转了一圈
// 模拟填充上行从左到右(左闭右开)
for (j = starty; j < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < n - offset; i++) {
res[i][j] = count++;
}
// 模拟填充下行从右到左(左闭右开)
for (; j > starty; j--) {
res[i][j] = count++;
}
// 模拟填充左列从下到上(左闭右开)
for (; i > startx; i--) {
res[i][j] = count++;
}
// 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
startx++;
starty++;
// offset 控制每一圈里每一条边遍历的长度
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};
python代码实现:
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
nums = [[0] * n for _ in range(n)]
startx, starty = 0, 0 # 起始点
loop, mid = n // 2, n // 2 # 迭代次数、n为奇数时,矩阵的中心点
count = 1 # 计数
for offset in range(1, loop + 1) : # 每循环一层偏移量加1,偏移量从1开始
for i in range(starty, n - offset) : # 从左至右,左闭右开
nums[startx][i] = count
count += 1
for i in range(startx, n - offset) : # 从上至下
nums[i][n - offset] = count
count += 1
for i in range(n - offset, starty, -1) : # 从右至左
nums[n - offset][i] = count
count += 1
for i in range(n - offset, startx, -1) : # 从下至上
nums[i][starty] = count
count += 1
startx += 1 # 更新起始点
starty += 1
if n % 2 != 0 : # n为奇数时,填充中心点
nums[mid][mid] = count
return nums