目录
思路:双指针法
对于有正有负的数值平方排序,无论是先取绝对值排序后平方,还是平方之后排序,直接排序这一步骤的时间复杂度均可视为O(nlogn)。减少时间复杂度的思路在于,利用数组的有序特性,从数组数值的两端(平方最大处)开始,使用双指针法对于数值的平方或绝对值进行比较。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int size = nums.size();
int leftIndex = 0;
int rightIndex = size-1;
vector<int> result(size, 0);
while (leftIndex <= rightIndex){
if (abs(nums[leftIndex]) >= abs(nums[rightIndex])){
result[size-1]=nums[leftIndex]*nums[leftIndex];
leftIndex++;
}
else{
result[size-1]=nums[rightIndex]*nums[rightIndex];
rightIndex--;
}
size--;
}
return result;
}
};
时间复杂度为O(n),比起暴力排序的时间复杂度提升很多。
基本语法:动态数组的初始化
vector<int> result(size, 0);
- 在局部作用域中(如在函数体内),直接声明一个引用而没有初始化是非法的。这会导致编译错误,因为
result
引用没有绑定到任何实际的vector<int>
对象。- 当创建一个新的
vector<int>
对象时,可以绑定一个现有对象,或者进行初始化。例如,上述代码创建了一个大小为size
的result
对象,且所有元素都被初始化为0
。
思路:滑动窗口(双指针)
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
但两个for循环时间复杂度太高, 如何使用一个for循环遍历可能的窗口呢?
只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口:满足其和 ≥ s 的长度最小的连续子数组;
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,起始位置前移从而缩小窗口;
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
滑动窗口的精妙之处在于:固定结束位置后,对于已经确定不满足条件的窗口不再进行缩放的遍历,减少了可能的子序列的起始位置,从而将O(n^2)暴力解法降为O(n)。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int leftIndex = 0;
int sum = 0;
int result = INT32_MAX;
int subLength = 0; // 滑动窗口的长度
for (int rightIndex = 0; rightIndex<nums.size(); rightIndex++){
sum += nums[rightIndex];
while (sum >= target){
subLength = rightIndex - leftIndex + 1; // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[leftIndex];
leftIndex++;
}
}
return result == INT32_MAX ? 0 : result;
}
};
要注意在进行对于窗口求和sum的计算时,在for、while循环里跟随进行即可。不要使用while循环单独运算,会增加复杂度和出错率。
基本语法:比较算法&INT32_MAX
1.比较运算:
result = result < subLength ? result : subLength;
这是一个比较运算,检查result
是否小于subLength
,相当于以下if-else语句:
if (result < subLength) {
result = result;
} else {
result = subLength;
}
2.INT32_MAX:
int result = INT32_MAX;
在C和C++中,
INT32_MAX
是一个预定义的宏,它定义在<limits.h>
(C)或<climits>
(C++)头文件中。INT32_MAX
表示32位有符号整数能够表示的最大值。在大多数现代系统上,这通常是2147483647
,也就是2^31 - 1
。
作为初始值:在寻找最小值的算法中,初始化一个变量为INT32_MAX
,实际上是在将result
设置为int
类型所能表示的最大值,随着算法的执行任何小于INT32_MAX
的int
值都将成功更新这个变量。
要点:
- 坚持循环不变量原则
- 设置填充时的横纵坐标i j,并按此设置循环条件,使用res[i][j]填充,一定程度上简化了算法
- 要注意循环条件,包括循环不变原则、数组下标从0开始
- 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
// 后面使用这个二维数组就和正常数组一样,使用res[i][j]即可
int dif = 0;
int count = 1;
int i,j;
while (dif*2<n){
// 保持左闭右开原则不变,按照横坐标为i、纵坐标为j填充
// 设置填充时的横纵坐标ij,一定程度上简化了算法,要注意循环条件
i = dif;
j = dif;
// 从左到右
for (j; j<n-dif-1; j++){
res[i][j] = count++;
}
// 从上到下
for (i; i<n-dif-1; i++){
res[i][j] = count++;
}
// 从右到左
for (j; j>dif; j--){
res[i][j] = count++;
}
// 从下到上
for (i; i>dif; i--){
res[i][j] = count++;
}
dif++;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2 == 1) {
res[n/2][n/2] = count;
// n/2即取除法后商的整数位
}
return res;
}
};
基本语法:二维vector数组
vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
vector<vector<int>>
:这是嵌套vector
的类型声明,意味着创建一个vector
,其元素也是vector<int>
。这实质上创建了一个可以动态调整大小的二维数组。
res(n, vector<int>(n, 0))
:这是使用vector
构造函数来初始化res
。这里的构造函数有两个参数:第一个参数n
表示外层vector
的大小,即有n
个vector<int>
。第二个参数是另一个vector<int>
,它本身由n
个元素组成,每个元素都初始化为0
。
vector<int>(n, 0)
:这是内部vector<int>
的构造函数调用,它创建了一个大小为n
的vector<int>
,并且每个元素都被初始化为0
。
数组总结
理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
- 数组下标都是从0开始的
- 数组内存空间的地址是连续的
- 数组的元素是不能删的,只能覆盖
Java的二维数组在内存中不是
3*4
的连续地址空间,而是四条连续的地址空间组成
经典方法
二分法
- 暴力解法时间复杂度:O(n)
- 二分法时间复杂度:O(logn)
要注意循环不变量原则,常见左闭右开、左闭右闭
双指针法
- 暴力解法时间复杂度:O(n^2)
- 双指针时间复杂度:O(n)
双指针法(快慢指针法):通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
数组中的元素为什么不能删除,主要是因为以下两点:
- 数组在内存中是连续的地址空间,不能释放单一元素,如果要释放,就是全释放(程序运行结束,回收内存栈空间)。
- C++中vector和array的区别一定要注意,vector底层实现是array,封装后使用更友好。
滑动窗口
- 暴力解法时间复杂度:O(n^2)
- 滑动窗口时间复杂度:O(n)
滑动窗口的精妙之处在于:固定结束位置后,对于已经确定不满足条件的窗口不再进行缩放的遍历,减少了可能的子序列的起始位置, 从而将O(n^2)的暴力解法降为O(n)。