代码随想录算法训练营第三十天:回溯加强

代码随想录算法训练营第三十天:回溯加强

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

示例 2:

  • 输入:s = “0000”
  • 输出:[“0.0.0.0”]

示例 3:

  • 输入:s = “1111”
  • 输出:[“1.1.1.1”]

示例 4:

  • 输入:s = “010010”
  • 输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:

  • 输入:s = “101023”
  • 输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

提示:

  • 0 <= s.length <= 3000
  • s 仅由数字组成

#算法公开课

《代码随想录》算法视频公开课 ****(opens new window)****​ 回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址 ****(opens new window)****​ ,相信结合视频再看本篇题解,更有助于大家对本题的理解

#思路

做这道题目之前,最好先把131.分割回文串 ​**(opens new window)** 这个做了。

这道题目相信大家刚看的时候,应该会一脸茫然。

其实只要意识到这是切割问题,切割问题就可以使用回溯搜索法把所有可能性搜出来,和刚做过的131.分割回文串 ​**(opens new window)** 就十分类似了。

切割问题可以抽象为树型结构,如图:

93.复原IP地址

#回溯三部曲

  • 递归参数

131.分割回文串 ​**(opens new window)** 中我们就提到切割问题类似组合问题。

startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。

本题我们还需要一个变量pointNum,记录添加逗点的数量。

所以代码如下:

vector<string> result;// 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
  • 递归终止条件

终止条件和131.分割回文串 ​**(opens new window)** 情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。

pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。

然后验证一下第四段是否合法,如果合法就加入到结果集里

代码如下:

if (pointNum == 3) { // 逗点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
  • 单层搜索的逻辑

131.分割回文串 ​**(opens new window)** 中已经讲过在循环遍历中如何截取子串。

for (int i = startIndex; i < s.size(); i++)​循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。

如果合法就在字符串后面加上符号.​表示已经分割。

如果不合法就结束本层循环,如图中剪掉的分支:

93.复原IP地址

然后就是递归和回溯的过程:

递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.​),同时记录分割符的数量pointNum 要 +1。

回溯的时候,就将刚刚加入的分隔符.​ 删掉就可以了,pointNum也要-1。

代码如下:

for (int i = startIndex; i < s.size(); i++) {
if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
pointNum++;
backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
pointNum--;                         // 回溯
s.erase(s.begin() + i + 1);         // 回溯删掉逗点
} else break; // 不合法,直接结束本层循环
}

#判断子串是否合法

最后就是在写一个判断段位是否是有效段位了。

主要考虑到如下三点:

  • 段位以0为开头的数字不合法
  • 段位里有非正整数字符不合法
  • 段位如果大于255了不合法

代码如下:

// 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
bool isValid(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}

根据关于回溯算法,你该了解这些! ​**(opens new window)** 给出的回溯算法模板:

void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}

可以写出如下回溯算法C++代码:

class Solution {
private:
vector<string> result;// 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
if (pointNum == 3) { // 逗点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
for (int i = startIndex; i < s.size(); i++) {
if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
s.insert(s.begin() + i + 1 , '.');  // 在i的后面插入一个逗点
pointNum++;
backtracking(s, i + 2, pointNum);   // 插入逗点之后下一个子串的起始位置为i+2
pointNum--;                         // 回溯
s.erase(s.begin() + i + 1);         // 回溯删掉逗点
} else break; // 不合法,直接结束本层循环
}
}
// 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
bool isValid(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
public:
vector<string> restoreIpAddresses(string s) {
result.clear();
if (s.size() < 4 || s.size() > 12) return result; // 算是剪枝了
backtracking(s, 0, 0);
return result;
}
};

  • 时间复杂度: O(3^4),IP地址最多包含4个数字,每个数字最多有3种可能的分割方式,则搜索树的最大深度为4,每个节点最多有3个子节点。
  • 空间复杂度: O(n)

#总结

131.分割回文串 ​**(opens new window)** 中我列举的分割字符串的难点,本题都覆盖了。

而且本题还需要操作字符串添加逗号作为分隔符,并验证区间的合法性。

可以说是131.分割回文串 ​**(opens new window)** 的加强版。

在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少!

78.子集

力扣题目链接(opens new window)

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]

#算法公开课

《代码随想录》算法视频公开课 ****(opens new window)****​ 回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集 ****(opens new window)****​ ,相信结合视频再看本篇题解,更有助于大家对本题的理解

#思路

求子集问题和77.组合 ​**(opens new window)** 和131.分割回文串 ​**(opens new window)** 又不一样了。

如果把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

其实子集也是一种组合问题,因为它的集合是无序的,子集{1,2} 和 子集{2,1}是一样的。

那么既然是无序,取过的元素不会重复取,写回溯算法的时候,for就要从startIndex开始,而不是从0开始!

有同学问了,什么时候for可以从0开始呢?

