【数据结构与算法】单调队列 | 单调栈

news2024/9/20 9:14:50

🌠作者:@阿亮joy.
🎆专栏:《数据结构与算法要啸着学》
🎇座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
在这里插入图片描述


目录

    • 👉滑动窗口的最大值👈
    • 👉单调队列的实现👈
    • 👉单调栈的实现👈
      • 数组无重复值版本的单调栈
      • 每日温度
      • 数组有重复值版本的单调栈
      • 指标A的最大值
    • 👉总结👈

👉滑动窗口的最大值👈

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

在这里插入图片描述

这是一道可以使用单调队列的经典题。如果使用暴力方法的话,就是遍历一遍的过程中每次从窗口中再找到最大的数值,这样很明显是 O(k × N) 的算法。而使用单调队列来解这道题目,时间复杂度是 O(N)。

那什么是单调队列呢?单调队列是具有单调性的队列,其里面存储的元素单增或者单减。单调队列需要保证的一个功能就是:每次插入元素或者删除元素,其队头的元素都是最大值或者最小值。为了实现这个功能,我们需要用双端队列 deque 来做适配器。

为了说明单调队列的功能,假设数组中的元素是 6 4 2 5 3。注:一下过程假设队头数据是最大值,队头数据是最小值同理。窗口的右边界往右移动,则说明要插入新数据。如果新插入的数据比队尾的数据小,则直接将数据从队尾插入;而如果新插入的数据对队尾的数据大,则需要将队尾数据弹出直至队尾数据大于新插入的数据或队列为空。窗口的左边界往右移动,则说明可能要弹出队头数据。如果从窗口出来的数据就是队头数据,则说明队头数据已过期,需要将队头数据弹出;而如果从窗口出来的数据不是队头数据,则不需要弹出队头数据。

在这里插入图片描述

为什么单调队列就能够保证它里面存储的就是窗口内的最大值呢?其实它是通过新加入的值是否会比前面的值大,如果是,就将前面的元素从尾部弹出。因为新加入的元素肯定比它们晚离开窗口,而且值有比它们大,所以只需要保留新加入元素就可以保证窗口内的最大值在队列中。

那如何估计单调队列解决滑动窗口最大值的时间复杂度呢?数组中的每个元素最多进队列一次,出队列一次,没有多余的操作,所以整体的时间复杂度为 O(N),每次操作的平均复杂度为 O(1)。

👉单调队列的实现👈

// MyQueue.h
#pragma once
#include <deque>
#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;

class MyQueue
{
public:
	// 因为范围是左必右开的,所以_left初始为-1,_right初始为0
	// [_left, _right)
	MyQueue(const vector<int>& arr)
		: _arr(arr)
		, _left(-1)
		, _right(0)
	{}

	void push()
	{
		// 数组中的元素都进入过单调队列中了
		if (_right == _arr.size())
			return;

		// 加入新元素时,将队尾元素弹出至队列为空或队尾元素大于新加入的元素
		while (!_q.empty() && _arr[_q.back()] <= _arr[_right])
		{
			_q.pop_back();
		}
		_q.push_back(_right);
		++_right;
	}

	// _arr [_left, _right)
	void pop()
	{
		// 左边界大于等于右边界,说明窗口内没有数据了,
		// 直接返回即可
		if (_left >= _right - 1)
			return;

		// 因为_left初始值是-1,所以需要先加加_left
		++_left;
		if (_q.front() == _left)
		{
			_q.pop_front();
		}
	}
	
	int front()
	{
		// 如果队列为空,直接断言报错
		assert(!_q.empty());	
		return _arr[_q.front()];
	}

private:
	int _left;	// 窗口的左边界
	int _right;	// 窗口的右边界的再右一个位置
	deque<int> _q;	// 使用双端队列来实现单调队列,双端队列中存的是下标
	vector<int> _arr;
};

MyQueue 类使用说明:

  • 申请一个滑动窗口对象时,需要传入一个 vector 对象。
  • 上面的滑动窗口类可以自己手动控制滑动窗口的大小。使用一次 push 接口,就表明滑动窗口的右边界向右移动;而使用一次 pop 接口,就表明滑动窗口的左边界向右移动。滑动窗口左右边界向右移动,双端队列中存储的数据会是否需要弹出,见上文讲解!

