LeetCode第十六题: 掌握双指针技巧 最接近的三数之和 【python】

简介: LeetCode第十六题: 掌握双指针技巧 最接近的三数之和 【python】

👤作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

image.png

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

题目描述

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:
 
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
 
输入:nums = [0,0,0], target = 1
输出:0

算法比较

下表总结了几种解决“最接近的三数之和”问题的算法的优劣势以及它们的时间和空间复杂度

  • 时间复杂度表示算法执行的速度,或者说完成算法需要的时间。时间复杂度低的算法在处理大数据集时更有优势。
  • 空间复杂度表示算法在执行过程中需要的存储空间大小。空间复杂度低意味着算法对内存的占用较小。

解题思路

1. 暴力法

这是最直接的方法,即尝试数组中所有可能的三数组合,并找到和最接近目标值 target 的组合。这种方法简单易懂,但时间复杂度非常高。

  1. 首先定义了一个函数 threeSumClosest 接收一个数组 nums 和一个目标值 target
  2. 它使用三层嵌套循环来穷举所有可能的三个数字的组合,并计算这三个数的和。如果这个和更接近目标值 target,就更新 closest_sum 为这个和。
  3. 最后,函数返回最接近 target 的三数之和。

代码实现

def threeSumClosest(nums, target):
    closest_sum = float('inf')  # 设置一个非常大的初始值
    min_diff = float('inf')  # 记录与target的最小差值
    
    # 对每个数进行遍历,计算所有可能的三数之和
    for i in range(len(nums)-2):
        for j in range(i+1, len(nums)-1):
            for k in range(j+1, len(nums)):
                sum = nums[i] + nums[j] + nums[k]
                diff = abs(target - sum)
                
                # 如果当前和更接近target,更新closest_sum
                if diff < min_diff:
                    min_diff = diff
                    closest_sum = sum
                    
    return closest_sum
 
# 示例
nums = [-1, 2, 1, -4]
target = 1
print(threeSumClosest(nums, target))

算法分析

  • 时间复杂度:O(n^3),因为需要三层循环遍历所有可能的组合。
  • 空间复杂度:O(1),因为只需要常数级别的额外空间。

2. 双指针法

  1. 排序:首先,我们需要对数组进行排序。排序是使用双指针技巧的前提,因为排序后才能通过指针的移动来控制和的大小。
  2. 双指针遍历:遍历数组,对于每个元素 nums[i],设置两个指针,一个指向 i+1,另一个指向数组的最末尾。通过比较三数之和与 target 的差值,移动指针来逼近目标值。
  3. 更新最接近值:在移动指针的过程中,如果遇到更接近目标值 target 的三数之和,就更新最接近值。

代码实现

def threeSumClosest(nums, target):
    nums.sort()
    closest_sum = float('inf')
    
    for i in range(len(nums) - 2):
        left, right = i + 1, len(nums) - 1
        while left < right:
            current_sum = nums[i] + nums[left] + nums[right]
            if abs(current_sum - target) < abs(closest_sum - target):
                closest_sum = current_sum
            
            if current_sum < target:
                left += 1
            elif current_sum > target:
                right -= 1
            else:
                return target  # 直接返回目标值
        
    return closest_sum

算法分析

  • 时间复杂度:排序的时间复杂度是 O(n log n),遍历数组并使用双指针的时间复杂度是 O(n^2),因此总体时间复杂度是 O(n^2)。
  • 空间复杂度:除了排序,我们只使用了常数额外空间,所以空间复杂度是 O(1)。

3. 排序加哈希表

这是一种折中的方法,将三数之和转换为两数之和的问题。对数组排序后,对于每个元素,使用哈希表快速查找是否存在满足条件的另外两个数。

解题思路

  1. 排序:首先对数组进行排序,这是为了后续能更高效地使用双指针技巧。
  2. 使用哈希表存储:考虑将每两个数的和以及这两个数的索引存储在哈希表中。这样做的目的是为了快速查找是否存在一个数,使得该数与某个和相加接近于目标值。
  3. 搜索和更新:遍历每个数,对于每个数,查找哈希表中是否存在与之配对的和,使得三数之和最接近于目标值,并更新最接近的和。

代码实现

def threeSumClosest(nums, target):
    nums.sort()  # 对数组进行排序
    closest_sum = float('inf')  # 初始化最接近的和为无穷大
    sums_hash = {}  # 初始化哈希表来存储两数之和和对应的索引
    
    # 填充哈希表
    for i in range(len(nums)):
        for j in range(i+1, len(nums)):
            two_sum = nums[i] + nums[j]
            # 哈希表的键为两数之和,值为这两个数的索引
            if two_sum not in sums_hash:
                sums_hash[two_sum] = []
            sums_hash[two_sum].append((i, j))
    
    # 遍历数组,使用哈希表查找最接近的和
    for k in range(len(nums)):
        for sum_val, indices in sums_hash.items():
            for index_pair in indices:
                if k not in index_pair:  # 确保不重复使用相同的元素
                    current_sum = sum_val + nums[k]
                    if abs(current_sum - target) < abs(closest_sum - target):
                        closest_sum = current_sum
                        
    return closest_sum

