刷题记录2

文章目录

  • 刷题记录2
    • 1047.删除字符串中的所有相邻重复项
    • 150.逆波兰表达式求值
    • 239.滑动窗口最大值
    • 347.前k个高频元素
    • 144.二叉树前序遍历(145、94后序、中序)
    • 102.二叉树的层序遍历
    • 226.翻转二叉树
    • 101.对称二叉树
    • 104.二叉树的最大深度
    • 111.二叉树的最小深度
    • 222.完全二叉树的节点个数

刷题记录2

1047.删除字符串中的所有相邻重复项

class Solution {
public:
    string removeDuplicates(string s) {
        string res = "";
        for(char a : s)
        {
            if(res.empty() || res.back() != a) res.push_back(a);
            else if(res.back() == a) res.pop_back();
        }
        return res;
    }
};

像这种消消乐类型的题目都是用到的栈这种数据结构来解决问题,而这里呢是将string来代替栈,就不需要用到stack容器,如果使用stack容器还需要将容器内的元素重新遍历赋值到string中,然后还需要一次reverse反转,如果直接使用string作为栈就节省了代码和时间。这里在解题的时候遇到了一个空间性能上的一个问题,就是我将res.push_back(a)改成了res = res+a这种写法会导致内存超限,并且耗时将近500ms。我就在像这是为什么呢?两个操作明明都是在字符串尾部添加字符a,为什么空间和时间性能就相差这么大呢?通过搜索得知:res = res+a这种操作是会重新生成一个string临时变量,先将原本的res拷贝到临时变量中再添加上a再返回给res,这个操作又是在循环当中,假如题目中给出的s十分长,那么就相当于是On^2的时间复杂度了,再加上每个循环开辟一个临时变量,所以空间消耗也十分大。对于res.push_back来说它仅仅只是在res的基础上在后面添加字符a,这样要比运算符+要节省空间和时间很多。所以在遇到对字符串进行添加或删除时最好用push_back和pop_back。其他相关函数front(), back(), append()。

150.逆波兰表达式求值

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        for(int i = 0; i < tokens.size(); i++)
        {
            if(tokens[i] == "+")
            {
                int num1 = s.top();
                s.pop();
                s.top() += num1;
            }
            else if(tokens[i] == "-")
            {
                int num1 = s.top();
                s.pop();
                s.top() -= num1;
            }
            else if(tokens[i] == "*")
            {
                int num1 = s.top();
                s.pop();
                s.top() *= num1;
            }
            else if(tokens[i] == "/")
            {
                int num1 = s.top();
                s.pop();
                s.top() /= num1;
            }
            else s.push(stoi(tokens[i]));
        }
        return s.top();
    }
};

这一题其实只要读懂题目的意思就行了,然后按照题目给的要求编程就行了,关键是要明白这个题要用栈来写,看到提示:遇到数字则入栈,遇到运算符则取出栈顶两个数字进行计算,并将结果压入栈中,这里着重强调一下一个数字字符串转换数字的方法:int num = stoi(“12345”);但是呢,好像int num = "12345"也是能实现的,都记住吧,技多不压身。。。

239.滑动窗口最大值

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans;
        deque<int> dq;
        for(int i = 0; i < nums.size(); i++)
        {
            //入
            while(!dq.empty() && nums[i] >= nums[dq.back()]) dq.pop_back();
            dq.push_back(i);

            //出
            if(i-dq.front() >= k) dq.pop_front();

            //记录数据
            if(i >= k-1) ans.push_back(nums[dq.front()]);
        }
        return ans;
    }
};

以前绝对遇到过这种题!我记得当时我单纯用for循环嵌套都写不出来着,现在至少能用for循环嵌套能够写出来了,当然如果暴力的话怎么可能解决力扣的困难题呢,果然不出意外的运行超时了。当然有提升是肯定的,加油!

