代码随想录算法训练营day23

代码随想录算法训练营

---day23

文章目录


前言

今天是算法营的第23天,希望自己能够坚持下来!

今日任务:

● 39. 组合总和

● 40.组合总和II

● 131.分割回文串


一、39. 组合总和

题目链接
文章讲解
视频讲解

思路:

  1. 递归函数的参数和返回值:参数:整数数组 candidates,目标整数 target,总和sum,遍历的开始索引startIndex。
  2. 终止条件:本题对path的大小没有限制,只有总和的限制,当总和=target的时候结束,将path放入结果集。
  3. 单层递归的逻辑:纵向遍历可以重复使用数字,所以for循环中递归用的是i(若不重复则是i+1)。
  4. 去重:先将数组排序,如果sum+candidates[i]>target那么后面的元素都会大于target,所以for循环的终止条件可以加上这个,提前退出循环。
cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) { //总和等于目标值则加入结果集
            result.push_back(path);
            return;
        }

        //如果sum+candidates[i]>target就终止遍历
        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i ++) {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates, target, sum, i); //这里因为同一个数字可以重复使用,所以用startIndex是i
            sum -= candidates[i]; //回溯
            path.pop_back(); //回溯
        }

        return;
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        path.clear();
        result.clear();
        sort(candidates.begin(), candidates.end()); //需要排序
        if (candidates.size() == 0) return result;
        backtracking(candidates, target, 0, 0);
        return result;
    }
};

二、40.组合总和II

题目链接
文章讲解
视频讲解

这道题目和39.组合总和 (opens new window)如下区别:

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

2.本题数组candidates的元素是有重复的,而39.组合总和 (opens new window)是无重复元素的数组candidates

最后本题和39.组合总和 (opens new window)要求一样,解集不能包含重复的组合。

本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。

本题是多加了一个used数组,去记录纵向遍历时使用过的数字。

树层去重的话,需要对数组排序!

思路:

  1. 递归函数参数以及返回值:参数数组candidates,目标值target,总和sum,索引startIndex,标记使用过的数字数组used。
  2. 终止条件:sum == target
  3. 单层处理逻辑:当前元素与前一位元素相等,且是前一位元素没用过,也就是以当前元素(重复元素)开始寻找新的结果,跳过这次循环

代码如下:

cpp 复制代码
class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;

    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; //去重

            path.push_back(candidates[i]);
            sum += candidates[i];
            used[i] = true; //记录使用当前结果path用了那些元素
            backtracking(candidates, target, sum, i + 1, used); //candidates里的数字只能用一次,i+1
            sum -= candidates[i];
            path.pop_back();
            used[i] = false;
        }
    }

    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());
        if (candidates.size() == 0) return result;
        backtracking(candidates, target, 0, 0, used);
        return result;
    }
};

三、131.分割回文串

题目链接
文章讲解
视频讲解

跟组合问题类似,递归遍历每个字符串的位置去切割,每一种结果都是一种多个切割点的组合。只是每次切割都需要判断切割后是否为回文字符子串,是则放入path,继续切割,否则继续遍历寻找合适的切割点。

思路:

  1. 递归函数的参数:传入字符串s,遍历索引startIndex
  2. 终止条件:startIndex已经到达字符串尾端(将判断回文放在了for循环中,这里直接放入结果集)
  3. 单层递归的逻辑:[startIndex,i]就是本次截取的字符串,判断其是否回文,是则加入path中,否则continue去寻找合适的切割点。递归寻找i+1为起始位置的子串。
cpp 复制代码
class Solution {
public:
    vector<string> path; //放已经回文的子串
    vector<vector<string>> result;

    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)) {  //判断[startIndex,i]之间的字符串是否为回文子串
                //是则截取出来,放入path
                string str = s.substr(startIndex, i - startIndex + 1); //substr是左闭右开区间所以+1
                path.push_back(str);
            } else {
                continue; //不是回文则跳过
            }
            backtracking(s, i + 1); //递归寻找i+1为起始位置的子串
            path.pop_back(); //回溯
        }
    }

    //双指针判断是否为回文子串
    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>> partition(string s) {
        result.clear();
        path.clear();

        if (s.size() == 0) return result;
        backtracking(s, 0);
        return result;
    }
};

总结

1.今天第一次涉及到去重,去重又分树枝(纵向)去重和树层(横向)去重。可以使用used数组去标记使用过的元素。

2.分割问题跟组合问题类似,也是求不同分割点的组合。在for循环中判断是否符合切割要求。

明天继续加油!

相关推荐
大怪v1 小时前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法
惯导马工3 小时前
【论文导读】ORB-SLAM3:An Accurate Open-Source Library for Visual, Visual-Inertial and
深度学习·算法
骑自行车的码农4 小时前
【React用到的一些算法】游标和栈
算法·react.js
博笙困了4 小时前
AcWing学习——双指针算法
c++·算法
感哥5 小时前
C++ 指针和引用
c++
moonlifesudo5 小时前
322:零钱兑换(三种方法)
算法
感哥15 小时前
C++ 多态
c++
沐怡旸1 天前
【底层机制】std::string 解决的痛点?是什么?怎么实现的?怎么正确用?
c++·面试
NAGNIP1 天前
大模型框架性能优化策略:延迟、吞吐量与成本权衡
算法
美团技术团队1 天前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法