后缀数组的应用:最长公共子串

news2025/1/31 2:59:22

题目描述

假设 str1 长度为 N N Nstr2 长度为 M M M,求 str1str2 的最长公共子串。

思路分析

示例:str1 = “12abcd456”, str2 = “7abcd89”,则str1和str2的最长公共子串为 abcd。

注意,子串是连续的。

动态规划解法思路

构建一张dp表,行对应 str1 的位置,列对应 str2 的位置, d p [ i ] [ j ] dp[i][j] dp[i][j] 表示如果必须以 str1 的 i i i 位置结尾,str2的 j j j 位置结尾,最长公共子串是多长。

在这里插入图片描述
所以状态转移方程:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 if  s t r [ i ] = s t r [ j ] 0 if  s t r [ i ] ≠ s t r [ j ] dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 &\text{if $str[i] = str[j]$}\\ 0 &\text{if $str[i] \ne str[j]$}\\ \end{cases} dp[i][j]={dp[i1][j1]+10if str[i]=str[j]if str[i]=str[j]

整张表中最大值就是最终的最长公共子串的长度,dp表的规模是 O ( N ∗ M ) O(N*M) O(NM),所以整体时间复杂度 O ( N ∗ M ) O(N*M) O(NM)

最长公共子串问题是面试常见题目之一,一般在面试场上回答出 O ( N ∗ M ) O(N*M) O(NM) 的解法已经是比较优秀了,因为得到 O ( N ∗ M ) O(N*M) O(NM) 的解法就已经需要用到动态规划了。但其实这个问题的最优解是 O ( N + M ) O(N+M) O(N+M),需要用到「后缀数组 + height数组」。

后缀数组解法思路

在这里插入图片描述
引入一个 h h h 数组,下标用 i n d ind ind 表示,长度与原始数组长度一样, h [ i n d ] h[ind] h[ind] 表示原始数组中以 i n d ind ind 位置开头的后缀串与它上一个排名的后缀串的最长公共前缀长度。

  1. 先查出来 i n d ind ind 位置开始的后缀串的排名 x x x
  2. 然后找到 x − 1 x-1 x1 名是 j j j 位置开始的后缀串;
  3. i n d ind ind 位置开头的后缀串」 和 「 j j j 位置开头的后缀串」的「最长公共前缀长度」记录在 h h h 数组中。

问题的关键就在第3步,两个后缀串的最长公共前缀要如何求?如果从头开始比较,则时间复杂度为 O ( N 2 ) O(N^2) O(N2),那么如何优化使得复杂度变成 O ( N ) O(N) O(N)呢?

优化的大体思路在于 h [ i − 1 ] h[i-1] h[i1] 的值能指导 h [ i ] h[i] h[i] 的值

首先抛出一个结论:当前位置的答案不会比上一个位置的答案 - 1 差,即 h [ i − 1 ] − 1 ≤ h [ i ] h[i-1]-1 \le h[i] h[i1]1h[i],如此一来,就不会回退了。

举例说明:
在这里插入图片描述
假设17开头的后缀串排名为5,而排名为4的是27开头的后缀串,则 “aaabc” 和 “aaad” 的最长公共前缀为3,所以 h [ 17 ] = 3 h[17] = 3 h[17]=3

而18位置就是17开头的位置往后挪了一个,所以 h [ 18 ] ≥ h [ 17 ] − 1 h[18] \ge h[17] - 1 h[18]h[17]1,即 h [ 18 ] ≥ 2 h[18] \ge 2 h[18]2

可能在计算 h [ 18 ] h[18] h[18] 的时候不是18位置和28位置进行比较,但一定不会比长度 2 小。

假设 18开头的排名为13,要和排名12的进行比较,而排名12的可能是28开头的,也可能不是,但是可以跳过前两个字符,直接从第三个字符开始进行比较。

这里就涉及到了 不回退

即,计算了 h [ i ] h[i] h[i],就得到了一个右边界值, 到了计算 h [ i + 1 ] h[i+1] h[i+1]时往右边界的左边退一个位置,从该位置开始进行比较。每次只回退一个位置,整体只有 N N N 个位置,所以复杂度是 O ( N ) O(N) O(N)

h e i g h t height height 数组, h e i g h t [ i ] height[i] height[i] 表示第 i i i 名对应的原始位置和当前排名减1的后缀串的最长公共前缀的长度。

举个例子:
在这里插入图片描述
在这里插入图片描述
求出 h h h 数组:

  • 原数组 0 位置开始的后缀串 “aabaabb”,它是第0名,没有前一名存在,所以 h [ 0 ] = 0 h[0] = 0 h[0]=0
  • 原数组 1 位置开始的后缀串 “abaabb”,它是第2名,而第1名是3位置开始的后缀串 “aabb”,本来应该依据 h [ 0 ] − 1 = − 1 h[0] -1 = -1 h[0]1=1 位置开始比较,这里就有个边界问题了,所以从0位置开始比较, h [ 1 ] = 1 h[1] = 1 h[1]=1
  • 原数组 2 位置开始的后缀串 “baabb”,它是第5名,而第4名是6位置开始的后缀串“b”,于是从后缀串的 h [ 1 ] − 1 = 0 h[1] - 1 = 0 h[1]1=0 位置开始比较,所以 h [ 2 ] = 1 h[2] = 1 h[2]=1
  • 原数组 3 位置开始的后缀串 “aabb”,它是第1名,和第0名的0位置开始的后缀串“aabaabb” 从 h [ 2 ] − 1 = 0 h[2] - 1 = 0 h[2]1=0 位置开始比较,得到结果为 h [ 3 ] = 3 h[3] = 3 h[3]=3
  • 原数组 4 位置开始的后缀串 “abb”,它是第3名,和第2名的1位置开始的后缀串"abaabb" 从 h [ 3 ] − 1 = 2 h[3] - 1 = 2 h[3]1=2 位置开始比较,发现 “abb” 的2位置的 b 和 "abaabb"的2位置的 a 不相同,所以 h [ 4 ] = 2 h[4] = 2 h[4]=2
  • 原数组 5 位置开始的后缀串“bb”,它是第6名,和第5名的2位置开始的后缀串"baabb" 从 h [ 4 ] − 1 = 1 h[4] - 1 = 1 h[4]1=1 位置开始比较,b ≠ a,所以 h [ 5 ] = 1 h[5] = 1 h[5]=1
  • 原数组 6 位置开始的后缀串"b",它是第4名,和第3名的4位置开始的后缀串“abb” 从 h [ 5 ] − 1 = 0 h[5] - 1 = 0 h[5]1=0 位置开始比较,b ≠ a,所以 h [ 6 ] = 0 h[6] = 0 h[6]=0

所以 h = [ 0 , 1 , 1 , 3 , 2 , 1 , 0 ] h=[0, 1, 1, 3, 2, 1, 0] h=[0,1,1,3,2,1,0],对于整个字符串来说,比较的过程中是不回退的。

通过 h h h 数组可以得到 h e i g h t height height 数组。

  • 第0名的是0开头的,于是将 h [ 0 ] h[0] h[0] 填入到 h e i g h t [ 0 ] height[0] height[0] 中,所以 h e i g h t [ 0 ] = h [ 0 ] = 0 height[0] = h[0] = 0 height[0]=h[0]=0
  • 第1名的是3开头的,于是将 h [ 3 ] h[3] h[3] 填入到 h e i g h t [ 1 ] height[1] height[1]中,所以 h e i g h t [ 1 ] = h [ 3 ] = 3 height[1] = h[3] = 3 height[1]=h[3]=3
  • 第2名的是1开头的,于是将 h [ 1 ] h[1] h[1] 填入到 h e i g h t [ 2 ] height[2] height[2]中,所以 h e i g h t [ 2 ] = h [ 1 ] = 1 height[2] = h[1] = 1 height[2]=h[1]=1
  • 第3名的是4开头的,于是将 h [ 4 ] h[4] h[4] 填入到 h e i g h t [ 3 ] height[3] height[3]中,所以 h e i g h t [ 3 ] = h [ 4 ] = 2 height[3] = h[4] = 2 height[3]=h[4]=2
  • 第4名的是6开头的,于是将 h [ 6 ] h[6] h[6] 填入到 h e i g h t [ 4 ] height[4] height[4]中,所以 h e i g h t [ 4 ] = h [ 6 ] = 0 height[4] = h[6] = 0 height[4]=h[6]=0
  • 第5名的是2开头的,于是将 h [ 2 ] h[2] h[2] 填入到 h e i g h t [ 5 ] height[5] height[5]中,所以 h e i g h t [ 5 ] = h [ 2 ] = 1 height[5] = h[2] = 1 height[5]=h[2]=1
  • 第6名的是5开头的,于是将 h [ 5 ] h[5] h[5] 填入到 h e i g h t [ 6 ] height[6] height[6]中,所以 h e i g h t [ 6 ] = h [ 5 ] = 1 height[6] = h[5] = 1 height[6]=h[5]=1;

所以 h e i g h t = [ 0 , 3 , 1 , 2 , 0 , 1 , 1 ] height = [0, 3, 1, 2, 0, 1, 1] height=[0,3,1,2,0,1,1]

h h h 数组的位置下标对应的是原数组的每个位置下标,讨论的是和上一个名次的公共前缀;
h e i g h t height height 数组的位置下标表示的是名次,讨论的是第 i i i 名的后缀串的开始位置和它排名最接近的后缀串的公共前缀

通过 h h h 数组遍历一遍就能生成 h e i g h t height height 数组。

h e i g h t [ i ] height[i] height[i] 的含义:其中 i i i 表示名次,通过 s a sa sa 数组找到排第 i i i 名的后缀串的开始位置 s a [ i ] = x sa[i] = x sa[i]=x 和 排第 i − 1 i-1 i1 名的后缀串的开始位置 s a [ i − 1 ] = y sa[i-1] = y sa[i1]=y,找到 x x x 开始的后缀串和 y y y 开始的后缀串的最长公共前缀。

用途:如果两个字符串有最长公共子串,如 str1 = “12abcd34” 和 str2 = “4567abcd89”,那么 str1 的2位置开头的后缀串的排名和 str2 的4位置开头的后缀串的排名一定是挨着的。

所以整体流程就是:

  1. 先将 str1 和 str2 合成一个数组 “str1 + 最小的ASCII码 + str2”,即 arr = “12abcd3404567abcd89”;
  2. 然后对这个数组求解 s a 、 r a n k 、 h e i g h t sa、rank、height sarankheight 数组;
  3. 接着考察 h e i g h t height height 数组中的值;
    假设来到 h e i g h t height height 数组的 i i i 位置,通过 s a sa sa 数组查出了它对应的后缀串的开始位置为 x,而它的前一名 i − 1 i-1 i1 对应的后缀串的开始位置为 y y y,如果 x x x y y y 位于 arr 数组的最小ASCII码的两侧,表示来自原始串的两个不同串,记录下此时的 h e i g h t [ i ] height[i] height[i],所有符合来自左右两侧的 h e i g h t height height 数组的值就是达标的,否则不达标。
  4. 最后在所有达标的 h e i g h t height height 数组中的值求最大值,就是最长共子串长度。

代码实现

// 最长公共子串问题是面试常见题目之一
// 假设str1长度N,str2长度M
// 因为最优解的难度所限,一般在面试场上回答出O(N*M)的解法已经是比较优秀了
// 因为得到O(N*M)的解法,就已经需要用到动态规划了
// 但其实这个问题的最优解是O(N+M),为了达到这个复杂度可是不容易
// 首先需要用到DC3算法得到后缀数组(sa)
// 进而用sa数组去生成height数组
// 而且在生成的时候,还有一个不回退的优化,都非常不容易理解
// 这就是后缀数组在面试算法中的地位 : 德高望重的噩梦
public class LongestCommonSubstringConquerByHeight {
   //动态规划的解法,还用了空间压缩
   //时间复杂度 O(N*M)
   public static int lcs1(String s1, String s2) {
   		if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
   			return 0;
   		}
   		char[] str1 = s1.toCharArray();
   		char[] str2 = s2.toCharArray();
   		int row = 0;
   		int col = str2.length - 1;
   		int max = 0;
   		while (row < str1.length) {
   			int i = row;
   			int j = col;
   			int len = 0;
   			while (i < str1.length && j < str2.length) {
   				if (str1[i] != str2[j]) {
   					len = 0;
   				} else {
   					len++;
   				}
   				if (len > max) {
   					max = len;
   				}
   				i++;
   				j++;
   			}
   			if (col > 0) {
   				col--;
   			} else {
   				row++;
   			}
   		}
   		return max;
   	}
   
   // 后缀数组的解法:O(N)
   public static int lcs2(String s1, String s2) {
   		if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
   			return 0;
   		}
   		//1. 两个数组合成一个数组
   		char[] str1 = s1.toCharArray();
   		char[] str2 = s2.toCharArray();
   		int N = str1.length;
   		int M = str2.length;
   		int min = str1[0];
   		int max = str1[0];
   		for (int i = 1; i < N; i++) {
   			min = Math.min(min, str1[i]);
   			max = Math.max(max, str1[i]);
   		}
   		for (int i = 0; i < M; i++) {
   			min = Math.min(min, str2[i]);
   			max = Math.max(max, str2[i]);
   		}
   		int[] all = new int[N + M + 1];
   		int index = 0;
   		for (int i = 0; i < N; i++) {
   			all[index++] = str1[i] - min + 2;
   		}
   		all[index++] = 1;
   		for (int i = 0; i < M; i++) {
   			all[index++] = str2[i] - min + 2;
   		}
   		// 2. 调用DC3算法求解sa、rank 和 height数组
   		DC3 dc3 = new DC3(all, max - min + 2);
   		
   		// 3. 利用高度数组求解最长公共子串
   		int n = all.length;
   		int[] sa = dc3.sa;
   		int[] height = dc3.height;
   		int ans = 0;
   		for (int i = 1; i < n; i++) {
   			int Y = sa[i - 1]; //排第i-1名的后缀串的开始位置
   			int X = sa[i]; //排第i名的后缀串的开始位置
   			if (Math.min(X, Y) < N && Math.max(X, Y) > N) { //判断x和y是否来自all数组的最小ASCII码的左右两侧
   				ans = Math.max(ans, height[i]);
   			}
   		}
   		return ans;
   }

   public static class DC3 {

   		public int[] sa;
   
   		public int[] rank;
   
   		public int[] height;
   
   		public DC3(int[] nums, int max) {
   			sa = sa(nums, max);
   			rank = rank();
   			height = height(nums);
   		}
   
   		private int[] sa(int[] nums, int max) {
   			int n = nums.length;
   			int[] arr = new int[n + 3];
   			for (int i = 0; i < n; i++) {
   				arr[i] = nums[i];
   			}
   			return skew(arr, n, max);
   		}
   
   		private int[] skew(int[] nums, int n, int K) {
   			int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2;
   			int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3];
   			for (int i = 0, j = 0; i < n + (n0 - n1); ++i) {
   				if (0 != i % 3) {
   					s12[j++] = i;
   				}
   			}
   			radixPass(nums, s12, sa12, 2, n02, K);
   			radixPass(nums, sa12, s12, 1, n02, K);
   			radixPass(nums, s12, sa12, 0, n02, K);
   			int name = 0, c0 = -1, c1 = -1, c2 = -1;
   			for (int i = 0; i < n02; ++i) {
   				if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) {
   					name++;
   					c0 = nums[sa12[i]];
   					c1 = nums[sa12[i] + 1];
   					c2 = nums[sa12[i] + 2];
   				}
   				if (1 == sa12[i] % 3) {
   					s12[sa12[i] / 3] = name;
   				} else {
   					s12[sa12[i] / 3 + n0] = name;
   				}
   			}
   			if (name < n02) {
   				sa12 = skew(s12, n02, name);
   				for (int i = 0; i < n02; i++) {
   					s12[sa12[i]] = i + 1;
   				}
   			} else {
   				for (int i = 0; i < n02; i++) {
   					sa12[s12[i] - 1] = i;
   				}
   			}
   			int[] s0 = new int[n0], sa0 = new int[n0];
   			for (int i = 0, j = 0; i < n02; i++) {
   				if (sa12[i] < n0) {
   					s0[j++] = 3 * sa12[i];
   				}
   			}
   			radixPass(nums, s0, sa0, 0, n0, K);
   			int[] sa = new int[n];
   			for (int p = 0, t = n0 - n1, k = 0; k < n; k++) {
   				int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2;
   				int j = sa0[p];
   				if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3])
   						: leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) {
   					sa[k] = i;
   					t++;
   					if (t == n02) {
   						for (k++; p < n0; p++, k++) {
   							sa[k] = sa0[p];
   						}
   					}
   				} else {
   					sa[k] = j;
   					p++;
   					if (p == n0) {
   						for (k++; t < n02; t++, k++) {
   							sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2;
   						}
   					}
   				}
   			}
   			return sa;
   		}
   
   		private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) {
   			int[] cnt = new int[k + 1];
   			for (int i = 0; i < n; ++i) {
   				cnt[nums[input[i] + offset]]++;
   			}
   			for (int i = 0, sum = 0; i < cnt.length; ++i) {
   				int t = cnt[i];
   				cnt[i] = sum;
   				sum += t;
   			}
   			for (int i = 0; i < n; ++i) {
   				output[cnt[nums[input[i] + offset]]++] = input[i];
   			}
   		}
   
   		private boolean leq(int a1, int a2, int b1, int b2) {
   			return a1 < b1 || (a1 == b1 && a2 <= b2);
   		}
   
   		private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) {
   			return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3));
   		}
   		
   		//生成rank数组
   		private int[] rank() {
   			int n = sa.length;
   			int[] ans = new int[n];
   			for (int i = 0; i < n; i++) {
   				ans[sa[i]] = i;
   			}
   			return ans;
   		}
   	
   		// 生成height数组
   		private int[] height(int[] s) {
   			int n = s.length;
   			int[] ans = new int[n];
   			// 因为求解 h[i] 只需要 h[i-1] 的值,所以不需要数组,用一个变量k滚动即可
   			// 依次求h[i] , 一开始最长公共前缀为0,所以 k = 0
   			for (int i = 0, k = 0; i < n; ++i) {
   				if (rank[i] != 0) { //只要不是第0名,就要去求最长公共前缀
   					if (k > 0) {
   						--k;
   					}
   					int j = sa[rank[i] - 1];
   					//枚举的过程,两个后缀串从第 k-1 个位置开始比较,如果相等则++,不等则结束循环
   					while (i + k < n && j + k < n && s[i + k] == s[j + k]) {
   						++k;
   					}
   					// 此时的 h[i] = k
   					ans[rank[i]] = k;
   				}
   			}
   			return ans;
   		}

   }

   // for test
   public static String randomNumberString(int len, int range) {
   		char[] str = new char[len];
   		for (int i = 0; i < len; i++) {
   			str[i] = (char) ((int) (Math.random() * range) + 'a');
   		}
   		return String.valueOf(str);
   }

   public static void main(String[] args) {
   		int len = 30;
   		int range = 5;
   		int testTime = 100000;
   		System.out.println("功能测试开始");
   		for (int i = 0; i < testTime; i++) {
   			int N1 = (int) (Math.random() * len);
   			int N2 = (int) (Math.random() * len);
   			String str1 = randomNumberString(N1, range);
   			String str2 = randomNumberString(N2, range);
   			int ans1 = lcs1(str1, str2);
   			int ans2 = lcs2(str1, str2);
   			if (ans1 != ans2) {
   				System.out.println("Oops!");
   			}
   		}
   		System.out.println("功能测试结束");
   		System.out.println("==========");
   
   		System.out.println("性能测试开始");
   		len = 80000;
   		range = 26;
   		long start;
   		long end;
   
   		String str1 = randomNumberString(len, range);
   		String str2 = randomNumberString(len, range);
   
   		start = System.currentTimeMillis();
   		int ans1 = lcs1(str1, str2);
   		end = System.currentTimeMillis();
   		System.out.println("方法1结果 : " + ans1 + " , 运行时间 : " + (end - start) + " ms");
   
   		start = System.currentTimeMillis();
   		int ans2 = lcs2(str1, str2);
   		end = System.currentTimeMillis();
   		System.out.println("方法2结果 : " + ans2 + " , 运行时间 : " + (end - start) + " ms");
   
   		System.out.println("性能测试结束");
   }
}

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

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

