《数据结构与算法之美》学习笔记一

前言:今天开始学习极客时间的课程《数据结构与算法之美》。为撒要学习这个?因为做力扣题太费劲了,自己的基础太差了!所以要学习学习。开一个系列记录一下学习笔记。认真学吧,学有所获才不负韶华!之前就学过视频课《JavaScript版数据结构与算法》,稍微有了一些基础,但是还是觉得不够深入,所以又继续进行这门新课程的学习。就是不知道前端开发学习这个会不会有什么限制,且学且看吧。

一、学习的痛点

在我自己日常学习的过程中,自己总结的几个学习路上的痛点:
第一个就是学习一个东西,学着学着就容易产生一种无意义感,因为工作中基本上也用不太到。如果是工作中用到的东西,只学表层用法甚至百度都够用了,这就让自己觉得有时候深入地去看源码、学习原理,有点浪费时间,也感觉可能排不上用场。但是在我学习完 Vue的Diff算法的原理 之后,我发现真的挺神奇的,当时正好学完 diff 算法,就正好有一个 对比节点 的需求,和 Vue 中的 diff 算法的场景非常的相似,用起来也非常的契合以及快速。其实如果我没有学习 diff 算法,对于这个需求,用其他的笨方法,大概也能做出来。所以说就像这门课程的作者王争老师提到的,不管是数据结构与算法,还是其他的原理性的知识、提高性的知识,你不学,就永远觉得没用,但是你学了,总有一天你会应用上,并且比普通的笨方法更优雅。所以说,学习的过程某些意义上,就是开拓自己眼界的过程,它锻炼的不是某一个能力,而是整体思维的提升。就像健身,练腿练的不只是腿,还会锻炼到全身的力量。
然后不容易坚持的另一个原因,就是太难太枯燥🥹🥹🥹。首先要对自己拥有信心,我一定能学会!然后就是要多写,多总结,多思考,多写博客。看着自己的博客越来越多,粉丝和点赞越来越多,是一件很有成就感的事情,所以说可以让自己更加能够坚持下去。其次我觉得方法很重要,之前我刷力扣,漫无目的,基本上就随便刷,就感觉刷了也记不住,好像没啥用。后来学了 《JavaScript 版数据结构与算法》,知道了刷题最好是一个类型一个类型的刷,相似题放在一起刷,就好很多。后来跟着 《代码随想录》一起刷。但是还是觉得基础知识不足,所以继续学习。所以多向优秀的人学习,自学真的更加枯燥更加困难。
还有我觉得比较有帮助的一点就是,多看别人写的文章,这也是和别人沟通交流的一种方式。在学习的时候,多看看评论区,也是很好的交流方式。我之前学习 Monaco Editor ,就会搜一些相关的文章来看。其实大部分都写的比较浅显,没有什么原理性的东西,但是偶尔确实会发现宝藏,特别值得探索。
这个系列主要记录《数据结构与算法之美》这门课程的学习过程。但是在学习这门课程的时候,也会继续刷力扣,所以会穿插着一些自己感觉比较有用的知识点,主要来自 代码随想录

二、KMP算法

跟着代码随想录做到了 28. 找出字符串中第一个匹配项的下标 这道题,本来以为挺简单的,结果写完之后修修补补半天没做出来,看了解析发现这么复杂,涉及到我以前从来没听过的 KMP 算法。所以就来先把这个算法学一学吧。

1、名称来源

其实就是发明这个算法的三人的首字母:Knuth,Morris和Pratt

2、KMP算法解决了什么问题?

解决的就是字符串匹配的问题。
具体例子:
aabaabaafa 中找是否存在子串 aabaaf
我们把 aabaabaafa 这个总的字符串叫做文本串,要找的目标子串叫做模式串
KMP就是用来解决此类字符串匹配的问题的。

3、必知名词
  • 前缀。一个字符串的前缀是包含头部字符,不包含尾部字符的所有子串
    aabaaf 为例,它所有的前缀是
    a
    aa
    aab
    aaba
    aabaa
  • 后缀。后缀和前缀相反,就是不包含头部字符,包含尾部字符的所有子串
    aabaaf 为例,它所有的后缀是(从第二个字符 a 开始)
    f
    af
    aaf
    baaf
    abaaf
  • 最长相等前后缀
    一个字符串的最长相等前后缀,指的是先对所有的子串求得相等的前缀和后缀,然后取其中最长的结果
    aabaaf 为例,咱们看一下它的子串的所有的相等前后缀
