本文由GarfieldTheOldCat原创,转载请标明dekkyandlappy-CSDN博客
今天是打卡第二天,继续研究数组内容
继续双指针
昨天的打卡里研究了快慢指针的用法,两个指针起点相同(以数组左侧为例),以不同的速度向终点(数组右侧)移动,当快指针移动到数组右侧时结束循环。
今天有另外一种双指针的写法:两个指针分别从数组两端向中间逼近,当指针相遇(确切的说,时两个指针迎面错过)时中止循环。
我对这两钟方法的理解:快慢指针两个指针的权重不同,担负不同功能,而双指针的两个指针地位是相同的。
话不多说,上题目:
LC977: 977. 有序数组的平方
上来先暴力解法:遍历数组,求平方,再排序(刚好我以前对java的内置排序并不清楚,借了这个机会也了解了一下)Java8 Stream 之sorted方法 排序讲解_stream().sorted-CSDN博客
// 暴力解法
public int[] sortedSquares_0(int[] nums) {
int[] sqrt=new int[nums.length];
for(int i=0;i<nums.length;i++){
sqrt[i]=nums[i]*nums[i];
}
sqrt= Arrays.stream(sqrt).sorted().toArray();
return sqrt;
}
对于这道题而言时间复杂度有O(nlogn),力扣上也可以跑通。
考虑到输入的数组是有序的,最大值会在输入数组的左侧元素或右侧元素上取到,可以使用双指针。创建一个新数组用于保存结果,两个指针读取对应下标的数求平方再比较,最后完成指针操作和保存数据。
java版本
// 双指针法
public int[] sortedSquares(int[] nums) {
int[] sqrt=new int[nums.length];
int left=0;
int right=nums.length-1;
int pos=nums.length-1;
while(left<=right){
int ls=nums[left]*nums[left];
int rs=nums[right]*nums[right];
if(ls < rs){
sqrt[pos]=rs;
right--;
}
else {
sqrt[pos]=ls;
left++;
}
pos--;
}
return sqrt;
}
python版本
class Solution(object):
def sortedSquares(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
left=0;
right=len(nums)-1
pos=len(nums)-1
sqrt=[0 for i in range(len(nums))]
while left<=right:
if nums[left]**2 >nums[right]**2:
sqrt[pos]=nums[left]**2
left=left+1
else:
sqrt[pos]=nums[right]**2
right=right-1
pos=pos-1
return sqrt
双指针只要一次遍历即可完成,不需要额外的排序操作,时间复杂度是O(N),比暴力解法还是更有优势的。
滑动窗口
基础
滑动窗口是双指针法的一个变种:双指针法(包括快慢指针)研究的都是指针对应的元素;而滑动窗口研究的是两个指针之间(包含这两个指针)的所有元素。
滑动窗口只需要一个if循环就可以完成,其思路如下:
- left和right都为0(数组最左侧),滑动窗口算法开始
- left不动,right右移一位,滑动窗口增大,直到left与right构成的窗口满足题目的要求
- right不动,left右移一位,滑动窗口缩小,直到窗口不满足要求
- 重复2和3,直到right到达数组最右侧
注意:使用for循环控制的应当是窗口的结尾right
如果用for控制left,则right还是需要遍历寻找,失去了滑动窗口的价值。
LC209:209. 长度最小的子数组
首先先写暴力解法:for循环遍历每个元素,再套一个for循环寻找满足要求的最短长度(即类似上文的用for控制left)。对于这道题,此方法会超时。
// 暴力解法,超时了
public int minSubArrayLen_0(int target, int[] nums) {
int result=-1;
for (int left = 0; left < nums.length; left++) {
int right;
int sum = 0;
for (right = left; right < nums.length; right++) {
sum = sum + nums[right];
if (sum >= target){
if (result == -1) result=right-left+1;
else if(right-left+1<result) result=right-left+1;
break;
}
}
}
if(result==-1) result=0;
return result;
}
再使用滑动窗口,开辟出一个变量sum存储窗口内数据的和,一个变量result记录窗口长度的最小值。
java版本
// 滑动窗口
public int minSubArrayLen(int target, int[] nums) {
int sum=0;
int result=Integer.MAX_VALUE;
int left=0;
for(int right =0; right<nums.length;right++){
sum=sum+nums[right];
while(sum>=target){
result=Integer.min(result,right-left+1);
sum=sum-nums[left];
left++;
}
}
if (result==Integer.MAX_VALUE) result=0;
return result;
}
注意到这里在初始化result时使用了最大的整数Integer.MAX_VALUE,从而可以直接与窗口长度比较,取最小值。而对于python,这个值需要调库实现,为了稳妥起见,我没有在力扣上调sys库(我也不知道是否可行),而是选取了输入数组nums长度+1。
class Solution(object):
def minSubArrayLen(self, target, nums):
"""
:type target: int
:type nums: List[int]
:rtype: int
"""
left = 0
result = len(nums)+1
sum = 0
for right in range(len(nums)):
sum = sum + nums[right]
while sum >= target:
result = min(result, right - left + 1)
sum = sum - nums[left]
left = left + 1
if result == len(nums)+1:
result = 0
return result
螺旋矩阵
这类问题主要考察对题述过程的实现,但是困难在于循环嵌套时的边界判断问题。
解决方法:抓住不变量:对各个边的处理规则(全部坚持左闭右开,包括起点但不包括终点)
LC59:59. 螺旋矩阵 II
先上代码
public int[][] generateMatrix(int n) {
int[][] result=new int[n][n];
int y=0; //纵坐标
int x=-1; //横坐标
int val=1;//需要赋的值
int move=0;//移动方向
int lenth=n-1;//移动步长
if (lenth==0) lenth=1;// 处理n=1
while(val <=n*n){
// System.out.println(x+" "+y+" "+lenth+" "+val+" "+move);
int move_count=0;//步数控制
//上边:左到右
if (move % 4==0){
x++;
while (move_count<lenth){
result[y][x]=val;
x++;
val++;
move_count++;
}
move++;
}
//右边:上到下
else if (move%4==1) {
while (move_count<lenth){
result[y][x]=val;
y++;
val++;
move_count++;
}
move++;
}
//下边:右到左
else if (move%4==2){
while (move_count<lenth){
result[y][x]=val;
x--;
val++;
move_count++;
}
move++;
}
//左边,下到上
else {
while (move_count<lenth){
result[y][x]=val;
y--;
val++;
move_count++;
}
move++;
lenth=lenth-2;
if (lenth<=0) lenth=1;
y++;
}
// System.out.println(Arrays.deepToString(result));
}
return result;
}
我看题解里每圈循环里的4条边的处理是用for循环实现的,为了避免后续for循环条件搞不清楚,我引入了一个变量move_count用于控制循环次数。
在写的过程中,发现有以下错误: 本文由GarfieldTheOldCat原创,转载请标明
- 每圈循环结束后,结束点在下一轮起点左上方(1,1),需要手动移到对应的位置
- 每圈步长减少2而不是1,若最后的步长<=0,需要补偿为1
- 初始步长若为0需要补偿为1以处理n=0
总结
今天学习了另一种双指针法和滑动窗口法,并写了螺旋矩阵
双指针法从两头向中间靠拢
滑动窗口for循环控制right
TODO
lc904,lc76
本文由GarfieldTheOldCat原创,转载请标明dekkyandlappy-CSDN博客