【LeetCode】16. 最接近的三数之和

文章目录

  • 16. 最接近的三数之和
    • 题目描述
    • 示例 1:
    • 示例 2:
    • 提示:
    • 解题思路
      • 算法分析
      • 问题本质分析
      • 排序+双指针法详解
      • 双指针移动策略
      • 搜索过程可视化
      • 各种解法对比
      • 算法流程图
      • 边界情况处理
      • 时间复杂度分析
      • 空间复杂度分析
      • 关键优化点
      • 实际应用场景
      • 测试用例设计
      • 代码实现要点
    • 完整题解代码

16. 最接近的三数之和

题目描述

给你一个长度为 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
解释:与 target 最接近的和是 0(0 + 0 + 0 = 0)。

提示:

  • 3 <= nums.length <= 1000
  • -1000 <= nums[i] <= 1000
  • -10^4 <= target <= 10^4

解题思路

这道题要求从数组中找出三个数,使它们的和与目标值最接近。这是第15题"三数之和"的变种,需要找到最接近而不是完全相等的组合。这是一个经典的数组搜索和优化问题。

算法分析

这道题的核心思想是排序+双指针优化,主要解法包括:

  1. 排序+双指针法:先排序,再使用双指针寻找最接近的和(推荐)
  2. 优化版本:添加提前剪枝和重复元素跳过
  3. 二分查找法:固定两个数,用二分查找找第三个数
  4. 暴力解法:三重循环枚举所有可能
  5. 递归方法:使用回溯思想逐步选择

问题本质分析

graph TDA[最接近的三数之和] --> B[数组排序]B --> C[固定第一个数]C --> D[双指针寻找]D --> E[更新最接近值]B --> F[时间复杂度优化]C --> G[减少搜索空间]D --> H[线性时间搜索]E --> I[绝对值比较]F --> J[排序O(n log n)]G --> K[跳过重复元素]H --> L[双指针O(n)]I --> M[距离计算]J --> N[总体复杂度O(n²)]K --> NL --> NM --> N

排序+双指针法详解

flowchart TDA[输入数组nums和目标值target] --> B[对数组排序]B --> C[初始化closestSum和minDiff]C --> D[固定第一个数i]D --> E{i < len(nums)-2?}E -->|否| F[返回closestSum]E -->|是| G[跳过重复元素]G --> H[设置双指针left=i+1, right=len(nums)-1]H --> I{left < right?}I -->|否| J[i++]J --> DI -->|是| K[计算sum = nums[i] + nums[left] + nums[right]]K --> L[计算diff = abs(sum - target)]L --> M{diff < minDiff?}M -->|是| N[更新minDiff和closestSum]M -->|否| O{sum < target?}N --> OO -->|是| P[left++]O -->|否| Q{sum > target?}O -->|否| R[返回sum]Q -->|是| S[right--]Q -->|否| RP --> IS --> IR --> F

双指针移动策略

graph TDA["当前和sum与target比较"] --> B{sum < target?}B -->|是| C[left指针右移]B -->|否| D{sum > target?}B -->|否| E[找到完全相等,直接返回]D -->|是| F[right指针左移]D -->|否| EC --> G[增大sum值]F --> H[减小sum值]G --> I[接近target]H --> II --> J[继续搜索更优解]E --> K[最优解找到]

搜索过程可视化

graph TDA["输入: nums = [-4, -1, 1, 2], target = 1"] --> B[排序后: [-4, -1, 1, 2]]B --> C["第1轮: i=0, nums[i]=-4"]C --> D["left=1, right=3, sum=-4+(-1)+2=-3"]D --> E["diff=|-3-1|=4, 更新closestSum=-3"]E --> F["sum < target, left++"]F --> G["left=2, right=3, sum=-4+1+2=-1"]G --> H["diff=|-1-1|=2, 更新closestSum=-1"]H --> I["sum < target, left++"]I --> J["left=3, right=3, 结束第1轮"]J --> K["第2轮: i=1, nums[i]=-1"]K --> L["left=2, right=3, sum=-1+1+2=2"]L --> M["diff=|2-1|=1, 更新closestSum=2"]M --> N["sum > target, right--"]N --> O["left=2, right=2, 结束第2轮"]O --> P["最终结果: 2"]

