【基础算法总结】滑动窗口

news2024/11/15 17:21:49

目录

  • 一,滑动窗口介绍
  • 二,算法原理和代码实现
    • 209.长度最小的子数组
    • 3.无重复字符的最长子串
    • 1004.最大连续1的个数III
    • 1658.将x减到0的最小操作数
    • 904.水果成篮
    • 438.找到字符串中所有字母异位词
    • 30.串联所有单词的子串
    • 76.最小覆盖子串
  • 三,算法总结

一,滑动窗口介绍

滑动窗口算法也是基础算法之一,它的本质是一对"同向双指针"。当我们分析的对象是⼀段连续的区间(子数组/子串),使用暴力解法发现两个指针可以不回退的一直往前走,并且可以利用单调性解决问题时,就可以使用滑动窗口时间复杂度是O(N).

如何使用滑动窗口呢?

(1) 初始化 left = 0, right = 0。用 left 和 right 来控制这个"窗口"
(2) 进"窗口"
(3)判断,是否要出"窗口",循环(2)(3)步。
(4) 更新结果但是什么时候更新结果是不固定的,可能是在进"窗口"的时候更新,也可能是判断成立时候更新,具体题目具体分析
在这里插入图片描述

通过下面若干到题目可以理解的更深刻

二,算法原理和代码实现

209.长度最小的子数组

在这里插入图片描述
在这里插入图片描述

算法原理

解法1:暴力枚举,O(N^2)。从前往后枚举数组中的任意⼀个元素,把它当成起始位置。然后从这个起始位置开始,然后寻找⼀段最短的区间,使得这段区间的和⼤于等于⽬标值。将所有元素作为起始位置所得的结果中,找到最⼩值即可。绝对超时

解法2:滑动窗口,O(N)。让滑动窗⼝满⾜:从i 位置开始,窗⼝内所有元素的和小于 target (那么当窗⼝内元素之和第⼀次⼤于等于⽬标值的时候,就是 i 位置开始,满⾜条件的最小长度)。
做法:将右端元素划⼊窗⼝中,统计出此时窗⼝内元素的和
(1) 如果窗⼝内元素之和⼤于等于target :更新结果,并且 left++ 将左端元素划出去的同时继续判断是否满⾜条件并更新结果(因为左端元素可能很小,划出去之后依旧满足条件)
(2) 如果窗⼝内元素之和不满足条件: right++ ,另下⼀个元素进入窗口。
在这里插入图片描述

细节/技巧问题

(1) 本题更新结果是在判断成立后更新
(2) 判断,根据判断结果是否出窗口,是循环过程

代码实现

class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int left = 0, right = 0, n = nums.size();
        int ret = INT_MAX;
        int sum = 0;

        while(right < n)
        {
            sum += nums[right]; // 进窗口
            while(sum >= target) // 判断
            {
                ret = min(ret, right - left + 1); // 更新结果
                sum -= nums[left++]; //出窗口
            }
            right++;
        }
        return ret == INT_MAX ? 0 : ret;
    }
};

时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者最多都往后移动 n 次。因此时间复杂度是O(N)

3.无重复字符的最长子串

在这里插入图片描述
在这里插入图片描述

算法原理

解法1:暴力枚举+哈希(判断字符是否重复),O(N^2)

解法2:滑动窗口+哈希(判断字符是否重复),O(N)。让滑动窗口满足:窗口内所有元素都是不重复的。通过在草稿纸上进行模拟,我们不难发现规律:
在这里插入图片描述
本题使用滑动窗口的流程是:
在这里插入图片描述

细节/技巧问题

(1) 本题可以不用真的使用哈希容器,因为 s 由英文字母、数字、符号和空格组成,所以可以定义一个128大小的数组模拟哈希,就可以找到该字符的映射位置
(2) 更新结果也是在每次判断结束后更新

代码实现

class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        int hash[128] = {0}; // 用数组模拟哈希,判断字符是否重复出现
        int left = 0, right = 0, n = s.size();
        int ret = 0;
        while(right < n)
        {
            hash[s[right]]++; // 进窗口

            while(hash[s[right]] > 1) // 判断字符是否重复出现
                hash[s[left++]]--; // 出窗口

            ret = max(ret, right - left + 1); // 更新结果
            right++;
        }
        return ret;
    }
};

下面是我一开始写的错误代码,以示警告:

