39, 40:Combination Sum 1, 2

本文深入探讨了组合求和问题的两种变体:Combination Sum 和 Combination Sum II,通过回溯算法解决。针对重复数字导致的重复解难题,提出了有效的避免策略,包括排序和检查前一个数字的选择状态。

39. Combination Sum

这道题就是简单的回溯,需要注意的是为了避免重复结果以及降低时间复杂度,可以先对cans数组进行排序,然后每次回溯的时候从chs数组的最后一个元素在cans数组中的位置开始进行,这样就保证了解序列是升序,从而避免了重复回溯,因此也减少了时间。

class Solution {
public:
    void DFS(int now, int target, vector<int>& chs, vector<int>& cans, int* idx, vector<vector<int>>& result){
        if(now == target){
            result.push_back(chs);
            return;
        }
        int i = (!chs.empty() ? idx[chs.back()] : 0);
        for(; i < cans.size(); i++){
            if(now + cans[i] > target) break;
            chs.push_back(cans[i]);
            DFS(now+cans[i], target, chs, cans, idx, result);
            chs.pop_back();
        } 
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<int> chs;
        vector<vector<int>> result;
        if(target == 0) return result;
        sort(candidates.begin(), candidates.end());
        int idx[1000];
        for(int i = 0; i < candidates.size(); i++){
            idx[candidates[i]] = i;
        }
        DFS(0, target, chs, candidates, idx, result);
        return result;
    }
};

40. Combination Sum II

本题由于重复数字的存在,比上一题难一些,主要难点是如何避免重复结果。当然可以选择使用set,虽然也能AC,不过那就大大增加时间复杂度了,当然是不能接受的。
为什么会产生重复结果呢?如果我们依旧先对数组进行排序,那么重复数字就是排列在一起的。比如对于 (1 1 1 1...),如果某个解包括4个1,那这个解当然是不会重复的,因为一共只有4个1;如果某个解包含1的个数小于4大于0,比如3个,那么此解一定会重复,因为回溯的会产生如下选择:(1 1 1 0), (0 1 1 1), (1 0 1 1), (1 1 0 1)(1代表选择该位置的解,0代表不选)。所以,产生重复的原因就是某个重复数字的小于其重复个数的组合数是大于1的。
那要如何避免重复呢?我们考虑一下本题中回溯的执行过程,只要到该位置的时候和不超过target,该位置上的数就会被选择的,也就是从该位置上的数开始新的DFS,如果和已超过,则由于已进行过排序,后面位置的数都被该位置的数大,后面也就不用进行了,直接回溯即可。如果现在选择情况是(0 1),即没有选择第一个1,选择了第二个1,从第二个1开始新的DFS,那么很明显选择(1 0)所生成的解与其是相同的,因为都是到了该选第三个数的时候前面选了一个1。再想一下就可以发现该情况其实发生在从第一个1开始的DFS结束之后,而(1 0)这种情况是在从第一个1开始的DFS中已经发生过的,所以(0 1)这种情况所产生的解其实是与之前已经得到的解相重复的。其实对于重复的数字来说,凡是这种没选排在前面的数却选后面的情况所产生的解都是重复的,实质上是从后面的数字开始DFS所产生的解被从前面数字开始DFS所生成的解所包含,这是显而易见的,因为对于重复n次的数字k来说,从第一个k开始DFS是可能选择1~n个k的,而从第二个k开始则只可能选择1~n-1个k(假设和不会超过target),后者所生成的解很明显是包含后者所生成的解的。那么我们只需要在每次决定是否选择该位置的数的时候判断一下左边一位与其相同的数是否被选择即可,如果没被选择,那么由其DFS所产生的解就是重复的,直接跳过该位置即可,这样一来就可以十分简单地去除重复解了。

class Solution {
public:
    void DFS(int now, int p, int target, vector<int>& chs, vector<int>& cans, int* used, vector<vector<int>>& result){
        if(now == target){
            result.push_back(chs);
            return;
        }
        for(int i = p + 1; i < cans.size(); i++){
            if(now + cans[i] > target) return;
            if(i && cans[i] == cans[i-1] && !used[i-1]) continue;
            used[i] = 1; chs.push_back(cans[i]);
            DFS(now+cans[i], i, target, chs, cans, used, result);
            used[i] = 0; chs.pop_back();
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<int> chs;
        vector<vector<int>> result;
        if(target == 0) return result;
        sort(candidates.begin(), candidates.end());
        int used[candidates.size()] = {0};
        DFS(0, -1, target, chs, candidates, used, result);
        return result;
    }
};

转载于:https://www.cnblogs.com/JingwangLi/p/10494403.html

提供了一个基于51单片机的RFID门禁系统的完整资源文件,包括PCB图、原理图、论文以及源程序。该系统设计由单片机、RFID-RC522频射卡模块、LCD显示、灯控电路、蜂鸣器报警电路、存储模块和按键组成。系统支持通过密码和刷卡两种方式进行门禁控制,灯亮表示开门成功,蜂鸣器响表示开门失败。 资源内容 PCB图:包含系统的PCB设计图,方便用户进行硬件电路的制作和调试。 原理图:详细展示了系统的电路连接和模块布局,帮助用户理解系统的工作原理。 论文:提供了系统的详细设计思路、实现方法以及测试结果,适合学习和研究使用。 源程序:包含系统的全部源代码,用户可以根据需要进行修改和优化。 系统功能 刷卡开门:用户可以通过刷RFID卡进行门禁控制,系统会自动识别卡片并判断是否允许开门。 密码开门:用户可以通过输入预设密码进行门禁控制,系统会验证密码的正确性。 状态显示:系统通过LCD显示屏显示当前状态,如刷卡成功、密码错误等。 灯光提示:灯亮表示开门成功,灯灭表示开门失败或未操作。 蜂鸣器报警:当刷卡或密码输入错误时,蜂鸣器会发出报警声,提示用户操作失败。 适用人群 电子工程、自动化等相关专业的学生和研究人员。 对单片机和RFID技术感兴趣的爱好者。 需要开发类似门禁系统的工程师和开发者。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值