LeetCode第40题:组合总和ll【python 40/1000】

2024-04-19 06:04

本文主要是介绍LeetCode第40题:组合总和ll【python 40/1000】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

LeetCode的第40题“组合总和II”要求在一个可能包含重复元素的整数数组中找出所有的组合,这些组合中的元素之和等于给定的目标数。这些组合中的每个数字在每个组合中只能使用一次。本文将探讨三种不同的方法来解决这个问题,并分析它们的时间和空间复杂度。

题目描述

输入:一个整数数组 candidates 和一个目标数 target

输出:所有唯一的组合,使得组合中数字的总和为 target。数组中的每个数字在每个组合中只能使用一次。

示例

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

方法一:回溯法

解题步骤

使用回溯法搜索所有可能的组合。为避免重复,先对数组进行排序,然后在递归过程中跳过相同的元素。

  1. 排序:首先对数组进行排序,这样可以方便后续步骤跳过重复的元素。
  2. 回溯:设计一个回溯函数,用于递归地探索所有可能的组合。
  3. 剪枝:在回溯过程中,如果当前组合的和已经大于目标值 target,则停止进一步探索。
  4. 跳过重复元素:在同一层递归中,如果一个数和前一个数相同,则跳过这个数,以避免生成重复的组合。

代码实现

from typing import Listdef combinationSum2(candidates: List[int], target: int) -> List[List[int]]:def backtrack(start: int, target: int, path: List[int]):# 当前路径和已经等于目标值,将其添加到结果中if target == 0:res.append(path.copy())return# 遍历起始位置到数组结束for i in range(start, len(candidates)):# 跳过同一树层使用过的元素,避免重复组合if i > start and candidates[i] == candidates[i - 1]:continue# 如果当前元素已大于目标值,则后续元素无需考虑if candidates[i] > target:break# 选择当前值,继续探索path.append(candidates[i])# 递归调用,注意新的起点为 i+1,因为每个数字在每个组合中只能使用一次backtrack(i + 1, target - candidates[i], path)# 撤销选择path.pop()# 结果列表res = []# 先排序,方便后续操作candidates.sort()# 从索引0开始,目标值为target的回溯backtrack(0, target, [])return res# 示例调用
print(combinationSum2([10,1,2,7,6,1,5], 8))  # 输出: [[1,1,6], [1,2,5], [1,7], [2,6]]

算法分析

  • 时间复杂度:O(2^N),其中 N 是数组 candidates 的长度。
  • 空间复杂度:O(N),递归栈的深度最大为 N。

方法二:动态规划

解题步骤

  1. 初始化动态规划列表:创建一个列表 dp,其中 dp[i] 是一个集合,存储所有加和为 i 的组合。
  2. 填充动态规划列表:遍历每个数字,并更新 dp 列表,使其包括当前数字能够形成的所有新组合。
  3. 处理重复元素:为防止同一数字重复使用,从目标值向下更新 dp 列表。
  4. 组合去重:使用集合而不是列表来存储每个 dp[i] 的组合,这样可以自动去除重复的组合。

代码实现

下面是完整的代码实现,包括详细的注释:

from typing import Listdef combinationSum2(candidates: List[int], target: int) -> List[List[int]]:candidates.sort()  # 排序以方便去重dp = [set() for _ in range(target + 1)]dp[0].add(tuple())  # 初始化,和为0的组合自然是空组合for num in candidates:# 从后向前更新dp数组,防止同一数字被重复使用for t in range(target, num - 1, -1):for prev in dp[t - num]:dp[t].add(prev + (num,))# 将每个组合转换为列表形式,以符合题目要求return [list(comb) for comb in dp[target]]# 示例调用
print(combinationSum2([10,1,2,7,6,1,5], 8))
# 输出: [[1,1,6], [1,2,5], [1,7], [2,6]]

代码说明

  • 初始化dp 列表初始化包含目标和 target + 1 个空集合,用于存储达到每个和的可能组合。
  • 动态更新:遍历 candidates 每个元素,并逆序更新 dp,这样可以保证每个元素只在其应在的组合中使用一次。
  • 组合存储:使用元组来存储组合中的元素,元组可以被集合自动去重,这避免了生成重复的组合。
  • 结果转换:最后将存储在 dp[target] 中的组合元组转换为列表,以符合题目输出格式的要求。

