学习目标:
第一章数组
- 977.有序数组的平方
- 209.长度最小的子数组
- 59.螺旋矩阵II
学习内容:
977.有序数组的平方
(1)暴力算法
思路:优先计算出数组中每个数值的平方值,然后根据快速排序算法完成排序操作。
public static int[] sortedSquares_01(int[] nums) {
//暴力算法
for (int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * nums[i];
}
quickSort(nums,0,nums.length - 1);
return nums;
}
public static void quickSort(int[] arr,int low,int high){
int i,j,temp,t;
if(low>high){
return;
}
i=low;
j=high;
//temp就是基准位
temp = arr[low];
while (i<j) {
//先看右边,依次往左递减,找到temp>arr[j]时退出该while循环
while (temp<=arr[j]&&i<j) {
j--;
}
//再看左边,依次往右递增,找到temp<arr[i]时退出该while循环
while (temp>=arr[i]&&i<j) {
i++;
}
//如果满足条件则交换,由于要退出上面两个while循环才能到达这一步,就已经证明arr[i]>arr[j]需要交换
if (i<j) {
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换,也就是将i = j位置的值改为temp,原始low位置的值改为原i = j位置的值完成交换
arr[low] = arr[i];
arr[i] = temp;
//递归调用左半数组
quickSort(arr, low, j-1);
//递归调用右半数组
quickSort(arr, j+1, high);
}
快速排序整体思路:
将数组中的第一个数值设置为基准值,并且设置left和right两个指针分别指向最前和最后,随后两个指针相向移动,每次将所指向的下标的值与基准值进行对比,优先看right的值,若right指向的值大于基准值则right–向前移动,否则停止移动并且看向left指向的值;若left指向的值小于基准值则left++向后移动,否则与right指向的值进行交换,因为都是与基准值进行比较了所以他们俩谁大谁小显而易见。最后循环结束后到了两个指针指向同一个位置,此时将基准值放入该位置,之后会发现该位置左边都是小于基准值的集合,该位置右边都是大于基准值的集合。因此可以采用递归思想对左边和右边的集合都进行快速排序,最终得到我们想要的有序数列数组。
(2)双指针算法
思路:由于原数组是递增排序的,因此平方后的最大值必然在最左或者最右边,例如[-7,0,1,3],因此整体思路为:定义两个下标指针,一个最左一个最右,每次都是比较最左和最右数据平方的值,将最大的值优先存入到result结果数组中逆序排放,也就顺便完成排序操作
public static int[] sortedSquares_02(int[] nums){
//双指针算法:由于原数组是递增排序的,因此平方后的最大值必然在最左或者最右边,例如[-7,0,1,3]
//因此整体思路为:定义两个下标指针,一个最左一个最右,每次都是比较最左和最右数据平方的值,将最大的值优先存入到result结果数组中逆序排放,也就顺便完成排序操作
int[] results = new int[nums.length];//创建新的数组用来存放平方后的数据
int index = results.length - 1;//将最大的数据逆序排放进数组
for (int i = 0,j = nums.length - 1;i <= j;){//注意:i++和j--不能直接写在循环中,因为只有当前数据存入result数组中后,下标才能进行移动,也可以使用while循环代替
if(nums[i] * nums[i] > nums[j] * nums[j]){//左右两侧比较谁的数据的平方更大
results[index--] = nums[i] * nums[i];//将最大的平方数据录入到result数组中,并且完成下标的移动
i++;
}
else{
results[index--] = nums[j] * nums[j];
j--;//左右下标移动方向是相向的
}
}
return results;
}
209.长度最小的子数组
(1)暴力解法
思路:先将最终结果赋予一个int类型的最大值,利用两个for循环来遍历出整个原始数组中每个连续的数组区间之和是否大于等于目标值,若满足条件则计算出区间的长度,否则进行下一循环,最后利用三目运算符进行判断该区间长度是否为最短长度,返回结果时也是采用三目运算符进行判断是否存在最短区间的数据之和大于等于目标值。
缺点:超时严重
public static int minSubArrayLen_01(int target, int[] nums) {
//暴力解法,会超时
//思路:先将最终结果赋予一个int类型的最大值,利用两个for循环来遍历出整个原始数组中每个连续的数组区间之和是否大于等于目标值
//若满足条件则计算出区间的长度,否则进行下一循环,最后利用三目运算符进行判断该区间长度是否为最短长度,返回结果时也是采用三目运算符进行判断是否存在最短区间的数据之和大于等于目标值
int result = Integer.MAX_VALUE;
int length = 0;
int sum = 0;
for (int i = 0; i < nums.length; i++) {//确定区间的起始位置
sum = 0;
for (int j = i; j < nums.length; j++) {//根据起始位置向后移动下标
sum += nums[j];//将各个下标的值进行相加
if(sum >= target){//判断是否大于等于目标值
length = j - i + 1;//满足条件则计算区间长度
result = result < length ? result : length;//通过三目运算符比较得出最短区间长度
break;
}
}
}
return result == Integer.MAX_VALUE ? 0 :result ;//通过三目运算符判断是否存在该区间,若存在则返回长度,否则返回0
}
(2)滑动窗口算法
思路:优先固定开始位置,使结束位置优先向后移动,移动的同时增加区间中的各个数据,并且判断是否大于等于目标值,若遍历完全部数组都不能大于等于目标值,则返回0;否则得到大于等于目标值的结束位置后,结束位置暂时不变,并且计算出当前的区间长度,随后开始位置开始移动,在开始位置移动前要将sum-nums[i]也就是减去移动后不在区间内的值,再次进行判断区间内部数据的和是否大于等于目标值。若大于则开始位置继续依照之前的规则向后移动;若小于则结束位置开始依照之前的规则向后移动,直至循环结束。每次区间长度更新时是利用三目运算符进行判断取得最终所需要的最小值。
public static int minSubArrayLen_02(int target, int[] nums){
//滑动窗口算法
int result = Integer.MAX_VALUE;//将初始结果赋值为int类型最大值
int length = 0;
int sum = 0;
for (int i = 0,j = 0;j < nums.length;j++){//j表示区间的结束位置,i为开始位置,根据算法思想开始位置先固定,j结束位置向后移动直到找到sum大于等于目标值
sum += nums[j];
while(sum >= target){//采用if只会进行一次判断,采用while可以持续判断使开始位置可以连续移动以便于能找到并且不会漏掉最小区间,例如[1,1,1,1,100],目标值为100,则需要不断移动以缩短区间长度
length = j - i + 1;//满足条件后计算区间的长度
result = result < length ? result :length;//根据三目运算符判断得到区间的最小长度
sum -= nums[i];//在区间开始位置开始移动时,优先在sum中减去移动之后不包括的值,保证sum的实时更新
i++;//随后开始位置i进行向后移动
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
59.螺旋矩阵II
思路:优先构建二维数组,要保证每次遍历规则应保持一致,因此每次遍历遵循左闭右开原则每行或每列遍历赋值时最后一个数值不予操作。随后可以发现最上层从左到右(其他侧面同理)进行遍历赋值操作,初始值为startx,最后一个值不予操作,因此需要在原来的基础上减去最后一个值的下标,越是内层减的越多,因此设置一个变量offset来控制,因为最后都会进行一次i++或者j++或者i–或者j–,因此每次完成一行或一列的遍历赋值操作之后,进行下一侧的遍历赋值操作时下标都已移至正确位置,直接使用即可。最后循环完一整圈之后,startx、starty、offset都应自增一以进入内圈不会出错。若需要转圈的次数为奇数时,则可以将最中间的数据进行直接赋值,不过要放在外圈赋值的最后,因为最中间的值肯定是最大的,且位置可以根据每圈结束后的startx、starty直接确定。
public int[][] generateMatrix(int n) {
int startx = 0 ;
int starty = 0 ;
int offset = 1 ;//每次给一条边赋值时都跳过最后一个数值,左闭右开
int count = 1 ;//赋值数据从1开始
int num = 0;//需要循环的次数
int[][] nums = new int[n][n];//二维数组构建矩阵
int i = 0;
int j = 0;
while(num++ < n / 2){//计算转几圈
for(j = startx;j < n - offset;j++){//最上面的从左到右赋值,n-offset表示最后一个值不在这赋值,但因为j++了,所以下次进行最右边从上到下赋值时j已经是目标列
nums[starty][j] = count++;//每次赋值更新数据
}
for(i = starty;i < n - offset;i++){//最右边的从上到下赋值,同理跳过最后一个值,因为i++了,所以下次进行最下面从右到左赋值时i已经是目标行
nums[i][j] = count++;
}
for(;j > startx;j--){//最下面的从右到左赋值,同理跳过最后一个值,因为j--了并且j>startx,说明跳过了最后一个值,并且下次进行最左边的从下到上赋值时j已经是目标列
nums[i][j] = count++;
}
for(;i > starty;i--){//最左边的从下到上赋值,同理跳过最后一个值
nums[i][j] = count++;
}
startx++;//循环完一圈之后,startx和starty都自增一表示进入内圈
starty++;
offset++;//进入内圈后为了做到最后一个值不赋值,因此需要减的值应该也要自增一
}
if(n % 2 == 1){
nums[startx][starty] = count;//若圈数为奇数时,最内部的数据是遍历不到的,因此可以在最后直接赋值为最后一个值
}
return nums;
}
学习时间:
下午三个半小时。