class Solution
{
public:
    int lengthOfLongestSubstring(string s)
    {
        int n = s.size();
        unordered_set<char> hash;
        int left = 0, right = 0;
        int len = 0;
        while (right < n)
        {
            while (hash.count(s[right]) == 0)
                hash.insert(s[right++]); // 进窗口

            len = max(len, right - left - 1);
            while (right < n && s[left] != s[right])
                hash.erase(s[left++]); // 判断且出窗口

            left++;
            right++;
        }
        return len;
    }
};

对比
正确的代码是每次发现一个重复字符,就会找到那个对应的字符出窗口,而错误的代码把两个重复字符之间的全部字符都出窗口了(包括重复字符)最后只能通过部分示例

1004.最大连续1的个数III

在这里插入图片描述
在这里插入图片描述

算法原理

这道题如果我们按照题目的意思直接翻转0,后续操作会十分麻烦,因为下一次还要把0变回1。所以正难则反,可以把题意转化成找出最长子数组,其中0的个数不超过 K 个这样就间接找出了连续 1 的最大个数

所以可以使用滑动窗口。先在草稿纸上进行模拟
在这里插入图片描述
本题使用滑动窗口的流程是:
在这里插入图片描述

代码实现

class Solution 
{
public:
    int longestOnes(vector<int>& nums, int k) 
    {
        int left = 0, right = 0, n = nums.size();
        int zero = 0; // 统计0的个数
        int ret = 0;

        while(right < n)
        {
            if(nums[right] == 0) zero++; // 进窗口
            while(zero > k) // 判断
                if(nums[left++] == 0) zero--; // 出窗口
            
            ret = max(ret, right - left + 1); // 更新结果
            right++;
        }
        return ret;
    }
};

我一开始写的错误代码,以示警告:

class Solution
{
public:
    int longestOnes(vector<int>& nums, int k)
    {
        int left = 0, right = 0, n = nums.size();
        int zero = 0; // 统计0的个数
        int ret = 0;

        while (right < n)
        {
            if (nums[right] == 0) zero++;
            while (zero > k)
            {
                ret = max(ret, right - left);
                if (nums[left++] == 0) zero--;
            }
            right++;
        }
        return ret == 0 ? n : ret;
    }
};

错误代码最大的问题就是没有在判断结束后更新结果,而是边判断边更新结果,这就会导致一些特殊情况,比如[0,0,0,0,0],k = 0的输出是5。在判断结束之后,窗口改变了再更新结果

1658.将x减到0的最小操作数

在这里插入图片描述
在这里插入图片描述

算法原理

这道题如果我们直接按照题目每次删除最左或最右边的数,会很复杂。所以正难则反,可以把题目转化为:找出最长子数组的长度,使得所有元素的和等于 sum - x,其中sum是全部数据的和,最后的结果用整个数组的长度 - 最长子数组的长度所以这又回到了我们第一题的思路

使用滑动窗口,本题的流程是:
在这里插入图片描述

细节/技巧问题

(1) 当 sum - x < 0 时,即 x > sum,就不存在最小操作数,直接返回 -1
(2) 只有窗口内的所有元素的和等于 sum - x 时,才更新结果
(3) 最后返回结果时,别忘记进行判断

代码实现

class Solution 
{
public:
    int minOperations(vector<int>& nums, int x) 
    {
        // 整个数组的和
        int sum = 0;
        for(auto e : nums) sum += e;

        int left = 0, right = 0, n = nums.size();
        int len = -1, tmp = 0;
        int target = sum - x;
        // 细节问题
        if(target < 0) return -1;

        while(right < n)
        {
            tmp += nums[right]; // 进窗口
            
            while(tmp > target) // 判断
                tmp -= nums[left++]; // 出窗口

            if(tmp == target) // 更新结果
                len = max(len, right - left + 1);
            
            right++;
        }
        
        if(len == -1) return -1;
        else return n - len;
    }
};

904.水果成篮

在这里插入图片描述
在这里插入图片描述

算法原理

把这道题"小作文"般的题干转化成找出一个最长子数组的长度,子数组中不超过两种类型的水果

涉及到一段连续区间,所以考虑滑动窗口思想

(1) 初始化哈希表hash来统计窗⼝内⽔果的种类和数量;
(2) 初始化变量:左右指针 left = 0,right = 0,记录结果的变量 ret = 0
(3) 当right⼩于数组⼤⼩的时候,⼀直执⾏下列循环:
(a) 将当前⽔果放⼊哈希表中
(b) 判断当前⽔果进来后,哈希表的⼤⼩
如果超过2:
将左侧元素滑出窗⼝,并且在哈希表中将该元素的频次减⼀
如果这个元素的频次减⼀之后变成了0,就把该元素从哈希表中删除
重复上述两个过程,直到哈希表中的⼤⼩不超过2;
(4) 更新结果ret;
right++,让下⼀个元素进⼊窗⼝;
(5) 循环结束后,ret 存的就是最终结果。

