leetcode题解之40. 组合总和 II

Posted 刷题之路1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode题解之40. 组合总和 II相关的知识,希望对你有一定的参考价值。

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

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

这道题与上一问的区别在于:

  • 第 39 题:candidates 中的数字可以无限制重复被选取。
  • 第 40 题:candidates 中的每个数字在每个组合中只能使用一次。

编码的不同在于下一层递归的起始索引不一样。

  • 第 39 题:还从候选数组的当前索引值开始。
  • 第 40 题:从候选数组的当前索引值的下一位开始。

相同之处:解集不能包含重复的组合。

为了使得解集不包含重复的组合。我们想一想,如何去掉一个数组中重复的元素,除了使用哈希表以外,我们还可以先对数组升序排序,重复的元素一定不是排好序以后的第 1 个元素和相同元素的第 1 个元素。根据这个思想,我们先对数组升序排序是有必要的。候选数组有序,对于在递归树中发现重复分支,进而“剪枝”也是有效的。

思路分析

这道题其实比上一问更简单,思路是:

以 target 为根结点,依次减去数组中的数字,直到小于 00 或者等于 00,把等于 00 的结果记录到结果集中。

当然你也可以以 00 为根结点,依次加上数组中的数字,直到大于 target 或者等于 target,把等于 target 的结果记录到结果集中。

  • “解集不能包含重复的组合”,就提示我们得对数组先排个序(“升序”或者“降序”均可,下面示例中均使用“升序”)。
  • “candidates 中的每个数字在每个组合中只能使用一次”,那就按照顺序依次减去数组中的元素,递归求解即可:遇到 00 就结算且回溯,遇到负数也回溯。
  • candidates 中的数字可以重复,可以借助「力扣」第 47 题:“全排列 II” 的思想,在搜索的过程中,找到可能发生重复结果的分支,把它剪去。

(温馨提示:下面的幻灯片中,有几页上有较多的文字,可能需要您停留一下,可以点击右下角的后退 “|?” 或者前进 “?|” 按钮控制幻灯片的播放。)

技术图片技术图片
1 / 2

参考代码 1:以 target 为根结点,依次减去数组中的数字,直到小于 00 或者等于 00,把等于 00 的结果记录到结果集中。

感谢用户 @rmokerone 提供的 C++ 版本的参考代码。

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;

public class Solution {

<span class="hljs-comment">/**
 * <span class="hljs-doctag">@param</span> candidates 候选数组
 * <span class="hljs-doctag">@param</span> len
 * <span class="hljs-doctag">@param</span> begin      从候选数组的 begin 位置开始搜索
 * <span class="hljs-doctag">@param</span> residue    表示剩余,这个值一开始等于 target,基于题目中说明的"所有数字(包括目标数)都是正整数"这个条件
 * <span class="hljs-doctag">@param</span> path       从根结点到叶子结点的路径
 * <span class="hljs-doctag">@param</span> res
 */</span>
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] candidates, <span class="hljs-keyword">int</span> len, <span class="hljs-keyword">int</span> begin, <span class="hljs-keyword">int</span> residue, Deque&lt;Integer&gt; path, List&lt;List&lt;Integer&gt;&gt; res)</span> </span>{
    <span class="hljs-keyword">if</span> (residue == <span class="hljs-number">0</span>) {
        res.add(<span class="hljs-keyword">new</span> ArrayList&lt;&gt;(path));
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = begin; i &lt; len; i++) {
        <span class="hljs-comment">// 大剪枝</span>
        <span class="hljs-keyword">if</span> (residue - candidates[i] &lt; <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">break</span>;
        }

        <span class="hljs-comment">// 小剪枝</span>
        <span class="hljs-keyword">if</span> (i &gt; begin &amp;&amp; candidates[i] == candidates[i - <span class="hljs-number">1</span>]) {
            <span class="hljs-keyword">continue</span>;
        }

        path.addLast(candidates[i]);

        <span class="hljs-comment">// 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i</span>
        dfs(candidates, len, i + <span class="hljs-number">1</span>, residue - candidates[i], path, res);

        path.removeLast();
    }
}