子串相等前后缀相等前后缀的长度
a没有前缀和后缀0
aaa1
aab0
aabaa1
aabaaaa2
aabaaf0

最长相等前后缀就是子串的相等前后缀中最长的,也就是 aa

  • 前缀表
    前缀表就是上面所有的子串的相等前后缀的长度组成的数组,就是[0, 1, 0, 1, 2, 0]
  • 前缀表的用法
    aabaabaafa 中找是否存在子串 aabaaf,肯定是需要从模式串的开头往后依次进行匹配。可以发现前面五个字符都是一样的,也就是说到 f 的时候匹配失败了。那么此时,不需要从头再开始匹配,因为前缀表中存储的有相等前后缀的信息。匹配失败的是 f,那么就找 f 的前面的字符串的最长相等前后缀,就是 aa。那么我们只需要从前缀 aa 的后面一个字符,也就是 b 开始,继续匹配即可,下标就是 最长相等前后缀 aa 的长度 2。
  • next 数组
    有些 KMP 算法中都会使用到 next 数组。next 数组是基于 前缀表 的,原理是一样的,只是具体实现不一样,有的会将前缀表依次减1,作为 next 数组,有的会把前缀表整体右移作为 next 数组,例如上面的前缀表,右移之后就变成了 [-1, 0, 1, 0, 1, 2, 0]但是原理都是一样的,只是实现上不一样。直接将前缀表作为 next 数组也没毛病。
4、构造 next 数组

上面我们已经讲过,aabaaf 的前缀表是 [0, 1, 0, 1, 2, 0]。关于 next 数组的构造,这里就使用前缀表作为 next 数组。这一小节来实现以下构造 next 数组的方法 getNext()
① 初始化
getNext() 接收的参数分别是 next 数组和目标字符串。
next 数组记录的是相等先后缀的长度,那么我们首先需要两个指针,一个 i 指向后缀的末尾位置;一个 j 指向前缀的末尾位置
由于 i 指向后缀的末尾位置,其实就是 for 循环中的循环变量的作用,所以 i 不用在额外的考虑它的初始化。而 当 i = 0 的时候,j 肯定也是0,所以j初始化为0。

function getNext(str) {let next = [];let j = 0;next[0] = j;for (let i = 0; i < str.length; i++) {}
}

② 处理前后缀相同的情况
假设此时的已经遍历到了 aaba ,此时的 i 指向的是 尾部的 aj 指向的是 头部的 a ,此时头尾相同,那么 j 就需要往前移动,去找 aa 能不能作为相等前后缀,此时的 j 是1,push到 next 数组中。