代码实现

class Solution 
{
public:
    int totalFruit(vector<int>& fruits) 
    {
        int n = fruits.size();
        unordered_map<int, int> hash; // 统计窗口内出现多少种水果
        int left = 0, right = 0, ret = 0;
        while(right < n)
        {
            hash[fruits[right]]++; // 进窗口

            while(hash.size() > 2) // 判断
            {
                hash[fruits[left]]--; // 出窗口
                if(hash[fruits[left]] == 0) 
                    hash.erase(fruits[left]); // 当该处水果为0个时,要删除
                
                left++;
            }

            ret = max(ret, right - left + 1); // 更新结果
            right++;
        }
        return ret;
    }
};

438.找到字符串中所有字母异位词

在这里插入图片描述
在这里插入图片描述

算法原理

这道题又是另一种的"滑动窗口",因为本题的"窗口"的大小是固定的,而前面题目的"窗口"是变化的

首先先来想清楚如何判断两个长度相同的字符串是否是"异位词"?使用哈希表。把这两个字符串分别扔进两个哈希表,再比较哈希表中每个字符出现的次数,次数相同,则是,否则不是

所以这道题也是用"滑动窗口 + 哈希表"来解决。先把 p 扔进 hash1 中,再对 s 使用滑动窗口的流程,进窗口,判断,出窗口,判断结束后再更新结果:
在这里插入图片描述

代码实现1

使用数组模拟哈希表。 因为本题只有小写字母,所以可以通过一个整形数组进行映射,在判断"异位词"时最差只要判断26次,也可以通过。

class Solution
{
public:
    vector<int> findAnagrams(string s, string p)
    {
        int hash1[26] = { 0 }, hash2[26] = { 0 };
        int n = s.size(), m = p.size();
        int left = 0, right = 0;

        for (auto ch : p) hash1[ch - 'a']++;

        vector<int> v;
        while (right < n)
        {
            hash2[s[right] - 'a']++; // 进窗口

            if (right - left + 1 > m) // 判断
                hash2[s[left++] - 'a']--; // 出窗口

            if (check(hash1, hash2))
                v.push_back(left); // 更新结果

            right++;
        }
        return v;
    }
	
	// 判断两字符串是否是"异位词"
    bool check(int* hash1, int* hash2)
    {
        for (int i = 0; i < 26; i++)
            if (hash1[i] != hash2[i])
                return false;

        return true;
    }
};

代码实现2

使用unordered系列容器。其实就是代码1的另一种形式。

class Solution
{
public:
    vector<int> findAnagrams(string s, string p)
    {
        int n = s.size(), m = p.size();
        int left = 0, right = 0;

        unordered_map<char, int> hash1;
        for (auto ch : p) hash1[ch]++;

        vector<int> v;
        unordered_map<char, int> hash2;
        while (right < n)
        {
            hash2[s[right]]++;

            if (right - left + 1 > m)
                hash2[s[left++]]--;

            if (right - left + 1 == m && check(hash1, hash2))
                v.push_back(left);

            right++;
        }
        return v;
    }

    bool check(unordered_map<char, int>& hash1, unordered_map<char, int>& hash2)
    {
        for (auto& [a, b] : hash2)
            if (b != hash1[a]) // hash1[a],返回的是key对应的value引用
                return false;

        return true;
    }
};

下面是我写的错误代码,以示警告

原因是我用 unordered_set 容器只是对字符进行了映射,但是没有对每个字符出现的次数进行统计

class Solution
{
public:
    vector<int> findAnagrams(string s, string p)
    {
        int n = s.size(), m = p.size();
        int left = 0, right = 0;

        unordered_set<char> hash1;
        for (auto ch : p) hash1.insert(ch);

        unordered_set<char> hash2;
        vector<int> v;
        while (right < n)
        {
            hash2.insert(s[right]);

            if (right - left + 1 > m)
                hash2.erase(s[left++]);

            if (hash2.size() == m && check(hash1, hash2))
                v.push_back(left);

            right++;
        }
        return v;
    }

    bool check(unordered_set<char>& hash1, unordered_set<char>& hash2)
    {
        for (char ch = 'a'; ch <= 'z'; ch++)
            if (hash1.count(ch) != hash2.count(ch))
                return false;

        return true;
    }
};

