Leetcode算法——40、组合之和II

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/HappyRocking/article/details/84137342

给定一个数组 candidates 和一个目标数 target,找到 candidates 中所有可以使数字之和等于 target 的组合。

数组中的每个元素只可以使用一次。

备注:
数组的元素和目标值都是正数。
答案中不能有重复组合。

Example 1:
Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

Example 2:
Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
  [1,2,2],
  [5]
]

思路

这道题和Leetcode算法——39、组合之和相似,有两个不同之处:

  • 本题的数组 candidates 中可以包含重复元素。
  • 本题的数组 candidates 中的同一位置的元素只能使用一次。

因此,需要对上一题的分治法+回溯法做出修改。

首先,既然不限制组合的大小,那么很容易将一个问题进行分解:比如数组第一个元素为1,那么目标是求一个组合相加等于5,这个问题可以转化为求一个组合相加等于4,最后所有的组合结果都再加上一个1即可。但是要注意,求一个组合相加等于4时,候选列表中需要去除第一个元素,避免重复使用。

其次,使用回溯法:如果分解到最后,候选数组中所有元素都比要求的和大,那么就无法满足要求。这时候应该知道,肯定是之前某一步,错误地将一个过大的数字加入了组合中,使得剩余的目标值过小。因此,需要使用回溯法,依次将上一个所选元素修改为其他候选元素,如果所有其他候选元素都不满足要求,则需要换上上一个元素,直至找到了那个过大的数字将其换掉。

但是需要注意一点:由于 candidates 中可以包含重复元素,因此在遍历时仅仅通过避免采用同一位置的元素的策略,仍然不够。比如要从 a=[1,1,2,2] 中取出组合之和等于 3,则可以取 a[0]+a[2],也可以取 a[1]+a[3],是不同位置,但是都是 1+2,因此只能出现一次。

怎么避免重复呢?可以在将一个新的元素拼接到半成品组合序列的末尾时,先判断一下这个新的组合是否和成品结果序列中的最后一个序列的前缀相同。如果相同,则说明已经取过同样的组合了,略过。
为什么只需要比较成品结果序列的最后一个序列呢?因为我们提前将数组排了序,所以相同元素都挨在一起,且我们使用递归方法的话,本质上是深度优先遍历,因此遍历的结果序列中如果有相同的,那么肯定也是挨在一起的。

python实现

def combinationSum2(candidates, target):
    """
    :type candidates: List[int]
    :type target: int
    :rtype: List[List[int]]
    回溯法。
    从列表中选择一个元素a,放入到结果池中。
    这样,剩下的任务就是从数组中挑选组合使得求和等于 target-a。
    因此,可以用递归方法继续执行子任务。
    如果任务最后完不成,则将元素a换成下一个元素b,继续执行新的子任务。
    """
    
    sorted_candidates = sorted(candidates)
    result_list = []
    def fun_rec(cur_result, start_idx, remain_value):
        '''
        找到可以满足相加等于remain的一个组合,
        与前缀cur_result连接,
        放入到result_list中
        '''
        if remain_value == 0: # 剩余恰好为0
            result_list.append(cur_result)
            return
        for i in range(start_idx, len(sorted_candidates)):
            value = sorted_candidates[i]
            if value > remain_value: # 之后的更大,更不会满足要求,不必继续循环
                break
            elif result_list and result_list[-1][0:len(cur_result)+1] == cur_result + [value]:
                # 避免结果重复
                start_idx = i + 1 # 避免一个元素多次使用
                continue
            else:
                fun_rec(cur_result + [value], i + 1, remain_value - value)
            
    fun_rec([], 0, target)
    return result_list

if '__main__' == __name__:
    candidates = [2,5,2,1,2]
    target = 5
    print(combinationSum2(candidates, target))

猜你喜欢

转载自blog.csdn.net/HappyRocking/article/details/84137342