一、二叉树
二叉树解题方法有两种,递归法和迭代法。递归法中包括前序遍历(求深度)、中序遍历和后序遍历(求高度)。除此之外,还有层序遍历。
递归三要素:
1.函数返回值和函数参数;
2.终止逻辑处理
3.单层逻辑处理
递归的本质就是嵌套,只需要处理好单层的逻辑,不需要考虑十分复杂的具体实现(谨记)!
1. 对称二叉数(leetcode题号:101)
1.1 递归法
1.2 迭代法
类似题目:
100.相同的数
572.另一棵树的子树
2. 二叉树的所有路径(leetcode题号:257)
时刻牢记!递归和回溯是不可拆分的!
2.1 递归 + 回溯
2.2 迭代 + 回溯
3.从中序和后序遍历序列构造二叉树(leetcode题号:106)
思路不难,难在优化和确定不变量!
4.修剪二叉树(leetcode题号:669)
总体还是递归想法!自己做可以解决,但是想法还是不够流畅吗,主要是在终止条件这一part,多处逻辑存在重合,应该多精简修改一下。
自己的代码:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == nullptr) return nullptr;
if((root->left == nullptr && root->val >= low) && (root->right == nullptr && root->val <= high)) return root;
else if((root->left == nullptr && root->right == nullptr) && (root->val < low || root->val > low)) return nullptr;
if(root->val < low) root = trimBST(root->right, low, high);
else if(root->val > high) root = trimBST(root->left, low, high);
else if(root->val >= low && root->val <= high){
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
}
return root;
}
人家的代码:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if(root == nullptr) return nullptr;
if(root->val < low){
root = trimBST(root->right, low, high);
return root;
}
if(root->val > high){
root = trimBST(root->left, low, high);
return root;
}
root->left = trimBST(root->left, low, high);
root->right = trimBST(root->right, low, high);
return root;
}
二、回溯
回溯是有模板的,大部分回溯题应该都可以套模板。其中主要是组合问题、分割问题、子集问题和排列问题(建议多多练习),其中还可以细分为是否可以重复,涉及去重问题。如果是树层去重,需要一个全局型的vector<bool> used(nums.size(), false),如果是树枝去重,可以在for循环外写一个unordered_map或者unordered_set。
1.组合总和Ⅱ(leetcode题号:40)
难点在于去重,即树层去重,而非树枝去重。可以新建一个bool类型的used来进行树层去重(代码1),或者直接使用startIndex去重(代码2,在自己写的代码中,if(i>startindex && ...)没有写对,写成了i > 0,未做到真正去重)。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
int sum = 0;
int len = 0;
void backtrack(int startIndex, int target, vector<int>& candidates, vector<bool>& used){
if(sum > target){
return;
}
if(sum == target){
res.push_back(path);
return;
}
for(int i = startIndex; i < len; 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;
backtrack(i + 1, target, candidates, used);
path.pop_back();
sum -= candidates[i];
used[i] = false;
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
len = candidates.size();
vector<bool> used(len, false);
backtrack(0, target, candidates, used);
return res;
}
};
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
int sum = 0;
int len = 0;
void backtrack(int startIndex, int target, vector<int>& candidates){
if(sum > target){
return;
}
if(sum == target){
res.push_back(path);
return;
}
for(int i = startIndex; i < len && sum + candidates[i] <= target; i ++){
if(i > startIndex && candidates[i] == candidates[i - 1]) continue;
path.push_back(candidates[i]);
sum += candidates[i];
backtrack(i + 1, target, candidates);
path.pop_back();
sum -= candidates[i];
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
len = candidates.size();
backtrack(0, target, candidates);
return res;
}
};
2.分割字符串(leetcode题号:131)
静下心来,动脑筋,思路没问题,但是优化存在问题,尤其是循环条件下!!!(好好反思!~)
class Solution {
public:
int len = 0;
bool isPartition(string sss){
int left = 0;
int right = sss.size() - 1;
while(left < right){
if(sss[left] != sss[right]){
return false;
}
left ++;
right --;
}
return true;
}
vector<vector<string>> res;
vector<string> path;
void backtrack(int startIndex, string& s){
if (startIndex >= len){ // 终止条件
res.push_back(path);
return;
}
for(int i = 1; startIndex + i <= len; i ++){ // 循环条件
if(!isPartition(s.substr(startIndex, i))){ // 该字符串不是回文字符串,跳过
continue;
}
path.push_back(s.substr(startIndex, i));
backtrack(startIndex + i, s);
path.pop_back();
}
}
vector<vector<string>> partition(string s) {
len = s.size();
backtrack(0, s);
return res;
}
};
3.递增子序列(leetcode题号:491)
整体思路不是很难,关键点有问题:即最大值,path.back()就是最大值了,语法不过关了属于是,谨记!!!首先欣赏我的shi山代码,其次是good代码。
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
int count = 0;
void backtracking(vector<int>& nums, int startIndex, int maxNum){
if(path.size() >= 2){
res.push_back(path);
}
else if(startIndex >= nums.size()){
return;
}
unordered_map<int, int> mp;
for(int i = startIndex; i < nums.size(); i ++){
if(count == 0) {
maxNum = nums[i];
count = 1;
}
if(nums[i] >= maxNum){ // 此处的maxNum应该是上一层传下来的最大值
if(mp[nums[i]] == 0 || !mp.count(nums[i])){
mp[nums[i]] ++;
path.push_back(nums[i]);
backtracking(nums, i + 1, nums[i]); // 此处的maxNum应该是nums[startIndex]
path.pop_back();
if(path.size() == 0) count = 0;
}
else {
if(path.size() == 0) count = 0;
continue;
}
}
else{
if(path.size() == 0) count = 0;
continue;
}
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0, nums[0]);
return res;
}
};
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(vector<int>& nums, int startIndex){
if(path.size() >= 2){
res.push_back(path);
}
unordered_set<int> usedset;
for(int i = startIndex; i < nums.size(); i ++){
if(!path.empty() && nums[i] < path.back() || usedset.find(nums[i]) != usedset.end()){
continue;
}
usedset.insert(nums[i]);
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0);
return res;
}
};
4.全排列Ⅱ(leetcode题号:47)
used[i] = true说明当前树枝使用过,used[i - 1]当前树层使用过,直接continue,看代码
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
unordered_map<int, int> mp;
void backtracking(vector<int>& nums, vector<bool>& used){
if(path.size() == nums.size()){
res.push_back(path);
return;
}
for(int i = 0; i < nums.size(); i ++){
if(i > 0 && used[i - 1] == false && nums[i] == nums[i - 1]) continue;
if(used[i] == false){
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
used[i] = false;
path.pop_back();
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return res;
}
};
三、贪心算法
1.分发饼干(leetcode题号:455)
2.摆动序列(leetcode题号:376)
3.最大子数组和(leetcode题号:53)
4.跳跃游戏(leetcode题号:55)
5.跳跃游戏Ⅱ(leetcode题号:45)
6.分发糖果(leetcode题号:135)
两个维度,先确定一个维度,再确定另外一个维度
7.根据身高重建队列(leetcode题号:406)
同样是两个维度
★ 解题方法
滑动窗口,双指针 。
★ 薄弱点
链表题:
21.合并两个有序链表
四、动态规划
五、单调栈
六、图论
1.图
图的基本概念:图的种类、度、连通性、连通图、强连通图、连通分量、强连通分量
图的构造:邻接矩阵、邻接表
图的遍历方式:深度优先遍历(dfs)、广度优先遍历(bfs)
2.并查集
并查集的的两个主要功能:
(1)将两个元素添加到一个集合中
(2)判断两个元素在不在同一个集合中
代码模板:
int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]); // 路径压缩
}
// 判断 u 和 v是否找到同一个根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u); // 寻找u的根
v = find(v); // 寻找v的根
if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u;
}
3.最小生成树
最小生成树是所有节点的最小连通子图:即:以最小的成本(边的权值)将图中所有节点链接到一起。
prim算法:三部曲
- 第一步,选距离生成树最近节点
- 第二步,最近节点加入生成树
- 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)
kruskal算法:核心,并查集
1.按权值从小到大排序
2.遍历边,判断两个点是否在同一个集合中
在:不处理
不在:加入到最小生成树,并把两个节点加入同一个集合
prim 和 kruskal 的区别:Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果 一个图中,节点多,但边相对较少,那么使用Kruskal 更优。
4.拓扑排序(大体思路)
定义:给出一个有向图,把这个有向图转成线性的排序,就叫拓扑排序。
注意:保证没环,否则没办法转成线性。
实现算法:卡恩算法(bfs算法),dfs算法。
拓扑排序的过程:
1.找到入度为0的节点,加入结果集。
2.将该节点从图中删除。