但是这道题还可以进一步优化更新结果的判断条件:利用变量 count 来统计窗口中"有效字符"的个数目的是判断结果时只需判断一次
1.什么是"有效字符"。
hash2里与hash1中字符的种类和个数都相同的字符
2.什么时候维护 count 变量。
(1) 进窗口后如果进窗口的这个字符在 hash2 里的个数 <= 在 hash1 里的个数,说明是有效字符,count++
(2) 出窗口前如果出窗口的这个字符在 hash2 里的个数 <= 在 hash1 里的个数,说明是有效字符,count - -
(3) 更新结果时:直接 count == m 即可
在这里插入图片描述

代码实现3

class Solution
{
public:
    vector<int> findAnagrams(string s, string p)
    {
        int hash1[26] = { 0 }, hash2[26] = { 0 };
        int n = s.size(), m = p.size();
        int left = 0, right = 0;

        for (auto ch : p) hash1[ch - 'a']++;

        vector<int> v;
        int count = 0; // 用来维护hash2中有效字符的个数
        while (right < n)
        {
            char in = s[right];
            //hash2[in]++;
            // 进窗口+维护count
            if (++hash2[in - 'a'] <= hash1[in - 'a']) count++;

            if (right - left + 1 > m) // 判断
            {
                char out = s[left++];
                // 出窗口+维护count
                // 只有当out字符是有效字符count才减,所以要<=,当为>时说明该字符是多余字符
                if (hash2[out - 'a']-- <= hash1[out - 'a']) count--;
                //hash2[out - 'a']--;
            }

            if (count == m) v.push_back(left); // 更新结果
            right++;
        }
        return v;
    }
};

30.串联所有单词的子串

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

76.最小覆盖子串

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法原理

显然,这道题也是使用"滑动窗口+哈希表"

(1) 先将t 的信息放⼊2 号哈希表中;
(2) 初始化⼀些变量:左右指针: left = 0,right = 0 ;⽬标⼦串的⻓度: len = INT_MAX ;⽬标⼦串的起始位置:begin = -1 😭通过⽬标⼦串的起始位置和⻓度,我们就能找到结果)
(3) 当right ⼩于字符串 s 的⻓度时,⼀直下列循环:
i. 将当前遍历到的元素扔进1 号哈希表中;
ii. 检测当前窗⼝是否满⾜条件:
如果满⾜条件:
判断当前窗⼝是否变⼩。如果变⼩:更新⻓度len ,以及字符串的起始位置 begin
判断完毕后,将左侧元素滑出窗⼝,顺便更新1 号哈希表
重复上⾯两个过程,直到窗⼝不满⾜条件;
iii. right++ ,遍历下⼀个元素;
(4) 判断其实位置 begin 是否等于 -1。 如果是,说明没有匹配,返回空串,如果不是,说明匹配,返回 s 中从 begin 位置往后 len ⻓度的字符串。
在这里插入图片描述

当然,这道题也可以像前面两题一样优化判断条件:使用 count 变量标记"有效字符的种类"。因为这道题中寻找的子字符串中该字符数量必须不少于 t 中该字符数量,所以不能用有效字符的个数来判断
在这里插入图片描述

代码实现

class Solution 
{
public:
    string minWindow(string s, string t)
    {
        int left = 0, right = 0, kinds = 0;
        int n = s.size();

        // 保存t中字符的次数
        int hash1[128] = {0}, hash2[128] = {0};
        for(auto ch : t) 
            if(hash1[ch]++ == 0) kinds++; // 统计有效字符有多少种

        int count = 0, len = INT_MAX, begin = -1; // 子串的起始位置
        while(right < n)
        {
            // 进窗口+维护count
            char in = s[right];
            if(++hash2[in] == hash1[in]) count++;

            // 判断
            while(count == kinds)
            {
                if(right - left + 1 < len) // 更新结果
                {
                    len = right - left + 1;
                    begin = left;
                }
                char out = s[left++];
                if(hash2[out]-- == hash1[out]) count--; // 出窗口
            }

            right++;
        }

        if(begin == -1) return "";
        else return s.substr(begin, len);
    }
};

下面是我用unordered系列容器写的错误代码,以示警告

