代码随想录算法训练营第二十七天|39. 组合总和、40.组合总和II、131.分割回文串

LeetCode 39. 组合总和

链接:39. 组合总和

思路:

还是组合总和,这次不同的是这次给了一个数组candidates,需要在这个数组内寻找符合条件的子数组。值得注意的是,这次选取的数是可以重复的,也就是说,每层子树的搜索范围和之前不一样了。假设我们给了一个candidates数组[1,2,3,4],在第一层我们选取了数字1,那么在下一层,子树的搜索范围还是[1,2,3,4],也就是说1可以重复选取

为了使1重复能够取到,回想先前我们怎么避免重复选取的。就是在循环每层子树的时候,给定了一个idx,它告诉我们这一层的子树搜索范围是多少的,为了不和上一层重复,每往前走一层我们都会把这个idx加上1,但是这里不需要避免重复了,所以可以直接把当前的i的值传到下一层,而不是i+1。

除此之外还有一个剪枝的操作。我们可以先给candidates排序,然后在每次相加的过程中,如果sum+candidates[i]的值大于target,那么i之后的值一定会大于target,这样就避免了i之后的子树的搜索过程,大大降低了搜索空间。

代码:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> ans;
    int idx = 0;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        backtrackting(candidates, target, 0, 0);
        return ans;
    }
    void backtrackting(vector<int>& candidates, int target, int sum, int idx)
    {
        if (ans.size() == 150)
            return;
        if (sum == target)
        {
            ans.push_back(path);
            return;
        }
        for (int i = idx; i < candidates.size(); i++)
        {
            // 剪枝
            if (sum + candidates[i] > target)
                break;
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtrackting(candidates, target, sum, i);
            path.pop_back();
            sum -= candidates[i];
        }
    }
};

LeetCode 40.组合总和II

链接:40.组合总和II

思路:

又是一道组合总和题,和39. 组合总和唯一的区别是出现了重复的数字,所以去重是必须要考虑的问题。注意重复的数字和取重复的索引是不一样的概念,以数组nums = [1,1,2]为例。在树的第一层搜索空间索引为[0,1,2],如果可以取重复的索引,那么在下一层的搜索空间索引还是[0,1,2],因为我们并没有把idx+1传入进去。在不可以取重复索引的场合,也就是idx+1传入到下一树层,搜索空间就变成了[1,2]。但是需要注意的是,虽然我们并没有取重复的索引,在第一层取的是nums[0],在第二层取的是nums[1],但因为数组里有重复的数字nums[0] == nums[1],这个时候还是会把同一个数字取两遍,所以需要对重复的数字去重。

很容易想到的一个去重代码为:

if (i > 0 && nums[i] == nums[i-1])
    continue;

 当nums[i] == nums[i-1]的时候,跳过这一次循环。实际上我们不能这样剪枝,因为数字1可以取两遍,题目要求的是每个数字在每个组合中只能使用一次,我们有两个1,两个1都需要分别使用1次。但是如果不跳过的话,会出现两组答案同为[1,2],因为我们对两个1都进行了搜索,都找到了2,满足target=3这个条件,所以我们只需要令第二个1只使用1次就好。

我们可以用一个数组来记录这个数是否用过,如果第i个数用过的话令used[i] = true,回溯的时候也需要令used[i] = false。当出现nums[i] == nums[i-1]的时候,查找nums[i-1]是否被用过,如果used[i-1] == false,说明i-1在前一颗子树被使用过,需要跳过:

if (i > 0 && candidates[i] == candidates[i - 1] && used[i-1] == false)
    continue;

如下图代码随想录中的图所示:

  • used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

只有used[i - 1] == false表示现在在同一层数,从i-1回溯过来的。而used[i - 1] == true说明现在在子树上,还没有回溯。所以used[i-1] == false相当于在树层上剪枝,而used[i-1] == true相当于在树枝上剪枝。

另外注意一点,使用的数组是需要排序的,否则的话当数组里有重复的数并且两个数没有挨在一起时nums[i-1]和nums[i]的判断会失效,没办法通过i-1来查找到是否与i重复。还有一点是,为了节省空间可以让i>idx代替used数组,因为i>idx的时候i必然是在数层中的第二个往后的节点查找,也相当于在树层上剪枝。

代码:

class Solution {
public:
    vector<int> path;
    vector<vector<int>> ans;
    int idx = 0;
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        backtrackting(candidates, target, 0, 0);
        return ans;
    }
    void backtrackting(vector<int>& candidates, int target, int sum, int idx)
    {
        if (sum == target)
        {
            ans.push_back(path);
            return;
        }
        for (int i = idx; i < candidates.size(); i++)
        {
            // 剪枝
            if (sum + candidates[i] > target)
                break;
            // 最关键的去重步骤
            // 遇到和前一个元素相同的情况(注意不是和后一个,所以是和i-1比较)
            // 直接continue跳过当前层的循环,因为同一层数不能重复
            // 注意去重的时机,可以在子树重复,所以i==idx的时候是不需要跳过的
            if (i > idx  && candidates[i] == candidates[i - 1])
                continue;
            path.push_back(candidates[i]);
            sum += candidates[i];
            backtrackting(candidates, target, sum, i + 1);
            // 回溯
            path.pop_back();
            sum -= candidates[i];
        }
    }
};

LeetCode 131.分割回文串

链接:131.分割回文串

思路:

题目要求在给定的字符串中找到所有为回文的子字符串。解这道题目的逻辑很简单,一共只有两个问题需要考虑:

  1. 如何分割子字符串
  2. 如何判断一个子字符串是回文

关于第一个问题,其实就是经典的回溯。用左指针和右指针表示子字符串的头和尾,用枚举的办法把所有可能的子字符串枚举出来。枚举出每一个子字符串后,就要考虑这个子字符串是不是回文,可以用双指针的方法分别指向头和尾,然后左右指针向中间靠拢,确保左右指针所指的字符是一致的,当左指针大于右指针即可。

递归停止的条件为left==s.size(),这说明指针遍历到了字符串的终点,把当前记录下的一组答案push到最终结果里,然后返回树的上一层。在每个树层循环遍历搜索空间的时候,右指针都往后移动,而左指针不动,然后判断右指针和左指针之间的子字符串是否为回文,是回文的话则进入树的下一层,同时更新左右指针,否则的话右指针继续向后移动一位。

代码:

class Solution {
public:
    int left;
    int right;
    vector<string> path;
    vector<vector<string>> ans;
    vector<vector<string>> partition(string s) {
        backtracking(s, 0, 0);
        return ans;
    }
    void backtracking(const string& s, int left, int right)
    {
        if (right == s.size())
        {
            ans.push_back(path);
            return;
        }
        for (int i = left; i < s.size(); i++)
        {
            // 检测子字符串是否为回文
            if (check(s, left, i))
            {
                // 如果是,将当前切割的子字符串放入path
                string temp;
                for (int j = left; j <= i; j++)
                    temp.push_back(s[j]);
                path.push_back(temp);
                backtracking(s, i + 1, i + 1);
                // 回溯
                path.pop_back();
            }
        }
    }
    bool check(const string& s, int left, int right)
    {
        while (left <= right)
        {
            if (s[left] != s[right])
                return false;
            left++;
            right--;
        }
        return true;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值