知道如何使用 MyQueue 类后,我们就可以轻松地解决最开始的题目了。

注:需要将 MyQueue 类拷贝到 LeetCode 上

class Solution 
{
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) 
    {
        MyQueue q(nums);
        // 形成大小为k的窗口
        for(int i = 0; i < k; ++i)
        {
            q.push();
        }

        vector<int> ret;
        ret.push_back(q.front());   // 将当前窗口中的最大值尾插到ret中
        for(int i = k; i < nums.size(); ++i)
        {
            // 左右边界同时向右移动一次即可保存滑动窗口的大小为k
            q.push();
            q.pop();
            ret.push_back(q.front());   // 记录滑动窗口的最大值
        }
        return ret;
    }
};

在这里插入图片描述

注:单调队列的实现方式有很多,需要根据具体的题目来定制相应的单调队列,不能死板地认为单调队列只有一种实现方式。

👉单调栈的实现👈

单调栈也是具有单调性的栈,其存储的元素是单增或者单剪。单调栈通常用于寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置。

给定数组 arr = { 5 4 3 6 1 2 0 7 },请你求出数组中每个元素的左边和右边第一个比自己大的元素。暴力的方法,来到 i 位置,向左向右遍历找出第一个比 i 位置上元素大的元素。很明显,这种方式的时间复杂度为 O(N^2)。

如果采用单调栈呢,解决上面问题的时间复杂度为 O(N)。过程如下图:

在这里插入图片描述
为什么上面的过程就能够求出每个元素左边和右边第一个比自己大的元素呢?证明如下图:

在这里插入图片描述

如果数组中有重复的元素,那么栈中里面放的就不再是下标了,而是存储下标的 list 或者 vector。当新加入的元素比栈顶元素大时,那么就生成信息了。那如何生成呢?新加入的元素就是第一个比栈顶元素大的元素,栈顶元素下面压着的链表尾部的元素就是左边第一个比栈顶元素大的元素。如果新加入的元素和栈顶元素相等,那么新加入元素的下标尾插到 list 或 vector 中。

单调栈的实现,在这里我就不实现成一个类了,而将其实现出一个函数。如果大家想将其实现成一个类,可以参考下面的代码来实现。

数组无重复值版本的单调栈

vector<vector<int>> getNearBiggerNoRepeat1(const vector<int> arr)
{
	// ret[i][0]存的值是左边第一个比arr[i]大的值的下标
	// ret[i][1]存的值是右边第一个比arr[i]大的值的下标
	vector<vector<int>> ret(arr.size(), vector<int>(2));
	stack<int> st;

	for (int i = 0; i < arr.size(); ++i)
	{
		// arr[i]为新插入的元素
		while (!st.empty() && arr[st.top()] < arr[i])
		{
			int popIndex = st.top();
			st.pop();
			// 如果左边没有比自己大的数,左边界的下标设置为-1
			int leftBiggerIndex = st.empty() ? -1 : st.top();
			ret[popIndex][0] = leftBiggerIndex;
			// 如果新插入元素比栈顶元素大,那么新插入元素就是
			// 右边第一个比栈顶元素大的元素
			ret[popIndex][1] = i;
		}
		// 添加元素操作不要忘了
		st.push(i);
	}

	// 生成栈中元素的信息
	while (!st.empty())
	{
		int popIndex = st.top();
		st.pop();
		// 如果左边没有比自己大的数,左边界的下标设置为-1
		int leftBiggerIndex = st.empty() ? -1 : st.top();
		ret[popIndex][0] = leftBiggerIndex;
		// 如果新插入元素比栈顶元素大,那么新插入元素就是
		// 右边第一个比栈顶元素大的元素
		ret[popIndex][1] = -1;
	}

	return ret;
}

现在数组无重复值版本的单调栈就实现好了,我们找到题目来验证一下写得对不对吧!

每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

在这里插入图片描述

