递归
定义
题目一:汉诺塔
面试题 08.06. 汉诺塔问题 - 力扣(LeetCode)
在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。
示例1:
输入:A = [2, 1, 0], B = [], C = [] 输出:C = [2, 1, 0]
示例2:
输入:A = [1, 0], B = [], C = [] 输出:C = [1, 0]
题目解析
class Solution
{
public:
void hanota(vector<int>& a, vector<int>& b, vector<int>& c)
{
hanotadfs(a,b,c,a.size());
}
//这个表示的意思是,将a中count个盘子,通过b的帮助,转移到c身上
void hanotadfs(vector<int>& a, vector<int>& b, vector<int>& c,int count)
{
if(count==1)
{
c.push_back(a.back());
a.pop_back();
return;
}
//将a的盘子通过c的帮助将上层n-1个盘子转移到b;
hanotadfs(a,c,b,count-1);
//将a中剩余的最大那个盘子直接转移到c;
c.push_back(a.back());
a.pop_back();
//将b中的n-1个盘子,通过a的帮助,全部转移到c;从而实现盘子的全部转移
hanotadfs(b,a,c,count-1);
}
};
题目二:反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2] 输出:[2,1]
示例 3:
输入:head = [] 输出:[]
提示:
- 链表中节点的数目范围是
[0, 5000]
-5000 <= Node.val <= 5000
题目解析
递归思想
先递归到尽头,找到反转后的链表的新的头节点,也就是当前链表的尾节点,进行标记,并返回,然后不断回溯修改链表节点指向;
class Solution
{
public:
ListNode* reverseList(ListNode* head)
{
//返回要反转后的链表的头节点
if(head==nullptr||head->next==nullptr)
{
return head;
}
//记录反转后的头节点
ListNode* newhead=reverseList(head->next);
head->next->next=head;
head->next=nullptr;
return newhead;
}
};
迭代思想
运用双指针解决问题:
ListNode* reverseList(ListNode* head)
{
ListNode* prev=nullptr;
ListNode* cur=head;
while(cur)
{
ListNode* next=cur->next;
cur->next=prev;
prev=cur;
cur=next;
}
return prev;
}
迭代的方法与递归方法不同的是,一个是往后去一个一个改变节点的指向,而递归是从后往前去改变节点指向。
深搜
定义
题目一:二叉树的所有路径
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5] 输出:["1->2->5","1->3"]
示例 2:
输入:root = [1] 输出:["1"]
题目解析
class Solution
{
//为记录路径结果
vector<string> allpath;
public:
vector<string> binaryTreePaths(TreeNode* root)
{
binaryTreePathsdfs(root,string());
return allpath;
}
//path:为记录当前路径
void binaryTreePathsdfs(TreeNode* root,string path)
{
path+=to_string(root->val);
if(root->left==nullptr&&root->right==nullptr)
{
allpath.push_back(path);
return;
}
path+="->";
if(root->left)
{
binaryTreePathsdfs(root->left,path);
}
if(root->right)
{
binaryTreePathsdfs(root->right,path);
}
}
};
注意:这道题path尽量设置为全局变量不要传参,因为需要将int->字符串的形式,恢复现场时无法确定需要几次删除。
题目二:二叉树的剪枝
题目解析
后序遍历按照左子树、右子树、根节点的顺序遍历⼆叉树的所有节点,通常用于父节点的状态依赖于子节点状态的题目。 如果我们选择从上往下删除,我们需要收集左右子树的信息,这可能导致代码编写相对困难。然而, 通过观察我们可以发现,如果我们先删除最底部的叶子节点,然后再处理删除后的节点,最终的结果 并不会受到影响。 因此,我们可以采用后序遍历的方法来解决这个问题。在后序遍历中,我们先处理左子树,然后处理右子树,最后再处理当前节点。在处理当前节点时,我们可以判断其是否为叶子节点且其值是否为 0, 如果满足条件,我们可以删除当前节点。
需要注意的是,在删除叶子节点时,其父节点很可能会成为新的叶子节点。因此,在处理完子节点后,我们仍然需要处理当前节点。这也是为什么选择后序遍历的原因(后序遍历首先遍历到的⼀定是叶子节点)。
通过使用后序遍历,我们可以逐步删除叶子节点,并且保证删除后的节点仍然满足删除操作的要
求。这样,我们可以较为方便地实现删除操作,而不会影响最终的结果。
若在处理结束后所有叶子节点的值均为 1,则所有子树均包含 1,此时可以返回。
class Solution
{
public:
TreeNode* pruneTree(TreeNode* root)
{
if(root==nullptr)
{
return nullptr;
}
if(root->left)
{
root->left=pruneTree(root->left);
}
if(root->right)
{
root->right=pruneTree(root->right);
}
if(root->left==nullptr&&root->right==nullptr&&root->val==0)
{
delete root;
return nullptr;
}
return root;
}
};
回溯
定义
题目一:全排序
题目解析
我们需要在每⼀个位置上考虑所有的可能情况并且不能出现重复。通过深度优先搜索的方式,不断地枚举每个数在当前位置的可能性,并回溯到上⼀个状态,直到枚举完所有可能性,得到正确的结果。
每个数是否可以放入当前位置,只需要判断这个数在之前是否出现即可。具体地,在这道题目中,我们可以通过⼀个递归函数和标记数组来实现全排列。
class Solution
{
bool isused[6];
vector<int> path;
vector<vector<int>> allpath;
public:
vector<vector<int>> permute(vector<int>& nums)
{
permutedfs(nums);
return allpath;
}
void permutedfs(vector<int>& nums)
{
if(path.size()==nums.size())
{
allpath.push_back(path);
return;
}
for(size_t i=0;i<nums.size();i++)
{
if(!isused[i])
{
path.push_back(nums[i]);
isused[i]=true;
permute(nums);
path.pop_back();
isused[i]=false;
}
}
}
};
题目二:子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3] 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
题目解析
决策树一
class Solution
{
vector<vector<int>> allchild;
vector<int> child;
int n;
public:
vector<vector<int>> subsets(vector<int>& nums)
{
n=nums.size();
subsetsdfs(nums,0);
return allchild;
}
void subsetsdfs(vector<int>& nums,int pos)
{
if(pos==nums.size())
{
allchild.push_back(child);
return;
}
//选进行递归
child.push_back(nums[pos]);
subsetsdfs(nums,pos+1);
//恢复现场
child.pop_back();
//不选进行递归
subsetsdfs(nums,pos+1);
}
};
决策树二
通过决策树二的方法实现得知:决策树的节点都是子集
class Solution
{
vector<vector<int>> allchild;
vector<int> child;
int n;
public:
vector<vector<int>> subsets(vector<int>& nums)
{
n=nums.size();
subsetsdfs(nums,0);
return allchild;
}
void subsetsdfs(vector<int>& nums,int pos)
{
allchild.push_back(child);
for(size_t i=pos;i<n;i++)
{
child.push_back(nums[i]);
subsetsdfs(nums,i+1);
child.pop_back();
}
}
};