LeetCode题解 回溯(二):17 电话号码的组合;39 组合总和;40 组合总和II

回溯

17 电话号码的组合 medium

其实这道题并不复杂,和组合总和类似,给出数字的长度就是遍历的深度,而每次递归时要遍历的字母次数就是此次递归时,对应数字对应的字符串的长度。

无非是有点儿近似真实的场景。

给出本题的代码:

const string numToLetter[8] = {
    
    
    "abc",
    "def",
    "ghi",
    "jkl",
    "mno",
    "pqrs",
    "tuv",
    "wxyz"};

string path = "";
vector<string> res;
void reback(int digitsLength, string digits, int digitsStartIndex) {
    
    
    if (path.size() == digitsLength) {
    
    
        res.push_back(path);
        return;
    }
    int num = digits[digitsStartIndex] - '2';
    for (int i = 0; i < numToLetter[num].size(); i++) {
    
    
        path.push_back(numToLetter[num][i]);
        reback(digitsLength, digits, digitsStartIndex + 1);
        path.pop_back();
    }
    return;
}
vector<string> letterCombinations(string digits) {
    
    
    if (digits == "") return {
    
    };

    int digitsLength = digits.size();
    reback(digitsLength, digits, 0);

    return res;
}

39 组合总和 medium

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。‘

这道题目比此前的组合总和IIII要复杂一点,难度在于候选序列中的数字可以无限制的选取。

递归的深度和广度都取决于目标和的大小与所选数字,

关键在于何时可以终止递归,我的想法是当当前路径和大于目标和的时候就可以退出(先对候选数组进行排序,这样是不是就可以剪枝了?)

因此,除了递归时需要多一个判断条件,递归处理时不需要每次都把起始索引值+1以外,这道题与组合总和IIII差别就不大了,代码如下:

vector<int> path;
vector<vector<int>> res;

void reback(vector<int>& candidates, int target, int sum, int startIndex) {
    
    
    if (sum > target) return;
    if (sum == target) {
    
    
        res.push_back(path);
        return;
    }

    for (int i = startIndex; i < candidates.size(); i++) {
    
    
        path.push_back(candidates[i]);
        reback(candidates, target, sum + candidates[i], i);
        path.pop_back();
    }
    return;
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    
    
    reback(candidates, target, 0, 0);
    return res;
}

这道题目中有个好玩儿点儿是,最早的时候我确实将候选数组进行排序了,但是发现及时没有如此处理,结果也是一样的)

其实本道题还是可以剪枝的,就按照我们说的来,先对候选数组进行排序,在进入递归前就判断总和是否大于目标值就好了,取消重复的终止条件,代码如下:

vector<int> path;
vector<vector<int>> res;

void reback(vector<int>& candidates, int target, int sum, int startIndex) {
    
    
    if (sum == target) {
    
    
        res.push_back(path);
        return;
    }

    for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
    
    
        path.push_back(candidates[i]);
        reback(candidates, target, sum + candidates[i], i);
        path.pop_back();
    }
    return;
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    
    
    sort(candidates.begin(), candidates.end());
    reback(candidates, target, 0, 0);
    return res;
}

40 组合总和II medium

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

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

注意:解集不能包含重复的组合。

看上去这道题貌似更简单了些,实则不然,这道题的难度在于候选数组中有重复的数字,但是题目要求相同的数字组合出的答案只能存在一组,例如[1,1,2],目标和为3,那么第一个1和2以及第二个1和2都符合条件,但是仅只能有一组答案。

所以当2已经用过一次后,就不能再用了。

首先这道题目如何想要方便处理,也是要对候选数组进行排序的。

去重的方式有两种,

一个是构造一个记录数组中的数是否用过的标志位数组,当两个数相同时,如果上个数已经被用过,那么当前的这个数就不再用了,代码如下:

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
    
    
    if (sum == target) {
    
    
        result.push_back(path);
        return;
    }
    for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
    
    
        if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
    
    
            continue;
        }
        sum += candidates[i];
        path.push_back(candidates[i]);
        used[i] = true;
        backtracking(candidates, target, sum, i + 1, used); 
        used[i] = false;
        sum -= candidates[i];
        path.pop_back();
    }
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
    
    
    vector<bool> used(candidates.size(), false);
    path.clear();
    result.clear();
    // 首先把给candidates排序,让其相同的元素都挨在一起。
    sort(candidates.begin(), candidates.end());
    backtracking(candidates, target, 0, 0, used);
    return result;
}

第二种方法是排序后,直接把那些相同的数字去除即可,代码如下:

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
    
    
    if (sum == target) {
    
    
        result.push_back(path);
        return;
    }
    for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
    
    
        if (i > startIndex && candidates[i] == candidates[i - 1]) {
    
    
            continue;
        }
        sum += candidates[i];
        path.push_back(candidates[i]);
        backtracking(candidates, target, sum, i + 1); 
        sum -= candidates[i];
        path.pop_back();
    }
}

vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
    
    
    path.clear();
    result.clear();
    // 首先把给candidates排序,让其相同的元素都挨在一起。
    sort(candidates.begin(), candidates.end());
    backtracking(candidates, target, 0, 0);
    return result;
}

猜你喜欢

转载自blog.csdn.net/qq_41205665/article/details/128651073