代码随想录算法训练营第二十五天

回溯[Leetcode39组合总和、Leetcode40组合总和II、Leetcode131分割回文串]

Posted by ThreeStones1029 on April 27, 2024

[TOC]

第一题

Leetcode39组合总和

解法一[暴力回溯法]

  • 会超时
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    int sum = 0;
    // 比较两个数组是否一样
    bool issame_vector(vector<int> array1, vector<int> array2) {
        sort(array1.begin(), array1.end());
        sort(array2.begin(), array2.end());
        if (array1.size() == array2.size()) {
            int i;
            for (i = 0; i < array1.size(); i++) {
                if (array1[i] != array2[i]) break;
            }
            if (i == array1.size()) return true;
            else return false;
        }
        else return false;
    }


    void backtracking(vector<int>& candidates, int target) {
        if (sum > target) {
            return;
        }
        else if (sum == target) {
            // 判断是否有一样的数组,没有则加入
            bool is_exist = false;
            for (int i = 0; i < result.size(); i++) {
                if (issame_vector(path, result[i])) is_exist = true;
            }
            if (!is_exist) result.push_back(path);
            return;
        }
        else {
            for (int i = 0; i < candidates.size(); i++) {
                sum += candidates[i];
                path.push_back(candidates[i]);
                backtracking(candidates, target);
                sum -= candidates[i];
                path.pop_back();
            }
        }
    }
    
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking(candidates, target);
        return result;
    }
};

解法二[回溯法]

  • 空间复杂度O(n)
  • 空间复杂度O(n)
class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    int sum = 0;
    // 加入开始下标,每种情况表示相当于固定不同的数字个数
    void backtracking1(vector<int>& candidates, int target, int start) {
        if (sum > target) {
            return;
        }
        else if (sum == target) {
            result.push_back(path);
            return;
        }
        else {
            for (int i = start; i < candidates.size(); i++) {
                sum += candidates[i];
                path.push_back(candidates[i]);
                backtracking1(candidates, target, i);
                sum -= candidates[i];
                path.pop_back();
            }
        }
    }
    
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        backtracking1(candidates, target, 0);
        return result;
    }
};

总结

  • 这一题主要在于排除重复组合,我自己的方法排除重复组合过于暴力会超时。
  • 加入开始下标这样可以避免重复的组合。

第二题

Leetcode40组合总和II

解法一[暴力排除重复组合法]

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

    // 比较两个数组是否一样
    bool issame_vector(vector<int> array1, vector<int> array2) {
        sort(array1.begin(), array1.end());
        sort(array2.begin(), array2.end());
        if (array1.size() == array2.size()) {
            int i;
            for (i = 0; i < array1.size(); i++) {
                if (array1[i] != array2[i]) break;
            }
            if (i == array1.size()) return true;
            else return false;
        }
        else return false;
    }

    void backtracking(vector<int>& candidates, int target, int start) {
        if (sum > target) return;
        else if (sum == target) {
            // 判断是否有一样的数组,没有则加入
            bool is_exist = false;
            for (int i = 0; i < result.size(); i++) {
                if (issame_vector(path, result[i])) is_exist = true;
            }
            if (!is_exist) result.push_back(path);
            return;
        }
        else {
            for (int i = start; i < candidates.size(); i++) {
                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;
    }
};

解法二[正常回溯法]

class Solution {
private:
    vector<vector<int>> result;
    vector<int> path;
    int sum = 0;
    void backtracking1(vector<int>& candidates, int target, int start) {
        if (sum > target) return;
        else if (sum == target) {
            result.push_back(path);
        }
        else {
            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]);
                backtracking1(candidates, target, i + 1);
                sum -= candidates[i];
                path.pop_back();
            }
        }
    }

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

总结

  • 和第一题一样,暴力排除重复组合会超时。
  • 先对数组排序,在回溯的过程中对同一树层已经使用过的元素进行排除,这里的同一树层指的是同一层的树的分支不出现相同的元素,但可以在同分支出现相同的元素。

第三题

Leetcode131分割回文串

解法一[回溯递归法]

  • 时间复杂度O(n)
  • 空间复杂度O(n)
class Solution {
private:
    vector<vector<string>> result;
    vector<string> path;

    
    bool is_palindromic_string(string s) {
        for (int i = 0; i < s.size() / 2; i++) {
            if (s[i] != s[s.size() - i - 1]) return false;
        }
        return true;
    }

    // 回溯
    void backtracking(string s, int start) {
        // 结束当前切割方法
        if (start == s.size()) {
            bool is_path = true;
            for (int i = 0; i < path.size(); i++) {
                if (!is_palindromic_string(path[i])) is_path = false;
            }
            if (is_path) result.push_back(path);
            return;
        }
        for (int i = start; i < s.size(); i++) {
            string cur(s.begin() + start, s.begin() + i + 1);
            path.push_back(cur);
            backtracking(s, i + 1);
            path.pop_back();
        }
    }

    
public:
    vector<vector<string>> partition(string s) {
        backtracking(s, 0);
        return result;
    }
};

解法二[优化的回溯递归法]

  • 时间复杂度O(n)
  • 空间复杂度O(n)
class Solution {
private:
    vector<vector<string>> result;
    vector<string> path;

    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;
    }


    void backtracking2(string s, int start) {
        if (start == s.size()) {
            result.push_back(path);
        }
        for (int i = start; i < s.size(); i++) {
            if (isPalindrome(s, start, i)) {
                string cur = s.substr(start, i - start + 1);
                path.push_back(cur);
            }
            else continue;
            backtracking2(s, i + 1);
            path.pop_back();
        }
    }

public:
    vector<vector<string>> partition(string s) {
        // 方法二
        backtracking2(s, 0);
        return result;
    }
};

总结

  • 确实第一次见这种题,感觉主要在于如何将它与组合问题类比起来,关键在于如何切割字符串。
  • 优化的方法主要在于判断回文字符串,第一种方式没有减枝,第二种方式进行了减枝。
  • 对于动态规划来查询是否是回文字符串还需要学习一下。

总结

  • 总体来说回溯法主要在于画出递归树,然后套上模板写好代码。
  • 根据是否可以重复,start是否加1。