Swift 数据结构与算法( ) + Leetcode 掘金 #日新计划更文活动
题目
给你一个由 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
你可以按 任意顺序 返回答案 。
示例 1:
输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入: nums = [2,2,2,2,2], target = 8
输出: [[2,2,2,2]]
提示:
1 <= nums.length <= 200-109 <= nums[i] <= 109-109 <= target <= 109
class Solution {
func fourSum(_ nums: [Int], _ target: Int) -> [[Int]] {
}
}
解题思路🙋🏻 ♀️
题目要求:给定一个整数数组和一个目标值,我们需要找到所有独特的四元组,它们的和等于目标值。
函数返回值:返回所有满足条件的四元组的列表。
题目类型:这是一个数组遍历和双指针问题。
解题思路:
-
排序:首先,对数组进行排序。排序的目的是使得我们可以使用双指针技术,并且可以轻松地跳过重复的元素以获得独特的四元组。
-
固定两个元素:对于每对元素 ( nums[i] ) 和 ( nums[j] ),使用双指针技术查找两个其他元素,使得四个元素的和为目标值。
-
使用双指针技术:对于每对 ( nums[i] ) 和 ( nums[j] ),将一个指针放在 ( j+1 ) 的位置,另一个指针放在数组的末尾。然后,根据这三个元素的和与目标值的关系,移动指针。
-
跳过重复的元素:为了得到独特的四元组,当我们移动指针时,我们需要跳过所有重复的元素。
现在,让我们图形化地描述这种方法。考虑以下数组和目标值的例子:
nums = [1,0,-1,0,-2,2], target = 0
排序后的数组为:
nums = [-2,-1,0,0,1,2]
演示:
-
当 ( i = 0 ) (即 ( nums[i] = -2 )) 和 ( j = 1 ) (即 ( nums[j] = -1 )) 时,左指针 ( L ) 指向 ( j+1 ),右指针 ( R ) 指向数组的末尾。
-2, -1, 0, 0, 1, 2 i j L R这四个元素的和为 0,所以我们得到一个结果:([-2,-1,0,2])。
然后,我们移动左指针 ( L ) 到下一个独特的元素,即 ( nums[L] = 0 )。
-2, -1, 0, 0, 1, 2 i j L R这四个元素的和为 1,大于目标值,所以我们移动右指针 ( R )。 但是,由于没有更多的元素,所以 ( j ) 移动到下一个位置。
-
重复上述过程,直到 ( i ) 和 ( j ) 指向数组的最后两个元素。
边界思考🤔
代码
class Solution {
func fourSum(_ nums: [Int], _ target: Int) -> [[Int]] {
// 对数组进行排序,这样我们可以利用双指针技巧
var sortedNums = nums.sorted()
// 存放最终的四元组结果
var result: [[Int]] = []
var i = 0
// 外层循环,遍历第一个数
while i < sortedNums.count - 3 {
// 跳过与前一个数相同的数,避免重复
while i > 0 && i < sortedNums.count - 3 && sortedNums[i] == sortedNums[i - 1] {
i += 1
}
var j = i + 1
// 第二层循环,遍历第二个数
while j < sortedNums.count - 2 {
// 跳过与前一个数相同的数,避免重复
while j > i + 1 && j < sortedNums.count - 2 && sortedNums[j] == sortedNums[j - 1] {
j += 1
}
// 使用双指针技巧遍历剩余的两个数
var left = j + 1
var right = sortedNums.count - 1
// 当左指针小于右指针时,继续遍历
while left < right {
// 计算当前四个数的和
var sum = sortedNums[i] + sortedNums[j] + sortedNums[left] + sortedNums[right]
// 当和等于目标值时
if sum == target {
// 跳过左指针重复的数
while left < right && sortedNums[left] == sortedNums[left + 1] {
left += 1
}
// 跳过右指针重复的数
while left < right && sortedNums[right] == sortedNums[right - 1] {
right -= 1
}
// 添加结果到最终数组
result.append([sortedNums[i],sortedNums[j],sortedNums[left],sortedNums[right]])
left += 1
right -= 1
} else if sum > target { // 当和大于目标值时,右指针左移
right -= 1
} else { // 当和小于目标值时,左指针右移
left += 1
}
}
j += 1
}
i += 1
}
return result // 返回最终结果
}
}
时空复杂度分析
错误与反思
1. 初始代码有误
错误片段:
for i in 0..<nums.count {
if nums[i] > 0 {
continue
}
...
}
错误原因:
试图通过检查 nums[i] > 0 来提前结束循环,但这种方法并不总是有效的,因为存在负数和正数之和为目标值的情况。
当时的思路:
可能想减少计算量,尝试提前结束循环。
修改:
删除这部分提前结束循环的代码。
2. 未考虑数组排序
错误片段:
原始代码中没有对 nums 进行排序。
错误原因:
双指针技巧在未排序的数组上不可靠。
当时的思路:
可能是想直接对原始数组进行操作,没有意识到排序的重要性。
修改:
在代码开始时对数组进行排序:
var sortedNums = nums.sorted()
3. 重复值处理不当
错误片段:
while sortedNums[i] == sortedNums[i + 1] {
continue
}
错误原因:
这种处理方式会导致无限循环,因为没有增加 i 的值。此外,这种方法无法正确处理重复值。
当时的思路:
可能试图避免添加重复的组合到结果中。
修改:
修改为以下逻辑,确保 i 值增加,并正确处理重复值:
while i > 0 && i < sortedNums.count - 3 && sortedNums[i] == sortedNums[i - 1] {
i += 1
}
以后如何避免:
- 明确思路:在开始编码之前,先明确解题思路,确保考虑到了所有可能的情况。
- 注意边界条件:确保所有循环和条件语句都有明确的边界,避免无限循环或数组越界。
- 逐步调试:不要等到整个解决方案都写完再测试。可以分阶段地完成并测试代码,确保每一步都是正确的。
- 多做类似题目:通过多做类似的题目,可以积累经验,更好地认识到某些常见的陷阱和错误。
概念
使用场景与应用
1. 学习的概念和实际应用
核心概念:
- 双指针技巧:在有序数组上,使用两个指针从两端开始移动,根据指针所指元素之和与目标值的关系来决定移动哪个指针。
- 数组排序:为了更高效地搜索和比较,常常需要对数组进行排序。
- 避免重复解:当数组中存在重复元素时,如何避免得到重复的组合或子序列。
实际应用场景:
-
数据库查询优化:当我们需要从大量数据中找到满足特定条件的记录组合时,双指针技巧和排序可以帮助我们更高效地执行查询。
技术点:数据库索引、查询优化、内存数据结构如B-tree。
-
图像处理:在处理像素数据时,双指针可以用于寻找满足特定条件的像素区域或模式。
技术点:图像二值化、边缘检测、区域增长算法。
2. 在iOS app开发中的应用
-
图片搜索与排序:在一个图片编辑应用中,用户可能想要根据特定的颜色或亮度范围搜索图片。双指针和排序可以帮助我们快速找到这些图片。
示例:用户想要找到所有亮度在40-60范围内的图片。应用首先根据亮度对所有图片进行排序,然后使用双指针找到满足条件的图片区间。
-
音频处理:在音频编辑或音乐应用中,用户可能想要找到满足特定频率范围的音频片段。与处理图像类似,我们可以使用排序和双指针来实现。
示例:在一个音乐应用中,用户想要找到所有频率在300-500Hz的音频片段。应用可以使用FFT (Fast Fourier Transform) 对音频进行频率分析,然后对结果进行排序,并使用双指针找到满足条件的音频片段。
-
快速滚动列表:在一个长列表中,用户可能想要快速滚动到满足特定条件的项目。通过预先对列表进行排序并使用双指针,应用可以迅速地导航到正确的位置。
示例:在一个购物应用中,用户想要看到所有价格在20之间的商品。应用可以根据价格对商品列表进行排序,并使用双指针快速滚动到这个价格范围。