助你深刻理解——最长公共子串、最长公共子序列(应该是全网数一数二的比较全面的总结了)
往事不堪回首,那些年处理过的字符串【的一些骚操作】
- 最长公共子串篇(20191120)
- 理论知识:
- 图形理解:
- 矩阵初始化:
- 矩阵数值演变:
- 类似算法:
- 代码实现(C++):
- 代码设计满足的要求:
- 测试样例:
- 代码理解:
- 说说题目(理解进阶):
- 代码优化/美化版本补充:
- 最长公共子序列篇(20191121)
- 理论知识
- 代码实现与初步理解:
- 测试样例强化理解
- 该矩阵对应的动态规划过程分析如下图:
- 换个路径走,就是另外一种结果:
- 路径选择
- 局限性的补充说明:
- 其他随意测试
- 代码精简版
- 再次测试
- 后记
- 代码优化(20191122)
- 二阶滚动数组优化物理存储空间
- 代码优化
- 代码实现(只求取长度)
- 测试样例
- 代码实现(最终版——另辟蹊径,通过递归实现路径回溯)
- 测试样例
- 路径回溯强化理解
- 图片理解(亲自动手,丰衣足食!)
- 再一次后记
- 纠正后的图片(最终版)
- 补充:暴力枚举法
- 知识拓展:
- 如果是 N 个字符串查找最长公共子序列呢?
- 进一步深入理解:如果是 N 个字符环呢?
最长公共子串篇(20191120)
理论知识:
推荐参考该博文:java实现字符串匹配问题之求两个字符串的最大公共子串
当然这篇也一样,看个人理解:求两个字符串的最长公共子串
图形理解:
矩阵初始化:
矩阵数值演变:
类似算法:
图论中的最短路径算法。
大致分有:迪杰斯特拉算法(Dijkstra)和弗洛伊德算法(Floyd)。
(对应着 贪心算法和动态规划 …… 别慌,名字起的高大尚并无影响理解。。。)
数据结构算法编程课、离散数学课、计算机网络课等都会涉及该算法。
本质都是化作矩阵,故线性代数一定要好好学。最好的理解方式是什么:
亲自动手 —— 图解,自己手动在草稿纸上推演一遍(小矩阵即可)。
代码实现(C++):
太长时间没写C了,一入python差些找不着回头路(哈哈)
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
typedef vector<string> VS;// 为避免重复,检查当前子串是否为不存在,不存在时返回true
bool IsNoRepetition(string& , vector<string>& );int main()
{string s1, s2; // 待输入的两字符串string sameSubString; // 临时存储相同子串VS sameSubStringVector; // 保存所有相同子串while (cin >> s1 >> s2) {int max = 0; // 最长子串长度(字符元素个数),初始置0、默认无相同子串int row = s1.length(); // 矩阵行数(长度)int col = s2.size(); // 矩阵列数(宽度)sameSubString.clear(); // 初始化时,需要重置中间存储变量(清空)// 申请动态二维数组// 也可以可直接 vector<vector<int>>不过建议多多学习、开拓视野int** dp = new int *[row];if (dp) {for (int i = 0; i < row; ++i) {dp[i] = new int[col];}}// 初始化矩阵,全部置false(即首先默认字符串不相同,其后相同再+1)// 此处是为了提醒学弟,注意学习 memset和fill的区别for (int i = 0; i < row; ++i) {/*for (int j = 0; j < col; ++j) {dp[i][j] = 0;}*/fill(dp[i], dp[i] + col, 0); // 两种初始化方式}for (int i = 0; i < row; ++i) {for (int j = 0; j < col; ++j) {int iTemp = i, jTemp = j; // 临时变量while (s1[iTemp] == s2[jTemp]) {dp[iTemp][jTemp] = dp[iTemp][jTemp] + 1;sameSubString += s1[iTemp];iTemp++;jTemp++;// 横纵都 +1是为了斜对角线(即 s1和s2串都往后移动一位)// 值得注意的是别造成数组越界(程序健壮性问题、bug)if (iTemp == row || jTemp == col) {break;}}// 相同子串不为空(即存在时)if (!sameSubString.empty()) {//cout << "sameSubString = " << sameSubString << endl; // 通过输出测试结果,是否如预期所想if (IsNoRepetition(sameSubString, sameSubStringVector)) {sameSubStringVector.push_back(sameSubString);}sameSubString.clear(); // 每遍历过一次相同子串,最后记得重置为空(细节)}}}// 矩阵变换完成后,查找最大值(即为最长相同子串长度)for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {if (max < dp[i][j]) {max = dp[i][j];}}}if (sameSubStringVector.empty()) {cout << endl;}else {// 将各个相同子串按照字典顺序排序sort(sameSubStringVector.begin(), sameSubStringVector.end());for (VS::iterator iter = sameSubStringVector.begin(); iter != sameSubStringVector.end(); ++iter) {// 直接输出的所有的相同子串//cout << *iter << endl;// 使用条件判断只输出最长的相同子串if ((*iter).size() == max) {cout << *iter << endl;break; // break是为了只输出一个最长的公共子串,即ASCLL码最小的那个}}}//cout << endl; // 此空行是为了排版好看,避免pe格式出错// 每执行一遍程序,重置为初始状态(为空)。至于,不放在else内,是编码经验释然。sameSubStringVector.clear();// new了内存空间就要delete// 注意这种表达方式for (int i = 0; i < row; ++i) {delete[] dp[i];}delete[] dp;}return 0;
}bool IsNoRepetition(string& str, vector<string>& vs) {for (int i = 0; i < vs.size(); ++i) {if (vs[i] == str)return false; //有重}return true; //无重
}
代码设计满足的要求:
对于每组测试数据,输出最大子串。
如果最大子串为空(即不存在),则输出一个空行。
测试样例:
输入:
abcded123456aabbcc
abcdaa1234
输出:
1234
代码理解:
本人代码很平民化了,如果看了不能理解实在是……不敢恭维你的编程基础。
实在不理解的话,可以评论区留言或者私信本人账号。
当然,2636105163@qq.com发送邮件或者添加好友也可。只要笔者上线。
说说题目(理解进阶):
为何此处说即可理解最长公共子串、最长公共子序列?
因为只需要理解了理论知识部分(其实就是极其简单的逐个字符匹配问题),
代码只需要修改一个条件即可从最长相同子串转为最长相同子序列:
即对while (s1[iTemp] == s2[jTemp])循环进行相应的修改。最长公共子串:字符一直匹配直到字符不再相同或者已经遍历完较短字符串;
最长公共子序列:一直遍历至较短字符串结束即可,当前字符不相同也要继续匹配下一对字符(各自向后挪动一位)
代码优化/美化版本补充:
由一道公共子串题目引起的自我反思
============ 我是分割线 ============
最长公共子序列篇(20191121)
理论知识
推荐博客:LCS(最长公共子序列)
讲解的很好了,以至于自己发现自己上边对最长公共子序列的理解过于想当然了。
上边的理解偏差在于:如何保证是在已有子序列的基础上去继续匹配下一对,这才是子序列的关键和难点。
代码实现与初步理解:
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;void printDP(int** dp, const int& row, const int& col) {for (int i = 0; i < row; ++i) {for (int j = 0; j < col; ++j) {if (j != col - 1) {cout << dp[i][j] << "\t";}else {cout << dp[i][j] << endl;}}}
}int main()
{string s1, s2; // 待输入的两字符串string longestCommonSubsequence; // 最长相同子序列;while (cin >> s1 >> s2) {int row = s1.length() + 1; // 矩阵行数(长度);int col = s2.size() + 1; // 矩阵列数(宽度);longestCommonSubsequence.clear(); // 初始化时,需要重置为空;// 申请动态二维数组。也可以可直接 vector<vector<int>>不过建议多多学习、开拓视野;// 先申请一列,该列的每个元素对应一个一维数组(一行);再每个元素位申请一行。(行、列都仅仅是指一维数组);int** dp = new int *[row];if (dp) {for (int i = 0; i < row; ++i) {dp[i] = new int[col];}}// 初始化矩阵,全部置false(即首先默认字符串不相同,其后相同再+1)。注意学习 memset和fill的区别;for (int i = 0; i < row; ++i) {//dp[i][0] = 0; // 矩阵第一列全都置0fill(dp[i], dp[i] + col, 0);}//for (int j = 0; j < col; ++j) {// dp[0][j] = 0; // 矩阵第一行全部置0//}//printDP(dp, row, col);// 注意内存空间范围,数组别越界了;for (int i = 0; i < row - 1; ++i) {for (int j = 0; j < col - 1; ++j) { 相等时,在已有的共同子序列的基础上,共同序列长度 +1; 对进行字符的比对时,记得 i、j 要 -1(即从开头起);//if (s1[i] == s2[j]){// dp[i + 1][j + 1] = dp[i][j] + 1;//} 如何理解?——在已有序列的基础上,字串末尾添加不等的字符而已//else {// dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);//}// 若是只输出长度而不要求保存共同子序列的字符,则可以三目运算符(加括号是为了可读性、便于读者理解代码)dp[i + 1][j + 1] = (s1[i] == s2[j] ? dp[i][j] + 1 : max(dp[i][j + 1], dp[i + 1][j]));}} 回溯,通过路径拼凑出LCSint i = row - 1;int j = col - 1;while (i > 0 && j > 0) {cout << "i = " << i << "\t" << "j = " << j << "\t\t";cout << "dp[i][j] = " << dp[i][j] << "\t" << "dp[i-1][j-1]" << dp[i - 1][j - 1] << "\t\t";cout << "s1[i-1] = " << s1[i - 1] << "\t" << "s2[j-1] = " << s2[j - 1] << endl;if (dp[i][j] == dp[i - 1][j - 1] + 1 && s1[i - 1] == s2[j - 1]) {if (i - 1 >= 0 && j - 1 >= 0) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;cout << "1" << "\t" << "longestCommonSubsequence = " << longestCommonSubsequence << endl;}--i;--j;// 走斜线(往左上方);}else if (dp[i - 1][j] > dp[i][j - 1]) {if (i - 1 >= 0 && j - 1 >= 0 && s1[i - 1] == s2[j - 1]) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;cout << "2" << "\t" << "longestCommonSubsequence = " << longestCommonSubsequence << endl;}--i;// 竖着走(往上);}else if (dp[i - 1][j] < dp[i][j - 1]) {if (i - 1 >= 0 && j - 1 >= 0 && s1[i - 1] == s2[j - 1]) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;cout << "3" << "\t" << "longestCommonSubsequence = " << longestCommonSubsequence << endl;}--j;// 横着走(往左);}else {if (i - 1 >= 0 && j - 1 >= 0 && s1[i - 1] == s2[j - 1]) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;cout << "4" << "\t" << "longestCommonSubsequence = " << longestCommonSubsequence << endl;}//--i;--j;// 横竖都行,往上、往左二选一,选择不同、最长公共子串的结果不同;}/*if (i - 1 >= 0 && j - 1 >= 0 && s1[i - 1] == s2[j - 1]) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;}*/}cout << dp[row - 1][col - 1] << endl;cout << longestCommonSubsequence << endl;printDP(dp, row, col);// new了内存空间就要delete;// 注意这种表达方式;for (int i = 0; i < row; ++i) {delete[] dp[i];}delete[] dp;}return 0;
}
测试样例强化理解
该矩阵对应的动态规划过程分析如下图:
换个路径走,就是另外一种结果:
路径选择
局限性的补充说明:
(2019/11/24 21:11 补充)
动态规划实现的最长公共子序列的路径回溯,存在局限性 —— 只能选择边缘路径;即:至多输出两种可能的最长公共子序列。
除非有人自己在横着走和竖着走都可行的那段代码,采用随机数选择法回溯路径。可是没有必要做这种费力又不讨好的无用功。而路径回溯只能输出一个最长公共子序列,如果公共序列存在的话。
其他随意测试
代码精简版
#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;
typedef vector<vector<int>> VVI;
typedef vector<int> VI;
void outResultVVI(const VVI&);
int main()
{string s1, s2; // 待输入的两字符串string longestCommonSubsequence; // 最长相同子序列;while (cin >> s1 >> s2) {int row = s1.length() + 1; // 矩阵行数(长度);int col = s2.size() + 1; // 矩阵列数(宽度);longestCommonSubsequence.clear(); // 初始化时,需要重置为空;VVI dp(row, VI(col));for (int i = 0; i < row; ++i) {fill(dp[i].begin(), dp[i].end(), 0);}//outResultVVI(dp);for (int i = 0; i < row - 1; ++i) {for (int j = 0; j < col - 1; ++j) {dp[i + 1][j + 1] = (s1[i] == s2[j] ? dp[i][j] + 1 : max(dp[i][j + 1], dp[i + 1][j]));}}// 回溯,通过路径拼凑出LCS;int i = row - 1;int j = col - 1;while (i > 0 && j > 0) {if (i - 1 >= 0 && j - 1 >= 0 && s1[i - 1] == s2[j - 1]) {longestCommonSubsequence = s1[i - 1] + longestCommonSubsequence;}// 位置敏感,若是不先进行判断是否添加字符而是直接回溯,将会遗漏最后一个元素if (dp[i][j] == dp[i - 1][j - 1] + 1 && s1[i - 1] == s2[j - 1]) {--i;--j; // 走斜线(往左上方);}else if (dp[i - 1][j] > dp[i][j - 1]) {--i; // 竖着走(往上);}else if (dp[i - 1][j] < dp[i][j - 1]) {--j; // 横着走(往左);}else {--i;//--j;// 横竖都行,往上、往左二选一,选择不同、最长公共子串的结果不同;}}cout << dp[row - 1][col - 1] << endl;cout << longestCommonSubsequence << endl;outResultVVI(dp);}return 0;
}
void outResultVVI(const VVI& vvi) {for (int i = 0; i < vvi.size(); ++i) {for (int j = 0; j < vvi[0].size(); ++j) {if (j == vvi[0].size() - 1) {cout << vvi[i][j] << endl;}else {cout << vvi[i][j] << "\t";}}}
}
再次测试
后记
亲自动手,丰衣足食。
2019/11/22 00:20
============ 我是分割线 ============
代码优化(20191122)
二阶滚动数组优化物理存储空间
代码优化
只求取最长公共子序列长度时,空间复杂度可从O(mn)降至O(min{m,n}),因为动态规划问题的本质仅仅是考虑:
dp[i][j]该 依据什么,从dp[i-1][j-1]、dp[i-1][j]和dp[i][j-1]三者中做出选择并生成自身数值;
其中:m,n为两字符串长度。
两行数组即可存储dp矩阵,实现动态滚动即可。
代码实现(只求取长度)
#include<bits/stdc++.h>
using namespace std;
int main()
{string s_little, s_large; // 待输入的两字符串while (cin >> s_little >> s_large) {if (s_little.length() > s_large.size()) { swap(s_little, s_large); }vector<vector<int>> dp(2, vector<int>(s_little.size() + 1));for (int i = 1; i <= s_large.size(); ++i) {for (int j = 1; j <= s_little.length(); ++j) {dp[i % 2][j] = (s_large[i - 1] == s_little[j - 1] ? dp[(i - 1) % 2][j - 1] + 1 : max(dp[(i - 1) % 2][j], dp[i % 2][j - 1]));}}cout << dp[s_large.size() % 2][s_little.size()] << endl;}return 0;
}
测试样例
代码实现(最终版——另辟蹊径,通过递归实现路径回溯)
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
typedef vector<vector<short>> VVI;
typedef vector<short> VI;
void outResultVVI(const VVI&);
void lcs_generator(int i, int j,const string& str,const VVI& path, string& lcm);
int main()
{string s_little, s_large; // 待输入的两字符串string longestCommonSubsequence; // 最长公共子序列while (cin >> s_little >> s_large) {// 以较短字符串的长决定两阶矩阵的列数(长度);if (s_little.length() > s_large.size()) {swap(s_little, s_large);}// 矩阵行数(宽度) row = 2; VVI dp(2, VI()); // 优化dp物理存储空间,二阶矩阵即可,O(min(s1.size(),s2.size()))空间复杂度for (int i = 0; i < 2; ++i) {dp[i].resize(s_little.size()+1);}VVI path(s_large.length()+1, VI(s_little.size()+1)); // 记录路径,以便回溯,此空间无法优化成滚动数组、数据覆盖、设计不来。。// 以上为 通过vector创建动态矩阵的两种方式// 对此涉及目的只有一个,防止访问二阶矩阵dp时产生索引越界,故必须定死了dp的列索引j必须对应小字符串的长度for (int i = 1; i <= s_large.size(); ++i) {for (int j = 1; j <= s_little.length(); ++j) {// dp[i%2][j] = (s_large[i-1] == s_little[j-1] ? dp[(i-1)%2][j-1] + 1 : max(dp[(i-1)%2][j], dp[i%2][j-1]));// 若无需输出 最长公共子序列 而只是输出最长公共子序列的长度,则上一行地三目运算代码直接搞定if (s_large[i - 1] == s_little[j - 1]) {dp[i % 2][j] = dp[(i - 1) % 2][j - 1] + 1;path[i][j] = 1;}else if (dp[(i - 1) % 2][j] > dp[i % 2][j - 1]) { // 条件若是改为 >=,则可能是另外一种回溯结果dp[i % 2][j] = dp[(i - 1) % 2][j];path[i][j] = 2;}else {dp[i % 2][j] = dp[i % 2][j - 1];path[i][j] = 3;}}// outResultVVI(dp);// outResultVVI(path); // 查看中间演变过程} cout << dp[s_large.size()%2][s_little.size()] << endl;// outResultVVI(dp);// outResultVVI(path); // 查看最终状态longestCommonSubsequence.clear();lcs_generator(s_large.size(), s_little.length(), s_large, path, longestCommonSubsequence);cout << longestCommonSubsequence << endl;}return 0;
}
// 输出动态矩阵
void outResultVVI(const VVI& vvi) {cout << endl;for (int i = 0; i < vvi.size(); ++i) {for (int j = 0; j < vvi[0].size(); ++j) {if (j == vvi[0].size() - 1) {cout << vvi[i][j] << endl;}else {cout << vvi[i][j] << "\t";}}}
}
// 动态矩阵的 行数i、列数j、(用i则是i对应的)字符串str、路径矩阵path
void lcs_generator(int i, int j,const string& str,const VVI& path, string& lcm) {if (!i || !j) { return; }if (1 == path[i][j]) {lcm = str[i - 1] + lcm;lcs_generator(i - 1, j - 1, str, path, lcm);}else if (2 == path[i][j]) {lcs_generator(i - 1, j, str, path, lcm);}else {lcs_generator(i, j - 1, str, path, lcm);}
}
测试样例
路径回溯强化理解
还是经典的测试样例:
357486782
13456778
两种路径两种结果:
横竖都可以走的时候,横着走:35778(下图中的椭圆)
竖着走:34678(下图中的小方块)
图片理解(亲自动手,丰衣足食!)
请忽略 path矩阵的第一行和第一列的全0数据;
剩下的,索引对应实现元素的回溯查找即可。
再一次后记
本来只是帮助大一学弟解答最长相同子串;演变成如此文章,岂非我本意。
不过,回过过往学习,还真的是、高度不一样了、理解也就更加深刻了。
经历过的人都会懂得的。
纠错:上图中,自左向右的倒数第二列的椭圆应该往下挪4个元素位。
纠正后的图片(最终版)
2019/11/22 19:24
以上纯属个人亲自测试结果,如有错误,可以评论区留言告知。
在此谢过!
转载请注明原文出处,再次感谢。
补充:暴力枚举法
二进制模拟串实现暴力破解——暴力枚举出(最长)公共子序列
2019/11/24 01:11
知识拓展:
如果是 N 个字符串查找最长公共子序列呢?
进一步深入理解:如果是 N 个字符环呢?
详情请看本人另外一篇子博客:
查找N个字符串(环)的最长公共子序列
如需转载,请注明出处!
https://blog.csdn.net/I_love_you_dandan/article/details/103173750
联系方式:2636105163@qq.com
欢迎各种友善交流。
2019/11/24 21:00
助你深刻理解——最长公共子串、最长公共子序列(应该是全网数一数二的比较全面的总结了)相关推荐
- 动态规划:最长公共子串 最长公共子序列
一.最长公共子串 1. 题目 给定两个序列 X 和 Y,如果 Z 即是 X 的子串,又是 Y 的子串,我们就称它是 X 和 Y 的公共子串,注意子串是连续的. 例如 X = { A, B, C, D, ...
- 最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和...
最长公共子序列|最长公共子串|最长重复子串|最长不重复子串|最长回文子串|最长递增子序列|最大子数组和 文章作者:Yx.Ac 文章来源:勇幸|Thinking (http://www.ahathi ...
- 【To Understand】动态规划:求最长公共子串/最长公共子序列
动态规划:求最长公共子串/最长公共子序列 本博客转载自:https://blog.csdn.net/u013074465/article/details/45392687 该博客中详细讲解了求最长公共 ...
- 【恋上数据结构】动态规划(找零钱、最大连续子序列和、最长上升子序列、最长公共子序列、最长公共子串、0-1背包)
动态规划(Dynamic Programming) 练习1:找零钱 找零钱 - 暴力递归 找零钱 - 记忆化搜索 找零钱 - 递推 思考题:输出找零钱的具体方案(具体是用了哪些面值的硬币) 找零钱 - ...
- 采用顺序结构存储串,设计实现求串S和串T的一个最长公共子串的算法。
算法分析 先固定字符串str1,取其第一个字符str1[0],(KMP算法)查找str1和str2中有没有以该字符开头的公共子串:即将str[0]与str2中的字符挨个比较,若遇到相等的,再接着比较s ...
- 最长公共子串问题-Java:解法一
分享一个大牛的人工智能教程.零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请轻击http://www.captainbed.net package live.every.day.Pro ...
- 最长公共子串LCS (Longest Common Subsequence) 算法
三个方法都有所借鉴,但代码部分是自己试着写出来的,虽然最后的运行结果都是正确的,但此过程中难免会有考虑不周全的地方,如发现代码某些地方有误,欢迎指正.同时有新的想法,也可以提出! 采用顺序结构存储串, ...
- 【动态规划】最长公共子序列与最长公共子串
1. 问题描述 子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串 cnblogs belong 比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与 ...
- 动态规划套路在最长公共子串、最长公共子序列和01背包问题中的应用
2019独角兽企业重金招聘Python工程师标准>>> 适合动态规划(DP,dynamic programming)方法的最优化问题有两个要素:最优子结构和重叠子问题. 最优子结构指 ...
最新文章
- Spring使用到了那些接口/第三方框架
- 对面向对象设计原则的总结
- MCMC采样和M-H采样
- java实现图形界面输入半径求圆面积_测试开发工程师系列之Android自动化测试Appium(Python)连载(7)安卓图形界面...
- 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面。
- 约瑟夫环问题之猴子选大王
- python socket udp并发_Python进阶----UDP协议使用socket通信,socketserver模块实现并发
- 自动备份SQL Server数据库中用户创建的Stored Procedures
- 出现身份验证错误 要求的函数不受支持_学习使用Kotlin创建Android应用程序第3部分:身份验证登录...
- 西南财大计算机学院官网,西南财经大学
- thinkphp5 insertAll 插入的数据列不对 对应关系不对
- CTFHub | HG泄露
- Visio PAD模板
- 全方位了解8.0系统下的Handler
- jsp网站用什么虚拟主机
- 出入库与库存系统的模型问题
- 手把手带你从0完成医疗行业影像图像检测三大经典模型InceptionV3-RestNet50-VGG16(附python源代码及数据库)——改变世界经典人工智能项目实战(一)手把手教学迁移学习
- 求点赞、被点赞,社交网络用户对点赞又爱又恨
- 微信小游戏学习日记1
- 使用自定义的评价函数优化高NA分束器
热门文章
- 多宫格视频是什么软件_怎么制作多宫格视频/九宫格视频
- Python爬虫一般用什么框架比较好?
- 学习 ES 的笔记、全文检索、倒排索引、Lucene、ik中文分词器、Kibana使用Dev Tools
- 宫保虾球,酸甜微辣,一人就能干掉一盘
- C++输入日期判断是周几
- 39. hive 在使用 count(distinct ) over 时报错,提示 Expression not in GROUP BY key
- STM8S 模拟I2C程序
- 【概念辨析】二维数组传参的几种可能性
- java Workbook接口 提供的方法
- vs2019开发android应用,VS 2019开发APP(一)界面和代码