leetcode算法难点(持续更新)

一、二叉树

        二叉树解题方法有两种,递归法和迭代法。递归法中包括前序遍历(求深度)、中序遍历和后序遍历(求高度)。除此之外,还有层序遍历。

        递归三要素:

                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算法:三部曲

  1. 第一步,选距离生成树最近节点
  2. 第二步,最近节点加入生成树
  3. 第三步,更新非生成树节点到生成树的距离(即更新minDist数组)

kruskal算法:核心,并查集

        1.按权值从小到大排序

        2.遍历边,判断两个点是否在同一个集合中

                在:不处理

                不在:加入到最小生成树,并把两个节点加入同一个集合

prim 和 kruskal 的区别:Kruskal 与 prim 的关键区别在于,prim维护的是节点的集合,而 Kruskal 维护的是边的集合。 如果 一个图中,节点多,但边相对较少,那么使用Kruskal 更优。

4.拓扑排序(大体思路)

定义:给出一个有向图,把这个有向图转成线性的排序,就叫拓扑排序。

        注意:保证没环,否则没办法转成线性。

实现算法:卡恩算法(bfs算法),dfs算法。

拓扑排序的过程:

        1.找到入度为0的节点,加入结果集。

        2.将该节点从图中删除。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值