<span class="hljs-keyword">public</span> List&lt;List&lt;Integer&gt;&gt; combinationSum2(<span class="hljs-keyword">int</span>[] candidates, <span class="hljs-keyword">int</span> target) {
    <span class="hljs-keyword">int</span> len = candidates.length;
    List&lt;List&lt;Integer&gt;&gt; res = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();
    <span class="hljs-keyword">if</span> (len == <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> res;
    }

    <span class="hljs-comment">// 先将数组排序,这一步很关键</span>
    Arrays.sort(candidates);

    Deque&lt;Integer&gt; path = <span class="hljs-keyword">new</span> ArrayDeque&lt;&gt;(len);
    dfs(candidates, len, <span class="hljs-number">0</span>, target, path, res);
    <span class="hljs-keyword">return</span> res;
}

}


from typing import List

class Solution:

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">combinationSum2</span><span class="hljs-params">(self, candidates: List[int], target: int)</span> -&gt; List[List[int]]:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dfs</span><span class="hljs-params">(begin, path, residue)</span>:</span>
        <span class="hljs-keyword">if</span> residue == <span class="hljs-number">0</span>:
            res.append(path[:])
            <span class="hljs-keyword">return</span>

        <span class="hljs-keyword">for</span> index <span class="hljs-keyword">in</span> range(begin, size):
            <span class="hljs-keyword">if</span> candidates[index] &gt; residue:
                <span class="hljs-keyword">break</span>

            <span class="hljs-keyword">if</span> index &gt; begin <span class="hljs-keyword">and</span> candidates[index - <span class="hljs-number">1</span>] == candidates[index]:
                <span class="hljs-keyword">continue</span>

            path.append(candidates[index])
            dfs(index + <span class="hljs-number">1</span>, path, residue - candidates[index])
            path.pop()

    size = len(candidates)
    <span class="hljs-keyword">if</span> size == <span class="hljs-number">0</span>:
        <span class="hljs-keyword">return</span> []

    candidates.sort()
    res = []
    dfs(<span class="hljs-number">0</span>, [], target)
    <span class="hljs-keyword">return</span> res


// author:rmokerone
#include <iostream>
#include <vector>

using namespace std;

class Solution {

private:
vector<int> candidates;
vector<vector<int>> res;
vector<int> path;
public:
void DFS(int start, int target) {
if (target == 0) {
res.push_back(path);
return;
}

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = start; i &lt; candidates.size() &amp;&amp; target - candidates[i] &gt;= <span class="hljs-number">0</span>; i++) {
        <span class="hljs-keyword">if</span> (i &gt; start &amp;&amp; candidates[i] == candidates[i - <span class="hljs-number">1</span>])
            <span class="hljs-keyword">continue</span>;
        path.push_back(candidates[i]);
        <span class="hljs-comment">// 元素不可重复利用,使用下一个即i+1</span>
        DFS(i + <span class="hljs-number">1</span>, target - candidates[i]);
        path.pop_back();
    }
}

<span class="hljs-function"><span class="hljs-built_in">vector</span>&lt;<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt;&gt; <span class="hljs-title">combinationSum2</span><span class="hljs-params">(<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; &amp;candidates, <span class="hljs-keyword">int</span> target)</span> </span>{
    sort(candidates.begin(), candidates.end());
    <span class="hljs-keyword">this</span>-&gt;candidates = candidates;
    DFS(<span class="hljs-number">0</span>, target);
    <span class="hljs-keyword">return</span> res;
}

};

这里按照用户 @Aspire 提供的思路,给出从 00 开始,一个使用加法,搜索加到目标数的写法,“前提是排序(升序降序均可)”,然后“剪枝”的操作和上面一样。

技术图片

参考代码 2:以 00 为根结点,依次加上数组中的数字,直到大于 target 或者等于 target,把等于 target 的结果记录到结果集中。

#include <iostream>
#include <vector>
#include <map>

using namespace std;

