代码随想录算法训练营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循环中判断是否符合切割要求。

明天继续加油!

相关推荐
君鼎2 小时前
More Effective C++ 条款01:仔细区别 pointers 和 references
c++
艾醒2 小时前
大模型面试题剖析:大模型微调与训练硬件成本计算
人工智能·后端·算法
啊嘞嘞?2 小时前
力扣(滑动窗口最大值)
算法·leetcode·职场和发展
快递鸟2 小时前
ISV系统开发中物流接口的第三方模块对接:技术选型与集成实践
算法
墨染点香2 小时前
LeetCode 刷题【53. 最大子数组和】
数据结构·算法·leetcode
2501_924879263 小时前
客流特征识别误报率↓76%!陌讯多模态时序融合算法在智慧零售的实战解析
大数据·人工智能·算法·目标检测·计算机视觉·视觉检测·零售
工藤新一¹3 小时前
C/C++ 数据结构 —— 树(2)
c语言·数据结构·c++·二叉树··c/c++
北京地铁1号线3 小时前
广告推荐模型2:因子分解机(Factorization Machines, FM)
人工智能·算法·推荐算法
源远流长jerry3 小时前
STM32之DMA详解
linux·网络·c++·stm32·单片机·嵌入式硬件
是店小二呀3 小时前
【C++】智能指针底层原理:引用计数与资源管理机制
android·java·c++