function getNext(str) {let next = [];let j = 0;next[0] = j;for (let i = 0; i < str.length; i++) {// 如果 i 和 j 指向的数值相等if(str[i] == str[j]){j++;}}
}

③ 处理前后缀不相同的情况
接着上面的 aabaa,此时 j = 2,下一次,字符串就会变成 aabaafi 就会 指向 fj 指向 b,此时已经不能相等了,那么就要回退 j ,此时要回退到 next[j-1] 的位置。这就是应用前缀表。但是!回退不能只进行一步,因为回退完,可能还不相等,所以要使用 while 进行回退

function getNext(str) {let next = [];let j = 0;next[0] = j;for (let i = 0; i < str.length; i++) {while (j > 0 && str[i] != str[j]) {j = next[j - 1]}// 如果 i 和 j 指向的数值相等if (str[i] == str[j]) {j++;}next[i] = j}
}

为什么要先处理不相等的情况?因为回退完可能找到了!
相关力扣试题:28. 找出字符串中第一个匹配项的下标

三、复杂度分析

每次做算法题,分析时间复杂度和空间复杂度都有那么七八分是靠猜测。那么今天就细致的研究总结一下,究竟怎么清晰地分析复杂度。
数据结构与算法,解决的就是代码运算过程中的 “快” 和 “省” 的问题,我们不断地优化算法,就是为了让代码运行的时间更短,更加节省空间。那么就需要一个标准去衡量我们的算法是不是真的更快、更省。所以复杂度这个概念应运而生。

(一)代码执行时间

我们需要通过分析代码知道代码的执行时间。当然我们不可能知道真实的执行时间,因为没有运行环境、网络环境等。但是我们可以知道相对的执行时间。对于 cpu 来说,执行一行代码所需的时间基本是相等的。那么我们可以假设这个事件为 unitTime。那么看一下下面这段代码

int cal(int n) {int sum = 0;  // 1int i = 1;  // 1for (; i <= n; ++i) {sum = sum + i;}  // 2nreturn sum;
}

这个方法,前两行都是 1 * unitTimefor 循环要执行 n 次,并且还有一个 ++i 的隐藏行,所以需要的时间是 2n * unitTime ,那么总共需要的时间为 (2 + 2n) * unitTime
根据这个逻辑,再看一下复杂一点的代码

int cal(int n) {int sum = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1;for (; j <= n; ++j) {sum = sum +  i * j;}}
}

前三行都是 1 * unitTime,外层 for 循环 是 2n * unitTime,内层 for 循环 是 n * 2n * unitTime
总共就是 (3 + 2n + 2n^2) * unitTime

虽然说我们没有办法获取真实的代码执行时间,但是可以知道一个规律:代码执行时间和每行代码的执行次数总和成正比。
我们用 T(n) 表示代码执行时间,用 f(n)表示每行代码的执行次数,引入 O 表示法,可以得到
T(n) = O(f(n))
n 表示数据规模,O 就表示代码执行时间和代码执行的总次数之间的正比关系,
那么第一个例子中的代码执行时间就可以表示为 T(n) = O(2 + 2n) ;第二个例子中的代码执行时间就可以表示为 T(n) = O(3 + 2n + 2n^2)
O 表示代码执行时间随着数据规模增长的变化趋势,所以,也叫作渐进时间复杂度,简称 时间复杂度
当 n 很大的时候,公式中的低阶、常量、系数三部分对代码执行时间的增长趋势影响不大,所以可以忽略。
那么,第一个例子的时间复杂度可以写作 T(n) = O(n) ,第二个时间复杂度可以写做 T(n) = O(n^2)

(二)时间复杂度分析

关于分析时间复杂度,这里有分析法则

  • 只关注循环次数最多的一段代码
    时间复杂度表示的只是一种变化趋势,在分析的时候,会忽略低阶、常量、系数这三部分内容,所以我们只需要关注循环次数最多的一段代码就可以了,拿上面的例子
 int cal(int n) {int sum = 0;int i = 1;for (; i <= n; ++i) {sum = sum + i;}return sum;}

常量级别的执行过程可以忽略,只需要看 for 循环的执行次数为 O(2n),然后再忽略系数就是 O(n)

  • 加法法则:总的时间复杂度等于量级最大的那段代码的复杂度
    还是找一段示例代码
int cal(int n) {int sum_1 = 0;int p = 1;for (; p < 100; ++p) {sum_1 = sum_1 + p;}int sum_2 = 0;int q = 1;for (; q < n; ++q) {sum_2 = sum_2 + q;}int sum_3 = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1; for (; j <= n; ++j) {sum_3 = sum_3 +  i * j;}}return sum_1 + sum_2 + sum_3;}

一行一行的常量级代码就可以直接忽略不看
第一个 for 循环,执行的是100 次,但是它也是一个有大小的常量,所以也忽略
第二个 for 循环,时间复杂度是 O(n)
第三个 for 循环,时间复杂度是 O(n^2)
低阶的时间复杂度也可以忽略,所以这段代码的时间复杂度是 O(n^2)
所以可以得出结论,两个时间复杂度相加的结果就等于那个更大的时间复杂度。作者大大的公式:
如果 T1(n)=O(f(n))T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).

  • 乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
    其实这个法则在上边的代码分析的过程中已经用到了