class Solution {
public:

<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; input;
<span class="hljs-keyword">int</span> target;
<span class="hljs-built_in">vector</span>&lt;<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt;&gt; result;
<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; vc;

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> index, <span class="hljs-keyword">int</span> sum)</span> </span>{
    <span class="hljs-comment">// index &gt;= input.size() ,写成 index == input.size() 即可</span>
    <span class="hljs-comment">// 因为每次都 + 1,在 index == input.size() 剪枝就可以了</span>
    <span class="hljs-keyword">if</span> (sum &gt;= target || index == input.size()) {
        <span class="hljs-keyword">if</span> (sum == target) {
            result.push_back(vc);
        }
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = index; i &lt; input.size(); i++) {
        <span class="hljs-keyword">if</span> (input[i] &gt; target) {
            <span class="hljs-keyword">continue</span>;
        }

        <span class="hljs-comment">// 【我添加的代码在这里】:</span>
        <span class="hljs-comment">// 1、i &gt; index 表明剪枝的分支一定不是当前层的第 1 个分支</span>
        <span class="hljs-comment">// 2、input[i - 1] == input[i] 表明当前选出来的数等于当前层前一个分支选出来的数</span>
        <span class="hljs-comment">// 因为前一个分支的候选集合一定大于后一个分支的候选集合</span>
        <span class="hljs-comment">// 故后面出现的分支中一定包含了前面分支出现的结果,因此剪枝</span>
        <span class="hljs-comment">// “剪枝”的前提是排序,升序或者降序均可</span>
        <span class="hljs-keyword">if</span> (i &gt; index &amp;&amp; input[i - <span class="hljs-number">1</span>] == input[i]) {
            <span class="hljs-keyword">continue</span>;
        }

        vc.push_back(input[i]);
        sum += input[i];
        dfs(i + <span class="hljs-number">1</span>, sum);
        vc.pop_back();
        sum -= input[i];
    }
}

<span class="hljs-function"><span class="hljs-built_in">vector</span>&lt;<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt;&gt; <span class="hljs-title">combinationSum2</span><span class="hljs-params">(<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; &amp;candidates, <span class="hljs-keyword">int</span> target)</span> </span>{
    <span class="hljs-comment">// “剪枝”的前提是排序,升序或者降序均可</span>
    sort(candidates.begin(), candidates.end());
    <span class="hljs-keyword">this</span>-&gt;input = candidates;
    <span class="hljs-keyword">this</span>-&gt;target = target;
    dfs(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>);
    <span class="hljs-keyword">return</span> result;
}

};

int main() {
cout << "LeetCode 第 40 题:组合问题 II" << endl;
Solution solution = Solution();

<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; candidates;
candidates.push_back(<span class="hljs-number">10</span>);
candidates.push_back(<span class="hljs-number">1</span>);
candidates.push_back(<span class="hljs-number">2</span>);
candidates.push_back(<span class="hljs-number">7</span>);
candidates.push_back(<span class="hljs-number">6</span>);
candidates.push_back(<span class="hljs-number">1</span>);
candidates.push_back(<span class="hljs-number">5</span>);

<span class="hljs-keyword">int</span> target = <span class="hljs-number">8</span>;
<span class="hljs-built_in">vector</span>&lt;<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt;&gt; res = solution.combinationSum2(candidates, target);
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; res.size(); ++i) {
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>; j &lt; res[i].size(); ++j) {
        <span class="hljs-built_in">cout</span> &lt;&lt; res[i][j] &lt;&lt; <span class="hljs-string">","</span>;
    }
    <span class="hljs-built_in">cout</span> &lt;&lt; <span class="hljs-string">""</span> &lt;&lt; <span class="hljs-built_in">endl</span>;
}
<span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

}

https://www.jianshu.com/p/887ca0b25ce5




















以上是关于leetcode题解之40. 组合总和 II的主要内容,如果未能解决你的问题,请参考以下文章

算法leetcode|40. 组合总和 II(rust重拳出击)

算法 ---- LeetCode回溯系列问题题解

算法 ---- LeetCode回溯系列问题题解

算法 ---- LeetCode回溯系列问题题解

算法 ---- LeetCode回溯系列问题题解

216. 组合总和 III-----回溯篇4