这里用到的数据结构是单调队列,做这么多题下来好像确实没怎么写过队列的题目。这里用到的思想是维护一个单调的队列来记录nums[i]的值,这里题目求的是滑动窗口的最大值,那么我们就需要维护一个单调递减的一个队列,将最大的元素一直停留在队列的首位。首先是入队操作,循环判断队尾的元素是不是小于要插入进来的元素,如果尾元素小于要插入的元素的话就不断地将尾元素剔除,目的就在于使这个队列是递减的。然后就是出操作:插入元素后通过下标的循环不变量i-dq.front() >= k时就需要将首元素剔除掉。最后是记录数据操作,由于不同测试样例的窗口大小不同,所以前k个数据插入的时候是不需要记录数据的,所以当下标i=k-1的时候才开始记录结果。

347.前k个高频元素

144.二叉树前序遍历(145、94后序、中序)

递归法:

class Solution {
public:

    void preorder(TreeNode* root, vector<int>& res)
    {
        if(root == nullptr) return;
        res.push_back(root->val);
        preorder(root->left, res);
        preorder(root->right, res);
    }

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        preorder(root, res);
        return res;
    }
};

中序和后序的递归方法还是很简单的,就是分别将res.push_back(root->val);放到两个递归函数的中间和后面,这样就分别达到了中序遍历和后序遍历的效果。

迭代法:

前序

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;

        if(root == nullptr) return {};
        
        s.push(root);
        while(!s.empty())
        {
            TreeNode* temp = s.top();
            s.pop();
            res.push_back(temp->val);
            if(temp->right) s.push(temp->right);
            if(temp->left) s.push(temp->left);
        }
        return res;
    }
};

首先回顾前序遍历,它的递归方式是中左右。这样就不难写出前序遍历的递归写法。题目中的提升中给出要写出其迭代写法,最开始还是没有思路的,甚至想到的是用队列这种数据结构,但是经过脑袋一次思考之后回想起用队列的话是实现树的层序遍历,这并不能达到前中后序遍历的效果。看了一眼题解发现用的是栈,而且在使用栈的时候也是有需要注意的点:首先是入栈要判断树是否为空树,再就是入栈顺序,这里是右子树先入栈,左子树后入栈,由于栈是先入后出,所以是先右后左,还有就是入栈前需要对左右节点进行判空操作,如果为空那么就不能入栈,如果入栈了的话会导致空指针异常。

后序

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        if(root == nullptr) return {};
        s.push(root);
        while(!s.empty())
        {
            TreeNode* temp = s.top();
            res.push_back(temp->val);
            s.pop();
            if(temp->left) s.push(temp->left);
            if(temp->right) s.push(temp->right);
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

最开始想借着前序遍历的写法来写后序遍历,结果还是卡住了,卡尔给出的方法也实在是妙啊,前序遍历是中左右,而后序遍历是左右中,它采用的方法就是在指针入栈方面进行了改动,先将左右指针入栈的顺序颠倒了一下,就使结果变成了中右左,然后将res数组进行翻转就变成了最后的左右中,我天,这是人能想出来的办法吗555,真的佩服!

中序

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        stack<TreeNode*> s;
        vector<int> res;
        TreeNode* cur = root;
        while(!s.empty() || cur != nullptr)
        {
            if(cur != nullptr)
            {
                s.push(cur);
                cur = cur->left;
            }
            else
            {
                cur = s.top();
                s.pop();
                res.push_back(cur->val);
                cur = cur->right;
            }
        }
        return res;
    }
};

中序和其他两个差别还是很大的,主要难点在于用栈来模拟递归的逻辑这一点第一次来写确实有点难写,这方面还是有点薄弱,虽说考试不可能考迭代法写二叉树遍历、但这也是考试和以后面试的两手准备吧!