很明显,每日温度这道题目就可以通过单调栈来解决。而且这道题目只要求求出右边第一个比自己大的值就行了,并不需要求左边第一个比自己大的值。那么就把上面的实现的单调栈代码拷贝到 LeetCode 去,再实现下面的代码就行了。

vector<vector<int>> getNearBiggerNoRepeat(const vector<int>& arr)
{
	// ret[i][0]存的值是左边第一个比arr[i]大的值的下标
	// ret[i][1]存的值是右边第一个比arr[i]大的值的下标
	vector<vector<int>> ret(arr.size(), vector<int>(2));
	stack<int> st;

	for (int i = 0; i < arr.size(); ++i)
	{
		// arr[i]为新插入的元素
		while (!st.empty() && arr[st.top()] < arr[i])
		{
			int popIndex = st.top();
			st.pop();
			// 如果左边没有比自己大的数,左边界的下标设置为-1
			int leftBiggerIndex = st.empty() ? -1 : st.top();
			ret[popIndex][0] = leftBiggerIndex;
			ret[popIndex][1] = i;
		}
		// 添加元素操作不要忘了
		st.push(i);
	}

	// 生成栈中元素的信息
	while (!st.empty())
	{
		int popIndex = st.top();
		st.pop();
		int leftBiggerIndex = st.empty() ? -1 : st.top();
		ret[popIndex][0] = leftBiggerIndex;
		ret[popIndex][1] = -1;
	}

	return ret;
}

class Solution 
{
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) 
    {
        vector<vector<int>> info = getNearBiggerNoRepeat(temperatures);
        vector<int> ret(temperatures.size());

        for(size_t i = 0; i < temperatures.size(); ++i)
        {
            // info[i][1] 等于 -1 表示 temperature[i] 右边没有比它更高的温度了
            // info[i][0] 不等于 -1 表示 temperature[i] 右边有比它更高的温度
            // 最近的一天是在 info[i][1] - i 天后
            if(info[i][1] != -1)
                ret[i] = info[i][1] - i;
            else
                ret[i] = 0;
        }

        return ret;
    }
};

在这里插入图片描述

用我们上面实现的单调栈来解决这道题目,简直就是杀鸡用牛刀。解决这道题目,我们只需要实现单调栈的主要逻辑就行了,如下方代码所示:

class Solution 
{
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) 
    {
        vector<int> ret(temperatures.size());
        stack<int> st;

        st.push(0); // 数组第一个元素先入栈
        for(int i = 1; i < temperatures.size(); ++i)
        {
            // 栈顶元素比新加入元素temperatures[i]小
            // 就要生成栈顶元素的信息
            while(!st.empty() && temperatures[st.top()] < temperatures[i])
            {
                int popIndex = st.top();
                st.pop();
                ret[popIndex] = i - popIndex;
            }
            st.push(i);
        }

        // 循环结束,栈中的元素也要生成信息
        while(!st.empty())
        {
            int popIndex = st.top();
            st.pop();
            ret[popIndex] = 0;
        }

        return ret;
    }
};

在这里插入图片描述

数组有重复值版本的单调栈

vector<vector<int>> getNearBiggerRepeat(const vector<int>& arr)
{
	// ret[i][0]存的值是左边第一个比arr[i]大的值的下标
	// ret[i][1]存的值是右边第一个比arr[i]大的值的下标
	vector<vector<int>> ret(arr.size(), vector<int>(2));
	stack<vector<int>> st;

	for (int i = 0; i < arr.size(); ++i)
	{
		while (!st.empty() && arr[st.top()[0]] < arr[i])
		{
			vector<int> popVector = st.top();
			st.pop();
			int leftBiggerIndex = st.empty() ? -1 : st.top()[st.top().size() - 1];
			for (int popIndex : popVector)
			{
				ret[popIndex][0] = leftBiggerIndex;
				ret[popIndex][1] = i;
			}
		}

		// 添加操作
		if (!st.empty() && arr[st.top()[0]] == arr[i])
		{
			st.top().push_back(i);
		}
		else
		{
			vector<int> v;
			v.push_back(i);
			st.push(v);
		}
	}

	while (!st.empty())
	{
		vector<int> popVector = st.top();
		st.pop();
		int leftBiggerIndex = st.empty() ? -1 : st.top()[st.top().size() - 1];
		for (int popIndex : popVector)
		{
			ret[popIndex][0] = leftBiggerIndex;
			ret[popIndex][1] = -1;
		}
	}

	return ret;
}