相关文章

二叉搜索树专题

二叉搜索树专题 特性篇LeetCode 230. 二叉搜索树中第K小的元素解题思路代码实现LeetCode 538. 把二叉搜索树转换为累加树解题思路代码实现 基操篇LeetCode 98. 验证二叉搜索树解题思路代码实现LeetCode 700. 二叉搜索树中的搜索代码实现LeetCode 701. 二叉搜索树中的插入操作解…

总结826

学习目标&#xff1a; 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 学习内容&#xff1a; 高等数学&#xff1a;复习12讲二元积分&#xff0c;第12讲习题&#xff0c;做了17道题 英语&#xff1a;早上背单词&am…

CAXA 3D 实体设计2020 caxa电子图板2020 64位/32位 详细安装方法

CAXA实体设计2016是国内软件公司根据美国最新的专利技术和多年在CAD/CAM领域积累的经验打造的专业3D模型设计软件&#xff0c;具有国际先进水平&#xff0c;支持创新模式和工程模式。创新模式将可视化自由设计与精准设计相结合&#xff0c;使产品设计跨越了传统参数化CAD软件的…

PHP项目——外卖点餐系统后台管理解析

项目介绍 系统基于总部多门店的连锁模式&#xff0c;拥有门店独立管理后台&#xff0c;支持总部定价和门店定价、LBS定位点餐&#xff0c;可堂食可外卖&#xff0c;适用于茶饮的外卖点餐场景&#xff0c;搭建自己的一点点、奈雪、喜茶点餐系统。 平台后台 1.商品 对门店总商…