102.二叉树的层序遍历

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        deque<TreeNode*> dq;
        vector<vector<int>> res;
        if(root == nullptr) return {};
        dq.push_back(root);
        while(!dq.empty())
        {
            int n = dq.size();
            vector<int> temp;
            for(int i = 0; i < n; i++)
            {
                TreeNode* tmp = dq.front();
                dq.pop_front();
                temp.push_back(tmp->val);
                if(tmp->left) dq.push_back(tmp->left);
                if(tmp->right) dq.push_back(tmp->right);
            }
            res.push_back(temp);
        }
        return res;
    }
};

这也是第二次做这道题了,唉又没做出来,还是忘得快啊,这才过了一个多月的题,就忘得一干二净了,当时在思考的时候还是想到的是以前自己的思路,就是没有回忆到以前题解的思路。这一题的层序遍历与其他不同点就在于每层的数据是一个一维数组,然后最后的结果是一个二维数组。其实感觉自己也是把题目想复杂化了,其实就是层多少个元素就将每层的元素传入到临时的一维数组中,多少个元素直接用dq.size()就能获取到。唉,给我最大的感觉就是还得回头看,重点的确不是在于多,还是在于你要掌握这个方法。

226.翻转二叉树

class Solution {
public:

    void reverse(TreeNode* root)
    {
        if(root == nullptr) return;
        swap(root->left, root->right);
        if(root->left) reverse(root->left);
        if(root->right) reverse(root->right);
    }

    TreeNode* invertTree(TreeNode* root) {
        reverse(root);
        return root;
    }
};

最开始我想着能不能取巧只交换节点的值是否能够达到效果,答案是不能,不能被题干中的示例给迷惑了,可能出现问题的情况是一个节点可能只有左右子树中的一个,这样就不能够通过交换左右节点的值来达到交换的效果,会出现空指针异常的错误。所以这里只能够用交换指针来达到目的,最开始我也对交换完指针之后是否会对后续递归产生问题产生疑惑,其实是不会的,无论是左右节点哪个先递归,最终目的是让所有其左右节点指针互换就行,所以只需要关注遍历整个树,这样就能达到我们最后想要的效果。

101.对称二叉树

class Solution {
public:

    bool isSame(TreeNode* p, TreeNode* q)
    {
        //这一行包含了很多条件:
        //1、左空右非空返回false
        //2、左非空右空返回false
        //3、左右都空返回true
        if(p == nullptr || q == nullptr) return p == q;

        //左右都非空
        bool outside = isSame(p->left, q->right);
        bool inside = isSame(p->right, q->left);
        bool val = p->val == q->val;
        return val && outside && inside;
    }

    bool isSymmetric(TreeNode* root) {
        return isSame(root->left, root->right);
    }
};

打开这一道题显示是1个月前做的题,现在一看,感觉跟新题没什么差别,唉还是没有思路。感觉前面跟灵神学做的算法题还是过于草率了,慢慢看出差别了,灵神讲题更注重于代码简洁和方法的灵活上面,这对于我们这种普通人来说接受起来只有那种昙花一现的效果,并不能将方法牢记于心,卡尔讲题确确实实落实在了干货上面,落在了基础上面,这才是真正的好讲师啊!

对于这一次提交的代码,我结合了灵神和卡尔的思路。首先对于递归退出的条件我只用一行解决了,但那一行代表的含义是丰富的,具体请看代码注释,再就是左右都非空的情况,这里就要用到后序遍历,为什么用后序遍历?卡尔给出的解释是:由于每一个节点都需要其孩子的真假信息才能判断当前节点的真假信息,需要将孩子的信息回溯到当前节点综合判断,所以用到的是后序遍历,解决二叉树问题无非就是四种遍历方式,但是具体采用哪一种一定要根据题意来采用,不同的遍历方式对于不同的题目产生的答案有可能不同,有时候一个题可以采用多种遍历方式,而有些题仅能采用某一种方式解题,这就是我之前刷题所遗漏的地方。最后解释一下val那一行,先进行的 == 运算,==运算的结果返回到val中,那一行也能写成bool val = p->val == q->val ? 1 : 0;还有就是return返回的值需要val、outside和inside综合判断真价值