求排列问题的时候,就要从0开始,因为集合是有序的,{1, 2} 和{2, 1}是两个集合,排列问题我们后续的文章就会讲到的。

以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:

78.子集

从图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合

#回溯三部曲

  • 递归函数参数

全局变量数组path为子集收集元素,二维数组result存放子集组合。(也可以放到递归函数参数里)

递归函数参数在上面讲到了,需要startIndex。

代码如下:

vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {

递归终止条件

从图中可以看出:

78.子集

剩余集合为空的时候,就是叶子节点。

那么什么时候剩余集合为空呢?

就是startIndex已经大于数组的长度了,就终止了,因为没有元素可取了,代码如下:

if (startIndex >= nums.size()) {
return;
}

其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了

  • 单层搜索逻辑

求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树

那么单层递归逻辑代码如下:

for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);    // 子集收集元素
backtracking(nums, i + 1);  // 注意从i+1开始,元素不重复取
path.pop_back();            // 回溯
}

根据关于回溯算法,你该了解这些! ​**(opens new window)** 给出的回溯算法模板:

void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}

可以写出如下回溯算法C++代码:

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
if (startIndex >= nums.size()) { // 终止条件可以不加
return;
}
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};

  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

在注释中,可以发现可以不写终止条件,因为本来我们就要遍历整棵树。

有的同学可能担心不写终止条件会不会无限递归?

并不会,因为每次递归的下一层就是从i+1开始的。

#总结

相信大家经过了

洗礼之后,发现子集问题还真的有点简单了,其实这就是一道标准的模板题。

但是要清楚子集问题和组合问题、分割问题的的区别,子集是收集树形结构中树的所有节点的结果

而组合问题、分割问题是收集树形结构中叶子节点的结果

本周小结!(回溯算法系列二)

例行每周小结

#周一

回溯算法:求组合总和(二) ​**(opens new window)** 中讲解的组合总和问题,和以前的组合问题还都不一样。

本题和回溯算法:求组合问题! ​**(opens new window)** ,回溯算法:求组合总和! ​**(opens new window)** 和区别是:本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。

不少录友都是看到可以重复选择,就义无反顾的把startIndex去掉了。

本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?

我举过例子,如果是一个集合来求组合的话,就需要startIndex,例如:回溯算法:求组合问题! ​**(opens new window)** ,回溯算法:求组合总和! ​**(opens new window)** 。

如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:回溯算法:电话号码的字母组合(opens new window)

注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我在讲解排列的时候会重点介绍

最后还给出了本题的剪枝优化,如下:

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)

这个优化如果是初学者的话并不容易想到。

在求和问题中,排序之后加剪枝是常见的套路!

回溯算法:求组合总和(二) ​**(opens new window)** 第一个树形结构没有画出startIndex的作用,这里这里纠正一下,准确的树形结构如图所示:

39.组合总和

#周二

回溯算法:求组合总和(三) ​**(opens new window)** 中依旧讲解组合总和问题,本题集合元素会有重复,但要求解集不能包含重复的组合。

所以难就难在去重问题上了

这个去重问题,相信做过的录友都知道有多么的晦涩难懂。网上的题解一般就说“去掉重复”,但说不清怎么个去重,代码一甩就完事了。

为了讲解这个去重问题,我自创了两个词汇,“树枝去重”和“树层去重”

都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因

40.组合总和II1

我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:

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

这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!

对于去重,其实排列问题也是一样的道理,后面我会讲到。

#周三

回溯算法:分割回文串 ​**(opens new window)** 中,我们开始讲解切割问题,虽然最后代码看起来好像是一道模板题,但是从分析到学会套用这个模板,是比较难的。

我列出如下几个难点:

  • 切割问题其实类似组合问题
  • 如何模拟那些切割线
  • 切割问题中递归如何终止
  • 在递归循环中如何截取子串
  • 如何判断回文

如果想到了用求解组合问题的思路来解决 切割问题本题就成功一大半了,接下来就可以对着模板照葫芦画瓢。

但后序如何模拟切割线,如何终止,如何截取子串,其实都不好想,最后判断回文算是最简单的了

除了这些难点,本题还有细节,例如:切割过的地方不能重复切割所以递归函数需要传入i + 1

所以本题应该是一个道hard题目了。

本题的树形结构中,和代码的逻辑有一个小出入,已经判断不是回文的子串就不会进入递归了,纠正如下:

131.分割回文串

#周四

如果没有做过回溯算法:分割回文串 ​**(opens new window)** 的话,回溯算法:复原IP地址 ​**(opens new window)** 这道题目应该是比较难的。

复原IP照回溯算法:分割回文串 ​**(opens new window)** 就多了一些限制,例如只能分四段,而且还是更改字符串,插入逗点。

树形图如下:

93.复原IP地址

在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少!