class Solution
{
public:
    string minWindow(string s, string t)
    {
        int left = 0, right = 0;
        int n = s.size();

        // 保存t中字符的次数
        unordered_map<char, int> hash1;
        for (auto ch : t) hash1[ch]++;

        unordered_map<char, int> hash2;
        string ret, tmp;
        int len = INT_MAX;
        while (right < n)
        {
            // 进窗口
            char in = s[right];
            hash2[in]++;

            // 判断+出窗口
            while (check(hash1, hash2))
            {
                int sz = right - left + 1;
                tmp = s.substr(left, sz);
                if (sz <= len) ret = tmp, len = sz; // 更新结果
                char out = s[left];
                hash2[out]--;
                left++;
            }

            right++;
        }
        return ret;
    }

    bool check(unordered_map<char, int>& hash1, unordered_map<char, int>& hash2)
    {
        for (auto e : hash1)
            if (hash2[e.first] < e.second)
                return false;

        return true;
    }
};

其实上面的错误代码并没有错,最大的问题就是 check 函数,当测试用例的字符串非常大时, check 函数非常拖后腿,结果如下:
在这里插入图片描述

三,算法总结

通过上面的若干道题目可以看出:首先滑动窗口使用的场景一般是一段连续的区间,里面的"窗口"大小可能是动态变化的,也可能是固定的。并且使用滑动窗口大致还是有固定的主体逻辑的:进窗口,判断,出窗口。判断,出窗口这两个过程一般情况下是循环操作的,更新结果要根据题意在上述过程的某一步中更新

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

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

相关文章

【Python知识宝库】错误与异常处理:编写健壮的Python代码

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、错误与异常的定义1. 语法错误2. 异常 二、异常处理1. try块2. except块3. finally块 三、异常处理的最佳实践…

数字IC前端:负的建立时间和保持时间

相关阅读数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 建立时间和保持时间是触发器的两个重要的时序参数&#xff1a;建立时间(setup time)指的是在有效时钟沿前&#xff0c;数据必须到达并稳定的时间&#xff1b;保持时间…

力扣416-分割等和子集(Java详细题解)

题目链接&#xff1a;416. 分割等和子集 - 力扣&#xff08;LeetCode&#xff09; 前情提要&#xff1a; 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 最近刚学完01背包&#xff0c;所以现在的题解都是以01背包问题为基础再来写的。 如果大家不懂01背包的话…

zabbix6.4连接邮箱发出警告

