0. 前言
每周坚持一定的刷题量,目的是了解各种常见的题目和解题思路,每周总结一次。
刷的题目题单来自于灵神:https://leetcode.cn/discuss/post/3141566/ru-he-ke-xue-shua-ti-by-endlesscheng-q3yd/。
刷的都是基础部分的,1700分以下的题目。
目录
文章目录
-
-
- [0. 前言](#0. 前言)
- [1. 相向双指针](#1. 相向双指针)
-
- [1.1 中等 - 找到k个最接近的元素](#1.1 中等 - 找到k个最接近的元素)
- [1.2 中等 - 数组中的k个最强值](#1.2 中等 - 数组中的k个最强值)
- [1.3 中等 - 两数之和](#1.3 中等 - 两数之和)
- [1.4 中等 - 平方数之和](#1.4 中等 - 平方数之和)
- [1.5 简单 - 统计和小于目标的下标对数目](#1.5 简单 - 统计和小于目标的下标对数目)
- [1.6 中等 - 统计公平数对的数目](#1.6 中等 - 统计公平数对的数目)
- [1.7 中等 - 三数之和](#1.7 中等 - 三数之和)
- [1.8 中等 - 最接近的三数之和](#1.8 中等 - 最接近的三数之和)
- [1.9 中等 - 四数之和](#1.9 中等 - 四数之和)
- [1.10 中等 - 有效三角形的个数](#1.10 中等 - 有效三角形的个数)
- [1.11 中等 - 数的平方等于两数乘积的方法数](#1.11 中等 - 数的平方等于两数乘积的方法数)
- [2. 原地修改](#2. 原地修改)
-
- [2.1 简单 - 移除元素](#2.1 简单 - 移除元素)
- [2.2 简单 - 删除有序数组中的重复项](#2.2 简单 - 删除有序数组中的重复项)
- [3. 总结](#3. 总结)
-
1. 相向双指针
1.1 中等 - 找到k个最接近的元素
题目
给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。
整数 a 比整数 b 更接近 x 需要满足:
|a - x| < |b - x|或者|a - x| == |b - x|且a < b
思考
因为数组是有序的,比如arr = [1,2,3,4,5], k = 4, x = 3,那么最远的就是5这个元素,而剩下的元素必须有k个,即4个。
这么想来,我们要做的就是删除元素,直到只剩下k个,即我们要删除n-k元素。
那么,问题就是如何判断要删除的元素,我们初始化left=0、right=n-1,判断left和right哪个更接近x,删除远的那个即可。
ok,可以实现了。
实现
python
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 初始化指针
n = len(arr)
remove = n-k
left,right = 0,n-1
# 开始判断
while remove:
# 找到远的那个元素
l = abs(arr[left] - x)
r = abs(arr[right] - x)
if l <= r:
# 保留left
right -= 1
else:
# 保留right
left += 1
remove -= 1
return arr[left:left+k]
反思
- 可以不用数组来删除,因为最终保留的结果可以用left、right指针来表示;反之,如果采用了数组,反倒会出现一个问题,就是删除了元素之后,长度变化,索引还不能用了,如下:
python
class Solution:
def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]:
# 初始化指针、复制一份数组
n = len(arr)
remove = n-k
left,right = 0,n-1
# 开始判断
while remove:
# 找到远的那个元素
l = abs(arr[left] - x)
r = abs(arr[right] - x)
if l <= r:
# 保留left
ans.pop(right)
right -= 1
else:
# 保留right
ans.pop(left)
left += 1
上面用pop会出现ans数组长度改变的问题,无法准确求出结果。
1.2 中等 - 数组中的k个最强值
题目
给你一个整数数组 arr 和一个整数 k 。
设 m 为数组的中位数,只要满足下述两个前提之一,就可以判定 arr[i] 的值比 arr[j] 的值更强:
-
|arr[i] - m| > |arr[j] - m| -
|arr[i] - m| == |arr[j] - m|,且arr[i] > arr[j]
请返回由数组中最强的 k 个值组成的列表。答案可以以 任意顺序 返回。
中位数 是一个有序整数列表中处于中间位置的值。形式上,如果列表的长度为 n ,那么中位数就是该有序列表(下标从 0 开始)中位于 ((n - 1) / 2) 的元素。
-
例如
arr = [6, -3, 7, 2, 11],n = 5:数组排序后得到arr = [-3, 2, 6, 7, 11],数组的中间位置为m = ((5 - 1) / 2) = 2,中位数arr[m]的值为6。 -
例如
arr = [-7, 22, 17, 3],n = 4:数组排序后得到arr = [-7, 3, 17, 22],数组的中间位置为m = ((4 - 1) / 2) = 1,中位数arr[m]的值为3。
思考
由于题目并没有说,给的是有序数组,所以先排序,然后拿到中位数。关于中位数,长度为n
- 如果为奇数,[1,2,3,4,5],那么n=5,n-1 // 2 = 2.对应的就是中位数3
- 如果是偶数,[1,2,3,4,5,6],那么n=6,n-1 //2 = 2,对应的就是中位数3
接下来和上面这道题非常类似。我们还是通过删除的思路来解决这道题。
初始化left=0,right=n-1。迭代k次,如果left大于right的话,说明left是最大的,放在结果中的第一个,并且left+=1(这是和上一道题的区别),否则,right-=1
实现
python
class Solution:
def getStrongest(self, arr: List[int], k: int) -> List[int]:
# 初始化:先排序,获得中位数
arr.sort()
n = len(arr)
m = arr[(n-1)//2]
left,right = 0,n-1
ans = []
# 开始迭代
while k:
# 获得左右的值
l = abs(arr[left]-m)
r = abs(arr[right]-m)
# 判断
if l > r:
ans.append(arr[left])
left += 1
else:
ans.append(arr[right])
right -= 1
k -= 1
return ans
1.3 中等 - 两数之和
题目
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
思考
先不考虑最后一个要求,尝试去解决这个问题。
这个是很明显的双指针问题,需要我们初始化left=0,right=n-1。因为数组是有序的并且是递增的,因此:
- 如果left+right > target,说明right的值太大了,right-=1,继续判断
- 如果left+right<target,说明left的值太小了,left+=1,继续判断
- 如果left+right=target,结束
实现
python
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
# 初始化
left,right = 0,len(numbers)-1
# 开始迭代
while left <= right:
# 判断
if numbers[left] + numbers[right] > target:
right -= 1
elif numbers[left] + numbers[right] < target:
left += 1
else:
# 不要忘记题目的索引从1开始
return [left+1,right+1]
1.4 中等 - 平方数之和
题目
给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c 。
思考
我的评价是,这简直是一个数学问题。对于一个数c,要找到两个数的平方和等于它,首先,这两个数肯定小于等于根号下c。
因此,我们创建一个列表,从0开始到根号下c。接着初始化left、right,然后按照上一道题的思路去判断即可。
实现
python
class Solution:
def judgeSquareSum(self, c: int) -> bool:
L = range(int(math.sqrt(c))+1)
left,right = 0,len(L)-1
# 移动
while left <= right:
if L[left]**2 + L[right]**2 > c:
right -= 1
elif L[left]**2 + L[right]**2 < c:
left += 1
else:
return True
return False
反思
- 这道题肯定有更优解法,我的解法只是在暴力迭代的情况下,减少了迭代的次数而已。
1.5 简单 - 统计和小于目标的下标对数目
题目
给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target ,请你返回满足 0 <= i < j < n 且 nums[i] + nums[j] < target 的下标对 (i, j) 的数目。
思考
这个也是,初始化left=0,right=n-1。
由于只要求数目,不要求具体的索引,因此,我们可以把数组排序后来处理,这样方便判断大小。
- left+right>=target,那么,right太大了,right-=1
- left+right<target,那么,说明left到right的所有索引都会满足这个条件,记录数目,并且left+=1,然后继续判断
- 直至left>right,结束
实现
python
class Solution:
def countPairs(self, nums: List[int], target: int) -> int:
# 初始化
nums.sort()
n = len(nums)
left,right = 0,n-1
ans = 0
# 开始迭代
while left <= right:
# 太大了
if nums[left]+nums[right] >= target:
right -=1
# 满足条件
elif nums[left]+nums[right] < target:
ans += right - left
left += 1
return ans
1.6 中等 - 统计公平数对的数目
题目
给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和两个整数 lower 和 upper ,返回 公平数对的数目 。
如果 (i, j) 数对满足以下情况,则认为它是一个 公平数对 :
0 <= i < j < n,且lower <= nums[i] + nums[j] <= upper
思考
同上,题目不要求具体的对索引值,因此可以先排序,然后初始化left=0、right=n-1.
思考了一会,发现这道题和上面虽然感觉一样,但是区别还是很大。因为它多了一个区间,变成双区间的判断问题。
感觉可以用两次指针去判断:初始化left、right
- 第一个边界:迭代left,找到满足left+right<=upper的个数
- 第二个边界:迭代left,找到满足left+right<lower的个数
- 这样,第一个边界包含了第二个边界的个数,然后两者相减即可
- 为了,可以提取出一个共同函数实现,把第二个边界条件改为:left+right <= lower - 1
实现
python
class Solution:
def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:
nums.sort()
def count(target):
# 初始化
ans = 0
right = len(nums) - 1
# 迭代left,遍历right
for left,v in enumerate(nums):
# 如果left+right大于target,则right-=1
while right > left and nums[right]+v > target:
right -= 1
# 结束条件
if right == left:
break
# 此时找到的是left+right <= target,那么left - right的区间都满足<target
ans += right - left
return ans
return count(upper) - count(lower-1)
1.7 中等 - 三数之和
题目
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
**注意:**答案中不可以包含重复的三元组。
思考
此问题涉及到三个元素,按照双指针的解决思路,我们需要转为双元素的问题,即变为`nums[i] + nums[j] = -nums[k] 。
并且,由于不考虑元素的顺序,我们可以从小到大排序,再来处理。
那么就存在一个解题思路了:
- 外层循环,迭代n-2次(三个元素,外层最多循环n-2次)
- 目标元素 = nums[k],内层的索引为i=k+1,j=n-1
- 内层循环:如果i<j,就迭代,找到三者和为0的元素
实现
python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 初始化
nums.sort()
ans = []
n = len(nums)
# 外层循环:三个元素,肯定只循环n-2次
for k in range(n-2):
x = nums[k]
# 跳过重复
if k > 0 and x == nums[k-1]:
continue
i = k+1
j = n-1
# 内层循环
while i<j:
s = x + nums[i] + nums[j]
if s > 0: # 太大了,右边需要向左移动
j -= 1
elif s < 0:
i += 1
else: # 相等的话
ans.append([x,nums[i],nums[j]])
i += 1
j -= 1
# 跳过相同的
while i < j and nums[i] == nums[i-1]: # 这里-1的原因是上面+1了
i += 1
while j > i and nums[j] == nums[j+1]:
j -= 1
return ans
1.8 中等 - 最接近的三数之和
题目
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
思考
同样采取上面的思路,分为两层循环去解决。
并且,这个返回三数的和,更不需要考虑顺序,先排序再说。
只是,注意:
- 没有管元素是否重复,因此外层循环可以写得很简单
- 内层循环的判断和上面一样,首先是更新left、right指针的索引,然后判断是否更接近即可
实现
python
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
# 初始化
nums.sort()
ans = inf
n = len(nums)
# 外层循环
for k in range(n-2):
# 当前元素值
x = nums[k]
# 下一层的索引
i = k + 1
j = n - 1
# 内层循环
while i<j:
sm = x + nums[i] + nums[j]
s = sm - target
# 如果s值大于0,说明right大了一些
if s > 0:
j -= 1
# 如果s值小于0,说明left小了一些
elif s < 0:
i += 1
# 刚好
else:
return sm
# 上面没有结束,说明没有找到恰好等于target的,需要找一个更近的值
if abs(s) < abs(ans-target):
ans = sm
return ans
1.9 中等 - 四数之和
题目
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复 的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < na、b、c和d互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
思考
从前面的三数之和变为四数,意味着由之前的双层循环,变为三层循环,先迭代一个数,再迭代第二个数,就变成双数问题,可以用双指针求解了。
首先,返回的顺序不重要,且要求元素互不相同,因此可以先将数组从小到大排序。
- 第一层循环,从第一个元素开始
- 第二层循环,从上一个的第二个元素开始
- 第三层循环,双指针判断求和
- 第二层循环,从上一个的第二个元素开始
先不考虑优化的事情,先弄出再说。
实现
python
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
# 初始化
nums.sort()
n = len(nums)
ans = []
# 第一层
for a in range(n-3):
a_n = nums[a]
# 记得跳过重复元素
if a > 0 and a_n == nums[a-1]:
continue
# 第二层
for b in range(a+1,n-2):
b_n = nums[b]
# 跳过重复元素
if b > a+1 and b_n == nums[b-1]:
continue
# 指针
left = b+1
right = n-1
# 第三层
while left < right:
sm = a_n + b_n + nums[left] + nums[right]
# 不同的话
if sm > target:
right -= 1
elif sm < target:
left += 1
else:
# 相等的时候
ans.append([a_n,b_n,nums[left],nums[right]])
# 跳过重复的
left += 1
while left < right and nums[left] == nums[left-1]:
left += 1
right -= 1
while left < right and nums[right] == nums[right+1]:
right -= 1
return ans
反思
- 当你掌握了三个数的求和后,再来做这道题就很简单了(我写完后看到这个代码长度也觉得无语,但是写下来还是比较容易)
1.10 中等 - 有效三角形的个数
题目
给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。
思考
这个题,可以轻松发现,就是一个三数之和的问题,这么想:
- 先把数组从小到大排序
- 三角形的判断就是:最小两边之和 大于 第三个边,等价于:a + b - c > 0
- 那么,就用我们上面学习到的几个数之和来做
实现
python
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
# 初始化
ans = 0
nums.sort()
n = len(nums)
# 第一层循环:循环最大值
for c in range(2,n):
# 不需要跳过重复的值
a = 0
b = c - 1
# 第二层循环:判断值
while a < b:
sm = nums[a] + nums[b] - nums[c]
# 判断是否合适
if sm <= 0:
# 不满足条件,即最短的边不可能再满足条件了
a += 1
else:
# 大于0,说明满足条件,那么,对于a-b中间的元素都满足
ans += b - a
b -= 1
return ans
1.11 中等 - 数的平方等于两数乘积的方法数
题目
给你两个整数数组 nums1 和 nums2 ,请你返回根据以下规则形成的三元组的数目(类型 1 和类型 2 ):
- 类型 1:三元组
(i, j, k),如果nums1[i]2 == nums2[j] * nums2[k]其中0 <= i < nums1.length且0 <= j < k < nums2.length - 类型 2:三元组
(i, j, k),如果nums2[i]2 == nums1[j] * nums1[k]其中0 <= i < nums2.length且0 <= j < k < nums1.length
思考
翻译一下题目就是:两个数组,一个数a来自于一个数组,另外两个数b、c来自于另外一个数组,要求a的平方恰好等于b与c的乘积。
我觉得可以先定义出一个函数,然后两个判断分两次做,然后求和可以得到最终的结果。
那么,我们假设a来自第一个数组,b、c来自第二个数组,就好做了,变成三数之和的版本了。
开始实现。
注意:因为没有要求返回具体索引,只看数量,可以对数组排序
实现
python
class Solution:
# 提取为一个函数
def getNumbers(self,nums1,nums2):
# 初始化
nums1.sort()
nums2.sort()
n1 = len(nums1)
n2 = len(nums2)
ans = 0
# 外层循环:迭代数组1
for a in range(n1):
# 内层循环b、c
b = 0
c = n2 - 1
while b < c:
# 和
sm = nums1[a]**2 - nums2[b]*nums2[c]
# 判断
if sm > 0:
# 说明b太小了
b += 1
elif sm < 0:
# 说明c太大了
c -= 1
else:
# 如果b和c相同,说明中间的元素都满足条件
if nums2[b] == nums2[c]:
ans += (c-b+1)*(c-b)//2
break
else:
b_t = b
# 如果两者不相同,需要单独统计,去除重复的选项
b += 1
while b < c and nums2[b] == nums2[b-1]:
b += 1
c_t = c
c -= 1
# 注意前面b跳过重复元素的时候,会影响原本b的值,而移动c的时候,判断必须是原本的b,记住这一点
while b_t < c and nums2[c] == nums2[c+1]:
c -= 1
# 更新结果值
ans += (b - b_t)*(c_t - c)
return ans
def numTriplets(self, nums1: List[int], nums2: List[int]) -> int:
# 调用函数
return self.getNumbers(nums1,nums2) + self.getNumbers(nums2,nums1)
2. 原地修改
2.1 简单 - 移除元素
题目
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。
int k = removeElement(nums, val); // 调用你的实现
assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
思考
这道题很简单,就是移除与val相等的元素。这里我看到注释里面说,nums中的元素值不会超过50,我直接把数组等于val的值变为100,然后从小到大排序即可。
(没有用双指针解决,后期再说)
实现
python
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
k = 0
for i in range(n):
if nums[i] == val:
nums[i] = 100 # 把值赋值为100
k += 1
nums.sort() # 排序
return n - k # 返回值即可
2.2 简单 - 删除有序数组中的重复项
题目
给你一个 非严格递增排列 的数组 nums ,请你**原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k。去重后,返回唯一元素的数量 k。
nums 的前 k 个元素应包含 排序后 的唯一数字。下标 k - 1 之后的剩余元素可以忽略。
思考
可以直接去迭代数组,不过这里从第二个元素开始迭代,并且初始化一个索引k=1,这个索引代表我们新的值要插入的地方。
- 初始化k=1
- 迭代1到n,如果1这个元素和0元素相同,跳过;如果1这个元素和0不同,保留,并且k+=1
- 最后返回k即可
实现
python
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
# 初始化
k = 1
# 迭代
for i in range(1,len(nums)):
# 判断
if nums[i] != nums[i-1]:
nums[k] = nums[i] # 保留
k += 1
return k
3. 总结
- 获得中位数的方法:(对于偶数情况,看题目如何定义中位数的)
- 如果为奇数,[1,2,3,4,5],那么n=5,n-1 // 2 = 2.对应的就是中位数3
- 如果是偶数,[1,2,3,4,5,6],那么n=6,n-1 //2 = 2,对应的就是中位数3
-
什么情况使用双指针?发现一个点,有的题目非涉及到两数之和与某个值的比较,有的题目说最强/弱的k个数字,这些都逃不脱一点,就是涉及到两个数的比较,并且一般是左边的某个元素和右边的某个元素比较。
-
三数之和以及四数之和 and n数之和的模板总结:在前面刷题的过程中,发现多数之和的问题存在一个通用模板,掌握这个模板,再去思考和实现就会简单很多
python
# 1. 初始化
nums.sort() # 排序一般针对不要求具体的顺序
n = len(nums)
ans = 0
# 2. 第一层循环:此时迭代的最小值
for a in range(n-xx): # xx,如果三数之和,xx=2,因为至少得有三个数;如果为四数之和,xx=3
# 看看是否要跳过一些重复值,如果没有,进入下一步
# 没有重复跳过的话,初始化b、c两个数(这里假设是三数之和,如果是四数的话,这里还得有一个for循环)
b = a + 1
c = n - 1
# 第二层循环,此时b表示第二小的值,c表示最大的那个
while b < c: # 结束条件就是相遇
sm = nums[a]+nums[b]+nums[c] # 求和
# 取判断
if sm > target:
xxx
elif sm < target:
xxx
else:
xxx
- 原地修改的题目都比较简单,并且目前感觉处理方法都不固定,因此不列很多,后面遇到了再刷。