Leetcode 剑指 Offer II 082.组合总和 II

2024-06-16 03:20
文章标签 leetcode 组合 ii offer 总和 082

本文主要是介绍Leetcode 剑指 Offer II 082.组合总和 II,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目难度: 中等

原题链接

今天继续更新 Leetcode 的剑指 Offer(专项突击版)系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~

题目描述

给定一个可能有重复数字的整数数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次,解集不能包含重复的组合。

示例 1:

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

示例 2:

  • 输入: candidates = [2,5,2,1,2], target = 5,
  • 输出:
[[1,2,2],[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

题目思考

  1. 如何做到不添加重复组合?
  2. 如果限制只能用递归或者迭代, 如何解决?

解决方案

方案 1

思路
  • 分析题目, 不难发现这道题和上一道题(剑指 Offer II 081.组合总和)非常类似, 区别只是数字存在重复, 且只能取一次
  • 但如果我们仍采用之前的两分支的做法, 即选取当前数字和不选当前数字, 则会出现重复组合
  • 举个例子, 假设 candidates 是[1,2,1], 而 target 是 3, 那么只选第一个 1 对应的组合是[1,2], 而只选第二个 1 对应的组合是[2,1], 这就导致重复了, 如何解决呢?
  • 既然在不同位置选择相同的数字会导致重复, 那么我们可以将相同数字聚合起来, 对应的就是计数字典{key:cnt}
  • 然后针对字典的每个 key, 我们都可以有 0,1,…,cnt 种选择, 对应就是组合里不添加该数字, 添加一个,…,添加 cnt 个
  • 然后其余部分就和上一道题非常类似了, 本质上来说, 就是相当于将上一道题的无限制添加某个数字改成最多添加 cnt 次
  • 我们可以基于上述分析进行递归求解, 具体做法如下:
    • 首先将原始数组转换成计数字典, 并得到 key 的列表
    • 传入当前下标, 当前组合, 以及当前组合的数字之和
    • 如果当前数字和恰好等于 target, 则将当前组合加入最终结果, 并返回
    • 如果当前数字和已经大于 target, 或者当前下标超出 key 列表的范围, 则没必要继续递归了, 直接返回
    • 如果不是上述两种情况, 则说明可以继续递归, 此时可以使用 0~cnt 个数字, 共有 cnt+1 种情况
    • 对于每种情况, 将对应数目的当前数字添加到当前组合并更新数字和, 然后下标加 1
  • 由于每条递归路径添加的数字个数都不一样, 所以每个递归出口形成的有效组合也各不相同, 无需手动去重
复杂度
  • 时间复杂度 O(2^N): 假设原始数组共有 N 个数字, 每个数字都要么添加到组合, 要么不添加, 所以最多判断 2^N 种组合, 总时间是 2^N
  • 空间复杂度 O(N): 计数字典和 key 列表需要存储最多 N 个元素, 而递归栈在最差情况下长度同样是 N
代码
Python 3
class Solution:def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:# 方法1: 转计数字典+多分支递归res = []# 将原始数组转成计数字典, 并记录key列表cnts = collections.Counter(candidates)keys = list(cnts.keys())# 递归传入当前下标, 当前组合以及当前组合的数字之和def dfs(i, path, sm):if sm == target:# 递归出口#1, 找到一个有效组合, 将其加入最终结果集res.append(path)returnif i >= len(keys) or sm > target:# 递归出口#2, 当前组合已经不可能满足要求了, 直接返回returnfor cnt in range(cnts[keys[i]] + 1):# 针对0~cnt, 将对应次数的数字加入组合并更新数字和, 然后继续处理下一个keydfs(i + 1, path + [keys[i]] * cnt, sm + cnt * keys[i])dfs(0, [], 0)return res

方案 2

思路
  • 接下来我们尝试用迭代的思路来解决
  • 我们可以将递归函数的三个参数作为一个三元组存储起来
  • 然后遍历这个三元组列表, 同样利用递归方案的分析来进行相应处理
  • 只是需要将递归出口的 return 改成 continue, 以及将递归调用改成追加新的三元组到列表中
  • 下面代码中有详细的注释, 方便大家理解
复杂度
  • 时间复杂度 O(2^N): 分析同方案 1
  • 空间复杂度 O(2^N): 需要保存所有可能的三元组
代码
Python 3
class Solution:def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:# 方法2: 转计数字典+三元组迭代res = []# 将原始数组转成计数字典, 并记录key列表cnts = collections.Counter(candidates)keys = list(cnts.keys())# (当前下标,当前组合,当前组合的数字之和)tuples = [(0, [], 0)]for i, path, sm in tuples:if sm == target:# 找到一个有效组合, 将其加入最终结果集, 继续循环res.append(path)continueif i >= len(keys) or sm > target:# 当前组合已经不可能满足要求了, 不再继续处理它, 继续循环continuefor cnt in range(cnts[keys[i]] + 1):# 针对0~cnt, 将对应次数的数字加入组合并更新数字和, 然后继续处理下一个keytuples.append((i + 1, path + [keys[i]] * cnt, sm + cnt * keys[i]))return res

大家可以在下面这些地方找到我~😊

我的 GitHub

我的 Leetcode

我的 CSDN

我的知乎专栏

我的头条号

我的牛客网博客

我的公众号: 算法精选, 欢迎大家扫码关注~😊

算法精选 - 微信扫一扫关注我

这篇关于Leetcode 剑指 Offer II 082.组合总和 II的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

hdu4869(逆元+求组合数)

//输入n,m,n表示翻牌的次数,m表示牌的数目,求经过n次操作后共有几种状态#include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdlib.h>#includ

AI基础 L9 Local Search II 局部搜索

Local Beam search 对于当前的所有k个状态,生成它们的所有可能后继状态。 检查生成的后继状态中是否有任何状态是解决方案。 如果所有后继状态都不是解决方案,则从所有后继状态中选择k个最佳状态。 当达到预设的迭代次数或满足某个终止条件时,算法停止。 — Choose k successors randomly, biased towards good ones — Close

leetcode-24Swap Nodes in Pairs

带头结点。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) { val = x; }* }*/public class Solution {public ListNode swapPairs(L

leetcode-23Merge k Sorted Lists

带头结点。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode(int x) { val = x; }* }*/public class Solution {public ListNode mergeKLists

C++ | Leetcode C++题解之第393题UTF-8编码验证

题目: 题解: class Solution {public:static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num &

从0到1,AI我来了- (7)AI应用-ComfyUI-II(进阶)

上篇comfyUI 入门 ,了解了TA是个啥,这篇,我们通过ComfyUI 及其相关Lora 模型,生成一些更惊艳的图片。这篇主要了解这些内容:         1、哪里获取模型?         2、实践如何画一个美女?         3、附录:               1)相关SD(稳定扩散模型的组成部分)               2)模型放置目录(重要)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

【JavaScript】LeetCode:16-20

文章目录 16 无重复字符的最长字串17 找到字符串中所有字母异位词18 和为K的子数组19 滑动窗口最大值20 最小覆盖字串 16 无重复字符的最长字串 滑动窗口 + 哈希表这里用哈希集合Set()实现。左指针i,右指针j,从头遍历数组,若j指针指向的元素不在set中,则加入该元素,否则更新结果res,删除集合中i指针指向的元素,进入下一轮循环。 /*** @param