添加告警媒介 默认接收人: 故障级别:{TRIGGER.STATUS}。 服务器:【{HOSTNAME1} 】 发生:{TRIGGER.NAME} 故障! 注:默认接收人:相当于邮件的主题 默认信息:邮件的主题 告警主机:{HOSTNAME1} 告警时间:{EVENT.DATE} {EVENT.TIME} 告警等级:{TRIGGER.SEVERITY} 告警信息:{TRIGGER.…

HTML5中canvas绘图基础详解

第7章 HTML5绘图基础 H5中新增了重要元素canvas,通过绘制任意图形&#xff0c;借助自带API&#xff0c;通过编写js可以控制各种图形&#xff0c;制作动画效果&#xff0c;对web具有划时代意义。 7.1 画布的基础知识 绘图三步骤&#xff1a; 步骤一&#xff1a;使用canvas创…

FreeRTOS学习笔记(五)任务进阶篇

文章目录 前言一、列表和列表项1.1 xList 和 xLIST_ITEM1.2 相关API函数1.3 任务就绪列表 二、任务调度器的启动过程2.1 PendSV 和 SysTick 寄存器2.2 prvStartFirstTask( )2.3 xPortStartScheduler( )2.4 vTaskStartScheduler( ) 的整体流程 三、任务切换3.1基于 SysTick 中断…

一名优秀的工程师应该学会在工作中提升自己,面试篇

xxx 进行 xxx 操作&#xff0c;为什么不行&#xff1f;有人知道吗&#xff1f; 此时&#xff0c;[黑人脸问好号.jpg]。 这里大家可以阅读下《提问的艺术》这本书&#xff0c;这是一本教你如何通过富有技巧性的提问来提高沟通效率并提升自身影响力的书。 Github 上一些开源项目…

zabbix6.4连接钉钉发出警告

zabbix6.4配置钉钉告警 注册钉钉 建一个内部群 添加自定义机器人 配置zabbix服务端 打开脚本告警的配置 # vim /etc/zabbix/zabbix_server.conf AlertScriptsPath/usr/lib/zabbix/alertscripts 准备脚本 安装一个依赖包 # dnf -y install python3-requests # vim /usr/li…

希尔排序/选择排序

前言&#xff1a; 本篇主要对常见的排序算法进行简要分析&#xff0c;代码中均以数组 arr[] { 5, 3, 9, 6, 2, 4, 7, 1, 8 } 为例&#xff0c;进行升序排列。 常见的排序算法有如下&#xff1a; 选择排序中&#xff0c;直接选择排序没有任何实际与教育意义&#xff0c;而堆排…

PopupInner源码分析 -- ant-design-vue系列

PopupInner源码分析 – ant-design-vue系列 1 综述 上一篇讲解了vc-align的工作原理&#xff0c;也就是对齐是如何完成的。这一篇主要讲述包裹 Align的组件&#xff1a;PopupInner组件是如何工作的。 PopupInner主要是对动画状态的管理&#xff0c;比如打开弹窗的时候&#…

【Hot100】LeetCode—763. 划分字母区间

目录 1- 思路哈希表 双指针 2- 实现⭐763. 划分字母区间——题解思路 3- ACM 实现 原题链接&#xff1a;763. 划分字母区间 1- 思路 哈希表 双指针 ① 找到元素最远的出现位置&#xff1a;哈希表② 根据最远出现位置&#xff0c;判断区间的分界线&#xff1a;双指针 实现 …

Java类和对象(详解)

前言&#xff1a; Java中类和对象是比较重要的一章&#xff0c;这一章可以让我们深刻认识到Java语言的"精妙之处"&#xff0c;它不像C语言那么"细"&#xff0c;也不想其他语言封装的那么"保守"。 游刃有余的解决一系列面向对象问题。 面向对象的…

数据集 视线估计-unityeyes-合成数据 >> DataBall

视线估计-合成数据-三维建模-人工智能unityeyes 人眼视线估计仿真合成数据集 inproceedings{wood2016_etra, title {Learning an Appearance-Based Gaze Estimator from One Million Synthesised Images}, author {Wood, Erroll and Baltru{\v{s}}aitis, Tadas and Morency,…

如何使div居中?CSS居中终极指南

前言 长期以来&#xff0c;如何在父元素中居中对齐一个元素&#xff0c;一直是一个让人头疼的问题&#xff0c;随着 CSS 的发展&#xff0c;越来越多的工具可以用来解决这个难题&#xff0c;五花八门的招式一大堆&#xff0c;这篇博客&#xff0c;旨在帮助你理解不同的居中方法…

【电子通识】半导体工艺——保护晶圆表面的氧化工艺

在文章【电子通识】半导体工艺——晶圆制造中我们讲到晶圆的一些基础术语和晶圆制造主要步骤&#xff1a;制造锭(Ingot)、锭切割(Wafer Slicing)、晶圆表面抛光(Lapping&Polishing)。 那么其实当晶圆暴露在大气中或化学物质中的氧气时就会形成氧化膜。这与铁(Fe)暴露在大气…

MySQL record 02 part

查看已建数据库的基本信息&#xff1a; show CREATE DATABASE mydb; 注意&#xff0c;是DATABASE 不是 DATABASEs&#xff0c; 命令成功执行后&#xff0c;回显的信息有&#xff1a; CREATE DATABASE mydb /*!40100 DEFAULT CHARACTER SET utf8mb3 / /!80016 DEFAULT ENCRYPTIO…

基于Python+大数据爬虫+数据可视化大屏的耳机信息的爬取与分析平台设计和实现(2025最新优质项目-系统+源码+部署文档)

博主介绍&#xff1a;✌全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HLM…

新手入门Python:Python类中自带的装饰器详解与应用

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 什么是装饰器?📝 常用装饰器详解📝 高级装饰器📝 综合应用示例⚓️ 相关链接 ⚓️📖 介绍 📖 在Python编程中,有一类特别的工具,它们可以改变或增强函数和方法的行为。这些工具被称为装饰器。对…

使用宝塔面板安装mrdoc

使用宝塔面板安装mrdoc 1、所需环境2、ubuntu系统安装3、宝塔面板安装4、NginxPHPMySQL安装5、python项目管理器安装6、 python版本安装7、mrdoc的部署7.1、下载项目源码7.2、新建python管理器项目 8、使用MySQL作为默认数据库8.1、安装mysqlclient插件8.2、配置数据库连接信息…

qt多线程的两种方法run和movetothread

qt多线程的有什么用&#xff1f; 将耗时长的操作丢入专属线程执行&#xff0c;这样就不会影响主线程的界面操作&#xff0c;操作完再用信号槽等的方式返回结果 1.界面和部件相关都必须在主界面运行&#xff0c;不要用子线程调用或者操作&#xff0c;会引起奇怪的bug&#xff…