Combination Sum题解

8 篇文章 0 订阅
7 篇文章 0 订阅

题干:
Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:
All numbers (including target) will be positive integers.
Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
The solution set must not contain duplicate combinations.
For example, given candidate set 2,3,6,7 and target 7,
A solution set is:
[7]
[2, 2, 3]
翻译:
给定一个待选数据的集合candidate和一个目标target,寻找所有和为target的子集合,并且不能有重复,要求:子集合必须单调不减,candidate中的元素可以重复使用
分析:
题目一眼看上去很像之间做过的sum系列,然而这里k不是固定的,没有确切要求几个元素的和是target,for循环+指针或者hash的方法显然不行,我们需要一种新的解题思路。再次回顾题目,如果我们把问题简化一下,不可以用重复元素,第一个元素要从candidate[0]开始,那么是不是就很像在一棵树中找到一条路径,让路径上的元素之和等于target呢?
想到这里,我们就可以引入今天利用的算法:回溯法
什么是回溯法?所谓回溯法,就是把求解空间抽象成一个图,然后从图中某一点出发,利用深度遍历找到所有满足条件的解
好了,我们再回到题目,加上我们之前分析时去掉的条件,那么就成了在一个图中从图中任何一个点出发,找到所有满足条件的路径,先上代码:

class Solution {
public:
    vector<vector<int> > ret;
    vector<int> curr;
public:
    void search(vector<int>& candidates, int next, int target){
        //返回条件
        if (target == 0){
            ret.push_back(curr);
            return;
        }
        if (next == candidates.size() || target - candidates[next] < 0)
            return;
        //回溯递归部分
        curr.push_back(candidates[next]);
        search(candidates, next, target - candidates[next]);
        curr.pop_back();

        search(candidates, next + 1, target);
    }
    vector<vector<int> > combinationSum(vector<int> &candidates, int target){
        sort(candidates.begin(), candidates.end());
        search(candidates, 0, target);
        return ret;
    }
};

首先对数列进行排序,便于后面的处理,然后我们调用DFS函数search(),search()中前两个返回条件很简单,当我们发现target减到0时就找到了一个解,如果指针超出范围,或者加上下一个指针指向的值已经超过了target,就返回,下面我们看看最重要的递归部分:

curr.push_back(candidates[next]);
search(candidates, next, target - candidates[next]);
curr.pop_back();
search(candidates, next + 1, target);

首先我们把当前指针指向的元素放进curr中,然后向下一个元素走,由于题目要求元素可重复使用,这里next指针先原地踏步,因为已经找到一个元素,所以目标变成了target - candidates[next],然后进入下一个递归函数,如果没有超出范围就一直循环下去,直到出错或者找到一条路径,如果出错跳回上层循环,将错误元素弹出,递归下一个元素直到完成,文字说明并不明了,我们举个例子说明:

数列:[10,2,7,6,3,5]
目标:8

search1是search(candidates, next, target - candidates[next])
search2是search(candidates, next + 1, target)
⑴排序,数列变为2,3,5,6,7,10
⑵next=0,target=8,curr=NULL;第一次递归,达到回溯条件,回溯,进入search2
⑶next=1,target=8,curr=”2”;第二次递归,未达到回溯条件,继续 ,进入search1
⑷next=1,target=6,curr=”2,2”;第二次递归,未达到回溯条件,继续 ,进入search1
⑸next=1,target=4,curr=”2,2,2”;第二次递归,未达到回溯条件,继续 ,进入search1
⑹next=1,target=2,curr=”2,2,2,2”;第二次递归,未达到回溯条件,继续 ,进入search1
⑺next=1,target=0,curr=”2,2,2,2,2”;第二次递归,达到回溯条件,保存,回退
⑻next=2,target=2,curr=”2,2,2,2,7”;第二次递归,达到回溯条件,回退
⑼next=3,target=2,curr=”2,2,2,2,6”;第二次递归,达到回溯条件,回退
and so on….
程序就这样一步步找到所有解
其实回溯法用递归实现非常简便,而且语意明确,这里如果熟悉递归的话,我们知道这里一直递归是有一点问题的,如果出现极端情况目标非常大,而数列中有很小的元素,比如

数列:[1,2,7,6,3,5]
目标:1000

这样,那么看看我们的递归程序会怎么样?一直递归1000遍!才能找到一个解,我们知道,递归程序每递归一次就会用栈保存上一个递归的现场以便返回,这样递归栈就会非常非常长,为了解决这个问题我们并不需要完全改成非递归,只要利用一次循环避免这样的极端情况:

class Solution {
    vector<vector<int>> ret;
    vector<int> curr;
public:
    void backtrack(vector<int>& candidates, int target, int i,int sum) {    
        if (i < 0 || i >= candidates.size() || sum > target) return;
        if (sum == target) {
            ret.push_back(curr);
            return;
        }
        //避免极端情况
        for (; i < candidates.size(); i++) {
            curr.push_back(candidates[i]);
            backtrack(candidates, target, i, sum + candidates[i]);
            curr.pop_back();
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        if (candidates.empty() || target == 0)
            return ret;
        sort(candidates.begin(), candidates.end());
        backtrack(candidates, target, 0, 0);
        return ret;
    }
};

思路基本上是一模一样的,只是利用一个for循环代替左指针右移的过程,实际就是代替search(candidates, next + 1, target);这步递归
但是有点要说明,实际上非极端情况下第一种解法的效率较高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值