int cal(int n) {int sum = 0;int i = 1;int j = 1;for (; i <= n; ++i) {j = 1;for (; j <= n; ++j) {sum = sum +  i * j;}}
}

for 循环嵌套得到的时间复杂度就是外层的执行次数 O(n) 乘以内层的 O(2n) ,结果就是 O(n^2)
也就是说,两个时间复杂度相乘的结果,其实就是将O里面的公式相乘
类比加法法则的公式,可以得出乘法法测的公式如下:
如果 T1(n)=O(f(n))T2(n)=O(g(n));那么 T(n)=T1(n)*T2(n)=O(f(n))*O(g(n))=O(f(n)*g(n)).

(三)几种常见的时间复杂度分析

总结常见的时间复杂度,按照量级从小到大排列
在这里插入图片描述
上面的时间复杂度量级可以分为两类:多项式量级和非多项式量级。飞多项式量级是两个画波浪线的指数阶和阶乘阶,它们的时间复杂度非常高,这类算法问题叫做NP(Non-Deterministic Polynomial,非确定多项式)问题,通常可用性都比较差,因为执行时间随着数据规模的增长,会快速的变得很长,因此尽量不要使用这种算法。
其他的多项式量级的算法是我们可以经常使用的。
🧞‍♀️ O(1)
常量级时间复杂度,只要代码执行时间不随数据规模的增大而增大,时间复杂度都是 O(1)。例如下面的代码:

int i = 8;
int j = 6;
int sum = i + j;

一般来说,只要没有循环、递归,即便代码再长,时间复杂度也是 O(1)
🧞‍♀️ O(logn)、O(nlogn)
对数阶时间复杂度是比较常见的一种时间复杂度,也是比较难分析的一种。
以下面的代码为例:

i=1;
while (i <= n)  {i = i * 2;
}

这段代码的时间复杂度怎么算呢?其实就是循环里面的代码执行的次数。循环结束的条件是 2^x = n,那么 x = log2(n) 以2为底的对数。
即时间复杂度就是 O(log2(n))
那么再分析下面一段代码

i=1;
while (i <= n)  {i = i * 3;
}

它的时间复杂度其实就是 O(log3(n)) 以3为底的对数
不管底数是多少,只要是对数级别,统统将其记为 O(logn),也就是以 10 为底的对数
因为对数之间是可以这样转化的:
log3(n) 就等于 log3(2) * log2(n),所以 O(log3(n)) = O(C * log2(n)),其中 C 是一个常量,在计算时间复杂度的时候可以忽略。所以,O(log3(n)) = O(log2(n))。因此对数级时间复杂度我们可以直接忽略底数,记作 O(logn)
对于 O(nlogn) 而言,根据之前讲到的乘法法则,代码循环 n 次,时间复杂度就乘以 n,那么对数级的运算执行 n 次的情况下,时间复杂度就是 O(nlogn)
🧞‍♀️ O(m+n)O(m*n)
这两种时间复杂度由两个数据规模决定,由于无法确定两个数据规模的大小,所以都不能直接忽略
第一种代码示例

int cal(int m, int n) {int sum_1 = 0;int i = 1;for (; i < m; ++i) {sum_1 = sum_1 + i;}int sum_2 = 0;int j = 1;for (; j < n; ++j) {sum_2 = sum_2 + j;}return sum_1 + sum_2;
}

两次循环分别是 m 次和 n 次,时间复杂度为 O(m+n)
第二种

int cal(int m, int n) {int sum_1 = 0;int i = 1;int sum_2 = 0;int j = 1;for (; i < m; ++i) {sum_1 = sum_1 + i;for (; j < n; ++j) {sum_2 = sum_2 + j;}}return sum_1 + sum_2;
}

嵌套循环,时间复杂度为 O(m*n)

(四)空间复杂度

时间复杂度的全称是渐进时间复杂度,表示代码执行时间和数据规模之间的增长关系。与之类似的,空间复杂度全称为渐进空间复杂度,表示的是算法的存储空间与数据规模之间的增长关系。来看一下一段简单的示例代码

void print(int n) {int i = 0;  // 常量级int[] a = new int[n];for (i; i <n; ++i) {a[i] = i * i;}for (i = n-1; i >= 0; --i) {print out a[i]}
}