算法分析

  • 时间复杂度:O(NTK),其中 N 是数组长度,T 是目标值,K 是平均每个 dp 元素的集合大小。
  • 空间复杂度:O(T*K),存储所有可能组合的空间。

方法三:优化的回溯法

解题步骤

  1. 排序:首先对数组进行排序,这有助于后续的剪枝操作和跳过重复项。
  2. 定义回溯函数:创建一个用于回溯的辅助函数,该函数将尝试构建满足条件的组合。
  3. 剪枝优化:在回溯的每一步中,如果当前组合的总和加上当前数字已经超过目标值,则停止该路径的进一步探索。
  4. 跳过重复元素:在同一层递归中,如果一个数字与前一个数字相同,则跳过当前数字,以避免产生重复的组合。
  5. 回溯逻辑实现
    • 如果组合的和等于目标值,将其添加到结果列表中。
    • 否则,继续探索加入更多的数字。

代码实现

from typing import Listdef combinationSum2(candidates: List[int], target: int) -> List[List[int]]:def backtrack(start: int, target: int, path: List[int]):# 目标金额减到0,添加路径到结果if target == 0:res.append(path.copy())returnif target < 0:return  # 剪枝:当前路径已不可能满足条件,直接返回for i in range(start, len(candidates)):# 跳过同一层树的重复元素if i > start and candidates[i] == candidates[i - 1]:continue# 剪枝:后续所有数都无需考虑if candidates[i] > target:break# 选择当前数,并递归探索path.append(candidates[i])backtrack(i + 1, target - candidates[i], path)path.pop()  # 撤销选择,回溯candidates.sort()  # 排序是剪枝的前提res = []backtrack(0, target, [])return res# 示例调用
print(combinationSum2([10,1,2,7,6,1,5], 8))
# 输出: [[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]]

代码说明

  • 排序:对数组进行排序,以便可以有效地跳过重复项并进行剪枝。
  • 回溯函数 backtrack
    • start:当前选择的起始索引,防止重复使用同一数字。
    • target:剩余目标值,用于判断当前路径的可行性。
    • path:当前路径,记录已选择的数字。
  • 跳过重复元素:通过在循环中添加判断条件,跳过之前已经使用过的并且与当前数字相同的数字。
  • 剪枝:在每次递归调用前检查,如果当前数字大于target,则后续数字都不需要再考虑;如果target减当前数字小于0,则当前路径不可能为解。

算法分析

  • 时间复杂度:O(2^N),其中 N 是数组 candidates 的长度。尽管有剪枝,但在最坏情况下,时间复杂度可能接近2的N次方。
  • 空间复杂度:O(N),递归栈的深度最大为 N,这是由于递归深度与数组长度成正比。

结论

特征回溯法动态规划优化的回溯法
时间复杂度O(2^N)O(N * T * K)O(2^N)
空间复杂度O(N)O(T * K)O(N)
优势- 直接且易于理解
- 灵活处理复杂约束
- 不重复计算子问题
- 状态存储使得查找有效率
- 通过剪枝减少不必要的计算
- 跳过重复元素提高效率
劣势- 可能产生大量重复计算
- 需要手动处理重复结果
- 空间复杂度较高
- 初始化和填充dp表可能复杂
- 逻辑比基本回溯复杂
- 依然可能达到2^N的时间复杂度
适用场景- 数据规模较小
- 需要直观展示所有可能结果
- 需要频繁查询组合结果
- 合适处理不同但相关的多次查询
- 数据规模中等
- 要求避免重复和高效率处理

这三种方法各有优缺点,回溯法是解决这类问题的经典方法,提供了直观的搜索策略和容易理解的结构;动态规划适用于需要重复利用之前结果的情况,但空间消耗相对较大;优化的回溯法在回溯法的基础上通过剪枝减少不必要的计算,提高了效率。在实际应用中,可以根据问题的具体情况和性能需求选择最适合的方法。

这篇关于LeetCode第40题:组合总和ll【python 40/1000】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/916772

相关文章

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步