各种解法对比

graph TDA[解法对比] --> B[排序+双指针]A --> C[优化版本]A --> D[二分查找]A --> E[暴力解法]A --> F[递归方法]B --> G[时间O_n²空间O_1]C --> H[时间O_n²空间O_1]D --> I[时间O_n²log_n空间O_1]E --> J[时间O_n³空间O_1]F --> K[时间O_n³空间O_n]B --> L[推荐解法]C --> M[性能最优]D --> N[二分优化]E --> O[基础解法]F --> P[回溯思想]L --> Q[平衡性能和可读性]M --> QN --> QO --> QP --> Q

算法流程图

flowchart TDA[开始] --> B[对数组排序]B --> C[初始化closestSum和minDiff]C --> D[i = 0]D --> E{i < len(nums)-2?}E -->|否| F[返回closestSum]E -->|是| G{跳过重复元素?}G -->|是| H[i++]H --> DG -->|否| I[left = i+1, right = len(nums)-1]I --> J{left < right?}J -->|否| K[i++]K --> DJ -->|是| L[计算sum和diff]L --> M{更新最接近值}M --> N{sum与target比较}N -->|sum < target| O[left++]N -->|sum > target| P[right--]N -->|sum == target| Q[返回sum]O --> JP --> JQ --> R[结束]

边界情况处理

graph TDA[边界情况] --> B[数组长度=3]A --> C[重复元素]A --> D[负数情况]A --> E[目标值超出范围]B --> F[直接返回三数之和]C --> G[跳过重复元素避免重复计算]D --> H[正常处理,注意绝对值计算]E --> I[仍然能找到最接近值]F --> J[特殊情况处理]G --> JH --> JI --> J

时间复杂度分析

graph TDA[时间复杂度分析] --> B[排序阶段]B --> C[搜索阶段]C --> D[总体复杂度]B --> E[O_n log n]C --> F[O_n²]D --> G[O_n²]E --> H[快速排序]F --> I[双指针优化]G --> J[最优解法]J --> K[无法进一步优化]

空间复杂度分析

空间复杂度分析
额外空间使用
排序空间
最终空间复杂度
常数空间
原地排序O_1
O_1
只使用局部变量
空间效率最优

关键优化点

优化策略
排序优化
双指针优化
剪枝优化
减少搜索空间
线性时间搜索
提前返回
跳过重复元素

实际应用场景

应用场景
数值逼近
优化问题
机器学习
金融计算
函数逼近
资源分配优化
参数调优
投资组合优化
核心算法组件

测试用例设计

测试用例
基础功能
边界情况
特殊情况
正常数组
有重复元素
负数数组
最小长度数组
最大长度数组
极值目标
完全相等
接近但不相等
所有元素相同
验证正确性
验证特殊情况

代码实现要点

  1. 排序策略

    • 先对数组排序,便于双指针操作
    • 排序后可以跳过重复元素
  2. 双指针优化

    • 固定第一个数,用双指针寻找另外两个数
    • 根据sum与target的关系移动指针
  3. 距离计算

    • 使用绝对值计算距离
    • 实时更新最接近的和
  4. 剪枝优化

    • 跳过重复元素避免重复计算
    • 找到完全相等时提前返回
  5. 边界处理

    • 处理数组长度为3的特殊情况
    • 确保所有边界条件都有正确输出

这个问题的关键在于理解双指针的移动策略掌握距离计算的优化方法,通过排序和双指针技术,将时间复杂度从O(n³)优化到O(n²),实现高效的最接近三数之和查找。特别是双指针的移动逻辑,需要根据当前和与目标值的关系来决定移动方向。

完整题解代码