void Test()
{
	vector<int> arr = { 2, 3, 2, 5,6,7,3,3,3,5,8 };
	vector<vector<int>> ret = getNearBiggerRepeat(arr);
	for (int i = 0; i < ret.size(); ++i)
	{
		cout << ret[i][0] << " : " << ret[i][1] << endl;
	}
}

在这里插入图片描述

指标A的最大值

数组中所有数都是正数,数组中累加和与最小值的乘积, 假设叫做指标 A。给定一个数组, 请返回子数组中, 指标 A 最大的值。

在这里插入图片描述

思路:求出数组中每个元素作为最小值时的指标 A,要使子数组的指标 A 最大,那么就要使子数组的累加和最大。那怎么才能让子数组的累加和最大呢?就是找到左边和右边第一个比自己小的元素,这样就能让子数组的累加和最大了。很明显,这就需要用到单调栈了。

class Solution
{
public:
	int max(vector<int>& arr)
	{
		int max = -1;
		stack<int> st;
		st.push(0);

		for (int i = 1; i < arr.size(); ++i)
		{
			// 该单调栈栈底元素是最大的,栈底元素是最小的
			while (!st.empty() && arr[st.top()] >= arr[i])
			{
				int popIndex = st.top();
				st.pop();
				// 栈为空时,说明从0到i-1的数中arr[popIndex]是最小的
				// 栈不为空时,说明从st.top()+1到i-1的数中arr[popIndex]是最小的
				int leftSmallerIndex = !st.empty() ? st.top() + 1 : 0;
				int sum = 0;
				for (int j = leftSmallerIndex; j < i; ++j)
				{
					sum += arr[j];
				}

				max = sum * arr[popIndex] > max ? sum * arr[popIndex] : max;
			}
			st.push(i);
		}
		
		// 循环结束,栈中的元素右边全是比自己大的数
		while (!st.empty())
		{
			int popIndex = st.top();
			st.pop();
			// 栈为空时,说明从0到i-1的数中arr[popIndex]是最小的
			// 栈不为空时,说明从st.top()+1到i-1的数中arr[popIndex]是最小的
			int leftSmallerIndex = !st.empty() ? st.top() + 1 : 0;
			int sum = 0;
			for (int j = leftSmallerIndex; j < arr.size(); ++j)
			{
				sum += arr[j];
			}

			max = sum * arr[popIndex] > max ? sum * arr[popIndex] : max;
		}

		return max;
	}
};

在这里插入图片描述
上面的代码还有可以优化的地方,就是先把前缀和存到一个数组中,需要累加和的时候直接取即可。

class Solution
{
public:
	int max(vector<int>& arr)
	{
        int size = arr.size();
        vector<int> sum(size);
        sum[0] = arr[0];
        // 求前缀和
        for(int i = 1; i < size; ++i)
        {
            sum[i] = sum[i - 1] + arr[i];
        }
        
		int ret = -1;
		stack<int> st;
		st.push(0);
        
        for(int i = 1; i < size; ++i)
        {
            while(!st.empty() && arr[st.top()] >= arr[i])
            {
                int popIndex = st.top();
                st.pop();
                // 栈为空,说明popIndex左边没有比它小的数,累加和为sum[i-1]
                // 栈不为空,说明popIndex左边有比它小的数,累加和为sum[i-1] - sum[st.top()]
                int Sum = st.empty() ? sum[i - 1] : (sum[i - 1] - sum[st.top()]);
                ret = ret > (Sum * arr[popIndex]) ? ret : (Sum * arr[popIndex]);
            }
            st.push(i);
        }
        
        
        while(!st.empty())
        {
            int popIndex = st.top();
            st.pop();
            // 在栈中的元素,右边没有比它们小的元素了
            // 栈为空,说明popIndex左边没有比它小的数,累加和为sum[size-1]
            // 栈不为空,说明popIndex左边有比它小的数,累加和为sum[size-1] - sum[st.top()]
            int Sum = st.empty() ? sum[size - 1] : (sum[size - 1] - sum[st.top()]);
            ret = ret > (Sum * arr[popIndex]) ? ret : (Sum * arr[popIndex]);
        }
                      
		return ret;
	}
};

