训练营第二十五天 | ● 216.组合总和III● 17.电话号码的字母组合39. 组合总和 40.组合总和II 131.分割回文串93.复原IP地址 78.子集

  • 216.组合总和III

剪枝优化如下:

  1. 已经选择的元素个数:path.size();

  2. 所需需要的元素个数为: k - path.size();

  3. 列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())

  4. 在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历

题目

力扣题目链接(opens new window)

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]]

示例 2: 输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]]

思路

确定参数值:n, k, start(开始标志)

确定终止条件:若sum大于目标和, 或者path.size()大于k,都可以return进行剪枝,若满足要求则将path存入result,再return。

单层搜索过程:用for循环,将值存入path中,并计算sum,然后递归,再pop,特别注意的是pop后,sum还要减去当前的值。

class Solution {
private:
    vector<vector<int>> result;
    int sum = 0;
    vector<int> path;

    void backtracking(int k, int n, int start) {
        // 结束条件
        if (sum > n || path.size() > k) return;
        if (sum == n && path.size() == k) {
            result.push_back(path);
            return;
        }
        
        // 递归过程
        for (int i = start; i <= 9; i++) {
            path.push_back(i);
            sum += i;  // 更新sum
            backtracking(k, n, i + 1);  // 注意:不修改k的值,并且从i+1开始下一层递归
            sum -= i;  
            path.pop_back();  // 回溯
        }
    }

public:
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear();
        path.clear();
        backtracking(k, n, 1);
        return result;
    }
};
  • 17.电话号码的字母组合

题目:

力扣题目链接(opens new window)

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

17.电话号码的字母组合

示例:

  • 输入:"23"
  • 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

说明:尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。

思路:

可以使用map或者定义一个二维数组,例如:string letterMap[10],来做映射.

二维数组:

const string letterMap[10] = {
    "", // 0
    "", // 1
    "abc", // 2
    "def", // 3
    "ghi", // 4
    "jkl", // 5
    "mno", // 6
    "pqrs", // 7
    "tuv", // 8
    "wxyz", // 9
};

map:

unordered_map<char, string> phoneMap{
            {'2', "abc"},
            {'3', "def"},
            {'4', "ghi"},
            {'5', "jkl"},
            {'6', "mno"},
            {'7', "pqrs"},
            {'8', "tuv"},
            {'9', "wxyz"}
        };
class Solution {
private:
    vector<string> result; // 存储最终结果
    string path; // 存储当前路径
    
    const string letterMap[10] = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz" // 9
    };

    void backtracking(string& digits, int cur) {
        if (cur == digits.size()) {
            result.push_back(path);
            return;
        }
        int digit = digits[cur] - '0'; // 将字符转化为数字
        string letters = letterMap[digit]; // 取数字对应的字符集
        for (char letter : letters) {
            path.push_back(letter); // 添加当前字母到路径
            backtracking(digits, cur + 1); // 递归处理下一个数字
            path.pop_back(); // 回溯
        }
    }

public:
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) return {}; // 如果输入为空,直接返回空结果
        backtracking(digits, 0); // 开始回溯
        return result; // 返回结果
    }
};

力扣题目链接(opens new window)

39. 组合总和

力扣题目链接(opens new window)

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

  • 输入:candidates = [2,3,6,7], target = 7,
  • 所求解集为: [ [7], [2,2,3] ]

示例 2:

  • 输入:candidates = [2,3,5], target = 8,
  • 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]

思路:

回溯法回溯三部曲,需要注意 backtracking 函数中的 candidates 参数为传引用(vector<int>& candidates),以避免每次递归时复制整个数组。resultpathsum 被定义为类的成员变量,以便在整个类中共享。

class Solution {
private:
    vector<int> path;
    vector<vector<int>> result;
    int sum = 0;
    void backtracking(vector<int>& candidates, int target, int start) { 
        if (sum > target) return;
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = start; i < candidates.size(); i++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, i);
            sum -= candidates[i];
            path.pop_back();
        }
    }
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target, 0);
        return result;
    }
};

40.组合总和II

力扣题目链接(opens new window)

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

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

说明: 所有数字(包括目标数)都是正整数。解集不能包含重复的组合。

  • 示例 1:
  • 输入: candidates = [10,1,2,7,6,1,5], target = 8,
  • 所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

思路:

该题比上一题多了个去重的操作,所以先给数组排序,然后在横向循环时跳过重复的元素。但需要注意:

  1. 避免越界检查:在循环中对 candidates[i - 1] 进行访问时,应该在访问之前加上 i > start 的检查,以避免越界访问。
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    int sum = 0;
    void backtracking(vector<int>& candidates, int target, int start) {
        if (sum > target) return;
        if (sum == target) {
            result.push_back(path);
            return;
        }
        for (int i = start; i < candidates.size(); i++) {
            if (i > start && candidates[i] == candidates[i - 1]) continue;
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, i + 1);
            sum -= candidates[i];
            path.pop_back();
        }

    }
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        backtracking(candidates, target, 0);
        return result;

    }
};