package mainimport ("fmt""sort"
)// threeSumClosest 最接近的三数之和 - 排序+双指针法
// 时间复杂度: O(n²),其中n是数组长度
// 空间复杂度: O(1)
func threeSumClosest(nums []int, target int) int {// 先对数组排序sort.Ints(nums)closestSum := nums[0] + nums[1] + nums[2]minDiff := abs(closestSum - target)// 固定第一个数,使用双指针寻找另外两个数for i := 0; i < len(nums)-2; i++ {// 跳过重复元素if i > 0 && nums[i] == nums[i-1] {continue}left := i + 1right := len(nums) - 1for left < right {sum := nums[i] + nums[left] + nums[right]diff := abs(sum - target)// 更新最接近的和if diff < minDiff {minDiff = diffclosestSum = sum}// 根据sum与target的关系移动指针if sum < target {left++} else if sum > target {right--} else {// 找到完全相等的,直接返回return sum}}}return closestSum
}// threeSumClosestOptimized 优化版本 - 提前剪枝
// 时间复杂度: O(n²)
// 空间复杂度: O(1)
func threeSumClosestOptimized(nums []int, target int) int {sort.Ints(nums)closestSum := nums[0] + nums[1] + nums[2]minDiff := abs(closestSum - target)for i := 0; i < len(nums)-2; i++ {// 跳过重复元素if i > 0 && nums[i] == nums[i-1] {continue}left := i + 1right := len(nums) - 1// 提前剪枝:如果当前最小值已经为0,直接返回if minDiff == 0 {return closestSum}for left < right {sum := nums[i] + nums[left] + nums[right]diff := abs(sum - target)if diff < minDiff {minDiff = diffclosestSum = sum}if sum < target {left++// 跳过重复元素for left < right && nums[left] == nums[left-1] {left++}} else if sum > target {right--// 跳过重复元素for left < right && nums[right] == nums[right+1] {right--}} else {return sum}}}return closestSum
}// threeSumClosestBinarySearch 二分查找版本
// 时间复杂度: O(n² log n)
// 空间复杂度: O(1)
func threeSumClosestBinarySearch(nums []int, target int) int {sort.Ints(nums)closestSum := nums[0] + nums[1] + nums[2]minDiff := abs(closestSum - target)for i := 0; i < len(nums)-2; i++ {for j := i + 1; j < len(nums)-1; j++ {// 使用二分查找寻找第三个数remaining := target - nums[i] - nums[j]left := j + 1right := len(nums) - 1// 二分查找最接近remaining的数for left <= right {mid := left + (right-left)/2sum := nums[i] + nums[j] + nums[mid]diff := abs(sum - target)if diff < minDiff {minDiff = diffclosestSum = sum}if nums[mid] < remaining {left = mid + 1} else if nums[mid] > remaining {right = mid - 1} else {return sum}}}}return closestSum
}// threeSumClosestBruteForce 暴力解法 - 三重循环
// 时间复杂度: O(n³)
// 空间复杂度: O(1)
func threeSumClosestBruteForce(nums []int, target int) int {closestSum := nums[0] + nums[1] + nums[2]minDiff := abs(closestSum - target)for i := 0; i < len(nums)-2; i++ {for j := i + 1; j < len(nums)-1; j++ {for k := j + 1; k < len(nums); k++ {sum := nums[i] + nums[j] + nums[k]diff := abs(sum - target)if diff < minDiff {minDiff = diffclosestSum = sum}}}}return closestSum
}// threeSumClosestRecursive 递归方法 - 回溯思想
// 时间复杂度: O(n³)
// 空间复杂度: O(n),递归调用栈
func threeSumClosestRecursive(nums []int, target int) int {closestSum := nums[0] + nums[1] + nums[2]minDiff := abs(closestSum - target)var backtrack func(index, count, currentSum int)backtrack = func(index, count, currentSum int) {if count == 3 {diff := abs(currentSum - target)if diff < minDiff {minDiff = diffclosestSum = currentSum}return}if index >= len(nums) {return}// 选择当前数backtrack(index+1, count+1, currentSum+nums[index])// 不选择当前数backtrack(index+1, count, currentSum)}backtrack(0, 0, 0)return closestSum
}// abs 计算绝对值
func abs(x int) int {if x < 0 {return -x}return x
}func main() {// 测试用例1nums1 := []int{-1, 2, 1, -4}target1 := 1result1 := threeSumClosest(nums1, target1)fmt.Printf("示例1: nums = %v, target = %d\n", nums1, target1)fmt.Printf("输出: %d\n", result1)fmt.Printf("期望: 2\n")fmt.Printf("结果: %t\n", result1 == 2)fmt.Println()// 测试用例2nums2 := []int{0, 0, 0}target2 := 1result2 := threeSumClosest(nums2, target2)fmt.Printf("示例2: nums = %v, target = %d\n", nums2, target2)fmt.Printf("输出: %d\n", result2)fmt.Printf("期望: 0\n")fmt.Printf("结果: %t\n", result2 == 0)fmt.Println()// 额外测试用例nums3 := []int{1, 1, 1, 0}target3 := -100result3 := threeSumClosest(nums3, target3)fmt.Printf("额外测试: nums = %v, target = %d\n", nums3, target3)fmt.Printf("输出: %d\n", result3)fmt.Printf("期望: 2\n")fmt.Printf("结果: %t\n", result3 == 2)fmt.Println()// 测试优化版本fmt.Println("=== 优化版本测试 ===")result1Opt := threeSumClosestOptimized(nums1, target1)result2Opt := threeSumClosestOptimized(nums2, target2)fmt.Printf("优化版本示例1: %d\n", result1Opt)fmt.Printf("优化版本示例2: %d\n", result2Opt)fmt.Printf("结果一致: %t\n", result1Opt == result1 && result2Opt == result2)fmt.Println()// 测试二分查找版本fmt.Println("=== 二分查找版本测试 ===")result1Bin := threeSumClosestBinarySearch(nums1, target1)result2Bin := threeSumClosestBinarySearch(nums2, target2)fmt.Printf("二分查找版本示例1: %d\n", result1Bin)fmt.Printf("二分查找版本示例2: %d\n", result2Bin)fmt.Printf("结果一致: %t\n", result1Bin == result1 && result2Bin == result2)fmt.Println()// 测试暴力解法fmt.Println("=== 暴力解法测试 ===")result1BF := threeSumClosestBruteForce(nums1, target1)result2BF := threeSumClosestBruteForce(nums2, target2)fmt.Printf("暴力解法示例1: %d\n", result1BF)fmt.Printf("暴力解法示例2: %d\n", result2BF)fmt.Printf("结果一致: %t\n", result1BF == result1 && result2BF == result2)fmt.Println()// 测试递归方法fmt.Println("=== 递归方法测试 ===")result1Rec := threeSumClosestRecursive(nums1, target1)result2Rec := threeSumClosestRecursive(nums2, target2)fmt.Printf("递归方法示例1: %d\n", result1Rec)fmt.Printf("递归方法示例2: %d\n", result2Rec)fmt.Printf("结果一致: %t\n", result1Rec == result1 && result2Rec == result2)fmt.Println()// 边界值测试fmt.Println("=== 边界值测试 ===")boundaryTests := []struct {nums   []inttarget int}{{[]int{1, 1, 1}, 3},                 // 最小值{[]int{1000, 1000, 1000}, 3000},     // 最大值{[]int{-1000, -1000, -1000}, -3000}, // 负值{[]int{0, 0, 0}, 0},                 // 零值{[]int{1, 2, 3}, 6},                 // 完全相等{[]int{1, 2, 3}, 5},                 // 接近但不相等}for i, test := range boundaryTests {result := threeSumClosest(test.nums, test.target)fmt.Printf("测试%d: nums = %v, target = %d, result = %d\n", i+1, test.nums, test.target, result)}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93787.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93787.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序实现蓝牙开启自动播放BGM

下面是一个完整的微信小程序实现方案&#xff0c;当蓝牙设备连接时自动播放背景音乐(BGM)。实现思路监听蓝牙设备连接状态当检测到蓝牙设备连接时&#xff0c;自动播放音乐当蓝牙断开时&#xff0c;停止音乐播放处理相关权限和用户交互完整代码实现1. 项目结构text/pages/index…

XML 序列化与操作详解笔记

一、XML 基础概念XML&#xff08;eXtensible Markup Language&#xff0c;可扩展标记语言&#xff09;是一种用于存储和传输数据的标记语言&#xff0c;由 W3C 制定&#xff0c;具有以下特点&#xff1a;可扩展性&#xff1a;允许自定义标记&#xff08;如<Student>、<…

第八十四章:实战篇:图 → 视频:基于 AnimateDiff 的视频合成链路——让你的图片“活”起来,瞬间拥有“电影感”!

AI图生视频前言&#xff1a;从“刹那永恒”到“动态大片”——AnimateDiff&#xff0c;让图片“活”起来&#xff01;第一章&#xff1a;痛点直击——静态图像到视频&#xff0c;不是“幻灯片”那么简单&#xff01;第二章&#xff1a;探秘“时间魔法”&#xff1a;AnimateDiff…

2025深大计算机考研复试经验贴(已上岸)

如果你在初试出分前看到此贴 我建议&#xff1a; 准备机试和简历&#xff0c;即使你不估分&#xff1a;因为如果要准备春招的话&#xff0c;也总要刷题和做简历的。尽早估分&#xff0c;查一下往年的复试线&#xff0c;如果有望进复试&#xff0c;可尽早开始准备。 Preface …

用Pygame开发桌面小游戏:从入门到发布

一、引言 Pygame是一个基于Python的跨平台游戏开发库,它提供了简单易用的图形、声音和输入处理功能,非常适合新手入门游戏开发。本文将以"经典游戏合集"项目为例,带你一步步了解如何使用Pygame开发、打包和发布自己的桌面小游戏。 二、开发环境搭建 安装Python:…

CSS backdrop-filter:给元素背景添加模糊与色调的高级滤镜

在现代网页设计中&#xff0c;半透明元素搭配背景模糊效果已成为流行趋势 —— 从毛玻璃导航栏、模态框遮罩&#xff0c;到卡片悬停效果&#xff0c;这种设计能让界面更具层次感和高级感。实现这一效果的核心 CSS 属性&#xff0c;正是backdrop-filter。它能对元素背后的内容&a…

检索增强生成(RAG) 缓存增强生成(CAG) 生成中检索(RICHES) 知识库增强语言模型(KBLAM)

以下是当前主流的四大知识增强技术方案对比&#xff0c;涵盖核心原理、适用场景及最新发展趋势&#xff0c;为开发者提供清晰的技术选型参考&#xff1a; &#x1f50d; 一、RAG&#xff08;检索增强生成&#xff09;​​ 核心原理​&#xff1a; 动态检索外部知识库&#xff0…

LLM(大语言模型)的工作原理 图文讲解

目录 1. 条件概率&#xff1a;上下文预测的基础 2. LLM 是如何“看着上下文写出下一个词”的&#xff1f; 补充说明&#xff08;重要&#xff09; &#x1f4cc; Step 1: 输入处理 &#x1f4cc; Step 2: 概率计算 &#x1f4cc; Step 3: 决策选择 &#x1f914; 一个有…

Python netifaces 库详解:跨平台网络接口与 IP 地址管理

一、前言 在现代网络编程中&#xff0c;获取本机的网络接口信息和 IP 配置是非常常见的需求。 例如&#xff1a; 开发一个需要选择合适网卡的 网络服务&#xff1b;在多网卡环境下实现 流量路由与控制&#xff1b;在系统诊断工具中展示 IP/MAC 地址、子网掩码、默认网关&#x…

HTML应用指南:利用POST请求获取上海黄金交易所金价数据

上海黄金交易所&#xff08;SGE&#xff09;作为中国唯一经国务院批准、专门从事黄金等贵金属交易的国家级市场平台&#xff0c;自成立以来始终秉持“公开、公平、公正”的原则&#xff0c;致力于构建规范、高效、透明的贵金属交易市场体系。交易所通过完善的交易机制、严格的风…

C++常见面试题-1.C++基础

一、C 基础 1.1 语言特性与区别C 与 C 的主要区别是什么&#xff1f;C 为何被称为 “带类的 C”&#xff1f; 主要区别&#xff1a;C 引入了面向对象编程&#xff08;OOP&#xff09;特性&#xff08;类、继承、多态等&#xff09;&#xff0c;而 C 是过程式编程语言&#xff1…

Tomcat里catalina.sh详解

在 Tomcat 中&#xff0c;catalina.sh&#xff08;Linux/macOS&#xff09;或 catalina.bat&#xff08;Windows&#xff09;是 核心的启动和关闭脚本&#xff0c;用于控制 Tomcat 服务器的运行。它是 Tomcat 的“主控脚本”&#xff0c;负责设置环境变量、启动/关闭 JVM 进程&…

STM32之MCU和GPIO

一、单片机MCU 1.1 单片机和嵌入式 嵌入式系统 以计算机为核心&#xff0c;tips&#xff1a;计算机【处理单元&#xff0c;内存 硬盘】 可以控制的外部设备&#xff0c;传感器&#xff0c;电机&#xff0c;继电器 嵌入式开发 数据源--> 处理器(CPU MCU MPU) --> 执行器 …

22_基于深度学习的桃子成熟度检测系统(yolo11、yolov8、yolov5+UI界面+Python项目源码+模型+标注好的数据集)

目录 项目介绍&#x1f3af; 功能展示&#x1f31f; 一、环境安装&#x1f386; 环境配置说明&#x1f4d8; 安装指南说明&#x1f3a5; 环境安装教学视频 &#x1f31f; 二、数据集介绍&#x1f31f; 三、系统环境&#xff08;框架/依赖库&#xff09;说明&#x1f9f1; 系统环…

数据结构:二叉树oj练习

在讲今天的题目之前&#xff0c;我们还需要讲一下二叉树的以下特点&#xff1a; 对任意一颗二叉树&#xff0c;如果度为0的节点个数是n0&#xff0c;度为2的节点个数是n2&#xff0c;则有n0n21. 证明&#xff1a;二叉树总的节点个数是n&#xff0c;那么有nn0n1n2 二叉树的度为…

RabbitMQ高级特性——TTL、死信队列、延迟队列、事务、消息分发

目录 一、TTL 1.1设置消息的TTL 1.2设置队列的TTL 1.3两者之间的区别 二、死信队列 2.1死信的概念 2.2死信产生的条件&#xff1a; 2.3死信队列的实现 死信队列的工作原理 2.4常⻅⾯试题 三、延迟队列 3.1概念 3.2应用场景 3.3RabbitMQ 实现延迟队列的核心原理 1…

神经网络设计中关于BN归一化(Normalization)的讨论

在神经网络的结构中&#xff0c;我们常常可以看见归一化&#xff08;Normalization&#xff09;如BN的出现&#xff0c;无论是模型的backbone或者是neck的设计都与它有着重大的关系。 因此引发了我对它的思考&#xff0c;接下来我将从 是什么&#xff08;知识领域&#xff0c;诞…

MacOS 安全机制与“文件已损坏”排查完整指南

1. 背景说明macOS 为了保护系统安全&#xff0c;内置了多个安全机制&#xff1a;机制作用是否影响第三方 AppSIP (System Integrity Protection)保护系统关键文件/目录不被篡改高风险 App/驱动可能受限Gatekeeper限制未签名/未认证 App 运行阻止“未知开发者” App文件隔离属性…

package.json文件中的devDependencies和dependencies对象有什么区别?

前端项目的package.json文件中&#xff0c;dependencies和devDependencies对象都用于指定项目所依赖的软件包&#xff0c;但它们在项目的开发和生产环境中的使用有所不同。1.dependencies&#xff1a;dependencies是指定项目在生产环境中运行所需要的依赖项。这些依赖项通常包括…

【最新版】CRMEB Pro版v3.4系统源码全开源+PC端+uniapp前端+搭建教程

一.系统介绍 crmebPro版 v3.4正式发布&#xff0c;智能任务推送、动态标签管理、商城AI生产力&#xff0c;焕然一新&#xff0c;不负期待&#xff01;页面DIY设计功能全面升级&#xff0c;组件更丰富&#xff0c;样式设计更全面&#xff1b;移动端商家管理&#xff0c;让商城管…