和时间复杂度类似,空间复杂度中的常量级、低阶、系数可以忽略,上述示例的空间复杂度由第三行代码决定,创建了一个长度为 n 的数组,即整段代码的空间复杂度为 O(n)
和时间复杂度相比,空间复杂度的分析很简单。常见的空间复杂度就是 O(1)O(n)O(n^2 ),其他的并不多见,不需要花费太多时间学习。

(五)四种时间复杂度分析

通过上面的学习我们掌握的基本的时间复杂度的算法,这一小节我们再来稍微拓展一丢丢,学习四种时间复杂度的分析:
最好情况时间复杂度(best case time complexity)
最坏情况时间复杂度(worst case time complexity)
平均情况时间复杂度(average case time complexity)
均摊时间复杂度
先看一下示例代码

// n表示数组array的长度
// n表示数组array的长度
int find(int[] array, int n, int x) {int i = 0;int pos = -1;for (; i < n; ++i) {if (array[i] == x) {pos = i;break;}}return pos;
}

这段代码是在数组中寻找 x,如果能找到 x 就停止循环,返回 x 的位置。

  • 最好情况时间复杂度、最坏情况时间复杂度

那么这段代码的时间复杂度怎么求呢?如果在第一个索引为 0 的位置就找到了 x,那么循环中的代码只需要执行一次;如果在最后的位置才找到 x 或者没有找到 x,循环中的代码就需要执行 n 次。对于这种情况,我们就需要使用最好情况时间复杂度和最坏情况时间复杂度。最好情况时间复杂度是在最理想情况下的时间复杂度,在这段代码中就是 O(1)最坏情况时间复杂度就是最糟糕的情况下的时间复杂度,在这段代码中就是 O(n)

  • 平均情况时间复杂度

上面的最好的情况和最糟糕的情况其实都不多见,我们还需要分析一下平均情况时间复杂度
首先,x 可能在数组中,也可能不在数组中,这两种情况,虽然概率不一样,但是可以简化一下,认为各占二分之一。另外,对于在数组中的情况,可能在 0 ~ n-1 位置的概率,都是 1/n,那么占总体的概率的 1/(2n)。时间复杂度就应该是出现在某位置的概率 * 出现在某位置处需要执行的次数,就是
在这里插入图片描述
这个值在概率论中叫做加权平均值,也叫做期望值;平均情况时间复杂度也叫做加权时间复杂度,或者期望时间复杂度
忽略系数和常量,平均情况时间复杂度就是 O(n)

  • 均摊时间复杂度
    下面再学习一个更加高级的概念–均摊时间复杂度
    均摊时间复杂度和平均情况时间复杂度比较容易弄混,它的应用场景比较有限,简单的了解一下。
    看一下下面的示例代码
 // array表示一个长度为n的数组// 代码中的array.length就等于n
int[] array = new int[n];
int count = 0;void insert(int val) {if (count == array.length) {int sum = 0;for (int i = 0; i < array.length; ++i) {sum = sum + array[i];}array[0] = sum;count = 1;}array[count] = val;++count;
}

上述代码实现了往数组中插入元素的功能,数组的长度为 n,调用 insert() 往数组中插入元素,count 表示实际插入了多少个元素,如果当前数组没有满, 就直接插入到 count 的位置;如果数组长度大于等于 n ,就将数组元素求和,放在第一个位置,再插入元素。
那么这段代码的时间复杂度是多少呢?我们分别分析一下,上面讲到的三种时间复杂度。
首先如果数组没有满,只需要把元素插入到 count 的位置,最好情况时间复杂度为 O(1);如果数组已经满了,那么就需要遍历数组求和,循环中的代码执行的次数为 n,最坏情况时间复杂度为 O(n);
平均时间复杂度的分析稍微复杂一些。
首先大的情况分为两种,一种是数组没满,count 可能是 0 ~ n-1 任意一个数字,共有 n 种情况;另外一种是数组满了,count 就等于 n + 1;当数组没满时时间复杂度都是 O(1),数组已经满时时间复杂度是 O(n),因此平均情况时间复杂度的计算如下
在这里插入图片描述