这一道题给了我很大的感触吧,刷题确实不能图快,有时回过头来做的题其实又是一个新题。。。不能一个劲的追求简洁,扎实才是王道啊!

104.二叉树的最大深度

class Solution {
public:

    int func(TreeNode* root)
    {
        if(root == nullptr) return 0;
        int leftNum = func(root->left);
        int rightNum = func(root->right);
        return max(leftNum, rightNum)+1;
    }

    int maxDepth(TreeNode* root) {
        return func(root);
    }
};

这里给出二叉树的两个定义:深度:指节点到根节点的距离,根节点的深度为1;高度:节点到叶子节点的距离,叶子节点的高度为1。所以求深度最好是用前序遍历采用迭代方法比较好,但是写的话会比较麻烦,这里采用的是后序遍历,为什么会是后序遍历呢?题干求的是最大深度,那么是不是可以理解成求二叉树的最大高度呢?求最大高度也就是求根节点的高度,这样由叶子节点一步一步往上传递就用到的是后序遍历。

111.二叉树的最小深度

class Solution {
public:
    int minDepth(TreeNode* root) {
        if(root == nullptr) return 0;

        int leftnum = minDepth(root->left);
        int rightnum = minDepth(root->right);

        if(root->left == nullptr && root->right != nullptr) return 1+rightnum;
        else if(root->right == nullptr && root->left != nullptr) return 1+leftnum;
        else return min(leftnum, rightnum)+1;
    }
};

整体上与前面一道题是相同的解法,也是用到的后序遍历的方法,但是再返回值上有一点不同,需要对二叉树的各种情况进行讨论,先是左空右不空的情况—>返回根节点右子树的最小深度+1、右空左不空与前面上一个情况相反,最后是都不空的情况,直接返回根节点的最小深度。

结合前面的对称二叉树的题目,我发现其实在一般情况你写完提交后不能通过全部样例时就需要考虑二叉树的几个特殊情况:左空右不空、左不空右空、左右都不空、左右都空。

222.完全二叉树的节点个数

方法一:

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(root == nullptr) return 0;
        return countNodes(root->left) + countNodes(root->right)+1;
    }
};

方法二:

class Solution {
public:
    int countNodes(TreeNode* root) {
        if(root == nullptr) return 0;

        TreeNode* p_left = root->left, *p_right = root->right;
        int leftDepth = 1, rightDepth = 1;
        while(p_left)
        {
            leftDepth++;
            p_left = p_left->left;
        }
        while(p_right)
        {
            rightDepth++;
            p_right = p_right->right;
        }
        if(leftDepth == rightDepth) return pow(2, leftDepth)-1;

        int leftNum = countNodes(root->left);
        int rightNum = countNodes(root->right);
        return leftNum+rightNum+1;
    }
};

方法一用的普通的后序遍历求完全二叉树的个数,因为遍历了整棵树所以时间复杂度是On的,而方法二用到了题干给出的完全二叉树的性质,利用完全二叉树的性质使时间复杂度降低了。具体的逻辑:在递归到每个节点的时候写一段代码来判断以当前节点为根节点的二叉树是否为完全二叉树,如果是,直接将2^深度-1返回给上一级,如果不是那么就继续后序遍历该树。如何判断一个子树是否为完全二叉树?—>由于题目中给出的树一定是完全二叉树,那么我们只需要从当前节点定义两个指针,分别一直往左迭代和往右迭代直到指为空,迭代的过程中用两个int型的变量记录各自的深度,最后判断两个深度是否相等,如果相等那么这个子树就是完全二叉树,反之则不是。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/608209.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(八)JSP教程——application对象

application对象是一个比较重要的对象&#xff0c;服务器在启动后就会产生这个application对象&#xff0c;所有连接到服务器的客户端application对象都是相同的&#xff0c;所有的客户端共享这个内置的application对象&#xff0c;直到服务器关闭为止。 可以使用application对…

