977-有序数组的平方
题目:给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100] 排序后,数组变为 [0,1,9,16,100]
解题思路
- 该题直接暴力平方后排序,时间复杂度O(n+nlogn);双指针法,时间复杂度O(n)
- 数组其实是有序的,只不过负数平方之后可能成为最大数了,暴力法没有充分利用这点
- 数组平方的最大值就在数组的两端,不是最左边就是最右边,越往中间越小,因此可以考虑双指针
暴力排序法
Class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
# 1.暴力排序
for i in range(0,len(nums)):
nums[i] *= nums[i];
nums.sort();
return nums
暴力排序法+列表推导法
Class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
# 2.暴力排序+列表推导
for i in range(0,len(nums)):
return sorted(x*x for x in nums)
注:列表推导法:结构是在一个中括号里包含一个表达式 + 一个for语句+ 0个 或 多个 for 或 if 语句;返回结果是一个新的列表;逻辑上等价于一个循环语句,只是形式上更加简洁。如:aList = [w.strip() for w in freshfruit]。
双指针法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
# 3.双指针
i,j=0,len(nums)-1;
loc=len(nums)-1;
results=[0]*len(nums) # 需要提前定义列表,存放结果
while i<=j:
if (nums[i]**2<nums[j]**2): # 左右边界进行对比,找出最大值
results[loc]=nums[j]**2;
j-=1; # 右指针往左移动
else:
results[loc]=nums[i]**2;
i+=1; # 左指针往右移动
loc-=1; # 存放结果的指针需要往前平移一位
return results
难点
-
双指针法变化较多,难以想到
总结
因为这个数值排列一定是两边的绝对值相互比较,中间的值更小,可以用双指针。与昨天不同的是,27-移除元素双指针(快慢指针)是同一个方向的,977双指针是相向而行的。
209-长度最小的子数组
题目:给定一个含有 n
个正整数的数组和一个正整数 target
。找出该数组中满足其 和 ≥ target
的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr]
并返回其长度。如果不存在符合条件的子数组,返回 0
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3]是该条件下的长度最小的子数组。
解题思路
- 该题直接用双重循环,时间复杂度O(n^2),力扣会超时
- 可以运用双指针的变种:滑动窗口法,时间复杂度O(n),通过不断的调节子序列的起始位置和终止位置,得出想要的结果。
- 在暴力解法中,是一个for循环为滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程;
- 滑动窗口法中,用一个for循环实现,那么这个循环的索引,一定是表示 滑动窗口的终止位置。否则又回到暴力解法。
暴力法(超时)
暴力的解法就是两层for循环
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
#1. 暴力(不用把起始值单独拿出来算
min_len = float("inf"); # 正无穷
for i in range(0,len(nums)):
cur_sum = 0; # 起始移动后需要清零
for j in range (i,len(nums)):
cur_sum += nums[j];
if cur_sum >= target:
min_len = min(min_len,j-i+1);
break;
return min_len if min_len!=float('inf') else 0;
双指针法(滑动窗口法)
一个for循环控制终止位置,再根据当前子序列和大小的情况,不断调节子序列的起始位置
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
#2. 双指针(滑动窗口法)
i,j = 0,0;
cur_sum = 0;
min_len = float('inf')
for j in range(0,len(nums)):
cur_sum += nums[j]; # 当前的累加值。不用每回清零,渐少重复运算
while cur_sum >= target: # 当前累加值大于目标值
min_len = min(min_len,j-i+1)
cur_sum -= nums[i]
i += 1;
return min_len if min_len!=float('inf') else 0
窗口的移动:
- 窗口的起始位置 i 如何移动:如果当前窗口的值大于target了,窗口就要向前移动了(也就是该缩小了)
- 窗口的结束位置 j 如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
难点
- 题目在什么条件限制下,可以利用滑动窗口的解法
- 关于时间复杂度:不要以为for里放一个while就以为是O(n^2), 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
总结
双指针法一般是减少循环层数,数组当中目前接6触到的感觉可分为2类:① 一个负责循环遍历,一个负责记录/更新(快慢指针法、滑动窗口法);② 相向的指针,职能相同,对称(977有序数组平方)。
59-螺旋矩阵Ⅱ
题目:给你一个正整数 n
,生成一个包含 1
到 n^2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
解题思路
- 本题属于模拟过程,不涉及算法
- 求解本题要坚持循环不变量原则(二分法也),解决拐角的处理
- 这里边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就要反复调节,很可能在循环中迷失方向。
- 一圈下来,我们要画四条边,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
nums = [[0]*n for _ in range(n)];
count = 1;
for offset in range (1,n//2+1): # 每循环一层偏移量加1;n为偶数循环n//2次,因为一次少2宽度
for j in range(offset-1,n-offset): # 从左至右,左闭右开
nums[offset-1][j] = count;
count += 1;
for i in range(offset-1,n-offset): # 从上至下,左闭右开
nums[i][n-offset] = count;
count += 1;
for j in range(n-offset,offset-1,-1): # 从右至左,左闭右开
nums[n-offset][j] = count;
count += 1;
for i in range(n-offset,offset-1,-1): # 从下至上,左闭右开
nums[i][offset-1] = count;
count += 1;
if n%2 != 0: # 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
nums[n//2][n//2] = count;
return nums;
时间复杂度 O(n^2):模拟遍历二维矩阵的时间;空间复杂度 O(1)
难点
- 找到循环不变量,确定边处理的统一原则,解决拐角问题
- 循环次数:n为偶数循环n//2次,因为一次少2宽度/高度;n为奇数,需要单独给矩阵最中间的位置赋值
总结
循环不变量原则是写程序中的重要原则。平时可能感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实真正解决题目的代码都是简洁的,或者有原则性的。