对于这个例子来说,时间复杂度的计算有一些特殊的地方,首先就是绝大部分时候的时间复杂度都是 O(1),只有个别情况时间复杂度是 O(n);另外时间复杂度的出现是一个循环过程,先是 n 次的 O(1),然后是一次 O(n),然后又是 n-1 次的O(1)……如此这般循环往复
针对这种特殊的场景,我们引入了一种特殊的分析方法:摊还分析法;通过摊还分析得到的时间复杂度我们称之为 均摊时间复杂度
每一次 O(n)的操作后面都跟随着 n-1 次的 O(1) 操作。所以把耗时多的操作,均摊到耗时少的 O(1) 操作上,均摊下来,这一组连续操作的均摊时间复杂度就是 O(1)
均摊时间复杂度的应用场景比较少,只需要稍微理解一下这个概念就行。简单总结一下它的应用场景:
对一组数据结构进行连续操作时,大部分情况下时间复杂度都很低,只有个别情况时间复杂度比较高,并且在高时间复杂度后面,跟着连续的低时间复杂度的操作,此时就可以将一组操作放到一块儿进行分析,将高时间复杂度平均到低时间复杂度上,能够使用均摊分析的场景中,一般均摊时间复杂度就等于最好情况时间复杂度。

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

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

相关文章

【算法】滑动窗口——找到字符串中所有字母异位词

本节博客是对题目——找到字符串中所有字母异位词的从读题到代码实现以及优化的详细解读&#xff0c;有需要借鉴即可。 目录 1.题目2.滑动窗口 哈希数组3.异位词优化4.总结 1.题目 题目链接&#xff1a;LINK 首先来解释一下什么是异位词&#xff0c;所谓“异位词”&#xf…

show profile

功能 当你执行一个复杂的 SQL 查询时&#xff0c;这个命令可以帮助你了解查询的各个部分花费了多少时间&#xff0c;从而找到可能的性能瓶颈。默认情况下&#xff0c;参数处于关闭状态&#xff0c;并保存最近15次的运行结果 开启 查看是否支持 SHOW VARIABLES LIKE profili…

【XR806开发板试用】试用SWD+Jlink调试

XR806开发板&#xff0c;只能使用编写代码&#xff0c;然后通过UART下载&#xff0c;没法在线debug&#xff0c; 效率会差很多&#xff0c;官方没有提供这一方面的资料。 先查CPU&#xff0c; 官方介绍是arm-china的MC1&#xff0c;通过armv8 Architecture refenence manual资料…

跨境电商行业蓬勃发展,武汉星起航引领卖家孵化新潮流

近年来&#xff0c;我国跨境电商行业在政府的大力扶持下呈现出强劲的发展势头。随着国内制造业结构的加速调整与居民消费需求升级态势的持续凸显&#xff0c;跨境出口规模占比稳步提升&#xff0c;跨境进口规模同样不断扩大&#xff0c;行业市场规模持续增长。在这一背景下&…

QT学习(4)——自定义控件

目录 引出自定义一个控件自定义控件定义方法函数widget窗口调用函数 总结 引出 QT学习&#xff08;4&#xff09;——自定义控件 自定义一个控件 自定义控件定义方法函数 #include "smallwid.h" #include "ui_smallwid.h"SmallWid::SmallWid(QWidget *par…

redis抖动问题导致延迟或者断开的处理方案

目录&#xff1a; 1、使用背景2、redis重试机制3、redis重连机制4、其他一些解决redis抖动问题方案 1、使用背景 客户反馈文件偶现打不开&#xff0c;报错现象是session not exist&#xff0c;最终定位是redis抖动导致的延迟/断开的现象&#xff0c;最终研发团方案是加入redis…

Mapreduce | 案例

根据提供的数据文件【test.log】 数据文件格式&#xff1a;姓名,语文成绩,数学成绩,英语成绩 完成如下2个案例&#xff1a; &#xff08;1&#xff09;求每个学科的平均成绩 &#xff08;2&#xff09;将三门课程中任意一门不及格的学生过滤出来 &#xff08;1&#xff09;求每…

Navicat安装配置(注册码)连接MySQL