【SpringBoot记录】自动配置原理(1):依赖管理

前言 我们都知道SpringBoot能快速创建Spring应用&#xff0c;其核心优势就在于自动配置功能&#xff0c;它通过一系列的约定和内置的配置来减少开发者手动配置的工作。下面通过最简单的案例分析SpringBoot的功能特性&#xff0c;了解自动配置原理。 SpringBoot简单案例 根据S…

Linux下的SPI通信

SPI通信 一. 1.SPI简介: SPI 是一种高速,全双工,同步串行总线。 SPI 有主从俩种模式通常由一个主设备和一个或者多个从设备组从。SPI不支持多主机。 SPI通信至少需要四根线,分别是 MISO(主设备数据输入,从设备输出),MOSI (主设数据输出从设备输入),SCLK(时钟信号),CS/SS…

leetcode尊享面试100题(549二叉树最长连续序列||,python)

题目不长&#xff0c;就是分析时间太久了。 思路使用dfs深度遍历&#xff0c;先想好这个函数返回什么&#xff0c;题目给出路径可以是子-父-子的路径&#xff0c;那么1-2-3可以&#xff0c;3-2-1也可以&#xff0c;那么考虑dfs返回两个值&#xff0c;对于当前节点node来说&…

BI赋能金融新质生产力,16家金融机构智能BI创新实践分享

2024年政府工作报告强调&#xff0c;要“大力发展科技金融、绿色金融、普惠金融、养老金融、数字金融”&#xff0c;同时“大力推进现代化产业体系建设&#xff0c;加快发展新质生产力”。对于金融行业而言&#xff0c;培育新质生产力是高质量发展的关键着力点。金融机构可以通…

vue项目启动后页面显示‘Cannot GET /’

1、npm run dev命令启动项目的时候没有报错&#xff0c;页面打开却提示 Cannot GET / 2.这个时候只需要找到config文件夹下面的index.js文件。把assetsPublicPath字符串的&#xff1a;‘./’修改成 ‘/’就行了。修改完之后记得关闭项目&#xff0c;然后重新启动。不然不会生效…

度小满——征信报告图建模

目录 背景介绍 发展趋势 技术演进 图在金融风控领域中的演进 度小满图机器学习技术体系 案例 征信报告介绍 征信报告图建模

postman接口测试中文汉化教程

想必同学们对于接口测试工具postman的使用并不陌生&#xff0c;以及最近大为流行的国产工具apifox。对于使用过的同学来说&#xff0c;两者区别以及优缺点很容易别展示出来&#xff0c;postman相比apifox来说更加轻量&#xff0c;但是apifox更加符合国人的使用习惯....中国人给…

Nest 快速上手 —— (三)中间件 / 异常过滤器

一、 中间件&#xff08;Middleware&#xff09; 1.特点 中间件是一个在路由处理程序之前被调用的函数。中间件函数可以访问请求和响应对象&#xff0c;以及应用程序请求-响应周期中的next()中间件函数。下一个中间件函数通常由一个名为next的变量表示。 中间件函数可以执行以…

车载测试系列:车载蓝牙测试(三)

HFP测试内容与测试方法 2.3 接听来电&#xff1a;测试手机来电时&#xff0c;能否从车载蓝牙设备和手机侧正常接听】拒接、通话是否正常。 1、预置条件&#xff1a;待测手机与车载车载设备处于连接状态 2、测试步骤&#xff1a; 1&#xff09;用辅助测试机拨打待测手机&…

BetterMouse for Mac激活版:鼠标增强软件

BetterMouse for Mac是一款鼠标增强软件&#xff0c;旨在取代笨重的、侵入性的和耗费资源的鼠标驱动程序&#xff0c;如罗技选项。它功能丰富&#xff0c;重量轻&#xff0c;效率优化&#xff0c;而且完全隐私安全&#xff0c;试图满足你在MacOS上使用第三方鼠标的所有需求。 B…