取消调休?这个公司好像知道员工要什么...

今年的五一小长假3天变5天&#xff0c;比以往多2天&#xff0c;但是为了多出来的这两天&#xff0c;前一个周末的周日&#xff0c;也就是本周的周日4月23日&#xff0c;要正常上班一天。 五一回来后的5月6日&#xff0c;也就是回来后的那个周六&#xff0c;也要上班&#xff0…

无线蓝牙耳机哪款音质好?目前音质最好的无线蓝牙耳机推荐

现如今&#xff0c;蓝牙耳机已经是一个非常实用且常见的数码产品了&#xff0c;不少人喜欢戴着蓝牙耳机听歌&#xff0c;玩游戏。一款音质好的蓝牙耳机不止能听个响&#xff0c;还能给人极致的听觉享受。在此&#xff0c;我来给大家分享几款目前音质最好的无线蓝牙耳机&#xf…

命令执行漏洞概述

命令执行漏洞概述 命令执行定义命令执行条件命令执行成因命令执行漏洞带来的危害远程命令执行漏洞相关函数assert()preg_replace()call_user_func() a ( a( a(b)可变函数远程命令执行漏洞的利用系统命令执行漏洞相关函数system()exec()shell_exec()passthru&#xff08;&#x…

网络请求实战-实战Fetch和Promise相关的架构

目录 Promise神器&#xff08;承诺&#xff09; PromiseCoding示例 Promise常见用法 简单的promise Fetch的基本用法 fetch Fetch基本用法 FetchPromise场景举例 小结 Promise神器&#xff08;承诺&#xff09; PromiseCoding示例 代表异步求值的过程和结果 promise…

搭建Spark Standalone集群

文章目录 一&#xff0c;Spark Standalone架构&#xff08;一&#xff09;client提交方式&#xff08;二&#xff09;cluster提交方式 二&#xff0c;Spark集群拓扑三&#xff0c;前提条件&#xff1a;安装配置了分布式Hadoop环境四&#xff0c;在master虚拟机上安装配置Spark&…

SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud微服务技术栈(nacos)

Nacos注册中心 &#xff08;一&#xff09;认识和安装Nacos 1、认识Nacos 2、安装nacos 这里下载1.4.1版本 默认端口是8848 下载解压后&#xff0c;终端进入到nacos/bin下&#xff0c;bash startup.sh -m standalone 然后查看start.out文件得到一个网址就可以查看nacos的服…

《Android 移动应用基础教程(Android Studio)(第2版)》【课本习题】【学习通2023春PDF】【参考答案】

文章目录 超星学习通智能终端软件开发&#xff08;基于Android Studio环境&#xff09;章节作业&#xff08;39&#xff09;第一章第二章 Android常见界面布局第三章 Android常见界面控件第四章第五章第六章&#xff08;略&#xff09;第七章第八章第九章第十章第十一章第十二章…

ChatGPT常见问题,Access denied的解决办法

今天&#xff0c;突然想登录一登录ChatGPT&#xff0c;提示 Access denied, You do not have access to chat.openai.com 怎么办&#xff1f; “Access denied You do not have access to chat.openai.com. The site owner may have set restrictions that prevent you from ac…

leetcode142. 环形链表 II

给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数…

vue3+electron开发桌面软件(7)——修改注册表,创建级联菜单

系列文章目录 系列第一篇&#xff1a; vue3electron开发桌面软件入门与实战&#xff08;0&#xff09;——创建electron应用 文章目录 系列文章目录前言一、创建右键级联菜单二、了解注册表结构1.手动添加注册表——一级菜单2.手动添加注册表——二级菜单父菜单3.手动添加注册…

IC-14W网络IC卡读写器_银河麒麟桌面操作系统V10适配测试报告

银河麒麟操作系统产品NeoCertify 认证测试报告 系统版本&#xff1a;银河麒麟桌面操作系统V10 厂商名称&#xff1a; 广州荣士电子有限公司 认证产品&#xff1a;IC-14W网络IC卡读写器 测试日期&#xff1a; 2022-11-04 …

基于SVG的HMI组件

人机界面是自动化领域不可或缺重要组成部分。人机界面系统的设计看上去并没有太大的技术门槛&#xff0c;但是设计一个HMI系统的工作量是巨大的&#xff0c;如果你没有足够的耐心和精力是难以完成一个通用HMI系统的。构建UI控件库就是一个似乎永远完不成的事情&#xff0c;用户…

12.基于蒙特卡洛抽样的电动汽车充电负荷计算

说明书 MATLAB代码&#xff1a;基于蒙特卡洛抽样的电动汽车充电负荷计算 关键词&#xff1a;电动汽车 蒙特卡洛模拟 抽样 充放电负荷 参考文档&#xff1a;《主动配电网多源协同运行优化研究_乔珊》第3.2节&#xff0c;完全复现 仿真平台&#xff1a;MATLAB 优势&#xf…

JavaWeb——IO、存储、硬盘、文件系统相关常识

目录 一、IO 1、定义 二、存储和硬盘 1、存储 2、硬盘 三、文件系统 1、文件 &#xff08;1&#xff09;、定义 &#xff08;2&#xff09;、分类 &#xff08;3&#xff09;、操作 2、树形结构和目录 3、路径 &#xff08;1&#xff09;、定义 &#xff08;2&…

elementui是怎么做表单验证的?

文章目录 前言elementui是怎么做表单验证&#xff1f;步骤 一、 表单验证校验代码&#xff1f;二、el-button提交验证代码2.validate方法深入了解1. 有参数2. 无参数 总结 前言 在项目开发中&#xff0c;我们经常会遇到表单保存的功能&#xff0c;在表单保存前&#xff0c;常常…

Salesforce如何防止黑客攻击和数据泄露?了解他们的安全措施!

安全性一直是Salesforce密切关注的问题。Google的安全浏览报告指出&#xff0c;2022年网络钓鱼网站的数量增加了80&#xff05;。面对着黑客攻击、安全漏洞、数据泄露等不安全事件频发&#xff0c;实施更强大的安全措施比以往更加重要。 调查显示&#xff0c;电子邮件目前是网…