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

文章介绍了组合总和II的问题,给定一组候选人编号和目标数,找到所有可能的不重复组合,使得数字和为目标数。文章提供了用Rust、Go、C++、C、Python和Java实现的解法,强调了排序、计数去重和递归回溯的重要性。


40. 组合总和 II:

给定一个候选人编号的集合 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

分析:

  • 面对这道算法题目,二当家的再次陷入了沉思。
  • 每个数字都有选择和不选两种操作,遍历或者递归都可以,但是递归比较直观,深度优先,回溯,套娃大法好。
  • 题目要求所有可能的组合,不能重复,本来是需要想办法去重的,循环暴力去重当然可以实现,但是非常慢,去重是瓶颈又绕不开,但是我们可以曲线救国,我们可以计数去重,和查找所有组合不同,要求了总和,所以还应该做排序,排序可以助力剪枝,当处理的数字已经大于目标和时,后面的数字就可以跳过了。
  • 计数排序,去重,计数,排序,一下子都搞定了,这操作真的是太合适了。
  • 由于提前知道数值的范围,我们可以直接使用数组存储计数,进一步提高效率,如果不知道数值取值范围,或者取值范围非常广,就需要使用映射表等动态数据结构来存储计数。
  • 主要影响效率的就是去重的实现,如果遍历已有组合做暴力去重,那效率就非常低,其次就是剪枝,后面已经不可能有结果的循环或者递归要想办法提前回溯。

题解:

rust

impl Solution {
    pub fn combination_sum2(candidates: Vec<i32>, target: i32) -> Vec<Vec<i32>> {
        fn dfs(nums: &Vec<i32>, target: i32, num: i32, row: &mut Vec<i32>, ans: &mut Vec<Vec<i32>>) {
            if target == 0 {
                // 符合条件的一个组合
                ans.push(row.clone());
                return;
            }
            if num as usize == nums.len() || target < num {
                // 尝试到底,开始回溯
                return;
            }
            let count = nums[num as usize].min(target / num);
            (1..count + 1).for_each(|i| {
                // 选择当前下标数字
                row.push(num);
                dfs(nums, target - num * i, num + 1, row, ans);
            });
            row.resize(row.len() - count as usize, 0);
            // 跳过当前下标数字
            dfs(nums, target, num + 1, row, ans);
        }

        // 1 <= candidates[i] <= 50
        let mut nums = vec![0; 51];
        candidates.iter().for_each(|&c| {
            nums[c as usize] += 1;
        });

        let mut ans = Vec::new();

        // 递归深度优先回溯套娃大法
        dfs(&nums, target, 1, &mut Vec::new(), &mut ans);

        return ans;
    }
}

go

func combinationSum2(candidates []int, target int) [][]int {
    var ans [][]int

	// 1 <= candidates[i] <= 50
	nums := make([]int, 51)
	for _, c := range candidates {
		nums[c]++
	}

	var dfs func(int, int, []int)
	dfs = func(target int, num int, row []int) {
		if target == 0 {
			// 符合条件的一个组合
			ans = append(ans, append([]int{}, row...))
			return
		}
		if num == len(nums) || target < num {
			// 尝试到底,开始回溯
			return
		}
		count := target / num
		if nums[num] < count {
			count = nums[num]
		}
		// 选择当前下标数字
		for i := 1; i <= count; i++ {
			row = append(row, num)
			dfs(target-num*i, num+1, row)
		}
		row = row[:len(row)-count]
		// 跳过当前下标数字
		dfs(target, num+1, row)
	}

	dfs(target, 1, []int{})

	return ans
}

c++

class Solution {
private:
    void dfs(vector<int>& nums, int target, int num, vector<int>& row, vector<vector<int>>& ans) {
        if (target == 0) {
            // 符合条件的一个组合
            ans.emplace_back(row);
            return;
        }
        if (num == nums.size() || target < num) {
            // 尝试到底,开始回溯
            return;
        }
        int count = min(target / num, nums[num]);
        for (int i = 1; i <= count; ++i) {
            // 选择当前下标数字
            row.emplace_back(num);
            dfs(nums, target - num * i, num + 1, row, ans);
        }
        for (int i = 1; i <= count; ++i) {
            row.pop_back();
        }
        // 跳过当前下标数字
        dfs(nums, target, num + 1, row, ans);
    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> ans;
        vector<int> row;

        vector<int> nums(51);
        for (int c: candidates) {
            ++nums[c];
        }

        dfs(nums, target, 1, row, ans);

        return ans;
    }
};