131.分割回文串

力扣题目链接(opens new window)

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例: 输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]

思路:

回溯法,针对横向for循环,选择的第一个区间内含有的字符数依次增大, 纵向的递归则是区间位置依次后移。其中有许多细节需要注意。

1,需要建立一个辅助函数判断字符串是否“回文”。

2,终止条件为startindex == s.size()(不用减1,因为在backtracking(s, i + 1);的过程中startindex已经变成了s.size())。

3, string str = s.substr(startindex, i - startindex + 1); 再赋值给path时,先定义子字符串。语法:s.substr(startIndex, length)

4, 若字符串不回文, 一定要用“else continue” 跳过。

class Solution {
private:
 bool isPalindrome(const string& s, int start, int end) {
     for (int i = start, j = end; i < j; i++, j--) {
         if (s[i] != s[j]) {
             return false;
         }
     }
     return true;
 }
    vector<vector<string>> result;
    vector<string> path;
    void backtracking(string s, int startindex) {
        if (startindex == s.size()) {
            result.push_back(path);
            return;
        }
        for (int i = startindex; i < s.size(); i++) {
             if (isPalindrome(s, startindex, i)) {
                string str = s.substr(startindex, i - startindex + 1);
                path.push_back(str);               
             }
             else continue;
             backtracking(s, i + 1);
             path.pop_back();
        }
    }
public:
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return result;
    }
};

93.复原IP地址

力扣题目链接(opens new window)

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效的 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效的 IP 地址。

示例 1:

  • 输入:s = "25525511135"
  • 输出:["255.255.11.135","255.255.111.35"]

示例 2:

  • 输入:s = "0000"
  • 输出:["0.0.0.0"]

思路:

这道题目和“分割回文串”的解题思路相似,只是多了许多剪枝操作,

path.size()>4, 可以直接return;

因为数字最多三位数,所以i < startindex + 3;

避免前导零时,注意要保证其是非一位数。

然后,用stoi()函数将字符转换为数字。在主函数中,只处理字符串位数在[4, 12]之间的.

class Solution {
private:
    vector<string> result;
    vector<string> path;

    void backtracking(string s, int startindex) {
        if (startindex == s.size() && path.size() == 4) {
            result.push_back(path[0] + '.' + path[1] + '.' + path[2] + '.' + path[3]);
            return;
        }
        if (path.size() > 4) {
            return;
        }
        for (int i = startindex; i < s.size() && i < startindex + 3; i++) {
            if (s[startindex] == '0' && i > startindex) continue;  
           // 避免前导零,需要保证非一位数
            string str = s.substr(startindex, i - startindex + 1);
            int num = stoi(str);
            if (num > 255) continue;  // 检查范围
            path.push_back(str);
            backtracking(s, i + 1);
            path.pop_back();
        }
    }

public:
    vector<string> restoreIpAddresses(string s) {
        result.clear();
        path.clear();
        if (s.size() >= 4 && s.size() <= 12) {
            backtracking(s, 0);
        }
        return result;
    }
};

78.子集

力扣题目链接(opens new window)

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3],   [1],   [2],   [1,2,3],   [1,3],   [2,3],   [1,2],   [] ]

思路:

回溯三部曲,只是result收集的是每个子节点,所以result收集结果放在终止条件以前,其中因为i<nums.size()已经限制了循环,所以上面的终止条件可以不写。

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
        if (startIndex >= nums.size()) { // 终止条件可以不加
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

90.子集II

力扣题目链接(opens new window)

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

思路:

该题相较于上题多了一个去重的操作,所以需要保证在横向for循环(即同一层级)跳过重复元素。

注意:

只跳过同一层级的重复元素,而不是整个数组的。为此,需要确保在检查重复元素时,判断是否是同一层级的元素。将 if (i > 0 && nums[i] == nums[i - 1]) 改为 if (i > startindex && nums[i] == nums[i - 1]),这样可以跳过当前层级的重复元素。

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startindex) {
        result.push_back(path);
        for (int i = startindex; i < nums.size(); i++) {
            if (i > startindex && nums[i] == nums[i - 1]) continue;  
            // 只跳过当前层级的重复元素
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());  // 排序以便检测重复元素
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

491.递增子序列

力扣题目链接(opens new window)

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

思路:

这道题需要两个元素以上,给个if语句判断,再收集结果。然后就是需要确保递增顺序:

if (path.empty() || nums[i] >= path.back())。注意判断是否为空。

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startindex) {
        if (path.size() > 1) result.push_back(path);
        for (int i = startindex; i < nums.size(); i++) {
            if (i > startindex && nums[i] == nums[i - 1]) continue;  // 跳过重复元素
            if (path.empty() || nums[i] >= path.back()) {  // 确保递增顺序
                path.push_back(nums[i]);
                backtracking(nums, i + 1);
                path.pop_back();
            }
        }
    }
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};

  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值