本题还可以有一个剪枝,合法ip长度为12,如果s的长度超过了12就不是有效IP地址,直接返回!

代码如下:

if (s.size() > 12) return result; // 剪枝

我之前给出的C++代码没有加这个限制,也没有超时,因为在第四段超过长度之后,就会截止了,所以就算给出特别长的字符串,搜索的范围也是有限的(递归只会到第三层),及时就会返回了。

#周五

回溯算法:求子集问题! ​**(opens new window)** 中讲解了子集问题,在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果

如图:

78.子集

认清这个本质之后,今天的题目就是一道模板题了。

其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整棵树。

有的同学可能担心不写终止条件会不会无限递归?

并不会,因为每次递归的下一层就是从i+1开始的。

如果要写终止条件,注意:result.push_back(path);​要放在终止条件的上面,如下:

result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
if (startIndex >= nums.size()) { // 终止条件可以不加
return;
}

#周六

早起的哈希表系列没有总结,所以哈希表:总结篇!(每逢总结必经典) ​**(opens new window)** 如约而至。

可能之前大家做过很多哈希表的题目,但是没有串成线,总结篇来帮你串成线,捋顺哈希表的整个脉络。

大家对什么时候各种set与map比较疑惑,想深入了解红黑树,哈希之类的。

如果真的只是想清楚什么时候使用各种set与map,不用看那么多,把​**关于哈希表,你该了解这些!** ****(opens new window)****​看了就够了

#总结

本周我们依次介绍了组合问题,分割问题以及子集问题,子集问题还没有讲完,下周还会继续。

我讲解每一种问题,都会和其他问题作对比,做分析,所以只要跟着细心琢磨相信对回溯又有新的认识

最近这两天题目有点难度,刚刚开始学回溯算法的话,按照现在这个每天一题的速度来,确实有点快,学起来吃力非常正常,这些题目都是我当初学了好几个月才整明白的。

所以大家能跟上的话,已经很优秀了!

还有一些录友会很关心leetcode上的耗时统计。

这个是很不准确的,相同的代码多提交几次,大家就知道怎么回事了。

leetcode上的计时应该是以4ms为单位,有的多提交几次,多个4ms就多击败50%,所以比较夸张,如果程序运行是几百ms的级别,可以看看leetcode上的耗时,因为它的误差10几ms对最终影响不大。

所以我的题解基本不会写击败百分之多少多少,没啥意义,时间复杂度分析清楚了就可以了,至于回溯算法不用分析时间复杂度了,都是一样的爆搜,就看谁剪枝厉害了。

一些录友表示最近回溯算法看的实在是有点懵,回溯算法确实是晦涩难懂,可能视频的话更直观一些,我最近应该会在B站(同名:「代码随想录」)出回溯算法的视频,大家也可以看视频在回顾一波。

就酱,又是充实的一周,做好本周总结,迎接下一周,冲!

90.子集II

力扣题目链接(opens new window)

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

#算法公开课

《代码随想录》算法视频公开课 ****(opens new window)****​ 回溯算法解决子集问题,如何去重?| LeetCode:90.子集II ****(opens new window)****​ ,相信结合视频再看本篇题解,更有助于大家对本题的理解

#思路

做本题之前一定要先做78.子集 ​**(opens new window)** 。

这道题目和78.子集 ​**(opens new window)** 区别就是集合里有重复元素了,而且求取的子集要去重。

那么关于回溯算法中的去重问题,​**40.组合总和II** ****(opens new window)****​中已经详细讲解过了,和本题是一个套路

剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要

用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序

90.子集II

从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!

本题就是其实就是回溯算法:求子集问题! ​**(opens new window)** 的基础上加上了去重,去重我们在回溯算法:求组合总和(三) ​**(opens new window)** 也讲过了,所以我就直接给出代码了:

C++代码如下:

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
result.push_back(path);
for (int i = startIndex; i < nums.size(); i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 而我们要对同一树层使用过的元素进行跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
used[i] = false;
path.pop_back();
}
}

public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0, used);
return result;
}
};
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)

使用set去重的版本。

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path);
unordered_set<int> uset;
for (int i = startIndex; i < nums.size(); i++) {
if (uset.find(nums[i]) != uset.end()) {
continue;
}
uset.insert(nums[i]);
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}

public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0);
return result;
}
};

#补充

本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。

如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。

代码如下:

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex) {
result.push_back(path);
for (int i = startIndex; i < nums.size(); i++) {
// 而我们要对同一树层使用过的元素进行跳过
if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex
continue;
}
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}

public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear();
path.clear();
sort(nums.begin(), nums.end()); // 去重需要排序
backtracking(nums, 0);
return result;
}
};

#总结

其实这道题目的知识点,我们之前都讲过了,如果之前讲过的子集问题和去重问题都掌握的好,这道题目应该分分钟AC。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值