在这里插入图片描述

👉总结👈

本篇博客主要讲解了两个非常实用的数据结构:单调队列和单调栈、用单调队列解决 LeetCode 中的滑动窗口最大值问题、用单调栈解决 LeetCode 中的每日问题和牛客网中的指标 A 的最大值问题等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python入门第七天】Python 数字

Python 数字 Python 中有三种数字类型&#xff1a; intfloatcomplex 为变量赋值时&#xff0c;将创建数值类型的变量&#xff1a; 实例 x 10 # int y 6.3 # float z 2j # complex如需验证 Python 中任何对象的类型&#xff0c;请使用 type() 函数&#xff1a; 实…

计算机图形学:中点Bresenham算法画椭圆

作者&#xff1a;非妃是公主 专栏&#xff1a;《计算机图形学》 博客地址&#xff1a;https://blog.csdn.net/myf_666 个性签&#xff1a;顺境不惰&#xff0c;逆境不馁&#xff0c;以心制境&#xff0c;万事可成。——曾国藩 文章目录专栏推荐专栏系列文章序一、实现思路二、…

分布式-分布式事务和分布式锁

分布式事务有哪些解决方案 分布式事务 指事务的参与者、支持事务操作的服务器、存储等资源分别位于分布式系统的不同节点之上。 分布式事务就是一个业务操作&#xff0c;是由多个细分操作完成的&#xff0c;而这些细分操作又分布在不同的服务器上&#xff1b;事务&#xff0c…

数据库(第一天)

文档信息 文档类别正式文档文档编号数据库基础课 1.2-001版本1.2-001文档名称数据库基础课编写负责人/编写时间梁昭东/2023 年 1 月 30 日审核负责人/审核时间年 月 日批准人/批准时间年 月 日 变更记录 日期版本号变更内容修订者2023.01.30v1.2版根据实际情况增删了部分内容…

LINUX【线程概念】

线程线程概念线程的优点线程的缺点线程异常线程用途linux进程和线程线程控制pthread 库创建线程线程等待线程退出线程分离线程互斥线程互斥相关概念线程互斥互斥量互斥量接口初始化互斥量销毁互斥量互斥量的加锁和解锁互斥量实现原理线程概念 线程是进程中的一个执行流 一个进程…

ShonyDanza:如何利用Shodan实现自定义的安全研究与网络防护

关于ShonyDanza ShonyDanza是一款支持自定义且易于使用的安全工具&#xff0c;该工具基于Shodan实现其功能&#xff0c;并且可以利用Shodan的强大能力帮助研究人员实现安全研究、安全测试和安全防护等任务。 ShonyDanza的功能包括&#xff1a; 1、根据搜索条件获取IP 2、根据…

LabWindows CVI 2017开发笔记--串口调试软件实例

一、新建工程 打开LabWindows CVI软件&#xff0c;在桌面新建SerialDebug文件夹用来保存工程文件&#xff0c;在欢迎页点击New–>Project 或者在软件首页点击File–>New–>Project 将Project创建在新的Workspace中&#xff0c;设置完成后点击OK 新建一个用户GUI界…

内存溢出、内存泄露的概述及常见情形

内存溢出&#xff08;OutofMemoryError&#xff09; 简述 java doc 中对 Out Of Memory Error 的解释是&#xff0c;没有空闲内存&#xff0c;并且垃圾收集器也无法提供更多内存。 JVM 提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理&#xff0c;由于 GC&…

functional interface

更优雅 案例 要在TodoTaskRepository写find方法找到对应task列表&#xff0c;传入自定义的如何find 要传入两个参数&#xff0c;返回一个bool&#xff0c;在文档里找这样的functional interface java8官方文档-Package java.util.function BiPredicate是这样的 接口函数是t…

Numpy基础——人工智能基础

