回溯常见算法题(1)

回溯

组合

力扣题目链接(opens new window)

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

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

class Solution {
public:
    // 暴力法: 对于每个节点都有两个分支可以走:放\不放
    // 回溯树如下.高度为n,节点个数为2^n-1
    //     1
    //   2    2
    // 3  3  3  3
    void dfs_more_violent(int startIdx, int n, int k) {
        if (numset.size() == k) {
            ans.push_back(numset);
            return ;
        }
        if (i > n) {
            return ;
        }

        // 放
        numset.emplace_back(i); 
        dfs(startIdx + 1, n, k);
        numset.pop_back();

        // 不放
        dfs(startIdx + 1, n, k);
    }

    // 更优的回溯
    // 从[startIdx,n]选取数加入到集合
    // 回溯数如下。高度为k,节点个数:不知道怎么算
    //         [1, n]
    //        /   |   \
    //    [2,n] [3,n] [4,n] ...
    //    /   \
    // [3,n] [4,n]
    void dfs(int startIdx, int n, int k) {
        if (numset.size() == k) {
            ans.push_back(numset);
            return ;
        }
        if (i > n) {
            return ;
        }
        for (int i = startIdx; i <= n; ++i) {
            numset.emplace_back(i);
            dfs(i + 1, n, k);
            numset.pop_back();
        }
    }

    vector<vector<int>> combine(int n, int k) {
        dfs(1, n, k, numset);
        return ans;
    }

private:
    vector<vector<int>> ans;
    vector<int> numset;
};

组合总和1-4

组合总和

组合总和

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

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

示例 2:

输入: candidates = [2,3,5], target = 8

输出: [[2,2,2,2],[2,3,3],[3,5]]

这道题的独特之处:“candidates 中的数字可以无限制重复被选取”。这里只给出for循环的部分。

for (int i = idx; i < candidates.size(); ++i) {
    for (int j = 1, currsumTmp = currsum + candidates[i]; currsumTmp <= target; ++j, currsumTmp += candidates[i]) {
        numset.emplace_back(candidates[i]);
        dfs(i + 1, currsumTmp, target, candidates);
    }
    for (int k = (target - currsum) / candidates[i]; k > 0; --k) {
        numset.pop_back();
    }
}

组合总和 II

组合总和 II

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

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

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

这道题的独特之处就是有可能会出现重复的组合,因为候选数字中有重复的数字。此时,整体框架与其他组合和一样,就是用一个set记录当前位置中已已经试过的数字。

组合总和III

力扣题目链接(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]]

组合是类似的

class Solution {
public:
    void dfs(int startIdx, int k, int targetSum, int curSum) {
        if (curSum > targetSum || numset.size() > k) {
            return ;
        }
        if (curSum == targetSum) {
            if (k == numset.size()) {
                ans.push_back(numset);
            }
            return ;
        }
        for (int i = startIdx; i <= 9; ++i) {
            numset.push_back(i);
            dfs(i + 1, k, targetSum, curSum + i);
            numset.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(1, k, n, 0);
        return ans;
    }

private:
    vector<vector<int>> ans;
    vector<int> numset;
};

组合总和 Ⅳ

组合总和 Ⅳ

给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。

题目数据保证答案符合 32 位整数范围。

此题的独特之处是“不仅同一个数字可以出现多次,而且不同排列也算不同的组合”

那么这就是一个完全背包,先遍历背包大小,再遍历物品。见完全背包篇。

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<int> dp(target + 1);
        dp[0] = 1;
        for (int i = 1; i <= target; i++) {
            for (int& num : nums) {
                if (num <= i && dp[i - num] < INT_MAX - dp[i]) {
                    dp[i] += dp[i - num];
                }
            }
        }
        return dp[target];
    }
};

电话号码的字母组合

力扣题目链接(opens new window)

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

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

17.电话号码的字母组合

示例:

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

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

分割回文串

力扣题目链接(opens new window)

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

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

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

先划分,再判断是否为回文串,是继续划分

class Solution {
public:
    vector<vector<string>> partition(string s) {
        dfs(0, s);
        return ans;
    }
    void dfs(int idx, const string& s) {
        if (idx >= s.size()) {
            ans.push_back(strset);
            return ;
        }
        for (int len = 1; len + idx <= s.size(); ++len) {
            if (huiwen(s, idx, idx + len - 1)) {
                strset.push_back(s.substr(idx, len));
                dfs(idx + len, s);
                strset.pop_back();
            }
        }
    }
    bool huiwen(const string& s, int left, int right) {
        while (left < right) {
            if (s[left] != s[right]) {
                return false;
            }
            ++left;
            --right;
        }
        return true;
    }

private:
    vector<vector<string>> ans;
    vector<string> strset;
};

复原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”]

尝试划分,和回文串划分差不多,就是判断条件变为判断数字是否合法

class Solution {
public:
    // 判断划分的数字是否符合要求
    bool isValid(const string& s, int start, int len) {
        if (s[start] == '0' && len > 1) return false;   // 前导0
        if (len > 3) return false;  // 数字长度大于3
        if (stoi(s.substr(start, len)) > 255) return false; // 大于255
        return true;
    }
    void dfs(int idx, const string& s) {
        if (idx == s.size()) {  // 达到字符串末尾
            if (curr.size() == 4) { // 刚好有4个数字,组成ip
                string ip;
                for (auto str : curr) {
                    ip += (str + ".");
                }
                ans.emplace_back(ip.substr(0, ip.size() - 1));
            }
            return ;
        }
        if (curr.size() == 4) {
            return ;
        }
        for (int len = 1; idx + len <= s.size(); ++len) {
            if (isValid(s, idx, len)) {
                curr.push_back(s.substr(idx, len));
                dfs(idx + len, s);
                curr.pop_back();
            }
        }
    }
    vector<string> restoreIpAddresses(string s) {
        dfs(0, s);
        return ans;
    }

private:
    vector<string> ans;
    vector<string> curr;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值