算法分析

  • 时间复杂度:O(n^2),对每个元素都可能需要一次线性时间的遍历。
  • 空间复杂度:O(n) 这种方法的空间复杂度较高,因为它存储了大量的两数之和和索引对。

4. 随机化算法

可以使用随机化技术,例如蒙特卡洛方法,随机选择三个数计算其和,多次迭代以提高找到最接近解的几率。

解题思路

  1. 随机选择:随机选择三个数,计算它们的和,并记录这个和与目标值target的差距。
  2. 迭代改进:重复上述过程多次,每次尝试时,如果找到了更接近目标值的和,就更新这个最接近的和。
  3. 停止条件:设定一个迭代次数作为停止条件,防止算法运行时间过长。

这种方法的优点是实现简单,能够在一定程度上探索解空间,但缺点是不能保证找到最优解,解的质量高度依赖于随机选择和迭代次数。

代码实现

import random
 
def threeSumClosest(nums, target):
    closest_sum = float('inf')  # 初始化最接近的和为无穷大
    iterations = 10000  # 设定迭代次数
    
    for _ in range(iterations):
        # 随机选择三个不同的索引
        indices = random.sample(range(len(nums)), 3)
        # 计算这三个数的和
        current_sum = sum(nums[i] for i in indices)
        # 如果这个和更接近target,更新closest_sum
        if abs(current_sum - target) < abs(closest_sum - target):
            closest_sum = current_sum
    
    return closest_sum
 
# 示例
nums = [-1, 2, 1, -4]
target = 1
print(threeSumClosest(nums, target))

算法分析

  • 时间复杂度:这取决于迭代次数,可能无法保证找到最优解。
  • 空间复杂度:O(1),不需要额外空间。

综合比较

这些方法与双指针方法相比,可能在特定情况下更加高效或简单。但从时间复杂度的角度看,双指针方法通常是最优的选择,尤其是对于大数据集。暴力法在实际应用中通常不可行,因为它的时间复杂度过高;二分查找法和哈希表方法在一些情况下可以提高效率,但它们要么需要额外的预处理步骤,要么需要额外的空间开销;随机化算法可能无法保证总是得到最接近的解。

在选择解题策略时,除了考虑时间和空间复杂度之外,还应该考虑实现的复杂性、代码的可读性以及算法的稳定性。在面试或竞赛中,双指针技术是一个常用且高效的解决方案,可以作为首选方法来解决类似的问题。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
16天前
|
存储 算法 数据可视化
【模拟面试问答】深入解析力扣163题:缺失的区间(线性扫描与双指针法详解)
【模拟面试问答】深入解析力扣163题:缺失的区间(线性扫描与双指针法详解)
|
16天前
|
算法 数据挖掘 Java
深入解析力扣167题:两数之和 II(双指针法详解及模拟面试问答)
深入解析力扣167题:两数之和 II(双指针法详解及模拟面试问答)
|
16天前
|
存储 算法 数据挖掘
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
【模拟面试问答】力扣165题:比较版本号(逐个比较与双指针法详解及模拟面试问答)
|
16天前
|
存储 算法 数据可视化
深入解析力扣160题:相交链表的解决方法(哈希表法与双指针法详细图解)
深入解析力扣160题:相交链表的解决方法(哈希表法与双指针法详细图解)
|
16天前
|
SQL 算法 数据可视化
LeetCode题目92:反转链表ll 【python 递归与迭代方法全解析】
LeetCode题目92:反转链表ll 【python 递归与迭代方法全解析】
|
13天前
|
算法
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表
|
13天前
|
算法 容器
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
【经典LeetCode算法题目专栏分类】【第1期】左右双指针系列:盛最多水的容器、接雨水、回文子串、三数之和
|
16天前
|
存储 算法 大数据
深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)
深入解析力扣170题:两数之和 III - 数据结构设计(哈希表与双指针法详解及模拟面试问答)
|
16天前
|
存储 SQL 算法
LeetCode 题目 117:填充每个节点的下一个右侧节点指针 II
LeetCode 题目 117:填充每个节点的下一个右侧节点指针 II
|
16天前
|
存储 SQL 算法
LeetCode 题目 116:填充每个节点的下一个右侧节点指针
LeetCode 题目 116:填充每个节点的下一个右侧节点指针