一位C++小白的力扣刷题_成长记录_welcome to visit ^_^
( 从这一篇开始,调整一下排版顺序。将 “学习心得” 放到 “实现(C++)” 后面 )
回溯算法_第2题:括号生成
题目描述:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
举例
示例:
输入:n = 3 输出:[ "((()))", "(()())", "(())()", "()(())", "()()()" ]
解题思路:利用 容器+递归+指针(其实是下标)。观察其特征:在储存 “(” 和 “)” 的容器内, “(” 的最大数量为 n ,而 “)” 的数量总是 小于等于 “(” 的数量。
要充分利用这一点来建立 回溯递归函数。
学习心得:今天做得每道题,我都先自己尝试敲一遍,但还是“回溯功夫”不到家,有些算法思维
没有掌握,所以后面都看了题解。但我没彻底理解前辈的谋篇代码,是不会轻易去学习的。这道题
归我最大的感触就是, 那一句“ left >= right ”。
实现:(C++)
class Solution {
public:
string temp=""; //临时字符串( 可以把它看成“伪”全局变量 ):用来储存含有“(”和“)”的字符串
void helper( vector<string>& vec,int n, int left, int right ) //left:表示 “(” 的数量。 right:表示“)” 的数量
{
if( temp.size()==2*n )
{
vec.push_back( temp ); //满足要求时,将 临时字符串 压入 二维容器
return ;
}
if( left<n ) //只要 “(” 的数量不超过 n,继续加“(”
{
temp.push_back( '(' );
helper( vec,n,left+1,right );
temp.pop_back();
}
if( left > right ) //建议画一画图,画几下就清晰明了了!
{
temp.push_back( ')' );
helper( vec,n,left,right+1 );
temp.pop_back();
}
}
vector<string> generateParenthesis(int n)
{
vector<string> vec;
if( n==0 ) return vec; //特殊情况,特殊处理
helper( vec,n,0,0 );
return vec;
}
};
运行结果:
代码执行结果:
我的输入
3
我的答案
["((()))","(()())","(())()","()(())","()()()"]
预期答案
["((()))","(()())","(())()","()(())","()()()"]
学习心得:今天做得每道题,我都先自己尝试敲一遍,但还是“回溯功夫”不到家,有些算法思维
没有掌握,所以后面都看了题解。但我没彻底理解前辈的谋篇代码,是不会轻易去学习的。这道题
归我最大的感触就是, 那一句“ if( left > right) ”。太6了!
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
回溯算法_第3题:全排列
题目描述:
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
举例:
示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
解题思路:先参考一下 力扣里的一位前辈的 解题思路,太棒啦!。。。参考完了,听我说,主要就是那个 “做选择”,之后 “撤销选择” 这个 算法思维很赞!
》》》》代码方面,回溯算法的框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单。
作者:labuladong
链接:https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。《《《《《《《
实现:(C++)
class Solution {
public:
void helper( vector<vector<int>>&vec,vector<int>& nums,int start,int n)
{
if( start==n ) //终止条件。
{
vec.push_back( nums );
return ;
}
int i;
for( i=start;i<n;i++ ) //这三句话,真得费神去理解呢....也是最好作一作图。而且友情提醒:坚持循环下去。到了后面的循环你就会发现奥秘!
{
swap( nums[i],nums[start] ); //有很多次都是原地交换,我还没搞清楚这是什么原因...
helper( vec,nums,start+1,n); //当 start值为2 作为递归返回时,真正的故事就开始了。 那个时候 i 的值也为2,但要进行循环 i++ 后为3。
// 之后又递归到 这一句。 i为1,start值为1。i再次进行循环 i++ ,而start没变, 全排列 拉开帷幕。
swap( nums[i],nums[start] );
}
return ;
}
vector<vector<int>> permute(vector<int>& nums)
{
vector<vector<int>> vec;
if( nums.size()==0 ) return {};
if( nums.size()==1 ) return { { nums[0]} }; //特殊情况,特殊处理
helper( vec,nums,0,nums.size() );
return vec;
}
};
运行结果:
代码执行结果:
我的输入
[1,2,3]
我的答案
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]]
预期答案
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
学习心得:这道回溯题,让我感受到,一种“简单到复杂思维”,也就是我们想就 比较简单的例子 而言
来敲代码,然后可以把这个代码延伸到更复杂的例子上。而且那个 “回溯算法的框架”,太赞了!(注:这个
全排列是从末尾开始逐渐 交换的。)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
回溯算法_第4题:子集
题目描述:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
举例:
示例:
输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
解题思路:和上一题 “全排列” 有很大的相似之处。连 “回溯算法的模板” 都一样。但是 终止条件和递归实参 不同。
实现:(C++)
class Solution {
public:
vector<int> temp;
void backtrack( vector<vector<int>>&res, vector<int>& nums, int start)
{
res.push_back( temp );
int i;
for( i=start;i<nums.size();i++ ) //其实 i<nums.size() 也就是终止条件。
{
temp.push_back( nums[i] ); //“做出选择”
backtrack( res,nums,i+1 ); //这里的 i+1 非同一般,为什么呢?在全排列那里, 是“start+1”, 而这里是 “i+1” 。作作图就好理解了。
temp.pop_back(); //“撤销选择”
}
}
vector<vector<int>> subsets(vector<int>& nums)
{
vector<vector<int>>res;
if( nums.size()==0 ) return {{}}; //特殊情况,特殊处理
backtrack( res,nums,0 );
return res;
}
};
运行结果:
代码执行结果:
我的输入
[1,2,3]
我的答案
[[],[1],[1,2],[1,2,3],[1,3],[2],[2,3],[3]]
预期答案
[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
学习心得:上一题的 全排列 和这道 子集 很相似,但想自己独立做出来,还是不容易。我发现它们都有个
for循环,然后在这个for循环里面进行 回溯 。现在我还没太关注 时间复杂度 和 空间复杂度 ,我觉得题解里
能把 空间时间复杂度 用 美丽的数学公式表示出来的,都厉害!回溯回溯,多练多记,然后 融会贯通。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
回溯算法_第5题: 单词搜索
题目描述:
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
举例:
示例:
board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ] 给定 word = "ABCCED", 返回 true 给定 word = "SEE", 返回 true 给定 word = "ABCB", 返回 false
提示:
board
和word
中只包含大写和小写英文字母。1 <= board.length <= 200
1 <= board[i].length <= 200
1 <= word.length <= 10^3
解题思路:两层for循环遍历二维数组,把二维数组里面的 每个元素作为 “火车头”,都来试一试,如果有满足情况的 “火车” ,就直接返回 true 了。另外在回溯函数中,其形参
要有一个 下标 ,专门用来记录 目标单词里面字母的 下标位置。
实现:(C++)
class Solution {
public:
bool backtrack( vector<vector<char>>& board,string& word,int wordindex,int i,int j ) //wordindex:目标单词里面的字母下标
{
if( word[wordindex]!=board[i][j] ) //只要 下一节 “火车厢” 不对口,返回 false
return false;
if( wordindex==word.size()-1 ) //这是一个终止条件,不可缺少
return true;
char temp=board[i][j];
board[i][j]='*'; // 前辈解题代码的 精髓,换一个符号,相当于做个标记,防止该节 车厢(或车头)被重复使用
wordindex++; //下标向右移一位
if( i>0 && backtrack(board,word,wordindex, i-1,j) //向左找合适的“车厢”
||i<board.size()-1 && backtrack(board,word,wordindex,i+1,j) //向右找合适的“车厢”
||j>0 && backtrack(board,word,wordindex,i,j-1) //向上找合适的“车厢”
||j<board[0].size()-1 && backtrack(board,word,wordindex,i,j+1) ) //向下找合适的“车厢”
return true;
board[i][j]=temp; //如果没找到合适的“车厢”,把标记过的前一节(或多节,因为递归时可能多次执行)“车厢”恢复原样。
return false;
}
bool exist(vector<vector<char>>& board, string word) {
int i,j;
for( i=0;i<board.size();i++ )
for( j=0;j<board[0].size();j++ )
if( backtrack(board,word,0,i,j) )
return true;
return false; //如果都不行,才返回false
}
};
运行结果:
代码执行结果:
我的输入
[["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]] "ABCCED"
我的答案
true
预期答案
true
学习心得:这道题很有趣的哟。我一看到,我就想用昨天学到的那个“感染函数”,但还是差那么一段距离
写出正确的代码。没想到的是,那个 if 语句可以写这么 长,连我都被惊呆了!_ !。做了这道题后,再次
加深了对 标记法 的认识与理解。