c

void dfs(int *nums, int numsSize, int target, int num, int *row, int rowSize, int **ans, int *returnSize,
         int **returnColumnSizes) {
    if (target == 0) {
        // 符合条件的一个组合
        ans[*returnSize] = (int *) malloc(sizeof(int) * rowSize);
        memcpy(ans[*returnSize], row, sizeof(int) * rowSize);
        (*returnColumnSizes)[*returnSize] = rowSize;
        ++(*returnSize);
        return;
    }
    if (num == numsSize || target < num) {
        // 尝试到底,开始回溯
        return;
    }

    int count = fmin(target / num, nums[num]);
    for (int i = 1; i <= count; ++i) {
        // 选择当前下标数字
        row[rowSize + i - 1] = num;
        dfs(nums, numsSize, target - num * i, num + 1, row, rowSize + i, ans, returnSize, returnColumnSizes);
    }
    // 跳过当前下标数字
    dfs(nums, numsSize, target, num + 1, row, rowSize, ans, returnSize, returnColumnSizes);
}

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
int **combinationSum2(int *candidates, int candidatesSize, int target, int *returnSize, int **returnColumnSizes) {
    *returnSize = 0;
    *returnColumnSizes = (int *) malloc(sizeof(int) * 150);
    int **ans = (int **) malloc(sizeof(int *) * 150);
    int row[target];
    int nums[51];
    memset(nums, 0, sizeof(nums));
    for (int i = 0; i < candidatesSize; ++i) {
        ++nums[candidates[i]];
    }

    dfs(nums, 51, target, 1, row, 0, ans, returnSize, returnColumnSizes);

    return ans;
}

python

class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        ans = []
        nums = [0] * 51
        for c in candidates:
            nums[c] += 1

        def dfs(target: int, num: int, row: List[int]):
            if target == 0:
                # 符合条件的一个组合
                ans.append(row.copy())
                return
            if num == len(nums) or target < num:
                # 尝试到底,开始回溯
                return
            count = min(target // num, nums[num])
            for i in range(1, count + 1):
                row.append(num)
                dfs(target - num * i, num + 1, row)
            for i in range(1, count + 1):
                row.pop()
            # 跳过当前下标数字
            dfs(target, num + 1, row)

        dfs(target, 1, [])

        return ans


java

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        // 1 <= candidates[i] <= 50
        int[] nums = new int[51];
        for (int c : candidates) {
            ++nums[c];
        }

        List<List<Integer>> ans = new ArrayList<>();

        // 递归深度优先回溯套娃大法
        this.dfs(nums, target, 1, new LinkedList<>(), ans);

        return ans;
    }

    private void dfs(int[] nums, int target, int num, Deque<Integer> row, List<List<Integer>> ans) {
        if (target == 0) {
            // 符合条件的一个组合
            ans.add(new ArrayList<>(row));
            return;
        }
        if (num == nums.length || target < num) {
            // 尝试到底,开始回溯
            return;
        }
        int count = Math.min(target / num, nums[num]);
        for (int i = 1; i <= count; ++i) {
            // 选择当前下标数字
            row.addLast(num);
            this.dfs(nums, target - num * i, num + 1, row, ans);
        }
        for (int i = 1; i <= count; ++i) {
            row.pollLast();
        }
        // 跳过当前下标数字
        this.dfs(nums, target, num + 1, row, ans);
    }
}

非常感谢你阅读本文~
欢迎【点赞】【收藏】【评论】~
放弃不难,但坚持一定很酷~
希望我们大家都能每天进步一点点~
本文由 二当家的白帽子:https://le-yi.blog.csdn.net/ 博客原创~


评论 39
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二当家的白帽子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值