文章目录一、Numpy概述1.优势2.numpy历史3.Numpy的核心&#xff1a;多维数组4.numpy基础4.1 ndarray数组4.2 内存中的ndarray对象一、Numpy概述 1.优势 Numpy(Nummerical Python),补充了Python语言所欠缺的数值计算能力&#xff1b;Numpy是其它数据分析及机器学习库的底层库&…

GitLab CI/CD实现代码推送后自动maven打包发布

1、GitLab CI/CD介绍 CI(Continuous Intergration)&#xff1a;即持续集成&#xff0c;将代码的合并、部署、自动化测试都在一起&#xff0c;不断地执行这个过程&#xff0c;并对结果反馈。 CD(Continuous Delivery)&#xff1a;即持续交付&#xff0c;持续交付是一种软件工程方…

别具一格的婚礼,VR全景+婚礼的优势展现在哪里?

随着90后、95后逐渐步入结婚的主力军中&#xff0c;如何策划一场别具一格的婚礼是许多年轻人所头疼的&#xff0c;那么今年我们就可以玩点新潮的&#xff0c;VR婚礼或许是个不错的选择。 VR全景婚礼就是通过全景摄像机对婚礼进行记录&#xff0c;不但可以帮助新人捕捉婚礼的精彩…

对S参数的理解II

本篇文章特别感谢粉丝朋友“千年的呢喃”&#xff0c;他给我推荐了一本书&#xff0c;写的非常好 Micro Wave and RF Design&#xff0c;有需要的朋友自行下载。 之前关于S参数也写过几篇文章了&#xff0c;但一直以来都有一个历史遗漏问题没有解决&#xff0c;那就是&#xf…

ElasticSearch-学习笔记05【SpringDataElasticSearch】

Java后端-学习路线-笔记汇总表【黑马程序员】ElasticSearch-学习笔记01【ElasticSearch基本介绍】【day01】ElasticSearch-学习笔记02【ElasticSearch索引库维护】ElasticSearch-学习笔记03【ElasticSearch集群】ElasticSearch-学习笔记04【Java客户端操作索引库】【day02】Ela…

【IVIF的超分重建】

Multimodal super-resolution reconstruction of infrared and visible images via deep learning &#xff08;基于深度学习的红外和可见光图像多模态超分辨率重建&#xff09; 提出了一种基于编解码器结构的红外-可见光图像融合方法。图像融合任务被重新表述为保持红外-可见…

2023年3月AMA-CDGA/CDGP数据治理认证考试这些城市可以报名

目前2023年3月5日CDGA&CDGP开放报名的城市有&#xff1a;北京、上海、广州、深圳、杭州、重庆&#xff0c;西安&#xff0c;成都&#xff0c;长沙&#xff0c;济南&#xff0c;更多考场正在增加中… DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业…

Echarts 设置折线图线条样式(虚线+粗细+阴影)

第012个点击查看专栏目录Echarts折线图的lineStyle属性可以设置折线的颜色&#xff0c;粗细&#xff0c;类型&#xff0c;线段末端类型&#xff0c;阴影&#xff0c;透明度&#xff0c;偏移等属性。文章目录示例效果示例源代码&#xff08;共128行&#xff09;相关资料参考专栏…

【Java|多线程与高并发】 使用Thread 类创建线程的5种方法如何查看程序中的线程

文章目录前言线程创建1.继承Thread类重写run()方法如何查看程序中的线程?2.实现Runnable接口3.使用匿名内部类,继承Thread4.使用匿名内部类,实现Runnable5.⭐使用Lambda表达式,创建线程(重要)Thread 的常见构造方法总结前言 在这里主要补充说明一些问题,方便更好地理解下面的…

conda安装nodejs版本过低解决方法

conda命令直接安装nodejs时&#xff0c;可能会由于镜像源中nodejs版本过低导致没法安装高本版的nodejs&#xff0c;导致无法jupyterlab使用一些扩展插件。 解决方法如下&#xff1a;&#xff08;windows环境下直接按提示下载版本安装就行&#xff0c;此处只介绍linux环境的解决…

2023上半年软考中级系统集成项目管理工程师2月25日开班

系统集成项目管理工程师是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目之一&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职…