1、对于数组上的双指针问题,主要是指两个变量在数组上移动或遍历的问题,之前整理的“二分查找”系列的算法题目,就是双指针解决数组上的索引相向移动。
2、双指针算法的关键在于:初始化双指针值和双指针值分别更新的条件,保持对以上两点的清晰认知,就能准确掌握这类题目。
3、本次博客总结的“有序数组的排序”题目,该类题目 需要双指针初值总是分别指向最大或最小值处,以便通过O(N)的时间复杂度完成仅一次数组遍历,并达到获取新的有序数组的题目要求。
977. 有序数组的平方
给定有序数组,对数组进行平方后,获取 有序的新数组,要求O(N)的时间复杂度。对于这种题目,数组本身肯定要完成一次遍历的,一次遍历对应的就是O(N)的时间复杂度,那么一次遍历的同时如何获取到有序的新数组呢?那必然需要一定的技巧了,即结合数组有序的特性,使用双指针(双指针初值总是分别指向最大或最小值处)。
from typing import List
'''
977. 有序数组的平方
题目描述:给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
题眼:排序数组+数字平方+排序新数组(有序总是和双指针联系在一起:双指针初值总是指向最大或最小值处)
思路:双指针:平方后的数组,最大值总是在左右两侧(双指针初始值),双指针初始化为数组首尾索引,哪个的平方大就更新哪个指针
'''
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
pointer1, pointer2 = 0, len(nums) - 1 # 双指针初始化为数组首尾索引
result, idx = [0] * len(nums), len(nums) - 1
while pointer1 <= pointer2: # 也可以用idx>=0的条件
if nums[pointer1] * nums[pointer1] >= nums[pointer2] * nums[pointer2]:
result[idx] = nums[pointer1] * nums[pointer1]
pointer1 += 1 # 更新指针1
else:
result[idx] = nums[pointer2] * nums[pointer2]
pointer2 -= 1 # 更新指针2
idx -= 1
return result
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
print(obj.sortedSquares(nums))
except EOFError:
break
360. 有序转化数组
“977. 有序数组的平方”的变体,要求二次函数变换之后的新的排序数组,需要分情况讨论,核心思想还是 结合数组有序的特性,使用双指针(双指针初值总是分别指向最大或最小值处)。
from typing import List
'''
360. 有序转化数组(Vip:答案未验证,题目是从其它博客那里copy的)
题目描述:给你一个已经 排好序 的整数数组 nums 和整数 a、b、c。对于数组中的每一个数 x,计算函数值 f(x) = ax^2 + bx + c,请将函数值产生的数组返回。
要注意,返回的这个数组必须按照 升序排列,并且我们所期望的解法时间复杂度为 O(n)。
示例 1:
示例 1:
输入: nums = [-4,-2,2,4], a = 1, b = 3, c = 5
输出: [3,9,15,33]
示例 2:
输入: nums = [-4,-2,2,4], a = -1, b = 3, c = 5
输出: [-23,-5,1,7]
题眼:排序数组+数字平方+排序新数组(有序总是和双指针联系在一起:双指针初值总是指向最大或最小值处)
思路:“977. 有序数组的平方”的变体,双指针:a不为0时是二次函数,a大于0时最大值总是在左右两侧(双指针初始值),a小于0时最小值总是在左右两侧(双指针初始值);
a为0时,b大于0是单调递增函数,b小于0是单调递减函数,b也等于0是常函数(很数学的一道题目)
'''
class Solution:
def sortTransformedArray(self, nums: List[int], a, b, c) -> List[int]:
result = [0] * len(nums)
# 情况1:a为0
if a == 0:
if b == 0:
return [c] * len(nums)
elif b > 0: # 单调递增函数
for i in range(len(nums)):
result[i] = b * nums[i] + c
elif b < 0: # 单调递减函数
for i in range(len(nums)-1, -1, -1):
result[i] = b * nums[i] + c
# 情况2:a大于0,最大值总是在左右两侧(双指针初始值)
elif a > 0:
pointer1, pointer2 = 0, len(nums) - 1 # 双指针初始化为数组首尾索引
idx = len(nums) - 1
while pointer1 <= pointer2: # 也可以用idx>=0的条件
if self.func(nums[pointer1], a, b, c) >= self.func(nums[pointer2], a, b, c):
result[idx] = self.func(nums[pointer1], a, b, c)
pointer1 += 1 # 更新指针1
else:
result[idx] = self.func(nums[pointer2], a, b, c)
pointer2 -= 1 # 更新指针2
idx -= 1
# 情况3:a小于0,最小值总是在左右两侧(双指针初始值)
elif a < 0:
pointer1, pointer2 = 0, len(nums) - 1 # 双指针初始化为数组首尾索引
idx = 0
while pointer1 <= pointer2: # 也可以用idx<len(nums)的条件
if self.func(nums[pointer1], a, b, c) <= self.func(nums[pointer2], a, b, c):
result[idx] = self.func(nums[pointer1], a, b, c)
pointer1 += 1 # 更新指针1
else:
result[idx] = self.func(nums[pointer2], a, b, c)
pointer2 -= 1 # 更新指针2
idx += 1
return result
def func(self, val, a, b, c):
return a * val * val + b * val + c
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
a = int(in_line[2].strip().split(',')[0])
b = int(in_line[3].strip().split(',')[0])
c = int(in_line[4])
print(obj.sortTransformedArray(nums, a, b, c))
except EOFError:
break
88. 合并两个有序数组
“977. 有序数组的平方”的变体,双指针:最大值总是在两数组末尾(双指针初始值),双指针初始化为两个数组的尾索引,哪个的大就更新哪个指针;不过赋值是要在nums1中本地完成,而不是在一个新的数组空间中。
from typing import List
'''
88. 合并两个有序数组
题目描述:给你两个按 非递减顺序 排列的整数数组nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
题眼:两个排序数组+排序新数组(有序总是和双指针联系在一起:双指针初值总是指向最大或最小值处)
思路:“977. 有序数组的平方”的变体,双指针:最大值总是在两数组末尾(双指针初始值),双指针初始化为两个数组的尾索引,哪个的大就更新哪个指针;不过赋值
是要在nums1中本地完成。
'''
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
# 情况1:nums2为空
if not nums2:
return
# 情况2:nums1为空nums2不为空也能处理!
pointer1, pointer2 = m - 1, n - 1 # 双指针初始化为两个数组的“有效”尾索引
idx = m + n - 1
while pointer1 >= 0 or pointer2 >= 0: # 有一个数组没访问完就继续循环;也可以用idx>=0的条件
if pointer1 < 0: # nums1元素访问完
nums1[idx] = nums2[pointer2]
pointer2 -= 1
elif pointer2 < 0: # nums2元素访问完
nums1[idx] = nums1[pointer1]
pointer1 -= 1
elif nums1[pointer1] >= nums2[pointer2]: # 两个数组都没访问完:nums1元素大
nums1[idx] = nums1[pointer1]
pointer1 -= 1
else: # 两个数组都没访问完:nums2元素大
nums1[idx] = nums2[pointer2]
pointer2 -= 1
idx -= 1
if __name__ == '__main__':
obj = Solution()
while True:
try:
in_line = input().strip().split('=')
nums1 = [int(n) for n in in_line[1].split(']')[0].split('[')[1].split(',')]
m = int(in_line[2].strip().split(',')[0])
nums2 = [int(n) for n in in_line[3].split(']')[0].split('[')[1].split(',')]
n = int(in_line[4].strip())
obj.merge(nums1, m, nums2, n)
print(nums1)
except EOFError:
break
个人总结体会
1、对于以上题目,给定的数组本身肯定要完成一次遍历的,一次遍历对应的就是O(N)的时间复杂度
2、一次遍历的同时获取到变换后的有序的新数组 需要 双指针访问这种技巧:双指针初值总是指向最大或最小值处,就是题目的解题套路。
3、通过和leetcode分类刷题:基于数组的双指针(一、基于元素移除的类型)对比,可以发现两者在使用双指针的不同motivation,有助于我们在更进阶的双指针题目上有更深的算法思想理解,而不是纯记忆结题套路与模板。