新火种AI|AI让大家都变“土”了!

作者&#xff1a;一号 编辑&#xff1a;美美 AI不仅要把人变“土”&#xff0c;还要把人变多样。 这个世界&#xff0c;终究是变“土”了。 今年五一假期&#xff0c;一个名为“Remini”的AI修图APP火遍了全网。注意&#xff0c;是Remini&#xff0c;而不是Redmi&#xff0…

MySQL-集群1

一、为什么要用mysql集群&#xff1f;&#xff1a; mysql单体架构在企业中很少用&#xff0c;原因&#xff1a;①会形成单点故障&#xff0c;没有高可用的效果&#xff1b;②mysql本身是一个I/O能力比较差&#xff0c;并发能力比较差的应用服务&#xff0c;在较高规模的网络I/…

部署JVS服务出现上传文件不可用,问题原因排查。

事情的起因是这样的&#xff0c;部门经理让我部署一下JVS资源共享框架&#xff0c;项目的地址是在这里 项目资源地址 各位小伙伴们做好了&#xff0c;我要开始发车了&#xff0c;全新的“裂开之旅” 简单展示一下如何部署JVS文档 直达链接 撕裂要开始了 本来服务启动的好好…

【计算机毕业设计】基于SSM++jsp的蜀都天香酒楼网站【源码+lw+部署文档+讲解】

目录 摘要 Abstract 目 录 1绪论 1.1研究背景与意义 1.2国内外研究现状 1.3研究内容 1.4论文结构 2相关技术介绍 2.1 B/S模式 2.2 MyEclipse开发环境 2.3 MySQL数据库 2.4 Java语言 2.5 JSP技术 2.6 Tomcat服务器 3系统分析 3.1需求分析 3.2可行性分析 3.2.1经济可行性 3.2.2技…

Python运维之多进程!!

本节的快速导航目录如下喔&#xff01;&#xff01;&#xff01; 一、创建进程的类Process 二、进程并发控制之Semaphore 三、进程同步之Lock 四、进程同步之Event 五、进程优先队列Queue 六、多进程之进程池Pool 七、多进程之数据交换Pipe 一、创建进程的类Process mu…

5.9gunplot绘图堆叠柱状图

gunplot绘图堆叠柱状图 plot"要用的数据&#xff08;后缀名是.dat)" using 2 t(或者title) 跟着是要命名的属性名称 这个名称可以用.dat里的每列列名&#xff0c;也可以直接在后面跟着定义 plot "data.dat" using 2 t columnheader(2), using 3 t column…

PLC数据采集网关的功能和特点-天拓四方

一、引言 随着工业自动化程度的不断提高&#xff0c;数据在生产线上的作用愈发重要。PLC作为工业自动化的核心设备&#xff0c;其数据采集和处理能力直接影响到整个生产线的效率和稳定性。而PLC数据采集网关&#xff0c;作为连接PLC与外部系统的桥梁&#xff0c;正日益受到人们…

vue3—win7搭建vue3环境

背景 vue3环境要求node.js18.3及以上版本&#xff0c;所以我们需要安装更高版本node.js&#xff0c;然而win7无法支持高版本node.js。下面我介绍一种安装方法。 步骤 1、下载 node-v13.14.0-x64.msi 安装&#xff0c;默认安装即可。安装完成后&#xff0c;进入cmd&#xff0c…

Hibernate认识

一、定义 Hibernate 是一种开源的 Java 对象关系映射 (ORM) 框架&#xff0c;用于将面向对象的领域模型持久化到关系数据库中。它为开发人员提供了一种简便的方法来操作数据库&#xff0c;而无需编写繁琐的 SQL 代码。 ORM&#xff08;对象关系映射&#xff09;&#xff1a;Ob…
最新文章