下载资源 博主给你打包好了安装包&#xff0c;在网盘里&#xff0c;防止你下载到钓鱼软件 快说谢谢博主&#xff08;然后心甘情愿的点个赞~&#x1f60a;&#xff09; navicatformysql.zip_免费高速下载|百度网盘-分享无限制 (baidu.com) 安装流程 ①下载好压缩包后并解压 ② …

【JavaEE精炼宝库】多线程1(认识线程 | 创建线程 | Thread 类)

目录 一、认识线程 1.1 线程的概念&#xff1a; 1.2 为什么需要线程&#xff1a; 1.3 面试题.谈谈进程和线程的区别&#xff1a; 1.4 Java的线程和操作系统线程的关系&#xff1a; 二、创建线程 2.1 创建线程的5种写法&#xff1a; 2.1.1 写法1.继承 Thread 类&#xf…

【redis】Redis五种常用数据类型和内部编码,以及对String字符串类型的总结

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

C++ | Leetcode C++题解之第80题删除有序数组中的重复项II

题目&#xff1a; 题解&#xff1a; class Solution { public:int removeDuplicates(vector<int>& nums) {int n nums.size();if (n < 2) {return n;}int slow 2, fast 2;while (fast < n) {if (nums[slow - 2] ! nums[fast]) {nums[slow] nums[fast];slo…

【doghead】mac与wsl2联通

mbp 设置为发送端,那么要能与windows上 wsl2的ubutnu通信。 mbp的 uv 构建ok zhangbin@zhangbin-mbp-2  ~/tet/Fargo/zhb-bifrost/Bifrost-202403/worker/third_party/libuv   main clion使用lldb cmake构建 更新git2.45.0啊

盘点自动驾驶的技术发展趋势

自动驾驶技术在不断发展变快&#xff0c;我们之前提过算法岗如今越来越卷&#xff0c;从今年的就业局势看&#xff0c;前年还属于蓝海行业的自动驾驶&#xff0c;今年就已经满满关上了招揽之门——呈红海之势。作为在这个行业中摸爬滚打的一以子&#xff0c;我们到底该如何纵观…

会员卡积分收银源码系统 支持多门店系统 带完整的安装代码包以及安装搭建教程

在数字化浪潮的推动下&#xff0c;传统零售行业面临着巨大的转型压力。为了满足现代消费者多样化的需求&#xff0c;提高门店管理效率和顾客满意度&#xff0c;小编给大家分享一款会员卡积分收银源码系统——支持多门店系统&#xff0c;并附带了完整的安装代码包以及安装搭建教…

君正T31移植电源IC—CW2015芯片简介

CW2015芯片简介 CW2015 是一款超紧凑、低成本、主机侧/电池组侧、无传感电阻器的电量计量系统 IC&#xff0c;适用于手持和便携式设备中的锂离子 (Li) 电池。CW2015 包括一个14 位Sigma-Delta ADC、一个精密电压基准和内置准确温度传感器。该 IC 允许最终用户消除占用大量电路板…

【Vue3进阶】- Pinia

什么是Pinia Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。它提供了类似于 Vuex 的功能&#xff0c;但比 Vuex 更加简单和直观。 需要在多个组件之间共享状态或数据时使用 Pinia 的 store&#xff0c;这样可以避免 props 和 eventBus 等传统方法…

【stm32笔记】DSP库调用

参考&#xff1a;DSP库调用 , __CC_ARM,__TARGET_FPU_VFP, __FPU_PRESENT1U, ARM_MATH_CM4 ,USE_HAL_DRIVER,STM32F407xx,ARM_MATH_CM4,__FPU_USED1U,__FPU_PRESENT1U,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING把需要的库复制出来单独用&#xff0c;方便移植

KEIL declaration may not appear after executable statement in block

KEIL declaration may not appear after executable statement in block 这个问题也是比较经典&#xff0c;就是不允许你的变量定义位置不允许在下边的代码区域&#xff0c;只允许在最上方 ‍ 修改编码模式为C99解决 ‍ ​​

(Java)心得:LeetCode——15.三数之和

一、原题 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。…

AI大模型探索之路-训练篇15:大语言模型预训练之全量参数微调

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…