diff --git "a/Algorithm/ACM\346\250\241\346\235\277(C++).md" "b/Algorithm/ACM\346\250\241\346\235\277(C++).md" new file mode 100644 index 00000000..d5013776 --- /dev/null +++ "b/Algorithm/ACM\346\250\241\346\235\277(C++).md" @@ -0,0 +1,2897 @@ +# ACM模板(C++) + + - [大数](#1) + - [二分](#2) + - [枚举排列](#3) + - [子集生成](#4) + - [n皇后回溯](#5) + - [并查集](#6) + - [树状数组](#7) + - [KMP,Sunday,BM](#8) + - [01背包,完全背包](#9) + - [最长(不)上升或下降子序列](#10) + - [最长公共子序列](#11) + - [拓扑排序](#12) + - [欧拉路径和回路](#13) + - [搜索](#14) + - [最小生成树](#15) + - [最短路](#16) + - [GCD和LCM](#17) + - [埃拉托斯特尼筛法](#18) + - [唯一分定理](#19) + - [扩展欧几里得](#20) + - [欧拉函数](#21) + - [快速幂](#22) + - [矩阵快速幂](#23) +*** +### 1、大数 + +加法,乘法模板 + +```cpp +//题目链接 : http://poj.org/problem?id=2506 +//题目大意 : 就是问你用2*1,1*2,2*2的砖拼成2*n的长方形,有多少种拼法 +//解题思路 : 考虑n的时候,假设我们已经铺好了n-1块砖,第n块只能竖着放 + //假设我们已经铺好了n-2块砖,最后两列有3种方式,但是其中有一种方法和上面是相同的 + //所以f[n] = 2* f[n-2] + f[n-1] +#include +#include +#include +using namespace std; +const int maxn = 10000 + 10; + +//加法 +string bigIntegerAdd(string s1,string s2){ + int a[maxn],b[maxn]; + memset(a,0,sizeof(a)); + memset(b,0,sizeof(b)); + int len1 = s1.size(),len2 = s2.size(); + int maxL = max(len1,len2); + for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0'; + for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0'; + for(int i = 0; i < maxL; i++){ + if(a[i]+b[i] >= 10){ + int temp = a[i]+b[i]; + a[i] = temp%10; + a[i+1] += (temp/10); + } + else a[i] += b[i]; + } + string c = ""; + if(a[maxL] != 0) + c += a[maxL] + '0'; + for(int i = maxL-1; i >= 0; i--)c += a[i] + '0'; + return c; +} + +//乘法 +string bigIntegerMul(string s1,string s2){ + int a[maxn],b[maxn],c[maxn*2 + 5]; + memset(a,0,sizeof(a)); + memset(b,0,sizeof(b)); + memset(c,0,sizeof(c)); + int len1 = s1.size(),len2 = s2.size(); + for(int i = 0; i < len1; i++)a[i] = s1[len1-1-i]-'0'; //倒置 + for(int i = 0; i < len2; i++)b[i] = s2[len2-1-i]-'0'; + for(int i = 0; i < len1; i++){ + for(int j = 0; j < len2; j++){ + c[i+j] += a[i]*b[j]; + } + } + for(int i = 0; i < maxn*2; i++){ + if(c[i] >= 10){ + c[i+1] += c[i]/10; + c[i] %= 10; + } + } + string ans = ""; + int i; + for(i = maxn * 2; i >= 0; i--) + if(c[i] != 0) + break; + for(;i >= 0; i--)ans += c[i] + '0'; + return ans; +} + +int main(){ + //freopen("in.txt","r",stdin); + int n; + string s[255]; + s[0] = "1",s[1] = "1"; //注意0的时候是1 + for(int i = 2;i <= 255; i++){ + string temp = bigIntegerMul("2",s[i-2]); + s[i] = bigIntegerAdd(s[i-1],temp); + } + while(~scanf("%d",&n)) + cout< +const int maxn = 200 + 10; +using namespace std; +typedef long long LL; + +//具体实现 +string subInfo(char *s1,char *s2){ + int a[maxn],b[maxn]; + memset(a,0,sizeof(a)); + memset(b,0,sizeof(b)); + int len1 = strlen(s1),len2 = strlen(s2); + int maxLen = max(len1,len2); + for(int i = 0; i < len1; i++) a[i] = s1[len1 - i - 1] - '0'; + for(int i = 0; i < len2; i++) b[i] = s2[len2 - i - 1] - '0'; + for(int i = 0; i < maxLen; i++){ + if(a[i]-b[i] < 0){ + a[i] = a[i]+10-b[i]; + a[i+1] -= 1; + } + else a[i] -= b[i]; + } + string str = ""; + int i; + for(i = maxLen-1; i >= 0; i--)if(a[i] != 0)break; + for(;i >= 0; i--)str += a[i]+'0'; + return str; +} + +//大数减法的模板 +string bigIntegerSub(char *s1,char *s2){ + if(s1 == s2) + return "0"; //相等 + int len1 = strlen(s1),len2 = strlen(s2); + if(len1 > len2) + return subInfo(s1,s2); + else if(len1 < len2) + return "-" + subInfo(s2,s1); //负数 + else { //长度相等时判断大小 + for(int i = 0; i < len1; i++){ + if(s1[i]-'0' > s2[i]-'0') + return subInfo(s1,s2); + else if(s1[i]-'0' < s2[i]-'0') + return "-" + subInfo(s2,s1); + } + } +} + +int main(){ + char s1[maxn],s2[maxn]; + scanf("%s\n%s",s1,s2); + cout< +using namespace std; +const int maxn = 100000 + 10; + +//大数计算阶乘位数 +//lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1; +int factorialDigit(int n){ + double sum = 0; + for(int i = 1; i <= n; i++){ + sum += log10(i); + } + return (int)sum+1; +} + +//大数计算阶乘 +string bigFactorial(int n){ + int ans[maxn],digit = 1; + ans[0] = 1; + for(int i = 2; i <= n; i++){ + int num = 0; + for(int j = 0; j < digit; j++){ + int temp = ans[j]*i + num; + ans[j] = temp%10; + num = temp/10; + } + while(num != 0){ + ans[digit] = num%10; + num /= 10; + digit++; + } + } + string str = ""; + for(int i = digit-1; i >= 0; i--) + str += ans[i] + '0'; + return str; +} + +int main(){ + int n; + while(~scanf("%d",&n)){ + //cout<二分 +二分的写法可以有很多种,这里列举几个常见的,主要是上界确定,区间以及查找元素是否重复的问题,也可以看看[这篇文章](https://blog.csdn.net/zxzxzx0119/article/details/82670761)。 + + - 求最小的`i`,使得`a[i] = key`,若不存在,则返回`-1`(`lowerbound`函数); + - 求最大的`i`的下一个元素的下标(`c++`中的`upperbound`函数),使得`a[i] = key`,若不存在,则返回`-1`; + - 求最大的`i`,使得`a[i] = key`,若不存在,则返回`-1`; + - 求最小的`i`,使得`a[i] > key`,若不存在,则返回`-1`; + - 求最大的`i`,使得`a[i] < key`,若不存在,则返回`-1`; + +```cpp +#include + +using namespace std; +const int maxn = 100 + 10; + +int cmp(const void *a, const void *b) { + return *(int *) a - *(int *) b; +} + +//普通的二分查找 +int bs(int *arr,int L,int R,int target){ + while( L <= R){ + int mid = (L) + (R-L)/2; + if(arr[mid] == target) + return mid; + if(arr[mid] > target) + R = mid - 1; + else + L = mid + 1; + } + return -1; // not find +} + +//求最小的i,使得a[i] = target,若不存在,则返回-1 +//返回 如果有重复的 下界(比如1,2,2,2,3,4)查找2,返回1 +int firstEqual(int arr[], int L, int R, int target) { + while (L < R) { + int mid = L + (R - L) / 2; + if (arr[mid] < target) + L = mid + 1; + else + R = mid; + } + if (arr[L] == target) + return L; + return -1; +} + +//求最大的i的下一个元素的下标(c++中的upperbound函数),使得a[i] = target,若不存在,则返回-1 +int lastEqualNext(int arr[], int L, int R, int target) { + while (L < R) { + int m = L + (R - L) / 2; + if (arr[m] <= target) + L = m + 1; + else + R = m; + } + if (arr[L - 1] == target) + return L; + return -1; +} + +//求最大的i,使得a[i] = target,若不存在,则返回-1 +int lastEqual(int arr[], int L, int R, int target) { + while (L < R) { + int mid = L + ((R + 1 - L) >> 1);//向上取整 + if (arr[mid] <= target) + L = mid; + else + R = mid - 1; + } + if (arr[L] == target) + return L; + return -1; +} + +//求最小的i,使得a[i] > target,若不存在,则返回-1 +int firstLarge(int arr[], int L, int R, int target) { + while (L < R) { + int m = L + ((R - L) >> 1);//向下取整 + if (arr[m] <= target) + L = m + 1; + else + R = m; + } + if (arr[R] > target) + return R; + return -1; +} + +//求最大的i,使得a[i] < target,若不存在,则返回-1 +int lastSmall(int arr[], int L, int R, int target) { + while (L < R) { + int m = L + ((R + 1 - L) >> 1);//向上取整 + if (arr[m] < target) + L = m; + else + R = m - 1; + } + if (arr[L] < target) + return L; + return -1; +} + +int main() { + //freopen("in.txt", "r", stdin); + int n, a[maxn], v; + scanf("%d", &n); + for (int i = 0; i < n; i++)scanf("%d", &a[i]); //1 3 2 9 4 1 3 7 2 2 + scanf("%d", &v); //input the number you need find + qsort(a, n, sizeof(a[0]), cmp); // 1 1 2 2 2 3 3 4 7 9 + printf("after sorted : \n"); + for (int i = 0; i < n; i++)printf("%d ", a[i]); + + printf("\n-------------test----------------"); + + printf("\n%d\n", firstEqual(a, 0, n, v)); //output 2 第一个 + printf("%d\n", lastEqualNext(a, 0, n, v)); //output 4 + 1,最后一个的下一个 + printf("%d\n", lastEqual(a, 0, n, v)); //output 4 最后一个 + printf("%d\n", firstLarge(a, 0, n, v)); //output 5(第一个3大于2) + printf("%d\n", lastSmall(a, 0, n, v)); //output 1(不是0) + return 0; +} +/* +测试数据: +10 +1 3 2 9 4 1 3 7 2 2 +2 +*/ +``` +输出 +![在这里插入图片描述](https://img-blog.csdn.net/20181009161320307?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +*** +### 枚举排列 +枚举排列的算法也有几个,包括刘汝佳书上的和经典的,还有做过的几个题[LeetCode47](https://blog.csdn.net/zxzxzx0119/article/details/81452471)。[LeetCode46](https://blog.csdn.net/zxzxzx0119/article/details/81452269)。 +```cpp +#include + +using namespace std; +const int maxn = 100 + 10; + +void permutation(int *arr, int n, int cur){ + if(cur == n){ // 边界 + for(int i = 0; i < n; i++) + printf("%d ",arr[i]); + printf("\n"); + } + else for(int i = 1; i <= n; i++){ //尝试在arr[cur]中填充各种整数 + bool flag = true; + for(int j = 0; j < cur; j++)if(i == arr[j]){ // 如果i已经在arr[0]~arr[cur-1]中出现过,则不能选 + flag = false; + break; + } + if(flag){ + arr[cur] = i; //把i填充到当前位置 + permutation(arr, n, cur+1); + } + } +} + +// 求 1 ~ n 的全排列,arr数组作为中间打印数组 +int main(int argc, char const **argv) +{ + int a[maxn], n; + scanf("%d", &n); + permutation(a, n, 0); + return 0; +} + +``` + +```cpp +//可重集的全排列 +#include +const int maxn = 100 + 10; + +void permutation(int *arr,int *p,int n,int cur){ + if(cur == n){ + for(int i = 0; i < n; i++) + printf("%d ",arr[i]); + printf("\n"); + }else for(int i = 0; i < n; i++)if(!i || p[i] != p[i-1]){ + int c1 = 0, c2 = 0; + for(int j = 0; j < n; j++) + if(p[j] == p[i]) // 重复元素的个数 + c1++; + for(int j = 0; j < cur; j++) + if(arr[j] == p[i]) // 前面已经排列的重复元素的个数 + c2++; + if(c2 < c1){ + arr[cur] = p[i]; + permutation(arr, p, n, cur+1); + } + } +} + +int main(){ + int a[maxn], p[maxn] = {5, 6, 7, 5}; //可以有重复元素的全排列 + std::sort(p, p+4); + permutation(a, p, 4, 0); + return 0; +} + +``` + +```cpp +//全排列的非去重递归算法 +#include +using namespace std; +const int maxn = 100 + 10; + +void permutation(int arr[], int cur, int n){ + if( cur == n){ + for(int i = 0; i < n; i++) + printf("%d ", arr[i]); + printf("\n"); + } + else for(int i = cur; i < n; i++){ + swap(arr[i], arr[cur]); + permutation(arr, cur+1, n); + swap(arr[i], arr[cur]); + } +} +int main(){ + int n, a[maxn]; + scanf("%d", &n); + for(int i = 0; i < n; i++) + scanf("%d", &a[i]); + permutation(a, 0, n); + return 0; +} + +``` +*** +### 子集生成 +增量构造法,位向量法,二进制法(常用) + +```cpp +#include +using namespace std; +const int maxn = 100 + 10; + +//打印0~n-1的所有子集 +//按照递增顺序就行构造子集 防止子集的重复 +void print_subset(int *arr, int n, int cur){ + for(int i = 0; i < cur; i++) + printf("%d ", arr[i]); + printf("\n"); + int s = cur ? arr[cur-1] + 1 : 0; //确定当前元素的最小可能值 + for(int i = s; i < n; i++){ + arr[cur] = i; + print_subset(arr, n, cur+1); + } +} +int main(){ + int n, arr[maxn]; + scanf("%d", &n); + print_subset(arr, n, 0); + return 0; +} + +``` + +这个其实很简单,就是枚举每个位置`0`和`1`两种情况即可。 +```cpp +// 1~n 的所有子集:位向量法 +#include +const int maxn = 100 + 10; +using namespace std; +int bits[maxn];//位向量bits[i] = 1,当且仅当i在子集 A 中 + +void print_subset(int n,int cur){ + if(cur == n){ + for(int i = 0; i < cur; i++) + if(bits[i]) + printf("%d ",i); + printf("\n"); + return; + } + bits[cur] = 1; + print_subset(n,cur + 1); + bits[cur] = 0; + print_subset(n,cur + 1); +} + +int main() { + int n; + scanf("%d", &n); + print_subset(n,0); + return 0; +} + +``` +二进制枚举子集用的多,这里举个例子 `n = 3`;则要枚举`0 - 7` 对应的是有`7`个子集,每个子集去找有哪些元素`print_subset`中的 `1<< i `,也就是对应的那个位置是有元素的,例如`1`的二进制是`0001`也就是代表`0`位置有元素,`0010`是`2`,代表第一个位置是`1`,`0100`代表第`2`个位置上有元素,相应的`1000 = 8`对应第`3`个位置上有元素。 +总结来说也就是对应`1<< i`对应` i`上是`1`(从`0`开始),其余位置是`0`。看图容易理解: +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181222112523325.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +```cpp +// 0 ~ n-1的所有子集:二进制法枚举0 ~ n-1的所有子集 +#include +const int maxn = 100 + 10; +using namespace std; +void print_subset(int n,int cur){ + //这一步其实就是判断 cur 的二进制的各个位上是不是1,如果是1,就输出对应的那个位置(位置从0开始) + for(int i = 0; i < n; i++) + if(1 & (cur >> i)) + printf("%d ",i); + printf("\n"); +} +int main(int argc, char const** argv) +{ + int n; + scanf("%d",&n); + for(int i = 0; i < (1 << n); i++) + print_subset(n,i);//枚举各子集对应的编码 0,1,2...pow(2,n) - 1 + return 0; +} +``` +### n皇后回溯 +可以看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81840774)。 + + +```cpp +//n皇后问题:普通回溯法 +#include +const int maxn = 100 + 10; +using namespace std; +int sum,n,cnt; //解的个数,n皇后,递归次数 +int C[maxn]; + +void Search(int cur){ //逐行放置皇后 + cnt++; + if(cur == n)sum++; + else for(int i = 0; i < n; i++){ //尝试在各列放置皇后 + bool flag = true; + C[cur] = i; //尝试把第cur行的皇后放在第i列//如果 等下不行的话 就下一个 i++ + for(int j = 0; j < cur; j++){ //检查是否和已经放置的冲突 + if(C[cur] == C[j] || C[cur] + cur == C[j] + j || cur - C[cur] == j - C[j]){//检查列,"副对角线","主对角线" + flag = false;break; + } + } + if(flag)Search(cur+1); + } +} +int main(){ + scanf("%d",&n); //输入n皇后 + sum = cnt = 0;//解的个数 和 递归的次数 + Search(0); + printf("%d %d\n",sum,cnt); + return 0; +} +``` +优化过的 + +```cpp +// n皇后问题:优化的回溯法 +#include +const int maxn = 100 + 10; +using namespace std; + +int sum,n,cnt; +int C[maxn]; +bool vis[3][maxn]; +int Map[maxn][maxn];//打印解的数组 + +//一般在回溯法中修改了辅助的全局变量,一定要及时把他们恢复原状 +void Search(int cur){ //逐行放置皇后 + cnt++; + if(cur == n){ + sum++; + for(int i = 0; i < cur; i++)Map[i][C[i]] = 1;//打印解 + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++)printf("%d ",Map[i][j]); + printf("\n"); + } + printf("\n"); + memset(Map,0,sizeof(Map)); //还原 + } + else for(int i = 0; i < n; i++){ //尝试在 cur行的 各 列 放置皇后 + if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n]){//判断当前尝试的皇后的列、主对角线 + vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = true; + C[cur] = i;//cur 行的列是 i + Search(cur+1); + vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = false;//切记!一定要改回来 + } + } +} + +int main(){ + scanf("%d",&n); + memset(vis,false,sizeof(vis)); + memset(Map,0,sizeof(Map)); + sum = cnt = 0; + Search(0); + printf("%d %d\n",sum,cnt);//输出 解决方案 和 递归次数 + return 0; +} +``` +### 并查集 +并查集详细讲解以及每一步的优化可以看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81536185)。 + +```cpp +//题目连接 : http://poj.org/problem?id=1611 +//题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染 +#include +const int maxn = 100000 + 10; + +int parent[maxn], rank[maxn]; //parent[]保存祖先,rank记录每个'树的高度' + +void init(){ + for(int i = 0; i < maxn; i++)parent[i] = i; //注意这里 + for(int i = 0; i < maxn; i++)rank[i] = 1; +} + +//int findRoot(int v){ +// return parent[v] == v ? v : parent[v] = findRoot(parent[v]); +//} + +// 非递归 +int findRoot(int v){ + while(parent[v] != v){ + parent[v] = parent[parent[v]]; // 路径压缩 + v = parent[v]; + } + return v; +} + +void unions(int a, int b){ + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + if (rank[aRoot] < rank[bRoot]) + parent[aRoot] = bRoot; + else if(rank[aRoot] > rank[bRoot]){ + parent[bRoot] = aRoot; + }else{ + parent[aRoot] = bRoot; + rank[bRoot]++; + } +} + +int is_same(int x,int y){ //检查是不是在同一个集合中 + return findRoot(x) == findRoot(y); +} + +int main(){ + int n,m,k,x,root; + while(~scanf("%d%d",&n,&m) && (n||m)){ + init(); + for(int i = 0; i < m; i++){ + scanf("%d%d",&k,&root); + for(int j = 1; j < k; j++){ + scanf("%d",&x); + unions(root,x); + } + } + int sum = 1; + for(int i = 1; i < n; i++) + if(findRoot(i) == findRoot(0)) + sum++; //找和0是一个集合的 + printf("%d\n",sum); + } + return 0; +} + +``` +### 树状数组 +树状数组的主要用于需要频繁的修改数组元素,同时又要频繁的查询数组内任意区间元素之和的时候。具体一些解释看图。 +![这里写图片描述](https://img-blog.csdn.net/20180407122904100?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)![这里写图片描述](https://img-blog.csdn.net/20180407122912685?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +所以计算`2^k`次方我们可以用如下代码 + +```cpp +int lowbit(int x){ + return x&(-x); //或者 return x&(x^(x-1)); +} +``` + +![这里写图片描述](https://img-blog.csdn.net/20180407122922906?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +![这里写图片描述](https://img-blog.csdn.net/20180407123210785?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + +这里给出一个例题[POJ2352Stars](http://poj.org/problem?id=2352) +题目意思就是给你一些星星的坐标,每个星星的级别是他左下方的星星的数量,要你求出各个级别的星星有多少个,看样例就懂了 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181222113140383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +题目中一个重要的信息就是输入是按照`y`递增,如果`y`相同则`x`递增的顺序给出的,所以,对于第`i`颗星星,它的`level`就是之前出现过的星星中,横坐标小于等于`i`的星星的数量。这里用树状数组来记录所有星星的`x`值。 +* 代码中有个小细节就是`x++`这是因为`lowbit`不能传值为`0`,否则会陷入死循环。 + + ```cpp + #include + #include + const int maxn = 32000 + 10; + int n,c[maxn],level[15000+10]; + //计算2^k + int lowbit(int x){ + return x&(-x); + } + //更新数组的值 + void update(int i,int val){ + while(i < maxn){ //注意这里是最大的x,没有记录所以用maxn,不能用n + c[i] += val; + i += lowbit(i); //不断的往上面更新 + } + } + //查询 + int sum(int i){ + int s = 0; + while(i > 0){ + s += c[i]; + i -= lowbit(i); //不断的往下面加 + } + return s; + } + int main(){ + int x,y; + scanf("%d",&n); + memset(c,0,sizeof(c)); + memset(level,0,sizeof(level)); + for(int i = 0; i < n; i++){ + scanf("%d%d",&x,&y); + x++; //加入x+1,是为了避免0,X是可能为0的,LOWBIT无法处理0的情况,因为它的结果也是0,那么最终就是一个死循环 + level[sum(x)]++; + update(x,1); //上面的层都要+1个 + } + for(int i = 0; i < n; i++) + printf("%d\n",level[i]); + return 0; + } + ``` + +*** +### KMP,Sunday,BM +这三个算法解决的问题都是 : 有一个文本串 S,和一个模式串 P,现在要查找 P 在 S 中的位置,三种算法的思路这里限于篇幅不多说,[这篇博客](http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html)对着三种算法讲解的比较详细。 +**`KMP`的较直观的分析也可以看看[我的另一篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81430392)** +​```cpp +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1711 +//题目大意 : 找第二个数组在第一个数组中出现的位置,如果不存在,输出-1 +#include +const int maxn = 1000000 + 10; +int n,m,a[maxn], b[10000 + 10],nexts[10000 + 10]; + +void getNext(int *p,int next[]) { //优化后的求next数组的方法 + int len = m; + next[0] = -1; //next 数组中的 最大长度值(前后缀的公共最大长度) 的第一个 赋值为 -1 + int k = -1,j = 0; + while (j < len - 1) { + if (k == -1 || p[j] == p[k]) { //p[k]表示前缀 p[j] 表示后缀 + k++; j++; + if(p[j] != p[k])next[j] = k; + else next[j] = next[k]; //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]] + } + else k = next[k]; + } +} + +int KMPSerach(int *s, int *p) { + int sLen = n,pLen = m; + int i = 0, j = 0; + while (i < sLen && j < pLen) { + if (j == -1 || s[i] == p[j])i++, j++; + else j = nexts[j]; + } + if (j == pLen)return i - j; + else return -1; +} + +int main() { + int T; + scanf("%d", &T); + while (T--) { + scanf("%d%d", &n, &m); + for(int i = 0; i < n; i++)scanf("%d", &a[i]); + for(int i = 0; i < m; i++)scanf("%d", &b[i]); + getNext(b,nexts); //获取next数组 + int ans = KMPSerach(a, b); + if (ans != -1)printf("%d\n", ans + 1); + else printf("-1\n"); + } + return 0; +} +``` +**`BM`算法,主要是根据两个规则去执行** +![这里写图片描述](https://img-blog.csdn.net/20180407140141237?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +​```cpp +//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551 +//题目大意:找一串字符中是否出现"bkpstor"这段字符 + +#include +#include +#include +using namespace std; +const int maxn = 1000; + +int last(char *p, char c) { //找到文本串的 "坏字符" 在模式串中的位置 + for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i; + return -1; +} + +int BM_index(char *T, char *p) { + int n = strlen(T); + int m = strlen(p); + int i = m - 1, j = m - 1; + while (i <= n - 1) { + if (T[i] == p[j]) + if (j == 0)return i; + else i--, j--; + else { + i = i + m - min(j, 1 + last(p, T[i]));//文本的不符合的那个 位置 串移动的步数 + j = m - 1;//模式串的新位置 + } + } + return -1; +} + +int Sum(char *T, char *P, int s) {//输出文本串中包含模式串的数量 + int e = BM_index(T + s, P); + return e == -1 ? 0 : 1 + Sum(T, P, s + e + 1); +} + +//测试数据 : 123bkpstor456bkpstor67 +int main() { + char s[maxn]; + while (~scanf("%s",s)) { + int cnt = BM_index(s, "bkpstor"); + //printf("%d\n",Sum(s,"bkpstor",0));出现次数输出2 + if (cnt == -1)printf("Safe\n"); + else printf("Warning\n"); + } + return 0; +} +``` +`Sunday`算法也是两个规则 +![这里写图片描述](https://img-blog.csdn.net/20180407141122454?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```cpp +//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551 +//题目大意: 找一串字符中是否出现"bkpstor"这段字符 +#include +#include +#include +const int maxn = 1000; +using namespace std; + +int last(char *p, char c) { + for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i; + return -1; +} + +int Sunday(char *s, char *p) { + int sLen = strlen(s); + int pLen = strlen(p); + int i = 0, j = 0; + while (i < sLen && j < pLen) { + if (s[i] == p[j])i++, j++; + else { + int index = i + pLen - j; // 字符串中右端对齐的字符 + if (last(p, s[index]) == -1) { i = index + 1; j = 0; } // 没有在匹配串中出现则直接跳过 + else { + i = index - last(p, s[index]); j = 0; //否则 其移动步长 = 匹配串中最 右端 的该字符到末尾的距离 + 1。 + } + } + } + if (j == pLen)return i - j; + return -1; +} + +int main() { + char s[maxn]; + while (~scanf("%s",s)) { + int cnt = Sunday(s,"bkpstor"); + if (cnt == -1)printf("Safe\n"); + else printf("Warning\n"); + } + return 0; +} + +``` +*** +### 01背包,完全背包 + +背包问题可以分为很多种,这里只简单讨论`01`背包和完全背包, +**后来改写的`01`背包: [博客](https://blog.csdn.net/zxzxzx0119/article/details/82530940)** +参考博客: + +https://blog.csdn.net/liusuangeng/article/details/38374405(很详细) +https://blog.csdn.net/xp731574722/article/details/70766804 +https://blog.csdn.net/na_beginning/article/details/62884939#reply +https://blog.csdn.net/u013445530/article/details/40210587 + +```cpp +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=2602 +#include +#include +#include +using namespace std; +const int maxn = 1000+5; +int w[maxn],v[maxn],dp[maxn][maxn],vis[maxn]; + +//打印解 +void print(int n,int C){ + for(int i = n; i > 1; i--){ + if(dp[i][C] == dp[i-1][C-w[i]] + v[i]){ + vis[i] = 1; + C -= w[i]; + } + } + vis[1] = (dp[1][C] > 0) ? 1: 0; +} + +int main(){ + freopen("in.txt","r",stdin); + int n,C,T; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1;i <= n; i++) scanf("%d",&v[i]); + for(int i = 1;i <= n; i++) scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); //dp[0][0~C]和dp[0~N][0]都为0,前者表示前0个物品无论装入多大的包中总价值都为0,后者表示体积为0的背包都装不进去。 + memset(vis,0,sizeof(vis)); + for(int i = 1; i <= n; i++){ + for(int j = 0;j <= C; j++){ + dp[i][j] = dp[i-1][j]; //如果j不大于v[i]的话就dp[i][j] = dp[i-1][j]; + if(j >= w[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]); + } + } + printf("%d\n",dp[n][C]); //n个物品装入C容量的包能获得的最大价值 + //print(n,C); + //for(int i = 1; i <= n; i++)if(vis[i])printf("%d ",i); //输出选中的物品 + } + return 0; +} +``` + +```cpp +#include +#include +#include +const int maxn = 1000+5; +using namespace std; + +int w[maxn],v[maxn],dp[maxn][maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= n; i++)scanf("%d",&v[i]); + for(int i = 1; i <= n; i++)scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); //dp全部初始化为0 + for(int i = n;i >= 1; i--){ + for(int j = 0; j <= C; j++){ + dp[i][j] = (i == n ? 0 : dp[i+1][j]); + if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i+1][j-w[i]]+v[i]); + } + } + printf("%d\n",dp[1][C]); + } + return 0; +} +``` + +```cpp +#include +#include +#include +const int maxn = 1000+5; +using namespace std; + +int w[maxn],v[maxn],dp[maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= n; i++)scanf("%d",&v[i]); + for(int i = 1; i <= n; i++)scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); + for(int i = 1; i <= n; i++){ + for(int j = C;j >= 0; j--){ + if(j >= w[i])dp[j] = max(dp[j],dp[j-w[i]]+v[i]); + } + } + printf("%d\n",dp[C]); + } + return 0; +} +``` + +完全背包和`01`背包的差别在于每个物品的数量有无数个,在考虑第`i`件物品的时候,不是考虑选不选这件,而是选多少件可以达到最大价值 +![这里写图片描述](https://img-blog.csdn.net/20180407180915113?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```cpp +for (int i = 1; i < n; i++){ + for (int j = 1; j <= v; j++){ + for (int k = 0; k*c[i] <= j; k++){ +   if(c[i] <= j) dp[i][j] = max{dp[i-1][j],dp[i-1][j - k * c[i]] + k * w[i]};/*要么不取,要么取0件、取1件、取2件……取k件*/ +    else dp[i][j] = dp[i-1][j]/*继承前i个物品在当前空间大小时的价值*/ + } + } +} +``` + +优化: +![这里写图片描述](https://img-blog.csdn.net/20180407181220323?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +注意完全背包的顺序问题 :因为每种背包都是无限的。当我们把i从1到N循环时,dp[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。为01背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态dp[i][v]是由状态dp[i-1] [v-c[i]]递推而来。这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果dp[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果dp[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。 +完全背包还有考虑是否要求恰好放满背包等问题,可以好好研究: +![这里写图片描述](https://img-blog.csdn.net/201804071825020?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +参考博客: +https://blog.csdn.net/Thousa_Ho/article/details/78156678 +https://wenku.baidu.com/view/eea4a76b0b1c59eef8c7b497.html +https://www.cnblogs.com/shihuajie/archive/2013/04/27/3046458.html +```cpp +#include +#include +#include + +using namespace std; +const int maxn = 50000 + 10; +const int INF = -0X7ffff; +int w[maxn],v[maxn],dp[maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= C; i++)dp[i] = INF; + dp[0] = 0; + for(int i = 0; i < n; i++)scanf("%d%d",&w[i],&v[i]); + for(int i = 0; i < n; i++){ + for(int j = w[i]; j <= C; j++){ //从前往后递推,这样才能保证一种物品可以被使用多次 + dp[j] = max(dp[j],dp[j-w[i]] + v[i]); + } + } + if(dp[C] > 0)printf("%d\n",dp[C]); + else printf("NO\n"); + } + return 0; +} +``` +### 最长(不)上升或下降子序列 +[Java编写与解释的博客](https://blog.csdn.net/zxzxzx0119/article/details/81224734)。 +先说`O(N*N)`的解法,第`i`个元素之前的最长递增子序列的长度要么是`1`(单独成一个序列),要么就是第`i-1`个元素之前的最长递增子序列加`1`,这样得到状态方程: +```java +LIS[i] = max{1,LIS[k]+1} (∀k arr[k]) +``` +这样`arr[i]`才能在`arr[k]`的基础上构成一个新的递增子序列。 +```cpp +//题目连接 : http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=17 +#include +#include +#include +using namespace std; +const int maxn = 10000 + 10; + +int dp[maxn]; /* dp[i]记录到[0,i]数组的LIS */ +int maxx;/* LIS长度,初始化为1 */ + +int LIS(char *arr, int n) { + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) // 注意i只遍历比它小的元素 + if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1); //改成arr[j] <= arr[i]就可以求非减的 + maxx = max(maxx, dp[i]); + } + return maxx; +} + +/* 递归输出LIS,因为数组dp还充当了“标记”作用 */ +void printLis(char *arr, int index) { + bool isLIS = false; + if (index < 0 || maxx == 0)return; + if (dp[index] == maxx) { + --maxx; + isLIS = true; + } + printLis(arr, --index); + if (isLIS) printf("%c ", arr[index + 1]); +} + +int main() { + char s[maxn]; + int n; + scanf("%d\n",&n); + while(n--){ + maxx = 1; + scanf("%s",s); + printf("%d\n",LIS(s,strlen(s))); + //printLis(s,strlen(s)-1);printf("\n"); + } + return 0; +} +``` +然后就是`nlog(n)`的解法: +参考博客: +https://segmentfault.com/a/1190000002641054 +https://blog.csdn.net/joylnwang/article/details/6766317 + +```cpp +//题目连接 : http://poj.org/problem?id=3903 +#include +#include +#include +#include +using namespace std; + +const int INF = 0x3f3f3f3f; +const int maxn = 100000 + 5; +int a[maxn], dp[maxn], pos[maxn], fa[maxn]; +vector ans; + +//用于最长非递减子序列种的lower_bound函数 +int cmp(int a,int b){ + return a <= b; +} + +//最长上升子序列 +//dp[i]表示长度为i+1的上升子序列的最末尾元素 找到第一个比dp末尾大的来代替 +int LIS(int n){ + memset(dp, 0x3f, sizeof(dp)); + pos[0] = -1; + int i,lpos; + for (i = 0; i < n; ++i){ + dp[lpos = (lower_bound(dp, dp + n, a[i]) - dp)] = a[i]; + pos[lpos] = i; // *靠后打印 + fa[i] = (lpos ? pos[lpos - 1] : -1); + } + n = lower_bound(dp, dp + n, INF) - dp; + for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]); + ans.push_back(a[i]); + return n; +} + +//非递减的LIS +int LISS(int n){ + memset(dp, 0x3f, sizeof(dp)); + pos[0] = -1; + int i,lpos; + for (i = 0; i < n; i++){ + dp[lpos = (lower_bound(dp, dp + n, a[i],cmp) - dp)] = a[i]; //注意这里cmp + pos[lpos] = i; // *靠后打印 + fa[i] = (lpos ? pos[lpos - 1] : -1); + } + n = lower_bound(dp, dp + n, INF) - dp; + for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]); + ans.push_back(a[i]); + return n; +} + +int main(){ + // freopen("in.txt","r",stdin); + int n; + while(~scanf("%d",&n)){ + ans.clear(); + for(int i = 0;i < n; i++)scanf("%d",&a[i]); + printf("%d\n",LIS(n)); + // for(int i = ans.size()-1; i >= 0; i--) printf("%d ",ans[i]);printf("\n"); //路径打印 + } + return 0; +} + +``` +### 最长公共子序列 +最长公共子序列利用动态规划的方式解决,具有最优子结构性质,和重叠子问题性质,`dp[i][j]`记录序列`Xi`和`Yi`的最长公共子序列的长度,当`i = 0`或`j =0` 时,空序列时`Xi`和`Yi`的最长公共子序列,其余情况如下 +![这里写图片描述](https://img-blog.csdn.net/20180407210404162?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +这里也给出一篇讲的[好的博客](https://blog.csdn.net/hrn1216/article/details/51534607) + +```cpp +//题目连接 : http://poj.org/problem?id=1458 +#include +#include +#include +using namespace std; +const int maxn = 1000 + 10; +int dp[maxn][maxn]; +int path[maxn][maxn]; //记录路径 + +int LCS(char *s1,char *s2){ + int len1 = strlen(s1)-1,len2 = strlen(s2)-1;//注意例如 abcfbc的strlen(s1)为7,所以是strlen(s1) - 1 + for(int i = 0; i <= len1; i++) dp[i][0] = 0; + for(int i = 0; i <= len2; i++) dp[0][i] = 0; + for(int i = 1; i <= len1; i++){ + for(int j = 1; j <= len2; j++) + if(s1[i] == s2[j]){ + dp[i][j] = dp[i-1][j-1] + 1; + path[i][j] = 1; + } + else if(dp[i-1][j] >= dp[i][j-1]) { + dp[i][j] = dp[i-1][j]; + path[i][j] = 2; + } + else { + dp[i][j] = dp[i][j-1]; + path[i][j] = 3; + } + } + return dp[len1][len2]; +} + +//打印解 +void print(int i,int j,char *s){ + if(i == 0 || j == 0)return ; + if(path[i][j] == 1){ + print(i-1,j-1,s); + printf("%c ",s[i]); + }else if(path[i][j] == 2){ + print(i-1,j,s); + }else print(i,j-1,s); +} + +int main(){ + char s1[maxn],s2[maxn]; + while(~scanf("%s%s",s1+1,s2+1)){ //注意s1[0]不取-注意例如 abcfbc的strlen(s1)为7 + memset(path,0,sizeof(path)); + printf("%d\n",LCS(s1,s2)); + //print(strlen(s1)-1,strlen(s2)-1,s1); + } + return 0; +} + +``` +*** +### 拓扑排序 +有向无环图上的一种排序方式,我的[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81533388)也讲解了一下。可以两种写法: + +```cpp +//题目链接 : https://vjudge.net/contest/169966#problem/O +#include + +using namespace std; +const int maxn = 100 + 10; + +int n, m; +int in[maxn]; +vectorG[maxn]; + +void topo(){ + queueq; + for(int i = 1; i <= n ; i++) + if(in[i] == 0) + q.push(i); + bool flag = true; + while(!q.empty()){ + int cur = q.front(); + q.pop(); + if(flag){ + printf("%d",cur); + flag = false; + } + else + printf(" %d",cur); + for(int i = 0; i < G[cur].size(); i++){ + if(--in[G[cur][i]] == 0) + q.push(G[cur][i]); + } + } +} + +int main(int argc, char const **argv) +{ + int from, to; + while(~scanf("%d%d", &n, &m) && (n || m)){ + memset(in, 0, sizeof(in)); + for(int i = 1; i <= n; i++) + G[i].clear(); + for(int i = 0; i < m; i++){ + scanf("%d%d", &from, &to); + in[to]++; //度 + G[from].push_back(to); + } + topo(); + printf("\n"); + } + return 0; +} + +``` + +```cpp +#include +#include +#include +using namespace std; +#define maxn 5005 + +typedef struct { ///邻接表实现 + int next_arc;///下一条边 + int point; +} Arc; ///边的结构体, + +Arc arc[maxn];///储存每条边 +int node[maxn],vis[maxn],top[maxn];///储存每个顶点,node[i]表示第i个顶点指向的第一条边在arc中的位置 +int n, m, t; + +void dfs(int v) { + for (int i = node[v]; i != -1; i = arc[i].next_arc) { + if (!vis[arc[i].point]) { + dfs(arc[i].point); + } + } + vis[v] = 1; + top[--t] = v; +} + + +int main() { + int a, b; + while (cin >> n >> m && (m || n)) { + memset(node, -1, sizeof(node)); + memset(vis, 0, sizeof(vis)); + for (int i = 1; i <= m; i++) { + cin >> a >> b; + arc[i].next_arc = node[a];///第一次是出发点,以后是下一个 + arc[i].point = b; + node[a] = i; + vis[arc[i].point] = 0; + } + t = n; + for (int j = 1; j <= n; j++) if (!vis[j]) dfs(j); + for (int i = 0; i < n - 1; i++) + cout << top[i] << " "; + cout << top[n - 1] << endl; + } + return 0; +} +``` +### 欧拉路径和回路 + +首先看定义 : + + 欧拉回路: + + - (1) 图`G`是连通的,不能有孤立点存在。 + - (2) 对于无向图来说度数为奇数的点个数为`0`,对于有向图来说每个点的入度必须等于出度。 + +欧拉路径: + + - (1) 图`G`是连通的,无孤立点存在。 + - (2) 对于无向图来说,度数为奇数的的点可以有`2`个或者`0`个,并且这两个奇点其中一个为起点另外一个为终点。对于有向图来说,可以存在两个点,其入度不等于出度,其中一个入度比出度大`1`,为路径的起点;另外一个出度比入度大`1`,为路径的终点。 + 判断连通的方式有`DFS`或者并查集,然后再判断一下是否满足条件即可,之前做的欧拉回路的几个好题在我的[github仓库](https://github.com/ZXZxin/ACM/tree/master/%E5%9B%BE%E8%AE%BA/%E6%AC%A7%E6%8B%89%E5%9B%9E%E8%B7%AF) + +```cpp +#include + +const int maxn = 1000 + 5; +using namespace std; + +bool vis[maxn]; +int in[maxn]; +int n, m; +vectorG[maxn]; + +void dfs(int u){ + vis[u] = 1; + for(int i = 0; i < G[u].size(); i++){ + if(!vis[G[u][i]]) + dfs(G[u][i]); + } +} + +int main(){ + //freopen("in.txt","r",stdin); + int from, to; + while(scanf("%d", &n) != EOF && n){ + scanf("%d", &m); + memset(vis, 0, sizeof(vis)); + memset(in, false, sizeof(in)); + for(int i = 1; i <= n; i++) + G[i].clear(); + bool flag = true; + for(int i = 0; i < m; i++){ + scanf("%d%d",&from, &to); + G[from].push_back(to); + G[to].push_back(from); + in[from]++; + in[to]++; + } + dfs(1); + for(int i = 1; i <= n; i++) + if(in[i] % 2 != 0) + flag = false; + for(int i = 1; i <= n; i++) + if(!vis[i]) + flag = false; + cout << (flag ? 1 : 0) << endl; + } + return 0; +} + +``` + +```cpp + +#include + +const int maxn = 1000 + 5; +using namespace std; + +int n, m, parent[maxn],ranks[maxn],in[maxn]; + +void init(){ + for(int i = 0; i < maxn; i++) + parent[i] = i; + for(int i = 0; i < maxn; i++) + ranks[i] = 1; +} + +//int findRoot(int v){ +// return parent[v] == v ? v : parent[v] = findRoot(parent[v]); +//} + +int findRoot(int v){ + while(parent[v] != v){ + parent[v] = parent[parent[v]]; + v = parent[v]; + } + return v; +} + +void unions(int a, int b){ + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + if (ranks[aRoot] < ranks[bRoot]) + parent[aRoot] = bRoot; + else if(ranks[aRoot] > ranks[bRoot]){ + parent[bRoot] = aRoot; + }else { + parent[aRoot] = bRoot; + ranks[bRoot]++; + } +} + + +int main(){ + //freopen("in.tat","r",stdin); + int u, v; + while(scanf("%d", &n) != EOF && n){ + init(); + scanf("%d", &m); + memset(in,0,sizeof(in)); + for(int i = 0; i < m; i++){ + scanf("%d%d",&u, &v); + unions(u, v); + in[u]++; + in[v]++; + } + bool flag = true; + int temp = findRoot(1); + for(int i = 1; i <= n; i++) + if(findRoot(i) != temp) + flag = false; + for(int i = 1; i <= n; i++) + if(in[i] % 2 != 0) + flag = false; //判断度 + cout << (flag ? 1 : 0) << endl; + } + return 0; +} + +``` +### 搜索 + +这里举几个比较好的题目: [POJ3984](http://poj.org/problem?id=3984),这个题目要求记录BFS最短路的路径,我这里使用三种方法: + + - BFS+在结构体中记录路径 + - BFS+记录路径 + - DFS加回溯法 + +```cpp +//题目链接;http://poj.org/problem?id=3984 +//题目大意:给你一个5*5的矩阵,让你找一条路,从左上点,到右下点 +//解题思路:利用BFS求解最短路,利用vector记录路径 + +//BFS+在结构体中记录路径 +#include +#include +#include +#include +#include +#include +#pragma warning(disable : 4996) +using namespace std; +const int maxn = 100 + 10; + +int n; +int map[maxn][maxn]; +bool vis[maxn][maxn]; +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};//对应上,右,下,左 + +struct Road {//路径记录 + int x,y,d;//d表示方向 + Road() {}//默认的构造函数 + Road(int a,int b,int c):x(a),y(b),d(c) {}//带参数的构造函数 +}; + +struct node {//节点类型 + int x,y; + vector v;//记录路径的结构体 +}; + +bool check(node u) { + if (!vis[u.x][u.y] && u.x >= 0 && u.x < n && u.y >= 0 && u.y < n && map[u.x][u.y] != 1) + return true; + else + return false; +} + +void BFS(int x, int y) { + queueq; + node now,next; + now.x = x; + now.y = y; + now.v.push_back(Road(x,y,-1)); + q.push(now); + while (!q.empty()) { + now = q.front(); + q.pop(); + if (now.x == n - 1 && now.y == n-1) { + for(int i = 0; i < now.v.size(); i++){ + printf("(%d, %d)\n",now.v[i].x,now.v[i].y); + } + break; + } + for (int i = 0; i < 4; i++) { + next.x = now.x + dir[i][0]; + next.y = now.y + dir[i][1]; + if (check(next)) { + vis[next.x][next.y] = true; + next.v = now.v; + next.v[now.v.size()-1].d = i; + next.v.push_back(Road(next.x,next.y,-1)); + q.push(next); + } + } + } +} + +int main() { + //freopen("in.txt", "r", stdin); + flag = false; + n = 5; + memset(vis, 0, sizeof(vis)); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + scanf("%d", &map[i][j]); + } + } + BFS(0, 0); + return 0; +} +``` + +```cpp +//BFS+记录路径 +#include +#include +#include + +using namespace std; +const int maxn = 100 + 10; + +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}}; +bool vis[maxn][maxn]; +int map[maxn][maxn]; + +struct Node { + int x,y,pre; +}; + +Node m_queue[maxn]; + +bool Check(int x,int y){ + if(!map[x][y] && !vis[x][y] && x >= 0 && x < 5&&y >= 0 && y < 5)return true; + else return false; +} + +void print(int u){ + if(m_queue[u].pre != -1){ + print(m_queue[u].pre); + printf("(%d, %d)\n",m_queue[u].x,m_queue[u].y); + } +} + +void BFS(int x,int y){ + int head = 0,rear = 1; + m_queue[head].x = x; + m_queue[head].y = y; + m_queue[head].pre = -1; + vis[x][y] = true; + while(head < rear){ + for(int i = 0; i < 4; i++){ + int xx = m_queue[head].x + dir[i][0]; + int yy = m_queue[head].y + dir[i][1]; + if(Check(xx,yy)){ + vis[xx][yy] = 1; + m_queue[rear].x = xx; + m_queue[rear].y = yy; + m_queue[rear].pre = head; + rear++; + } + if(xx == 4&&yy == 4)print(rear - 1); + } + head++;//出队 + } +} + + +int main(){ + //freopen("in.txt","r",stdin); + int n = 5; + memset(vis,0,sizeof(vis)); + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + scanf("%d",&map[i][j]); + } + } + printf("(0, 0)\n"); + BFS(0,0); + return 0; +} +``` + +```cpp +//DFS加回溯法 +#include +#include +#include +#include +#include +#include + +using namespace std; +const int maxn = 100+10; +const int INF = 0x3f3f3f3f; + +int n,k,ans = INF; +int map[maxn][maxn]; +bool vis[maxn][maxn]; +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}}; + +struct node { + int x,y; +}; +vectordict; +stacks; + +void memcpy(stacks){ + dict.clear(); + while(!s.empty()){ + dict.push_back(s.top()); + s.pop(); + } +} + +bool Check(int x,int y){ + if(!vis[x][y] && map[x][y] != 1 && x >= 0 && x < n && y >= 0 && y < n)return true; + else return false; +} + +void DFS(int x,int y,int step){//深刻理解递归的含义 + node now; + now.x = x; + now.y = y; + s.push(now); + if(now.x == n - 1 &&now.y == n - 1){ + if(step < ans){//记录最短的路径 + ans = step; + memcpy(s); + } + } + for(int i = 0; i < 4; i++){ + int xx = now.x + dir[i][0]; + int yy = now.y + dir[i][1]; + if(Check(xx,yy)){ + vis[xx][yy] = true; + DFS(xx,yy,step + 1); + vis[xx][yy] = false;//回溯 + } + } + s.pop();//反弹的时候重新还原栈 +} + +int main(){ + //freopen("in.txt","r",stdin); + n = 5; + memset(vis,false,sizeof(vis)); + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + scanf("%d",&map[i][j]); + } + } + DFS(0,0,0); + for(int i = dict.size() - 1; i >= 0; i--){ + printf("(%d, %d)\n",dict[i].x,dict[i].y); + } + return 0; +} +``` +这里再给出DFS的一个好题,印象比较深刻的 + +```cpp +//题目链接:https://vjudge.net/contest/169966#problem/V +//题目大意: 有一个正方形的 战场看,边长为1000,西南方向在坐标原点,战场上有n 个敌人,坐标x,y,攻击范围y + //要你从西边界出发,东边界离开,求西边界和东边界上的坐标,坐标要尽量靠北,如果不能到达东边界输出IMPOSSIBLE +//解题思路: 按照 刘汝佳书上的思路,先判断有无解,从上边界开始DFS如果可以一直到达下边界,则相当于堵住了,无解 + //否则,也是从上边界开始,dfs和东西边界 相连的圆(如果没有就是1000),找到最南边的圆的 与边界相交的那个点就 0k + //主要还是DFS的运用 +#include +#include +#include +#include +#include + +const int maxn = 1000 + 10; +const double W = 1000.0; +using namespace std; + +double x[maxn], y[maxn], r[maxn],Left,Right; +bool vis[maxn]; +int n; + +bool isinterect(int u, int v) { + return sqrt((x[u] - x[v])*(x[u] - x[v]) + (y[u] - y[v])*(y[u] - y[v])) <= r[u] + r[v]; +} + +void check_circle(int u) { + if (x[u] <= r[u])Left = min(Left, y[u] - sqrt(r[u] * r[u] - x[u] * x[u])); + if (x[u] + r[u] >= W)Right = min(Right, y[u] - sqrt(r[u] * r[u] - (W-x[u]) * (W-x[u]))); +} + +bool dfs(int u) {// 检查是否有解 + if (vis[u] == true)return false; + vis[u] = true; + if (y[u] <= r[u])return true; //刚好碰到边界 以及 相切都可以 + for (int i = 0; i < n; i++)if (isinterect(u,i) && dfs(i))return true;// if (&& dfs(i) && isinterect(u,i) )是错的,要先判断是否相交然后再dfs + check_circle(u); + return false; +} + +int main() { + //freopen("in.txt", "r", stdin); + while (scanf("%d", &n) != EOF) { + memset(vis, false, sizeof(vis)); + Left = Right = W; + bool flag = true; + for (int i = 0; i < n; i++) { + scanf("%lf%lf%lf", &x[i], &y[i], &r[i]); + } + for (int i = 0; i < n; i++) { + if (y[i] + r[i] >= W) { //从上往下 DFS + if (dfs(i)) { flag = false; break; }//如果能够从最上面 DFS 到最下面 则无解 + } + } + if (flag) printf("0.00 %.2lf 1000.00 %.2lf\n", Left,Right); + else printf("IMPOSSIBLE\n"); + } + return 0; +} +``` +### 最小生成树 +直接给出模板`prim`和`kruskal`(`prim`是堆优化的) + +另外,我的另外[一篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81540155)也总结了这两个算法。 + + +```cpp +//Prim模板 +//题目链接 : http://poj.org/problem?id=1287 +#include +#include +#include +#include + +using namespace std; +const int maxn = 100 + 10; +const int INF = 1<<30; + +struct Node{ + int v,w; + Node (){} + Node (int v,int w):v(v),w(w){} + bool operator < (const Node&rhs ) const { + return rhs.w < w; + } +}; + +int n,d[maxn]; +bool vis[maxn]; +int Map[maxn][maxn]; + +void init(){ + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; +} + +int Prim(int s){ + priority_queueq; + q.push(Node(s,0)); + int ans = 0; + while(!q.empty()){ + Node Now = q.top(); q.pop(); + int v = Now.v; + if(vis[v]) continue; + ans += Now.w; + vis[v] = true; + for(int i = 1; i <= n; i++){ + int w2 = Map[v][i]; + if(!vis[i] && d[i] > w2){ + d[i] = w2; + q.push(Node(i,d[i])); + } + } + } + return ans; +} + +int main(){ + //freopen("in.txt","r",stdin); + int m,a,b,c; + while(~scanf("%d",&n)&&n){ + scanf("%d",&m); + init(); + for(int i = 1; i <= n; i++){ + Map[i][i] = INF; + for(int j = 1; j < i; j++)Map[i][j] = Map[j][i] = INF; + } + for(int i = 0; i < m; i++){ + scanf("%d%d%d",&a,&b,&c); + if(c < Map[a][b])Map[a][b] = Map[b][a] = c; //注意重边,取小的 + } + printf("%d\n",Prim(1)); + } + return 0; +} +``` +```cpp +//题目链接 : http://poj.org/problem?id=1287 +//kruskal模板 +#include +#include +#include +#include + +using namespace std; +const int maxn = 1e5 + 10; + +int Fa[maxn],Rank[maxn]; + +void init(){ + for(int i = 0; i <= maxn; i++)Fa[i] = i; + for(int i = 0; i <= maxn; i++)Rank[i] = 1; +} + +int Find(int v){ + return Fa[v] == v ? v : Fa[v] = Find(Fa[v]); +} + +void Union(int x, int y){ + int fx = Find(x); + int fy = Find(y); + if (fx == fy)return; + if (Rank[fx] < Rank[fy]) + Fa[fx] = fy; + else{ + Fa[fy] = fx; + if (Rank[fx] == Rank[fy])Rank[fy]++; + } +} + +struct Edge{ + int u,v,w; + Edge(){} + Edge(int u,int v,int w):u(u),v(v),w(w){} +}edge[maxn*2]; + + +int cmp(const void *a,const void *b){ + Edge *aa = (Edge*)a; + Edge *bb = (Edge*)b; + return aa->w > bb->w ? 1 : -1; //降序 +} + +int krusal(int n,int m){ + qsort(edge,m,sizeof(edge[0]),cmp); + int ans = 0; + int cnt = 0; + for(int i = 0; i < m; i++){ + int u = edge[i].u; + int v = edge[i].v; + if(Find(u) != Find(v)){ + Union(u,v); + cnt++; + ans += edge[i].w; + } + if(cnt == n-1)break; + } + return ans; +} + +int main(){ + //freopen("in.txt","r",stdin); + int n,m; + while(~scanf("%d",&n)&&n){ + scanf("%d",&m); + init(); + for(int i = 0; i < m; i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); //注意这题有重边但是没事 + printf("%d\n",krusal(n,m)); + } + return 0; +} +``` +### 最短路 +`dijkstra`,`bellman_ford`,`floyd`,`SPFA` +最经典的[Hdu1874畅通工程续](http://acm.hdu.edu.cn/showproblem.php?pid=1874) + +```cpp +//堆优化dijkstra +#include +using namespace std; +const int maxn = 1e4 + 10; +const int INF = 1e9; + +struct Node{ + int v,w; + Node(int v,int w):v(v),w(w){} + bool operator < (const Node&rhs) const { + return rhs.w < w; + } +}; + +vectorG[maxn]; +bool vis[maxn]; +int d[maxn]; +int n,m; + +void init(){ + for(int i = 0; i < maxn; i++)G[i].clear(); + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; +} + +int dijkstra(int s,int e){ //传入起点终点 + priority_queueq; + q.push(Node(s,0)); + d[s] = 0; + while(!q.empty()){ + Node now = q.top(); q.pop(); + int v = now.v; + if(vis[v])continue; + vis[v] = true; + for(int i = 0; i < G[v].size(); i++){ + int v2 = G[v][i].v; + int w = G[v][i].w; + if(!vis[v2] && d[v2] > w+d[v]){ + d[v2] = w+d[v]; + q.push(Node(v2,d[v2])); + } + } + } + return d[e]; +} + +int main(){ + //freopen("in.txt","r",stdin); + int s,e; + while(~scanf("%d%d",&n,&m)){ + init(); + for(int i = 0; i < m; i++){ + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + G[x].push_back(Node(y,z)); + G[y].push_back(Node(x,z)); + } + scanf("%d%d",&s,&e); + int ans = dijkstra(s,e); + if(INF != ans)printf("%d\n",ans); + else printf("-1\n"); + } + return 0; +} +``` + +```cpp +#include +const int maxn = 1000; +#define INF 0x1f1f1f1f +using namespace std; +bool flag[maxn]; +int graph[maxn][maxn],low[maxn]; + +void DIJ(int n, int s) { + memset(flag, false, sizeof(flag)); + flag[s] = true; + for (int i = 0; i < n; i++)low[i] = graph[s][i]; + for (int i = 1; i < n; i++) { + int min = INF, p; + for (int j = 0; j < n; j++) + if (!flag[j] && min > low[j]) { + min = low[j]; + p = j; + } + flag[p] = true; + for (int j = 0; j < n; j++) + if (!flag[j] && low[j] > graph[p][j] + low[p]) + low[j] = graph[p][j] + low[p]; + } +} + +int main() { + int n, m, begin, t; + while (~scanf("%d%d",&n,&m)) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 1; i <= m; i++) { + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + if (z < graph[x][y]) graph[x][y] = graph[y][x] = z; + } + cin >> begin >> t; + DIJ(n, begin); + if (low[t] < INF) cout << low[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +#include +#define INF 0x1f1f1f1f +using namespace std; +const int maxn = 1000; +vector >Edge[maxn]; + +int n, m, dist[maxn]; +void init() { + for (int i = 0; i < maxn; i++)Edge[i].clear(); + for (int i = 0; i < maxn; i++)dist[i] = INF; +} + +int main() { + int s, t; + while (cin >> n >> m) { + init(); + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + Edge[x].push_back(make_pair(y, z)); + Edge[y].push_back(make_pair(x, z)); + } + cin >> s >> t; + priority_queue>q; + dist[s] = 0; + q.push(make_pair(-dist[s], s)); + while (!q.empty()) { + int now = q.top().second; + q.pop(); + for (int i = 0; i < Edge[now].size(); i++) { + int v = Edge[now][i].first; + if (dist[v] > dist[now] + Edge[now][i].second) { + dist[v] = dist[now] + Edge[now][i].second; + q.push(make_pair(-dist[v], v)); + } + } + } + if (dist[t] < INF)cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(3)bellman_ford +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +struct Edge { + int u, v; + int weight; +}; +Edge edge[maxn * 2]; + +int dist[maxn]; + +void bellman_ford(int s, int n, int m) { + for (int i = 0; i < n; i++) dist[i] = INF; + dist[s] = 0; + for (int i = 0; i < n; i++) + for (int j = 0; j < 2 * m; j++) + if (dist[edge[j].u] > dist[edge[j].v] + edge[j].weight) + dist[edge[j].u] = dist[edge[j].v] + edge[j].weight; +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < m; i++) { + scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].weight); + edge[i + m].v = edge[i].u; + edge[i + m].u = edge[i].v; + edge[i + m].weight = edge[i].weight; + } + cin >> s >> t; + bellman_ford(s, n, m); + if (dist[t] < INF) cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(4)floyd +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +int graph[maxn][maxn]; +int low[maxn]; + +void floyd(int n) { + for (int k = 0; k < n; k++) + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]); +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 1; i <= m; i++) { + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + if (graph[x][y] > z) graph[x][y] = graph[y][x] = z; + } + floyd(n); + scanf("%d%d", &s, &t); + if (graph[s][t] < INF) cout << graph[s][t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(5)SPFA +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +int graph[maxn][maxn],flag[maxn],dist[maxn]; + +void SPFA(int s, int n) { + queueq; + memset(flag, false, sizeof(flag)); + for (int i = 0; i < n; i++) dist[i] = INF; + q.push(s); + dist[s] = 0; + flag[s] = true; + while (!q.empty()) { + int temp = q.front(); + q.pop(); + flag[temp] = false; + for (int i = 0; i < n; i++) { + if (dist[i] > dist[temp] + graph[temp][i]) { + dist[i] = dist[temp] + graph[temp][i]; + if (!flag[i]) { + q.push(i); + flag[i] = true; + } + } + } + } +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + if (z < graph[x][y]) graph[x][y] = graph[y][x] = z; + } + cin >> s >> t; + SPFA(s, n); + if (dist[t] < INF) cout << dist[t] << endl; + else cout << "-1" << endl; + + } + return 0; +} + +``` + +```cpp +//(6)pair+SPFA +#include +#define INF 0x1f1f1f1f +using namespace std; + +const int maxn = 1000; +vector >Edge[maxn]; + +int n, m,dist[maxn]; +bool flag[maxn]; + +void init() { + for (int i = 0; i < maxn; i++)Edge[i].clear(); + for (int i = 0; i < maxn; i++)flag[i] = false; + for (int i = 0; i < maxn; i++)dist[i] = INF; +} + +int main() { + int s, t; + while (cin >> n >> m) { + init(); + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + Edge[x].push_back(make_pair(y, z)); + Edge[y].push_back(make_pair(x, z)); + } + cin >> s >> t; + queueq; + dist[s] = 0; + flag[s] = true; + q.push(s); + while (!q.empty()) { + int now = q.front(); + q.pop(); + flag[now] = false; + for (int i = 0; i < Edge[now].size(); i++) { + int v = Edge[now][i].first; + if (dist[v] > dist[now] + Edge[now][i].second) { + dist[v] = dist[now] + Edge[now][i].second; + if (!flag[Edge[now][i].first]) { + q.push(Edge[now][i].first); + flag[Edge[now][i].first] = true; + } + } + } + } + if (dist[t] < INF)cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` +这里给出一个比较特殊的最短路[题目](https://www.patest.cn/contests/gplt/L2-001) +> 1.最短路的条数 + 2.多条时选择消防员(最短路经过的点的那些权值)多的那一条 + 3.记录路径 +```cpp +#include +using namespace std; +const int maxn = 1e4 + 10; +const int INF = 1e9; +typedef long long LL; + +struct Node{ + int v,w; + Node(int v,int w):v(v),w(w){} + bool operator < (const Node&rhs) const { + return rhs.w < w; + } +}; + +vectorG[maxn]; +bool vis[maxn]; +int Pa[maxn],Weight[maxn],TotalWeight[maxn],TotalRoad[maxn]; +int d[maxn]; +int n,m,s,e; + +void init(){ + for(int i = 0; i < maxn; i++)G[i].clear(); + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; + for(int i = 0; i < maxn; i++)Pa[i] = -1; //记录路径 + for(int i = 0; i < maxn; i++)TotalRoad[i] = 0; + for(int i = 0; i < maxn; i++)TotalWeight[i] = 0; +} + +void PrintPath(int x){ + if(Pa[x] == -1) printf("%d",x); + else { + PrintPath(Pa[x]); + printf(" %d",x); + } +} + +int dijkstra(int s,int e){ //传入起点终点 + priority_queueq; + q.push(Node(s,0)); + d[s] = 0, Pa[s] = -1, TotalWeight[s] = Weight[s],TotalRoad[s] = 1; + while(!q.empty()){ + Node now = q.top(); q.pop(); + int v = now.v; + if(vis[v])continue; + vis[v] = true; + for(int i = 0; i < G[v].size(); i++){ + int v2 = G[v][i].v; + int w = G[v][i].w; + if(!vis[v2] && d[v2] > w+d[v]){ + d[v2] = w + d[v]; + TotalWeight[v2] = TotalWeight[v] + Weight[v2]; + Pa[v2] = v; + TotalRoad[v2] = TotalRoad[v]; + q.push(Node(v2,d[v2])); + } + else if(!vis[v2] && d[v2] == w+d[v]){ + if(TotalWeight[v2] < TotalWeight[v] + Weight[v2]){ //如果消防员个数更多 + TotalWeight[v2] = TotalWeight[v] + Weight[v2]; + Pa[v2] = v; + //q.push(Node(v2,d[v2]));不需要入队了 + } + TotalRoad[v2] += TotalRoad[v]; //加上之前的条数 + } + } + } + return d[e]; +} + +int main(){ + //freopen("in.txt","r",stdin); + int a,b,c; + scanf("%d%d%d%d",&n,&m,&s,&e); + for(int i = 0; i < n; i++)scanf("%d",&Weight[i]); + init(); + for(int i = 0; i < m; i++){ + scanf("%d%d%d",&a,&b,&c); + G[a].push_back(Node(b,c)); + G[b].push_back(Node(a,c)); + } + int ans = dijkstra(s,e); + //if(INF != ans)printf("%d\n",ans); else printf("-1\n"); + printf("%d %d\n",TotalRoad[e],TotalWeight[e]); + PrintPath(e); + printf("\n"); + return 0; +} +``` +### GCD和LCM +注意一下`n`个数的`GCD`和`LCM` + +```cpp +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1019 +#include +using namespace std; +const int maxn = 100 + 10; + +int gcd(int a,int b){ + return b == 0?a:gcd(b,a%b); +} + +int lcm(int a,int b){ + return a/gcd(a,b)*b; +} + +int ngcd(int v[],int n){ + if(n == 1) return v[0]; + return gcd(v[n-1],ngcd(v,n-1)); +} + +int nlcm(int v[],int n){ + if(n == 1) return v[0]; + return lcm(v[n-1],nlcm(v,n-1)); +} +int main(){ + int n,m,a[maxn]; + scanf("%d",&n); + while(n--){ + scanf("%d",&m); + for(int i = 0; i < m; i++)scanf("%d",&a[i]); + //printf("%d\n",ngcd(a,m)); + printf("%d\n",nlcm(a,m)); + } + return 0; +} +``` +### 埃拉托斯特尼筛法 +**素数和分解定理等看[另一篇博客总结](https://blog.csdn.net/zxzxzx0119/article/details/82810246)。** +比较经典的快速筛素数,给个[例题](http://poj.org/problem?id=3126),用`BFS`和筛素数解决 + +```cpp +#include +#include +#include +using namespace std; +const int maxn = 10000 + 10; +int prime[maxn],s,e,vis[maxn]; +bool is_prime[maxn],flag; + +//素数筛选的模板 : |埃式筛法| +int Sieve(int n) { + int k = 0; + for(int i = 0; i <= n; i++)is_prime[i] = true; + is_prime[0] = false,is_prime[1] = false; + for(int i = 2; i <= n; i++) { + if(is_prime[i]) { + prime[k++] = i; + for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数 + } + } + return k; +} + +struct Node { + int v,step; + Node(){} + Node(int v,int step):v(v),step(step){} +}; + +void cut(int n,int *a){ //将各位存到a数组中 + int index = 3; + while(n > 0){ + a[index--] = n%10; + n /= 10; + } +} + +int rev(int *a){ //还原 + int s = 0; + for(int i = 0; i < 4; i++)s = s*10 + a[i]; + return s; +} + +void BFS(int s,int step) { + queueq; + Node now,next; + q.push(Node(s,step)); + vis[s] = 1; + while(!q.empty()) { + now = q.front();q.pop(); + if(now.v == e) { + flag = true; + printf("%d\n",now.step); + return ; + } + int a[4],b[4]; + cut(now.v,a); + for(int i = 0; i < 4; i++){ + memcpy(b,a,sizeof(b)); + for(int j = 0; j <= 9; j++){ + b[i] = j; + next.v = rev(b); + if(next.v < 10000 && next.v > 1000 && is_prime[next.v] && !vis[next.v] && (next.v != now.v)) { + vis[next.v] = 1; + next.step = now.step+1; + q.push(next); + } + } + } + } +} +int main() { + int T; + Sieve(maxn); //先把素数筛选出来 + scanf("%d",&T); + while(T--) { + flag = false; + memset(vis,0,sizeof(vis)); + scanf("%d%d",&s,&e); + BFS(s,0); + if(flag == 0)printf("Impossible\n"); + } + return 0; +} +``` +### 唯一分定理 + +![这里写图片描述](https://img-blog.csdn.net/20180408111353844?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +具体的不细说,看一个比较简单的[模板题](https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html)其余还有题目在我的代码仓库。 + +```cpp +//题目连接 : https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html +#include +using namespace std; +const int maxn = 30000; + +int prime[maxn]; +bool is_prime[maxn]; + +//素数筛选的模板 : |埃式筛法| +int Sieve(int n) { + int k = 0; + for(int i = 0; i <= n; i++)is_prime[i] = true; + is_prime[0] = false,is_prime[1] = false; + for(int i = 2; i <= n; i++) { + if(is_prime[i]) { + prime[k++] = i; + for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数 + } + } + return k; +} + +int main(){ + int T,n; + scanf("%d",&T); + int len = Sieve(maxn); + while(T--){ + scanf("%d",&n); + int ans[maxn],k = 0; + for(int i = 0; i < len; i++){ //唯一分解定理 + while(n % prime[i] == 0){ + ans[k++] = prime[i]; + n /= prime[i]; + } + } + for(int i = 0; i < k; i++)printf(i == 0 ? "%d": "*%d",ans[i]); + printf("\n"); + } + return 0; +} +``` +*** +### 扩展欧几里得 + +基本算法:对于不完全为 `0 `的非负整数 `a,b`,`gcd(a,b)`表示` a,b `的最大公约数,必然存在整数对` x,y `,使得 `gcd(a,b)=ax+by`。,具体用处在于: + * 求解不定方程; + * 求解模线性方程(线性同余方程); + * 求解模的逆元; + + 给出一个讲的不错的[博客](https://www.cnblogs.com/frog112111/archive/2012/08/19/2646012.html)。 + 这里给出一个求解不定方程组解的测试程序: + +```cpp +//扩展欧几里得的学习 +#include + +int exgcd(int a,int b,int &x,int &y){ + if(b == 0){ + x = 1; + y = 0; + return a; + } + int d = exgcd(b,a%b,x,y); //d 就是gcd(a,b) + int t = x; + x = y; + y = t-(a/b)*y; + return d; +} + +bool linear_equation(int a,int b,int c,int &x,int &y){ + int d = exgcd(a,b,x,y); //d是gcd(a,b)//求出a*x+b*y = gcd(a,b)的一组解 + + printf("%d*x + %d*y = %d(gcd(a,b))的一些解是\n",a,b,d); + printf("%d %d\n",x,y); //第一组 + for(int t = 2; t < 10; t++){ //输出 a*x + b*y = gcd(a,b)的其他一些解 + int x1 = x + b/d*t; + int y1 = y - a/d*t; + printf("%d %d\n",x1,y1); //其余组 + } + printf("\n\n"); //第一组 + + + if(c%d) return false; + int k = c/d; //上述解乘以 c/gcd(a,b)就是 a*x +b*y = c的解 + x *= k; y *= k; //求得的只是其中一组解 + return true; +} + +int main(){ + //freopen("in.txt","r",stdin); + int a = 6,b = 15,c = 9; //求解 6*x + 15*y = 9的解 + int x,y; + int d = exgcd(a,b,x,y); + + if(!linear_equation(a,b,c,x,y))printf("无解\n"); + + printf("%d*x + %d*y = %d的一些解是\n",a,b,c); + printf("%d %d\n",x,y); //第一组 + for(int t = 2; t < 10; t++){ //输出其他的一些解 + int x1 = x + b/d*t; + int y1 = y - a/d*t; + printf("%d %d\n",x1,y1); //其余组 + } + return 0; +} +``` +再给出一个比较简单的[模板题](http://poj.org/problem?id=1061) + +```cpp +//题目链接:http://poj.org/problem?id=1061 +//题目大意:中文题,给出青蛙A,B的起点坐标,以及跳跃距离m,n以及环的长度L,求什么时候能相遇 +/*解题思路: 假设跳了T次以后,青蛙1的坐标便是x+m*T,青蛙2的坐标为y+n*T。它们能够相遇的情况为(x+m*T)-(y+n*T)==P*L,其中P为某一个整数,变形一下设经过T次跳跃相遇,得到(n-m)*T-P*L==x-y 我们设a=(n-m),b=L,c=x-y,T=x,P=y. + 得到ax+by==c,直接利用欧几里得扩展定理可以得到一组x,y但是这组x,y不一定是符合条件的最优解, + 首先,当gcd(a,b)不能整除c的时候是无解的,当c能整除gcd(a,b)时,把x和y都要变为原来的c/gcd(a,b)倍, + 我们知道它的通解为x0+b/gcd(a,b)*t要保证这个解是不小于零的最小的数, + 我们先计算当x0+b/gcd(a,b)*t=0时的t值, + 此时的t记为t0=-x0/b/gcd(a,b)(整数除法),代入t0如果得到的x小于零再加上一个b/gcd(a,b)就可以了*/ +#include +using namespace std; +typedef long long LL; + +LL exgcd(LL a,LL b,LL &x,LL &y){ + if(b == 0){ + x = 1; + y = 0; + return a; + } + LL d = exgcd(b,a%b,x,y); + LL t = x; + x = y; + y = t-(a/b)*y; + return d; +} + +int main(){ + //freopen("in.txt","r",stdin); + LL x,y,m,n,L,T,P; + while(cin>>x>>y>>m>>n>>L){ + LL a,b,c; + a = n-m; + b = L; + c = x-y; + LL d = exgcd(a,b,T,P); + if(c%d!=0){ cout<<"Impossible"<欧拉函数 +欧拉函数只是工具: +* 提供`1`到`N`中与`N`互质的数; +* 对于一个正整数`N`的素数幂分解为`N = P1^q1 * P2^q2 * ... * Pn^qn`,则欧拉函数 `γ(N) = N*(1-1/P1) * (1-1/P2) * ... * (1-1/Pn)`。 + +这里也给出一个[博客讲解](https://blog.csdn.net/sentimental_dog/article/details/52002608)。 + +```cpp +//欧拉函数模板 +//题目连接 : https://vjudge.net/contest/185827#problem/G +//题目意思 : 就是要你求1~n互素的对数(注意1~1不要重复) +#include +#include +const int maxn = 50000; +int phi[maxn+1], phi_psum[maxn+1]; + +int euler_phi(int n){ + int m = sqrt(n+0.5); + int ans = n; + for(int i = 2; i < m; i++)if(n % i == 0){ + ans = ans/i*(i-1); + while(n % i == 0)n /= i; + } + if(n > 1)ans = ans/n*(n-1); + return ans; +} + + +//筛素数的方法,求解1~n所有数的欧拉phi函数值 +void phi_table(int n) { + phi[1] = 0; //这里不计算第一个1,1和1,1是重复的,等下直接2*phi_psum[n] + 1 + for(int i = 2; i <= n; i++) if(phi[i] == 0) + for(int j = i; j <= n; j += i) { + if(phi[j] == 0) phi[j] = j; + phi[j] = phi[j] / i * (i-1); + } +} + +int main() { + int n; + phi_table(maxn); + phi_psum[0] = 0; + for(int i = 1; i <= maxn; i++)phi_psum[i] = phi_psum[i-1] + phi[i]; + while(scanf("%d", &n) == 1 && n) printf("%d\n",2*phi_psum[n] + 1); + + return 0; +} +``` +这里再贴上大牛的模板: + +```cpp +const int MAXN = 10000000; +bool check[MAXN+10]; +int phi[MAXN+10]; +int prime[MAXN+10]; +int tot;//素数的个数 + +// 线性筛(同时得到欧拉函数和素表) +void phi_and_prime_table(int N) { + memset(check,false,sizeof(check)); + phi[1] = 1; + tot = 0; + for(int i = 2; i <= N; i++) { + if( !check[i] ) { + prime[tot++] = i; + phi[i] = i-1; + } + for(int j = 0; j < tot; j++) { + if(i * prime[j] > N)break; + check[i * prime[j]] = true; + if( i % prime[j] == 0) { + phi[i * prime[j]] = phi[i] * prime[j]; + break; + } else { + phi[i * prime[j]] = phi[i] * (prime[j] - 1); + } + } + } +} +``` + +*** +### 快速幂 +**乘法快速幂看[这里](https://blog.csdn.net/zxzxzx0119/article/details/82816131)。** + +```cpp +#include + +//计算(a*b)%c +long long mul(long long a,long long b,long long mod) { + long long res = 0; + while(b > 0){ + if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = ( res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + return res; +} + +//幂取模函数 +long long pow1(long long a,long long n,long long mod){ + long long res = 1; + while(n > 0){ + if(n&1) + res = (res * a)%mod; + a = (a * a)%mod; + n >>= 1; + } + return res; +} + + +// 计算 ret = (a^n)%mod +long long pow2(long long a,long long n,long long mod) { + long long res = 1; + while(n > 0) { + if(n & 1) + res = mul(res,a,mod); + a = mul(a,a,mod); + n >>= 1; + } + return res; +} + +//递归分治法求解 +int pow3(int a,int n,int Mod){ + if(n == 0) + return 1; + int halfRes = pow1(a,n/2,Mod); + long long res = (long long)halfRes * halfRes % Mod; + if(n&1) + res = res * a % Mod; + return (int)res; +} + +int main(){ + printf("%lld\n",mul(3,4,10)); + printf("%lld\n",pow1(2,5,3)); + printf("%lld\n",pow2(2,5,3)); + printf("%d\n",pow3(2,5,3)); + return 0; +} +``` + +*** +### 矩阵快速幂 +这个也是比较常用的,主要还是在于常数矩阵的求解和递推公式的求解,这里给出一个[博客讲解](https://blog.csdn.net/wust_zzwh/article/details/52058209)和自己写过的一道模板题,**以及后面自己的[总结](https://blog.csdn.net/zxzxzx0119/article/details/82822588)。** +![这里写图片描述](https://img-blog.csdn.net/20180408171318555?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +转换之后,直接把问题转化为求矩阵的`n-9`次幂。 + +```cpp +//题目链接:https://vjudge.net/contest/172365#problem/A +//题目大意:函数满足——If x < N f(x) = x. +//If x >= N f(x) = a0 * f(x - 1) + a1 * f(x - 2) + a2 * f(x - 3) + …… + a9 * f(x - N); +//ai(0<=i<=9) can only be 0 or 1 . +//要求f(k) % mod; +//解题思路:构造矩阵求解N*N矩阵 + +#include +#include +const int N = 10; +using namespace std; + +int Mod; +struct Matrix { + int m[N][N]; + Matrix(){} +}; + +void Init(Matrix &matrix){ + for(int i = 0;i < N; i++)scanf("%d",&matrix.m[0][i]); + for(int i = 1;i < N; i++){ + for(int j = 0;j < N; j++){ + if(i == (j+1))matrix.m[i][j] = 1; + else matrix.m[i][j] = 0; + } + } +} + +//矩阵相乘 +Matrix Mul(Matrix &a,Matrix &b){ + Matrix c; + for(int i = 0; i < N; i++){ + for(int j = 0;j < N; j++){ + c.m[i][j] = 0; + for(int k = 0; k < N; k++)c.m[i][j] += a.m[i][k]*b.m[k][j]; + c.m[i][j] %= Mod; + } + } + return c; +} + +//矩阵幂 +Matrix Pow(Matrix& matrix, int k) { + Matrix res; + for (int i = 0; i < N; ++i) + for (int j = 0; j < N; ++j) + if (i == j) res.m[i][j] = 1; + else res.m[i][j] = 0; + while (k) { + if (k & 1) res = Mul(matrix,res); + k >>= 1; + matrix = Mul(matrix,matrix); + } + return res; +} + +int main() { + int k,a[N]; + while (~scanf("%d%d",&k,&Mod)) { + Matrix matrix; + Init(matrix); + if (k < 10) { printf("%d\n",k % Mod); continue; } + matrix = Pow(matrix,k-9); + int sum = 0; + for (int i = 0; i < N; i++)sum += matrix.m[0][i] * (9 - i) % Mod; + printf("%d\n",sum%Mod); + } + return 0; +} +``` +*** + + diff --git "a/Algorithm/ACM\346\250\241\346\235\277(Java).md" "b/Algorithm/ACM\346\250\241\346\235\277(Java).md" new file mode 100644 index 00000000..9652c1d6 --- /dev/null +++ "b/Algorithm/ACM\346\250\241\346\235\277(Java).md" @@ -0,0 +1,3115 @@ +# ACM模板(Java) + + - [大数](#大数) + - [二分](#二分) + - [枚举排列](#枚举排列) + - [子集生成](#子集生成) + - [n皇后](#n皇后) + - [并查集](#并查集) + - [树状数组](#树状数组) + - [线段树](#线段树) + - [KMP,Sunday,BM](#kmpsundaybm) + - [01背包,完全背包](#01背包完全背包) + - [最长(不)上升或下降子序列](#最长(不)上升或下降子序列) + - [最长公共子序列](#最长公共子序列) + - [拓扑排序](#拓扑排序) + - [欧拉路径和回路](#欧拉路径和回路) + - [搜索](#搜索) + - [最小生成树](#最小生成树) + - [最短路](#最短路) + - [GCD和LCM](#gcd和lcm) + - [埃拉托斯特尼筛法](#埃拉托斯特尼筛法) + - [唯一分定理](#唯一分定理) + - [扩展欧几里得](#扩展欧几里得) + - [欧拉函数](#欧拉函数) + - [快速幂](#快速幂) + - [矩阵快速幂](#矩阵快速幂) +*** +### 大数 + +加法,乘法模板 + +```java +//题目链接 : http://poj.org/problem?id=2506 +//题目大意 : 就是问你用2*1,1*2,2*2的砖拼成2*n的长方形,有多少种拼法 +//解题思路 : 考虑n的时候,假设我们已经铺好了n-1块砖,第n块只能竖着放 + //假设我们已经铺好了n-2块砖,最后两列有3种方式,但是其中有一种方法和上面是相同的 + //所以f[n] = 2* f[n-2] + f[n-1] +import java.io.*; +import java.util.*; + +public class Main{ + + //大数加法 + static String add(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int maxL = Math.max(n1, n2); + int[] a = new int[maxL + 1];//注意a,b的数组大小都必须是maxL+1 + int[] b = new int[maxL + 1]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < maxL; i++){ + if(a[i] + b[i] >= 10){ + int tmp = a[i] + b[i];//注意一定要先抽取出来 + a[i] = tmp%10; + a[i+1] += tmp/10; + }else + a[i] += b[i]; + } + StringBuilder sb = new StringBuilder(); + if(a[maxL] != 0) sb.append((char)(a[maxL] + '0')); + for(int i = maxL-1; i >= 0; i--) sb.append((char)(a[i] + '0')); + return sb.toString(); + } + + // 大数乘法 + static String mul(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int[] a = new int[n1]; + int[] b = new int[n2]; + int[] c = new int[n1 + n2]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < n1; i++){ + for(int j = 0; j < n2; j++){ + c[i+j] += a[i] * b[j]; + } + } + for(int i = 0; i < n1 + n2 - 1; i++){ + if(c[i] >= 10){ + c[i+1] += c[i]/10; + c[i] %= 10; + } + } + int i; + for(i = n1 + n2 - 1; i >= 0; i--) if(c[i] != 0) break; + StringBuilder sb = new StringBuilder(); + for(; i >= 0; i--) sb.append( (char)(c[i] + '0')); + return sb.toString(); + } + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + String[] s = new String[255]; + s[0] = "1"; s[1] = "1"; + for(int i = 2; i < 255; i++){ + String mul = mul("2", s[i-2]); + s[i] = add(mul, s[i-1]); + } + while(in.hasNext()){ + int n = in.nextInt(); + out.println(s[n]); + } + } +} +``` +求阶乘以及位数模板 + +```java +//http://nyoj.top/problem/28 +//大数阶乘的模板 +import java.io.*; +import java.util.*; + +public class Main { + //大数计算阶乘位数 + //lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1; + static int factorialDigit(int n) { + double sum = 0; + for (int i = 1; i <= n; i++) { + sum += Math.log10(i); + } + return (int) sum + 1; + } + + //大数计算阶乘 + static String bigFactorial(int n) { + int[] ans = new int[100001]; + int digit = 1; + ans[0] = 1; + for (int i = 2; i <= n; i++) { + int num = 0; + for (int j = 0; j < digit; j++) { + int temp = ans[j] * i + num; + ans[j] = temp % 10; + num = temp / 10; + } + while (num != 0) { + ans[digit] = num % 10; + num /= 10; + digit++; + } + } + StringBuilder sb = new StringBuilder(); + for (int i = digit - 1; i >= 0; i--) + sb.append( (char)(ans[i] + '0')); + return sb.toString(); + } + + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + while(in.hasNext()){ + int n = in.nextInt(); + out.println(bigFactorial(n)); + } + } +} +``` + +*** +### 枚举排列 + +非去重写法: + +```java +import java.io.*; +import java.util.*; + +/** + * 求 1 ~ n 的全排列,arr数组作为中间打印数组 + */ +public class Main { + + static PrintStream out = System.out; + + static int n; + + static void permutation(int[] arr, int cur){ + if(cur == n){ // 边界 + for(int i = 0; i < n; i++) + out.print(arr[i] + " "); + out.println(); + } + else for(int i = 1; i <= n; i++){ //尝试在arr[cur]中填充各种整数 (1~n) + boolean flag = true; + for(int j = 0; j < cur; j++)if(i == arr[j]){ // 如果i已经在arr[0]~arr[cur-1]中出现过,则不能选 + flag = false; + break; + } + if(flag){ + arr[cur] = i; //把i填充到当前位置 + permutation(arr, cur+1); + } + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + permutation(new int[n], 0); + } +} +``` + +经典写法 + +```java +//全排列的非去重递归算法 +public class Main { + + static PrintStream out = System.out; + + static int n; + + static void permutation(int arr[], int cur){ + if( cur == n){ + for(int i = 0; i < n; i++) + out.print(arr[i] + " "); + out.println(); + } else for(int i = cur; i < n; i++){ + swap(arr, i, cur); + permutation(arr, cur+1); + swap(arr, i, cur); + } + } + static void swap(int[] arr, int a, int b){ + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + + n = in.nextInt(); + int[] arr = new int[n]; + for(int i = 0; i < n; i++) arr[i] = in.nextInt(); + permutation(arr, 0); + } +} +``` +*** +### 子集生成 +增量构造法,位向量法,二进制法(常用) + +```cpp +public class Main { + + static PrintStream out = System.out; + + static int n; + //打印0~n-1的所有子集 + //按照递增顺序就行构造子集 防止子集的重复 + static void print_subset(int[] arr, int cur){ + for(int i = 0; i < cur; i++) + out.print(arr[i] + " "); + out.println(); + int s = cur != 0 ? arr[cur-1] + 1 : 0; //确定当前元素的最小可能值 + for(int i = s; i < n; i++){ + arr[cur] = i; + print_subset(arr, cur+1); + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + print_subset(new int[n],0); + } +} +``` + +这个其实很简单,就是枚举每个位置`0`和`1`两种情况即可。 +```cpp +/** + * 1~n 的所有子集:位向量法 + */ +public class Main { + + static PrintStream out = System.out; + + static boolean[] bits; //位向量bits[i] = 1,当且仅当i在子集 A 中 + + static int n; + + static void print_subset(int cur) { + if (cur == n+1) { + for (int i = 1; i < cur; i++) + if (bits[i]) + out.print(i + " "); + out.println(); + return; + } + bits[cur] = true; + print_subset(cur + 1); + bits[cur] = false; + print_subset(cur + 1); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + bits = new boolean[n + 1]; + print_subset(1); + } +} +``` +二进制枚举子集用的多,这里举个例子 `n = 3`;则要枚举`0 - 7` 对应的是有`7`个子集,每个子集去找有哪些元素`print_subset`中的 `1<< i `,也就是对应的那个位置是有元素的,例如`1`的二进制是`0001`也就是代表`0`位置有元素,`0010`是`2`,代表第一个位置是`1`,`0100`代表第`2`个位置上有元素,相应的`1000 = 8`对应第`3`个位置上有元素。 +总结来说也就是对应`1<< i`对应` i`上是`1`(从`0`开始),其余位置是`0`。看图容易理解: + +![acm_1.png](images/acm_1.png) + +代码: + +```java +// 0 ~ n-1的所有子集:二进制法枚举0 ~ n-1的所有子集 +public class Main { + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int n = in.nextInt(); + for(int mask = 0; mask < (1 << n); mask++){ + for(int i = 0; i < n; i++) + if( ((mask >> i) & 1) == 1) //和下面一样 +// if( ((1 << i) & mask) != 0) + System.out.print(i + " "); + System.out.println(); + } + } +} +``` +### n皇后回溯 + +求解个数: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + static int n, count; + static boolean[] cols, d1, d2; // cols[i]代表的是 第i行的第cols列已经有了 + + static void dfs(int r){ // 当前是r行 + if(r == n){ + count++; + }else { + for(int c = 0; c < n; c++){ //考察的是每一列 + int id1 = r + c; //主对角线 + int id2 = r - c + n - 1; // 副对角线 + if(cols[c] || d1[id1] || d2[id2]) continue; + cols[c] = d1[id1] = d2[id2] = true; + dfs(r+1); + cols[c] = d1[id1] = d2[id2] = false; + } + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + cols = new boolean[n]; + d1 = new boolean[n * 2 - 1]; + d2 = new boolean[n * 2 - 1]; + dfs(0); + out.println(count); + } +} +``` + +可以将三个数组合成一个第一维 = 3的二维数组,同时再用一个整形`cols`记录解: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + static int n, count; + static boolean[][] vis; + static int[] cols; + + static void dfs(int r) { //逐行放置皇后 + if (r == n) { + count++; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (cols[i] == j) + out.print("0 "); + else + out.print(". "); + } + out.println(); + } + out.println("--------------"); + } else for (int c = 0; c < n; c++) { //尝试在 cur行的 各 列 放置皇后 + if (vis[0][c] || vis[1][r + c] || vis[2][r - c + n - 1]) continue;//判断当前尝试的皇后的列、主对角线 + vis[0][c] = vis[1][r + c] = vis[2][r - c + n - 1] = true; + cols[r] = c; //r行的列是 c + dfs(r + 1); + vis[0][c] = vis[1][r + c] = vis[2][r - c + n - 1] = false;//切记!一定要改回来 + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + cols = new int[n]; + vis = new boolean[3][2 * n - 1]; + dfs(0); + out.println(count); + } +} +``` + +第二种写法(效率没有上的好): + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + static int n, count; + static int[] cols; + + static void dfs(int r){ // 当前是r行 + if(r == n){ + count++; + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + if(cols[i] == j) + out.print("0 "); + else + out.print(". "); + } + out.println(); + } + out.println("-------------------"); + }else { + for(int c = 0; c < n; c++){ // 考察的是每一列 + cols[r] = c; // 尝试将 r行的皇后放在第c列 + boolean ok = true; + for(int i = 0; i < r; i++) { //检查是否和已经放置的冲突 + //检查列,"副对角线","主对角线" + if (cols[r] == cols[i] || r - i == cols[r] - cols[i] || r - i == cols[i] - cols[r]){ + ok = false; + break; + } + } + if(ok) dfs(r+1); + } + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + n = in.nextInt(); + cols = new int[n]; + dfs(0); + out.println(count); + } +} +``` + +### 并查集 +并查集。 + +基于高度`rank`版本的。 + +```cpp +//题目连接 : http://poj.org/problem?id=1611 +//题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染 +import java.io.*; +import java.util.*; + +public class Main { + + static int[] f; + static int[] rank; + + static int findRoot(int p) { + while (p != f[p]) { + f[p] = f[f[p]]; + p = f[p]; + } + return p; + } + + static void union(int a, int b) { + int aR = findRoot(a); + int bR = findRoot(b); + if (aR == bR) return; + if (rank[aR] < rank[bR]) { + f[aR] = f[bR]; + } else if (rank[aR] > rank[bR]) { + f[bR] = f[aR]; + } else { + f[aR] = f[bR]; + rank[bR]++; + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + while (in.hasNext()) { + int n = in.nextInt(); + int m = in.nextInt(); + if(n == 0 && m == 0) break; + f = new int[n]; + rank = new int[n]; + for(int i = 0; i < n; i++) { + f[i] = i; + rank[i] = 1; + } + for(int i = 0; i < m; i++){ + int c = in.nextInt(); + int root = in.nextInt(); + for(int j = 0; j < c - 1; j++) { + int num = in.nextInt(); + union(root, num); + } + } + int res = 1; // 0已经感染 + for(int i = 1; i < n; i++) if(findRoot(0) == findRoot(i)) res++; + out.println(res); + } + } +} +``` +基于集合大小`size`版本的。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int[] f; + static int[] sz; // size + + static int findRoot(int p) { + while (p != f[p]) { + f[p] = f[f[p]]; + p = f[p]; + } + return p; + } + + // 将元素个数少的集合合并到元素个数多的集合上 + static void union(int a, int b) { + int aR = findRoot(a); + int bR = findRoot(b); + if (aR == bR) return; + if (sz[aR] < sz[bR]) { + f[aR] = f[bR]; + sz[bR] += sz[aR]; // 更新集合元素个数 + }else{ + f[bR] = f[aR]; + sz[aR] += sz[bR]; + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + while (in.hasNext()) { + int n = in.nextInt(); + int m = in.nextInt(); + if(n == 0 && m == 0) break; + f = new int[n]; + sz = new int[n]; + for(int i = 0; i < n; i++) { + f[i] = i; + sz[i] = 1; + } + for(int i = 0; i < m; i++){ + int c = in.nextInt(); + int root = in.nextInt(); + for(int j = 0; j < c - 1; j++) { + int num = in.nextInt(); + union(root, num); + } + } + int res = 1; // 0已经感染 + for(int i = 1; i < n; i++) if(findRoot(0) == findRoot(i)) res++; + out.println(res); + } + } +} +``` + +### 树状数组 + +[**LeetCode - 307. Range Sum Query - Mutable**](https://leetcode.com/problems/range-sum-query-mutable/)例题: + +题目: + +![acm_3.png](images/acm_3.png) + +树状数组代码: + +```java +class NumArray { + + private int[] sums;// 树状数组中求和的数组 + private int[] data;//真实存放数据的数组 + private int n; + + private int lowbit(int x) {return x & (-x);} + + private int query(int i){ + int s = 0; + while(i > 0){//树状数组中索引是1~n + s += sums[i]; + i -= lowbit(i); + } + return s; + } + + // fenWick update + private void renewal(int i, int delta){// delta是增量,不是新值 + while(i <= n){//树状数组中索引是1~n + sums[i] += delta; + i += lowbit(i); + } + } + + public NumArray(int[] nums) { + n = nums.length; + sums = new int[n+1]; + data = new int[n]; + for(int i = 0; i < n; i++) { + data[i] = nums[i]; + renewal(i+1, nums[i]); + } + } + + public void update(int i, int val) { + renewal(i+1, val - data[i]); + data[i] = val; + } + + public int sumRange(int i, int j) { + return query(j+1) - query(i); + } +} +``` + +再看一个例题[**POJ - 2352. Stars**](http://poj.org/problem?id=2352) + +题目意思就是给你一些星星的坐标,每个星星的级别是他左下方的星星的数量,要你求出各个级别的星星有多少个,看样例就懂了。 + +![acm_2.png](images/acm_2.png) + +题目中一个重要的信息就是输入**是按照`y`递增,如果`y`相同则`x`递增的顺序**给出的,所以,对于第`i`颗星星,它的`level`就是之前出现过的星星中,横坐标小于等于`i`的星星的数量。这里用树状数组来记录所有星星的`x`值。 + +代码中有个小细节就是`x++`这是因为`lowbit`不能传值为`0`,否则会陷入死循环。 + +```cpp +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static class FastReader { + public BufferedReader br; + public StringTokenizer token; + + public FastReader(InputStream in) { + br = new BufferedReader(new InputStreamReader(in), 32768); + token = null; + } + + public String next() { + while (token == null || !token.hasMoreTokens()) { + try { + token = new StringTokenizer(br.readLine()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return token.nextToken(); + } + + public int nextInt() { + return Integer.parseInt(next()); + } + } + + static int[] sums; // 注意树状数组中 索引从1开始 + + static int n; + + static int lowbit(int x) { + return x & (-x); + } + + static int query(int i) { + int res = 0; + while (i > 0) { + res += sums[i]; + i -= lowbit(i); + } + return res; + } + + // delta是增量, 不是新值 + static void update(int i, int delta) { + while (i <= 32010) { //注意这里是最大的x,没有记录所以用 32010,不能用n + sums[i] += delta; + i += lowbit(i); + } + } + + // write code + static void solve(InputStream stream) { + FastReader in = new FastReader(stream); + n = in.nextInt(); + sums = new int[32010]; // 用x作为索引 + int[] level = new int[n]; + for (int i = 1; i <= n; i++) { + int x = in.nextInt() + 1; //防止 x == 0的情况,lowbit会陷入死循环 + int y = in.nextInt(); + level[query(x)]++; // 求的是左边的和, 然后这个和就是一个级别,这个级别多了一个 + update(x, 1);// 从x向上都+1 + } + for (int i = 0; i < n; i++) System.out.println(level[i]); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` +*** +### 线段树 + +例题和上一个一样,[**LeetCode - 307. Range Sum Query - Mutable**](https://leetcode.com/problems/range-sum-query-mutable/): 线段树可以用数组来写,也可以用树引用来写。 + +数组方式 + +```java +class NumArray { + + class SegTree { + + int[] tree; + int[] data; + + public SegTree(int[] arr) { + data = new int[arr.length]; + for (int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = new int[4 * arr.length]; //最多需要4 * n + buildTree(0, 0, arr.length - 1); + } + + public void buildTree(int treeIndex, int start, int end) { + if (start == end) { + tree[treeIndex] = data[start]; + return; + } + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + int m = start + (end - start) / 2; + buildTree(treeLid, start, m); + buildTree(treeRid, m + 1, end); + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //区间求和 + } + + public int query(int qL, int qR) { + if (qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR) return -1; + return query(0, 0, data.length - 1, qL, qR); + } + + private int query(int treeIndex, int start, int end, int qL, int qR) { + if (start == qL && end == qR) { + return tree[treeIndex]; + } + int mid = start + (end - start) / 2; + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + + if (qR <= mid) { //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeLid, start, mid, qL, qR); + } else if (qL > mid) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeRid, mid + 1, end, qL, qR); + } else { //在两边都有,查询的结果 合并 + return query(treeLid, start, mid, qL, mid) + //注意是查询 [qL,m] + query(treeRid, mid + 1, end, mid + 1, qR); //查询[m+1,qR] + } + } + + public void update(int index, int val) { + data[index] = val; //首先修改data + update(0, 0, data.length - 1, index, val); + } + + private void update(int treeIndex, int start, int end, int index, int val) { + if (start == end) { + tree[treeIndex] = val; // 最后更新 + return; + } + int m = start + (end - start) / 2; + int treeLid = 2 * treeIndex + 1; + int treeRid = 2 * treeIndex + 2; + if (index <= m) { //左边 + update(treeLid, start, m, index, val); + } else { + update(treeRid, m + 1, end, index, val); + } + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //更新完左右子树之后,自己受到影响,重新更新和 + } + } + + private SegTree segTree; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) return; + segTree = new SegTree(nums); + } + + public void update(int i, int val) { + segTree.update(i, val); + } + + public int sumRange(int i, int j) { + return segTree.query(i, j); + } +} +``` + +树的引用的代码: + +```java +class SegNode{ + int start; // 表示的区间的左端点 + int end; // 表示区间的右端点 , 当start == end的时候就只有一个元素 + int sum; + SegNode left; + SegNode right; + + public SegNode(int start, int end, int sum, SegNode left, SegNode right) { + this.start = start; + this.end = end; + this.sum = sum; + this.left = left; + this.right = right; + } +} + +class NumArray { + + SegNode root; + int[] arr; + + private SegNode buildTree(int s, int e){ + if(s == e) + return new SegNode(s, e, arr[s], null, null); + int mid = s + (e - s) / 2; + SegNode L = buildTree(s, mid); + SegNode R = buildTree(mid+1, e); + return new SegNode(s, e, L.sum + R.sum, L, R); + } + + private void update(SegNode node, int i, int val){ + if(node.start == node.end && node.start == i){ + node.sum = val; + return; + } + int mid = node.start + (node.end - node.start) / 2; + if(i <= mid) + update(node.left, i, val); + else + update(node.right, i, val); + node.sum = node.left.sum + node.right.sum; // 记得下面的更新完之后,更新当前的和 + } + + private int query(SegNode node, int i, int j){ + if(node.start == i && node.end == j) + return node.sum; + int mid = node.start + (node.end - node.start) / 2; + if(j <= mid){ // 区间完全在左边 + return query(node.left, i, j); + }else if(i > mid) { // 区间完全在右边 + return query(node.right, i, j); + }else { + return query(node.left, i, mid) + query(node.right, mid+1, j); + } + } + + public NumArray(int[] nums) { + arr = new int[nums.length]; + for(int i = 0; i < nums.length; i++) arr[i] = nums[i]; + if(nums.length != 0) + root = buildTree(0, nums.length-1); + } + + public void update(int i, int val) { + arr[i] = val; + update(root, i, val); + } + + public int sumRange(int i, int j) { + return query(root, i, j); + } +} +``` + +### KMP,Sunday,BM + +这三个算法解决的问题都是 : 有一个文本串 S,和一个模式串 P,现在要查找 P 在 S 中的位置,三种算法的思路这里限于篇幅不多说,[这篇博客](http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html)对着三种算法讲解的比较详细。 +**`KMP`的较直观的分析也可以看看[我的另一篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81430392)** + +```cpp + +``` + +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1711 +//题目大意 : 找第二个数组在第一个数组中出现的位置,如果不存在,输出-1 + +```cpp +#include + +const int maxn = 1000000 + 10; + +int n,m,a[maxn], b[10000 + 10],nexts[10000 + 10]; + +void getNext(int *p,int next[]) { //优化后的求next数组的方法 + + int len = m; + + next[0] = -1; //next 数组中的 最大长度值(前后缀的公共最大长度) 的第一个 赋值为 -1 + + int k = -1,j = 0; + + while (j < len - 1) { + + if (k == -1 || p[j] == p[k]) { //p[k]表示前缀 p[j] 表示后缀 + + k++; j++; + + if(p[j] != p[k])next[j] = k; + + else next[j] = next[k]; //因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]] + + } + + else k = next[k]; + + } + +} + +int KMPSerach(int *s, int *p) { + + int sLen = n,pLen = m; + + int i = 0, j = 0; + + while (i < sLen && j < pLen) { + + if (j == -1 || s[i] == p[j])i++, j++; + + else j = nexts[j]; + + } + + if (j == pLen)return i - j; + + else return -1; + +} + +int main() { + + int T; + + scanf("%d", &T); + + while (T--) { + + scanf("%d%d", &n, &m); + + for(int i = 0; i < n; i++)scanf("%d", &a[i]); + + for(int i = 0; i < m; i++)scanf("%d", &b[i]); + + getNext(b,nexts); //获取next数组 + + int ans = KMPSerach(a, b); + + if (ans != -1)printf("%d\n", ans + 1); + + else printf("-1\n"); + + } + + return 0; + +} + +``` + +**`BM`算法,主要是根据两个规则去执行** +![这里写图片描述](https://img-blog.csdn.net/20180407140141237?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +​```cpp +//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551 +//题目大意:找一串字符中是否出现"bkpstor"这段字符 + +#include +#include +#include +using namespace std; +const int maxn = 1000; + +int last(char *p, char c) { //找到文本串的 "坏字符" 在模式串中的位置 + for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i; + return -1; +} + +int BM_index(char *T, char *p) { + int n = strlen(T); + int m = strlen(p); + int i = m - 1, j = m - 1; + while (i <= n - 1) { + if (T[i] == p[j]) + if (j == 0)return i; + else i--, j--; + else { + i = i + m - min(j, 1 + last(p, T[i]));//文本的不符合的那个 位置 串移动的步数 + j = m - 1;//模式串的新位置 + } + } + return -1; +} + +int Sum(char *T, char *P, int s) {//输出文本串中包含模式串的数量 + int e = BM_index(T + s, P); + return e == -1 ? 0 : 1 + Sum(T, P, s + e + 1); +} + +//测试数据 : 123bkpstor456bkpstor67 +int main() { + char s[maxn]; + while (~scanf("%s",s)) { + int cnt = BM_index(s, "bkpstor"); + //printf("%d\n",Sum(s,"bkpstor",0));出现次数输出2 + if (cnt == -1)printf("Safe\n"); + else printf("Warning\n"); + } + return 0; +} +``` +`Sunday`算法也是两个规则 +![这里写图片描述](https://img-blog.csdn.net/20180407141122454?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +​```cpp +//题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1551 +//题目大意: 找一串字符中是否出现"bkpstor"这段字符 +#include +#include +#include +const int maxn = 1000; +using namespace std; + +int last(char *p, char c) { + for (int i = strlen(p) - 1; i >= 0; i--)if (p[i] == c)return i; + return -1; +} + +int Sunday(char *s, char *p) { + int sLen = strlen(s); + int pLen = strlen(p); + int i = 0, j = 0; + while (i < sLen && j < pLen) { + if (s[i] == p[j])i++, j++; + else { + int index = i + pLen - j; // 字符串中右端对齐的字符 + if (last(p, s[index]) == -1) { i = index + 1; j = 0; } // 没有在匹配串中出现则直接跳过 + else { + i = index - last(p, s[index]); j = 0; //否则 其移动步长 = 匹配串中最 右端 的该字符到末尾的距离 + 1。 + } + } + } + if (j == pLen)return i - j; + return -1; +} + +int main() { + char s[maxn]; + while (~scanf("%s",s)) { + int cnt = Sunday(s,"bkpstor"); + if (cnt == -1)printf("Safe\n"); + else printf("Warning\n"); + } + return 0; +} + +``` +*** +### 01背包,完全背包 + +背包问题可以分为很多种,这里只简单讨论`01`背包和完全背包, +**后来改写的`01`背包: [博客](https://blog.csdn.net/zxzxzx0119/article/details/82530940)** +参考博客: + +https://blog.csdn.net/liusuangeng/article/details/38374405(很详细) +https://blog.csdn.net/xp731574722/article/details/70766804 +https://blog.csdn.net/na_beginning/article/details/62884939#reply +https://blog.csdn.net/u013445530/article/details/40210587 + +```cpp +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=2602 +#include +#include +#include +using namespace std; +const int maxn = 1000+5; +int w[maxn],v[maxn],dp[maxn][maxn],vis[maxn]; + +//打印解 +void print(int n,int C){ + for(int i = n; i > 1; i--){ + if(dp[i][C] == dp[i-1][C-w[i]] + v[i]){ + vis[i] = 1; + C -= w[i]; + } + } + vis[1] = (dp[1][C] > 0) ? 1: 0; +} + +int main(){ + freopen("in.txt","r",stdin); + int n,C,T; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1;i <= n; i++) scanf("%d",&v[i]); + for(int i = 1;i <= n; i++) scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); //dp[0][0~C]和dp[0~N][0]都为0,前者表示前0个物品无论装入多大的包中总价值都为0,后者表示体积为0的背包都装不进去。 + memset(vis,0,sizeof(vis)); + for(int i = 1; i <= n; i++){ + for(int j = 0;j <= C; j++){ + dp[i][j] = dp[i-1][j]; //如果j不大于v[i]的话就dp[i][j] = dp[i-1][j]; + if(j >= w[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]); + } + } + printf("%d\n",dp[n][C]); //n个物品装入C容量的包能获得的最大价值 + //print(n,C); + //for(int i = 1; i <= n; i++)if(vis[i])printf("%d ",i); //输出选中的物品 + } + return 0; +} +``` + +```cpp +#include +#include +#include +const int maxn = 1000+5; +using namespace std; + +int w[maxn],v[maxn],dp[maxn][maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= n; i++)scanf("%d",&v[i]); + for(int i = 1; i <= n; i++)scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); //dp全部初始化为0 + for(int i = n;i >= 1; i--){ + for(int j = 0; j <= C; j++){ + dp[i][j] = (i == n ? 0 : dp[i+1][j]); + if(j >= w[i])dp[i][j] = max(dp[i][j],dp[i+1][j-w[i]]+v[i]); + } + } + printf("%d\n",dp[1][C]); + } + return 0; +} +``` + +```cpp +#include +#include +#include +const int maxn = 1000+5; +using namespace std; + +int w[maxn],v[maxn],dp[maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= n; i++)scanf("%d",&v[i]); + for(int i = 1; i <= n; i++)scanf("%d",&w[i]); + memset(dp,0,sizeof(dp)); + for(int i = 1; i <= n; i++){ + for(int j = C;j >= 0; j--){ + if(j >= w[i])dp[j] = max(dp[j],dp[j-w[i]]+v[i]); + } + } + printf("%d\n",dp[C]); + } + return 0; +} +``` + +完全背包和`01`背包的差别在于每个物品的数量有无数个,在考虑第`i`件物品的时候,不是考虑选不选这件,而是选多少件可以达到最大价值 +![这里写图片描述](https://img-blog.csdn.net/20180407180915113?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```cpp +for (int i = 1; i < n; i++){ + for (int j = 1; j <= v; j++){ + for (int k = 0; k*c[i] <= j; k++){ +   if(c[i] <= j) dp[i][j] = max{dp[i-1][j],dp[i-1][j - k * c[i]] + k * w[i]};/*要么不取,要么取0件、取1件、取2件……取k件*/ +    else dp[i][j] = dp[i-1][j]/*继承前i个物品在当前空间大小时的价值*/ + } + } +} +``` + +优化: +![这里写图片描述](https://img-blog.csdn.net/20180407181220323?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +注意完全背包的顺序问题 :因为每种背包都是无限的。当我们把i从1到N循环时,dp[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。为01背包中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态dp[i][v]是由状态dp[i-1] [v-c[i]]递推而来。这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的 子结果dp[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果dp[i][v-c[i]],所以就可以并且必须采用v=0..V的顺序循环。 +完全背包还有考虑是否要求恰好放满背包等问题,可以好好研究: +![这里写图片描述](https://img-blog.csdn.net/201804071825020?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +参考博客: +https://blog.csdn.net/Thousa_Ho/article/details/78156678 +https://wenku.baidu.com/view/eea4a76b0b1c59eef8c7b497.html +https://www.cnblogs.com/shihuajie/archive/2013/04/27/3046458.html +```cpp +#include +#include +#include + +using namespace std; +const int maxn = 50000 + 10; +const int INF = -0X7ffff; +int w[maxn],v[maxn],dp[maxn]; + +int main(){ + int T,n,C; + scanf("%d",&T); + while(T--){ + scanf("%d%d",&n,&C); + for(int i = 1; i <= C; i++)dp[i] = INF; + dp[0] = 0; + for(int i = 0; i < n; i++)scanf("%d%d",&w[i],&v[i]); + for(int i = 0; i < n; i++){ + for(int j = w[i]; j <= C; j++){ //从前往后递推,这样才能保证一种物品可以被使用多次 + dp[j] = max(dp[j],dp[j-w[i]] + v[i]); + } + } + if(dp[C] > 0)printf("%d\n",dp[C]); + else printf("NO\n"); + } + return 0; +} +``` +### 最长(不)上升或下降子序列 +[Java编写与解释的博客](https://blog.csdn.net/zxzxzx0119/article/details/81224734)。 +先说`O(N*N)`的解法,第`i`个元素之前的最长递增子序列的长度要么是`1`(单独成一个序列),要么就是第`i-1`个元素之前的最长递增子序列加`1`,这样得到状态方程: +```java +LIS[i] = max{1,LIS[k]+1} (∀k arr[k]) +``` +这样`arr[i]`才能在`arr[k]`的基础上构成一个新的递增子序列。 +```cpp +//题目连接 : http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=17 +#include +#include +#include +using namespace std; +const int maxn = 10000 + 10; + +int dp[maxn]; /* dp[i]记录到[0,i]数组的LIS */ +int maxx;/* LIS长度,初始化为1 */ + +int LIS(char *arr, int n) { + for (int i = 0; i < n; i++) { + dp[i] = 1; + for (int j = 0; j < i; j++) // 注意i只遍历比它小的元素 + if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1); //改成arr[j] <= arr[i]就可以求非减的 + maxx = max(maxx, dp[i]); + } + return maxx; +} + +/* 递归输出LIS,因为数组dp还充当了“标记”作用 */ +void printLis(char *arr, int index) { + bool isLIS = false; + if (index < 0 || maxx == 0)return; + if (dp[index] == maxx) { + --maxx; + isLIS = true; + } + printLis(arr, --index); + if (isLIS) printf("%c ", arr[index + 1]); +} + +int main() { + char s[maxn]; + int n; + scanf("%d\n",&n); + while(n--){ + maxx = 1; + scanf("%s",s); + printf("%d\n",LIS(s,strlen(s))); + //printLis(s,strlen(s)-1);printf("\n"); + } + return 0; +} +``` +然后就是`nlog(n)`的解法: +参考博客: +https://segmentfault.com/a/1190000002641054 +https://blog.csdn.net/joylnwang/article/details/6766317 + +```cpp +//题目连接 : http://poj.org/problem?id=3903 +#include +#include +#include +#include +using namespace std; + +const int INF = 0x3f3f3f3f; +const int maxn = 100000 + 5; +int a[maxn], dp[maxn], pos[maxn], fa[maxn]; +vector ans; + +//用于最长非递减子序列种的lower_bound函数 +int cmp(int a,int b){ + return a <= b; +} + +//最长上升子序列 +//dp[i]表示长度为i+1的上升子序列的最末尾元素 找到第一个比dp末尾大的来代替 +int LIS(int n){ + memset(dp, 0x3f, sizeof(dp)); + pos[0] = -1; + int i,lpos; + for (i = 0; i < n; ++i){ + dp[lpos = (lower_bound(dp, dp + n, a[i]) - dp)] = a[i]; + pos[lpos] = i; // *靠后打印 + fa[i] = (lpos ? pos[lpos - 1] : -1); + } + n = lower_bound(dp, dp + n, INF) - dp; + for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]); + ans.push_back(a[i]); + return n; +} + +//非递减的LIS +int LISS(int n){ + memset(dp, 0x3f, sizeof(dp)); + pos[0] = -1; + int i,lpos; + for (i = 0; i < n; i++){ + dp[lpos = (lower_bound(dp, dp + n, a[i],cmp) - dp)] = a[i]; //注意这里cmp + pos[lpos] = i; // *靠后打印 + fa[i] = (lpos ? pos[lpos - 1] : -1); + } + n = lower_bound(dp, dp + n, INF) - dp; + for (i = pos[n - 1]; ~fa[i]; i = fa[i]) ans.push_back(a[i]); + ans.push_back(a[i]); + return n; +} + +int main(){ + // freopen("in.txt","r",stdin); + int n; + while(~scanf("%d",&n)){ + ans.clear(); + for(int i = 0;i < n; i++)scanf("%d",&a[i]); + printf("%d\n",LIS(n)); + // for(int i = ans.size()-1; i >= 0; i--) printf("%d ",ans[i]);printf("\n"); //路径打印 + } + return 0; +} + +``` +### 最长公共子序列 +最长公共子序列利用动态规划的方式解决,具有最优子结构性质,和重叠子问题性质,`dp[i][j]`记录序列`Xi`和`Yi`的最长公共子序列的长度,当`i = 0`或`j =0` 时,空序列时`Xi`和`Yi`的最长公共子序列,其余情况如下 +![这里写图片描述](https://img-blog.csdn.net/20180407210404162?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +这里也给出一篇讲的[好的博客](https://blog.csdn.net/hrn1216/article/details/51534607) + +```cpp +//题目连接 : http://poj.org/problem?id=1458 +#include +#include +#include +using namespace std; +const int maxn = 1000 + 10; +int dp[maxn][maxn]; +int path[maxn][maxn]; //记录路径 + +int LCS(char *s1,char *s2){ + int len1 = strlen(s1)-1,len2 = strlen(s2)-1;//注意例如 abcfbc的strlen(s1)为7,所以是strlen(s1) - 1 + for(int i = 0; i <= len1; i++) dp[i][0] = 0; + for(int i = 0; i <= len2; i++) dp[0][i] = 0; + for(int i = 1; i <= len1; i++){ + for(int j = 1; j <= len2; j++) + if(s1[i] == s2[j]){ + dp[i][j] = dp[i-1][j-1] + 1; + path[i][j] = 1; + } + else if(dp[i-1][j] >= dp[i][j-1]) { + dp[i][j] = dp[i-1][j]; + path[i][j] = 2; + } + else { + dp[i][j] = dp[i][j-1]; + path[i][j] = 3; + } + } + return dp[len1][len2]; +} + +//打印解 +void print(int i,int j,char *s){ + if(i == 0 || j == 0)return ; + if(path[i][j] == 1){ + print(i-1,j-1,s); + printf("%c ",s[i]); + }else if(path[i][j] == 2){ + print(i-1,j,s); + }else print(i,j-1,s); +} + +int main(){ + char s1[maxn],s2[maxn]; + while(~scanf("%s%s",s1+1,s2+1)){ //注意s1[0]不取-注意例如 abcfbc的strlen(s1)为7 + memset(path,0,sizeof(path)); + printf("%d\n",LCS(s1,s2)); + //print(strlen(s1)-1,strlen(s2)-1,s1); + } + return 0; +} + +``` +*** +### 拓扑排序 +有向无环图上的一种排序方式,我的[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81533388)也讲解了一下。可以两种写法: + +```cpp +//题目链接 : https://vjudge.net/contest/169966#problem/O +#include + +using namespace std; +const int maxn = 100 + 10; + +int n, m; +int in[maxn]; +vectorG[maxn]; + +void topo(){ + queueq; + for(int i = 1; i <= n ; i++) + if(in[i] == 0) + q.push(i); + bool flag = true; + while(!q.empty()){ + int cur = q.front(); + q.pop(); + if(flag){ + printf("%d",cur); + flag = false; + } + else + printf(" %d",cur); + for(int i = 0; i < G[cur].size(); i++){ + if(--in[G[cur][i]] == 0) + q.push(G[cur][i]); + } + } +} + +int main(int argc, char const **argv) +{ + int from, to; + while(~scanf("%d%d", &n, &m) && (n || m)){ + memset(in, 0, sizeof(in)); + for(int i = 1; i <= n; i++) + G[i].clear(); + for(int i = 0; i < m; i++){ + scanf("%d%d", &from, &to); + in[to]++; //度 + G[from].push_back(to); + } + topo(); + printf("\n"); + } + return 0; +} + +``` + +```cpp +#include +#include +#include +using namespace std; +#define maxn 5005 + +typedef struct { ///邻接表实现 + int next_arc;///下一条边 + int point; +} Arc; ///边的结构体, + +Arc arc[maxn];///储存每条边 +int node[maxn],vis[maxn],top[maxn];///储存每个顶点,node[i]表示第i个顶点指向的第一条边在arc中的位置 +int n, m, t; + +void dfs(int v) { + for (int i = node[v]; i != -1; i = arc[i].next_arc) { + if (!vis[arc[i].point]) { + dfs(arc[i].point); + } + } + vis[v] = 1; + top[--t] = v; +} + + +int main() { + int a, b; + while (cin >> n >> m && (m || n)) { + memset(node, -1, sizeof(node)); + memset(vis, 0, sizeof(vis)); + for (int i = 1; i <= m; i++) { + cin >> a >> b; + arc[i].next_arc = node[a];///第一次是出发点,以后是下一个 + arc[i].point = b; + node[a] = i; + vis[arc[i].point] = 0; + } + t = n; + for (int j = 1; j <= n; j++) if (!vis[j]) dfs(j); + for (int i = 0; i < n - 1; i++) + cout << top[i] << " "; + cout << top[n - 1] << endl; + } + return 0; +} +``` +### 欧拉路径和回路 + +首先看定义 : + + 欧拉回路: + + - (1) 图`G`是连通的,不能有孤立点存在。 + - (2) 对于无向图来说度数为奇数的点个数为`0`,对于有向图来说每个点的入度必须等于出度。 + +欧拉路径: + + - (1) 图`G`是连通的,无孤立点存在。 + - (2) 对于无向图来说,度数为奇数的的点可以有`2`个或者`0`个,并且这两个奇点其中一个为起点另外一个为终点。对于有向图来说,可以存在两个点,其入度不等于出度,其中一个入度比出度大`1`,为路径的起点;另外一个出度比入度大`1`,为路径的终点。 + 判断连通的方式有`DFS`或者并查集,然后再判断一下是否满足条件即可,之前做的欧拉回路的几个好题在我的[github仓库](https://github.com/ZXZxin/ACM/tree/master/%E5%9B%BE%E8%AE%BA/%E6%AC%A7%E6%8B%89%E5%9B%9E%E8%B7%AF) + +```cpp +#include + +const int maxn = 1000 + 5; +using namespace std; + +bool vis[maxn]; +int in[maxn]; +int n, m; +vectorG[maxn]; + +void dfs(int u){ + vis[u] = 1; + for(int i = 0; i < G[u].size(); i++){ + if(!vis[G[u][i]]) + dfs(G[u][i]); + } +} + +int main(){ + //freopen("in.txt","r",stdin); + int from, to; + while(scanf("%d", &n) != EOF && n){ + scanf("%d", &m); + memset(vis, 0, sizeof(vis)); + memset(in, false, sizeof(in)); + for(int i = 1; i <= n; i++) + G[i].clear(); + bool flag = true; + for(int i = 0; i < m; i++){ + scanf("%d%d",&from, &to); + G[from].push_back(to); + G[to].push_back(from); + in[from]++; + in[to]++; + } + dfs(1); + for(int i = 1; i <= n; i++) + if(in[i] % 2 != 0) + flag = false; + for(int i = 1; i <= n; i++) + if(!vis[i]) + flag = false; + cout << (flag ? 1 : 0) << endl; + } + return 0; +} + +``` + +```cpp + +#include + +const int maxn = 1000 + 5; +using namespace std; + +int n, m, parent[maxn],ranks[maxn],in[maxn]; + +void init(){ + for(int i = 0; i < maxn; i++) + parent[i] = i; + for(int i = 0; i < maxn; i++) + ranks[i] = 1; +} + +//int findRoot(int v){ +// return parent[v] == v ? v : parent[v] = findRoot(parent[v]); +//} + +int findRoot(int v){ + while(parent[v] != v){ + parent[v] = parent[parent[v]]; + v = parent[v]; + } + return v; +} + +void unions(int a, int b){ + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + if (ranks[aRoot] < ranks[bRoot]) + parent[aRoot] = bRoot; + else if(ranks[aRoot] > ranks[bRoot]){ + parent[bRoot] = aRoot; + }else { + parent[aRoot] = bRoot; + ranks[bRoot]++; + } +} + + +int main(){ + //freopen("in.tat","r",stdin); + int u, v; + while(scanf("%d", &n) != EOF && n){ + init(); + scanf("%d", &m); + memset(in,0,sizeof(in)); + for(int i = 0; i < m; i++){ + scanf("%d%d",&u, &v); + unions(u, v); + in[u]++; + in[v]++; + } + bool flag = true; + int temp = findRoot(1); + for(int i = 1; i <= n; i++) + if(findRoot(i) != temp) + flag = false; + for(int i = 1; i <= n; i++) + if(in[i] % 2 != 0) + flag = false; //判断度 + cout << (flag ? 1 : 0) << endl; + } + return 0; +} + +``` +### 搜索 + +这里举几个比较好的题目: [POJ3984](http://poj.org/problem?id=3984),这个题目要求记录BFS最短路的路径,我这里使用三种方法: + + - BFS+在结构体中记录路径 + - BFS+记录路径 + - DFS加回溯法 + +```cpp +//题目链接;http://poj.org/problem?id=3984 +//题目大意:给你一个5*5的矩阵,让你找一条路,从左上点,到右下点 +//解题思路:利用BFS求解最短路,利用vector记录路径 + +//BFS+在结构体中记录路径 +#include +#include +#include +#include +#include +#include +#pragma warning(disable : 4996) +using namespace std; +const int maxn = 100 + 10; + +int n; +int map[maxn][maxn]; +bool vis[maxn][maxn]; +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};//对应上,右,下,左 + +struct Road {//路径记录 + int x,y,d;//d表示方向 + Road() {}//默认的构造函数 + Road(int a,int b,int c):x(a),y(b),d(c) {}//带参数的构造函数 +}; + +struct node {//节点类型 + int x,y; + vector v;//记录路径的结构体 +}; + +bool check(node u) { + if (!vis[u.x][u.y] && u.x >= 0 && u.x < n && u.y >= 0 && u.y < n && map[u.x][u.y] != 1) + return true; + else + return false; +} + +void BFS(int x, int y) { + queueq; + node now,next; + now.x = x; + now.y = y; + now.v.push_back(Road(x,y,-1)); + q.push(now); + while (!q.empty()) { + now = q.front(); + q.pop(); + if (now.x == n - 1 && now.y == n-1) { + for(int i = 0; i < now.v.size(); i++){ + printf("(%d, %d)\n",now.v[i].x,now.v[i].y); + } + break; + } + for (int i = 0; i < 4; i++) { + next.x = now.x + dir[i][0]; + next.y = now.y + dir[i][1]; + if (check(next)) { + vis[next.x][next.y] = true; + next.v = now.v; + next.v[now.v.size()-1].d = i; + next.v.push_back(Road(next.x,next.y,-1)); + q.push(next); + } + } + } +} + +int main() { + //freopen("in.txt", "r", stdin); + flag = false; + n = 5; + memset(vis, 0, sizeof(vis)); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + scanf("%d", &map[i][j]); + } + } + BFS(0, 0); + return 0; +} +``` + +```cpp +//BFS+记录路径 +#include +#include +#include + +using namespace std; +const int maxn = 100 + 10; + +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}}; +bool vis[maxn][maxn]; +int map[maxn][maxn]; + +struct Node { + int x,y,pre; +}; + +Node m_queue[maxn]; + +bool Check(int x,int y){ + if(!map[x][y] && !vis[x][y] && x >= 0 && x < 5&&y >= 0 && y < 5)return true; + else return false; +} + +void print(int u){ + if(m_queue[u].pre != -1){ + print(m_queue[u].pre); + printf("(%d, %d)\n",m_queue[u].x,m_queue[u].y); + } +} + +void BFS(int x,int y){ + int head = 0,rear = 1; + m_queue[head].x = x; + m_queue[head].y = y; + m_queue[head].pre = -1; + vis[x][y] = true; + while(head < rear){ + for(int i = 0; i < 4; i++){ + int xx = m_queue[head].x + dir[i][0]; + int yy = m_queue[head].y + dir[i][1]; + if(Check(xx,yy)){ + vis[xx][yy] = 1; + m_queue[rear].x = xx; + m_queue[rear].y = yy; + m_queue[rear].pre = head; + rear++; + } + if(xx == 4&&yy == 4)print(rear - 1); + } + head++;//出队 + } +} + + +int main(){ + //freopen("in.txt","r",stdin); + int n = 5; + memset(vis,0,sizeof(vis)); + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + scanf("%d",&map[i][j]); + } + } + printf("(0, 0)\n"); + BFS(0,0); + return 0; +} +``` + +```cpp +//DFS加回溯法 +#include +#include +#include +#include +#include +#include + +using namespace std; +const int maxn = 100+10; +const int INF = 0x3f3f3f3f; + +int n,k,ans = INF; +int map[maxn][maxn]; +bool vis[maxn][maxn]; +int dir[4][2] = {{-1,0},{0,1},{1,0},{0,-1}}; + +struct node { + int x,y; +}; +vectordict; +stacks; + +void memcpy(stacks){ + dict.clear(); + while(!s.empty()){ + dict.push_back(s.top()); + s.pop(); + } +} + +bool Check(int x,int y){ + if(!vis[x][y] && map[x][y] != 1 && x >= 0 && x < n && y >= 0 && y < n)return true; + else return false; +} + +void DFS(int x,int y,int step){//深刻理解递归的含义 + node now; + now.x = x; + now.y = y; + s.push(now); + if(now.x == n - 1 &&now.y == n - 1){ + if(step < ans){//记录最短的路径 + ans = step; + memcpy(s); + } + } + for(int i = 0; i < 4; i++){ + int xx = now.x + dir[i][0]; + int yy = now.y + dir[i][1]; + if(Check(xx,yy)){ + vis[xx][yy] = true; + DFS(xx,yy,step + 1); + vis[xx][yy] = false;//回溯 + } + } + s.pop();//反弹的时候重新还原栈 +} + +int main(){ + //freopen("in.txt","r",stdin); + n = 5; + memset(vis,false,sizeof(vis)); + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + scanf("%d",&map[i][j]); + } + } + DFS(0,0,0); + for(int i = dict.size() - 1; i >= 0; i--){ + printf("(%d, %d)\n",dict[i].x,dict[i].y); + } + return 0; +} +``` +这里再给出DFS的一个好题,印象比较深刻的 + +```cpp +//题目链接:https://vjudge.net/contest/169966#problem/V +//题目大意: 有一个正方形的 战场看,边长为1000,西南方向在坐标原点,战场上有n 个敌人,坐标x,y,攻击范围y + //要你从西边界出发,东边界离开,求西边界和东边界上的坐标,坐标要尽量靠北,如果不能到达东边界输出IMPOSSIBLE +//解题思路: 按照 刘汝佳书上的思路,先判断有无解,从上边界开始DFS如果可以一直到达下边界,则相当于堵住了,无解 + //否则,也是从上边界开始,dfs和东西边界 相连的圆(如果没有就是1000),找到最南边的圆的 与边界相交的那个点就 0k + //主要还是DFS的运用 +#include +#include +#include +#include +#include + +const int maxn = 1000 + 10; +const double W = 1000.0; +using namespace std; + +double x[maxn], y[maxn], r[maxn],Left,Right; +bool vis[maxn]; +int n; + +bool isinterect(int u, int v) { + return sqrt((x[u] - x[v])*(x[u] - x[v]) + (y[u] - y[v])*(y[u] - y[v])) <= r[u] + r[v]; +} + +void check_circle(int u) { + if (x[u] <= r[u])Left = min(Left, y[u] - sqrt(r[u] * r[u] - x[u] * x[u])); + if (x[u] + r[u] >= W)Right = min(Right, y[u] - sqrt(r[u] * r[u] - (W-x[u]) * (W-x[u]))); +} + +bool dfs(int u) {// 检查是否有解 + if (vis[u] == true)return false; + vis[u] = true; + if (y[u] <= r[u])return true; //刚好碰到边界 以及 相切都可以 + for (int i = 0; i < n; i++)if (isinterect(u,i) && dfs(i))return true;// if (&& dfs(i) && isinterect(u,i) )是错的,要先判断是否相交然后再dfs + check_circle(u); + return false; +} + +int main() { + //freopen("in.txt", "r", stdin); + while (scanf("%d", &n) != EOF) { + memset(vis, false, sizeof(vis)); + Left = Right = W; + bool flag = true; + for (int i = 0; i < n; i++) { + scanf("%lf%lf%lf", &x[i], &y[i], &r[i]); + } + for (int i = 0; i < n; i++) { + if (y[i] + r[i] >= W) { //从上往下 DFS + if (dfs(i)) { flag = false; break; }//如果能够从最上面 DFS 到最下面 则无解 + } + } + if (flag) printf("0.00 %.2lf 1000.00 %.2lf\n", Left,Right); + else printf("IMPOSSIBLE\n"); + } + return 0; +} +``` +### 最小生成树 +直接给出模板`prim`和`kruskal`(`prim`是堆优化的) + +另外,我的另外[一篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81540155)也总结了这两个算法。 + + +```cpp +//Prim模板 +//题目链接 : http://poj.org/problem?id=1287 +#include +#include +#include +#include + +using namespace std; +const int maxn = 100 + 10; +const int INF = 1<<30; + +struct Node{ + int v,w; + Node (){} + Node (int v,int w):v(v),w(w){} + bool operator < (const Node&rhs ) const { + return rhs.w < w; + } +}; + +int n,d[maxn]; +bool vis[maxn]; +int Map[maxn][maxn]; + +void init(){ + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; +} + +int Prim(int s){ + priority_queueq; + q.push(Node(s,0)); + int ans = 0; + while(!q.empty()){ + Node Now = q.top(); q.pop(); + int v = Now.v; + if(vis[v]) continue; + ans += Now.w; + vis[v] = true; + for(int i = 1; i <= n; i++){ + int w2 = Map[v][i]; + if(!vis[i] && d[i] > w2){ + d[i] = w2; + q.push(Node(i,d[i])); + } + } + } + return ans; +} + +int main(){ + //freopen("in.txt","r",stdin); + int m,a,b,c; + while(~scanf("%d",&n)&&n){ + scanf("%d",&m); + init(); + for(int i = 1; i <= n; i++){ + Map[i][i] = INF; + for(int j = 1; j < i; j++)Map[i][j] = Map[j][i] = INF; + } + for(int i = 0; i < m; i++){ + scanf("%d%d%d",&a,&b,&c); + if(c < Map[a][b])Map[a][b] = Map[b][a] = c; //注意重边,取小的 + } + printf("%d\n",Prim(1)); + } + return 0; +} +``` +```cpp +//题目链接 : http://poj.org/problem?id=1287 +//kruskal模板 +#include +#include +#include +#include + +using namespace std; +const int maxn = 1e5 + 10; + +int Fa[maxn],Rank[maxn]; + +void init(){ + for(int i = 0; i <= maxn; i++)Fa[i] = i; + for(int i = 0; i <= maxn; i++)Rank[i] = 1; +} + +int Find(int v){ + return Fa[v] == v ? v : Fa[v] = Find(Fa[v]); +} + +void Union(int x, int y){ + int fx = Find(x); + int fy = Find(y); + if (fx == fy)return; + if (Rank[fx] < Rank[fy]) + Fa[fx] = fy; + else{ + Fa[fy] = fx; + if (Rank[fx] == Rank[fy])Rank[fy]++; + } +} + +struct Edge{ + int u,v,w; + Edge(){} + Edge(int u,int v,int w):u(u),v(v),w(w){} +}edge[maxn*2]; + + +int cmp(const void *a,const void *b){ + Edge *aa = (Edge*)a; + Edge *bb = (Edge*)b; + return aa->w > bb->w ? 1 : -1; //降序 +} + +int krusal(int n,int m){ + qsort(edge,m,sizeof(edge[0]),cmp); + int ans = 0; + int cnt = 0; + for(int i = 0; i < m; i++){ + int u = edge[i].u; + int v = edge[i].v; + if(Find(u) != Find(v)){ + Union(u,v); + cnt++; + ans += edge[i].w; + } + if(cnt == n-1)break; + } + return ans; +} + +int main(){ + //freopen("in.txt","r",stdin); + int n,m; + while(~scanf("%d",&n)&&n){ + scanf("%d",&m); + init(); + for(int i = 0; i < m; i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w); //注意这题有重边但是没事 + printf("%d\n",krusal(n,m)); + } + return 0; +} +``` +### 最短路 +`dijkstra`,`bellman_ford`,`floyd`,`SPFA` +最经典的[Hdu1874畅通工程续](http://acm.hdu.edu.cn/showproblem.php?pid=1874) + +```cpp +//堆优化dijkstra +#include +using namespace std; +const int maxn = 1e4 + 10; +const int INF = 1e9; + +struct Node{ + int v,w; + Node(int v,int w):v(v),w(w){} + bool operator < (const Node&rhs) const { + return rhs.w < w; + } +}; + +vectorG[maxn]; +bool vis[maxn]; +int d[maxn]; +int n,m; + +void init(){ + for(int i = 0; i < maxn; i++)G[i].clear(); + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; +} + +int dijkstra(int s,int e){ //传入起点终点 + priority_queueq; + q.push(Node(s,0)); + d[s] = 0; + while(!q.empty()){ + Node now = q.top(); q.pop(); + int v = now.v; + if(vis[v])continue; + vis[v] = true; + for(int i = 0; i < G[v].size(); i++){ + int v2 = G[v][i].v; + int w = G[v][i].w; + if(!vis[v2] && d[v2] > w+d[v]){ + d[v2] = w+d[v]; + q.push(Node(v2,d[v2])); + } + } + } + return d[e]; +} + +int main(){ + //freopen("in.txt","r",stdin); + int s,e; + while(~scanf("%d%d",&n,&m)){ + init(); + for(int i = 0; i < m; i++){ + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + G[x].push_back(Node(y,z)); + G[y].push_back(Node(x,z)); + } + scanf("%d%d",&s,&e); + int ans = dijkstra(s,e); + if(INF != ans)printf("%d\n",ans); + else printf("-1\n"); + } + return 0; +} +``` + +```cpp +#include +const int maxn = 1000; +#define INF 0x1f1f1f1f +using namespace std; +bool flag[maxn]; +int graph[maxn][maxn],low[maxn]; + +void DIJ(int n, int s) { + memset(flag, false, sizeof(flag)); + flag[s] = true; + for (int i = 0; i < n; i++)low[i] = graph[s][i]; + for (int i = 1; i < n; i++) { + int min = INF, p; + for (int j = 0; j < n; j++) + if (!flag[j] && min > low[j]) { + min = low[j]; + p = j; + } + flag[p] = true; + for (int j = 0; j < n; j++) + if (!flag[j] && low[j] > graph[p][j] + low[p]) + low[j] = graph[p][j] + low[p]; + } +} + +int main() { + int n, m, begin, t; + while (~scanf("%d%d",&n,&m)) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 1; i <= m; i++) { + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + if (z < graph[x][y]) graph[x][y] = graph[y][x] = z; + } + cin >> begin >> t; + DIJ(n, begin); + if (low[t] < INF) cout << low[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +#include +#define INF 0x1f1f1f1f +using namespace std; +const int maxn = 1000; +vector >Edge[maxn]; + +int n, m, dist[maxn]; +void init() { + for (int i = 0; i < maxn; i++)Edge[i].clear(); + for (int i = 0; i < maxn; i++)dist[i] = INF; +} + +int main() { + int s, t; + while (cin >> n >> m) { + init(); + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + Edge[x].push_back(make_pair(y, z)); + Edge[y].push_back(make_pair(x, z)); + } + cin >> s >> t; + priority_queue>q; + dist[s] = 0; + q.push(make_pair(-dist[s], s)); + while (!q.empty()) { + int now = q.top().second; + q.pop(); + for (int i = 0; i < Edge[now].size(); i++) { + int v = Edge[now][i].first; + if (dist[v] > dist[now] + Edge[now][i].second) { + dist[v] = dist[now] + Edge[now][i].second; + q.push(make_pair(-dist[v], v)); + } + } + } + if (dist[t] < INF)cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(3)bellman_ford +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +struct Edge { + int u, v; + int weight; +}; +Edge edge[maxn * 2]; + +int dist[maxn]; + +void bellman_ford(int s, int n, int m) { + for (int i = 0; i < n; i++) dist[i] = INF; + dist[s] = 0; + for (int i = 0; i < n; i++) + for (int j = 0; j < 2 * m; j++) + if (dist[edge[j].u] > dist[edge[j].v] + edge[j].weight) + dist[edge[j].u] = dist[edge[j].v] + edge[j].weight; +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < m; i++) { + scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].weight); + edge[i + m].v = edge[i].u; + edge[i + m].u = edge[i].v; + edge[i + m].weight = edge[i].weight; + } + cin >> s >> t; + bellman_ford(s, n, m); + if (dist[t] < INF) cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(4)floyd +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +int graph[maxn][maxn]; +int low[maxn]; + +void floyd(int n) { + for (int k = 0; k < n; k++) + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]); +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 1; i <= m; i++) { + int x, y, z; + scanf("%d%d%d", &x, &y, &z); + if (graph[x][y] > z) graph[x][y] = graph[y][x] = z; + } + floyd(n); + scanf("%d%d", &s, &t); + if (graph[s][t] < INF) cout << graph[s][t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` + +```cpp +//(5)SPFA +#include +#define maxn 1000 +#define INF 0x1f1f1f1f +using namespace std; + +int graph[maxn][maxn],flag[maxn],dist[maxn]; + +void SPFA(int s, int n) { + queueq; + memset(flag, false, sizeof(flag)); + for (int i = 0; i < n; i++) dist[i] = INF; + q.push(s); + dist[s] = 0; + flag[s] = true; + while (!q.empty()) { + int temp = q.front(); + q.pop(); + flag[temp] = false; + for (int i = 0; i < n; i++) { + if (dist[i] > dist[temp] + graph[temp][i]) { + dist[i] = dist[temp] + graph[temp][i]; + if (!flag[i]) { + q.push(i); + flag[i] = true; + } + } + } + } +} + +int main() { + int n, m, s, t; + while (cin >> n >> m) { + for (int i = 0; i < n; i++) + for (int j = 0; j < n; j++) + if (i == j) + graph[i][j] = 0; + else + graph[i][j] = INF; + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + if (z < graph[x][y]) graph[x][y] = graph[y][x] = z; + } + cin >> s >> t; + SPFA(s, n); + if (dist[t] < INF) cout << dist[t] << endl; + else cout << "-1" << endl; + + } + return 0; +} + +``` + +```cpp +//(6)pair+SPFA +#include +#define INF 0x1f1f1f1f +using namespace std; + +const int maxn = 1000; +vector >Edge[maxn]; + +int n, m,dist[maxn]; +bool flag[maxn]; + +void init() { + for (int i = 0; i < maxn; i++)Edge[i].clear(); + for (int i = 0; i < maxn; i++)flag[i] = false; + for (int i = 0; i < maxn; i++)dist[i] = INF; +} + +int main() { + int s, t; + while (cin >> n >> m) { + init(); + for (int i = 0; i < m; i++) { + int x, y, z; + cin >> x >> y >> z; + Edge[x].push_back(make_pair(y, z)); + Edge[y].push_back(make_pair(x, z)); + } + cin >> s >> t; + queueq; + dist[s] = 0; + flag[s] = true; + q.push(s); + while (!q.empty()) { + int now = q.front(); + q.pop(); + flag[now] = false; + for (int i = 0; i < Edge[now].size(); i++) { + int v = Edge[now][i].first; + if (dist[v] > dist[now] + Edge[now][i].second) { + dist[v] = dist[now] + Edge[now][i].second; + if (!flag[Edge[now][i].first]) { + q.push(Edge[now][i].first); + flag[Edge[now][i].first] = true; + } + } + } + } + if (dist[t] < INF)cout << dist[t] << endl; + else cout << "-1" << endl; + } + return 0; +} +``` +这里给出一个比较特殊的最短路[题目](https://www.patest.cn/contests/gplt/L2-001) +> 1.最短路的条数 + 2.多条时选择消防员(最短路经过的点的那些权值)多的那一条 + 3.记录路径 +```cpp +#include +using namespace std; +const int maxn = 1e4 + 10; +const int INF = 1e9; +typedef long long LL; + +struct Node{ + int v,w; + Node(int v,int w):v(v),w(w){} + bool operator < (const Node&rhs) const { + return rhs.w < w; + } +}; + +vectorG[maxn]; +bool vis[maxn]; +int Pa[maxn],Weight[maxn],TotalWeight[maxn],TotalRoad[maxn]; +int d[maxn]; +int n,m,s,e; + +void init(){ + for(int i = 0; i < maxn; i++)G[i].clear(); + for(int i = 0; i < maxn; i++)vis[i] = false; + for(int i = 0; i < maxn; i++)d[i] = INF; + for(int i = 0; i < maxn; i++)Pa[i] = -1; //记录路径 + for(int i = 0; i < maxn; i++)TotalRoad[i] = 0; + for(int i = 0; i < maxn; i++)TotalWeight[i] = 0; +} + +void PrintPath(int x){ + if(Pa[x] == -1) printf("%d",x); + else { + PrintPath(Pa[x]); + printf(" %d",x); + } +} + +int dijkstra(int s,int e){ //传入起点终点 + priority_queueq; + q.push(Node(s,0)); + d[s] = 0, Pa[s] = -1, TotalWeight[s] = Weight[s],TotalRoad[s] = 1; + while(!q.empty()){ + Node now = q.top(); q.pop(); + int v = now.v; + if(vis[v])continue; + vis[v] = true; + for(int i = 0; i < G[v].size(); i++){ + int v2 = G[v][i].v; + int w = G[v][i].w; + if(!vis[v2] && d[v2] > w+d[v]){ + d[v2] = w + d[v]; + TotalWeight[v2] = TotalWeight[v] + Weight[v2]; + Pa[v2] = v; + TotalRoad[v2] = TotalRoad[v]; + q.push(Node(v2,d[v2])); + } + else if(!vis[v2] && d[v2] == w+d[v]){ + if(TotalWeight[v2] < TotalWeight[v] + Weight[v2]){ //如果消防员个数更多 + TotalWeight[v2] = TotalWeight[v] + Weight[v2]; + Pa[v2] = v; + //q.push(Node(v2,d[v2]));不需要入队了 + } + TotalRoad[v2] += TotalRoad[v]; //加上之前的条数 + } + } + } + return d[e]; +} + +int main(){ + //freopen("in.txt","r",stdin); + int a,b,c; + scanf("%d%d%d%d",&n,&m,&s,&e); + for(int i = 0; i < n; i++)scanf("%d",&Weight[i]); + init(); + for(int i = 0; i < m; i++){ + scanf("%d%d%d",&a,&b,&c); + G[a].push_back(Node(b,c)); + G[b].push_back(Node(a,c)); + } + int ans = dijkstra(s,e); + //if(INF != ans)printf("%d\n",ans); else printf("-1\n"); + printf("%d %d\n",TotalRoad[e],TotalWeight[e]); + PrintPath(e); + printf("\n"); + return 0; +} +``` +### GCD和LCM +注意一下`n`个数的`GCD`和`LCM` + +```cpp +//题目连接 : http://acm.hdu.edu.cn/showproblem.php?pid=1019 +#include +using namespace std; +const int maxn = 100 + 10; + +int gcd(int a,int b){ + return b == 0?a:gcd(b,a%b); +} + +int lcm(int a,int b){ + return a/gcd(a,b)*b; +} + +int ngcd(int v[],int n){ + if(n == 1) return v[0]; + return gcd(v[n-1],ngcd(v,n-1)); +} + +int nlcm(int v[],int n){ + if(n == 1) return v[0]; + return lcm(v[n-1],nlcm(v,n-1)); +} +int main(){ + int n,m,a[maxn]; + scanf("%d",&n); + while(n--){ + scanf("%d",&m); + for(int i = 0; i < m; i++)scanf("%d",&a[i]); + //printf("%d\n",ngcd(a,m)); + printf("%d\n",nlcm(a,m)); + } + return 0; +} +``` +### 埃拉托斯特尼筛法 +**素数和分解定理等看[另一篇博客总结](https://blog.csdn.net/zxzxzx0119/article/details/82810246)。** +比较经典的快速筛素数,给个[例题](http://poj.org/problem?id=3126),用`BFS`和筛素数解决 + +```cpp +#include +#include +#include +using namespace std; +const int maxn = 10000 + 10; +int prime[maxn],s,e,vis[maxn]; +bool is_prime[maxn],flag; + +//素数筛选的模板 : |埃式筛法| +int Sieve(int n) { + int k = 0; + for(int i = 0; i <= n; i++)is_prime[i] = true; + is_prime[0] = false,is_prime[1] = false; + for(int i = 2; i <= n; i++) { + if(is_prime[i]) { + prime[k++] = i; + for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数 + } + } + return k; +} + +struct Node { + int v,step; + Node(){} + Node(int v,int step):v(v),step(step){} +}; + +void cut(int n,int *a){ //将各位存到a数组中 + int index = 3; + while(n > 0){ + a[index--] = n%10; + n /= 10; + } +} + +int rev(int *a){ //还原 + int s = 0; + for(int i = 0; i < 4; i++)s = s*10 + a[i]; + return s; +} + +void BFS(int s,int step) { + queueq; + Node now,next; + q.push(Node(s,step)); + vis[s] = 1; + while(!q.empty()) { + now = q.front();q.pop(); + if(now.v == e) { + flag = true; + printf("%d\n",now.step); + return ; + } + int a[4],b[4]; + cut(now.v,a); + for(int i = 0; i < 4; i++){ + memcpy(b,a,sizeof(b)); + for(int j = 0; j <= 9; j++){ + b[i] = j; + next.v = rev(b); + if(next.v < 10000 && next.v > 1000 && is_prime[next.v] && !vis[next.v] && (next.v != now.v)) { + vis[next.v] = 1; + next.step = now.step+1; + q.push(next); + } + } + } + } +} +int main() { + int T; + Sieve(maxn); //先把素数筛选出来 + scanf("%d",&T); + while(T--) { + flag = false; + memset(vis,0,sizeof(vis)); + scanf("%d%d",&s,&e); + BFS(s,0); + if(flag == 0)printf("Impossible\n"); + } + return 0; +} +``` +### 唯一分定理 + +![这里写图片描述](https://img-blog.csdn.net/20180408111353844?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +具体的不细说,看一个比较简单的[模板题](https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html)其余还有题目在我的代码仓库。 + +```cpp +//题目连接 : https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html +#include +using namespace std; +const int maxn = 30000; + +int prime[maxn]; +bool is_prime[maxn]; + +//素数筛选的模板 : |埃式筛法| +int Sieve(int n) { + int k = 0; + for(int i = 0; i <= n; i++)is_prime[i] = true; + is_prime[0] = false,is_prime[1] = false; + for(int i = 2; i <= n; i++) { + if(is_prime[i]) { + prime[k++] = i; + for(int j = i*i; j <= n; j += i)is_prime[j] = false; // 轻剪枝,j必定是i的倍数 + } + } + return k; +} + +int main(){ + int T,n; + scanf("%d",&T); + int len = Sieve(maxn); + while(T--){ + scanf("%d",&n); + int ans[maxn],k = 0; + for(int i = 0; i < len; i++){ //唯一分解定理 + while(n % prime[i] == 0){ + ans[k++] = prime[i]; + n /= prime[i]; + } + } + for(int i = 0; i < k; i++)printf(i == 0 ? "%d": "*%d",ans[i]); + printf("\n"); + } + return 0; +} +``` +*** +### 扩展欧几里得 + +基本算法:对于不完全为 `0 `的非负整数 `a,b`,`gcd(a,b)`表示` a,b `的最大公约数,必然存在整数对` x,y `,使得 `gcd(a,b)=ax+by`。,具体用处在于: + * 求解不定方程; + * 求解模线性方程(线性同余方程); + * 求解模的逆元; + + 给出一个讲的不错的[博客](https://www.cnblogs.com/frog112111/archive/2012/08/19/2646012.html)。 + 这里给出一个求解不定方程组解的测试程序: + +```cpp +//扩展欧几里得的学习 +#include + +int exgcd(int a,int b,int &x,int &y){ + if(b == 0){ + x = 1; + y = 0; + return a; + } + int d = exgcd(b,a%b,x,y); //d 就是gcd(a,b) + int t = x; + x = y; + y = t-(a/b)*y; + return d; +} + +bool linear_equation(int a,int b,int c,int &x,int &y){ + int d = exgcd(a,b,x,y); //d是gcd(a,b)//求出a*x+b*y = gcd(a,b)的一组解 + + printf("%d*x + %d*y = %d(gcd(a,b))的一些解是\n",a,b,d); + printf("%d %d\n",x,y); //第一组 + for(int t = 2; t < 10; t++){ //输出 a*x + b*y = gcd(a,b)的其他一些解 + int x1 = x + b/d*t; + int y1 = y - a/d*t; + printf("%d %d\n",x1,y1); //其余组 + } + printf("\n\n"); //第一组 + + + if(c%d) return false; + int k = c/d; //上述解乘以 c/gcd(a,b)就是 a*x +b*y = c的解 + x *= k; y *= k; //求得的只是其中一组解 + return true; +} + +int main(){ + //freopen("in.txt","r",stdin); + int a = 6,b = 15,c = 9; //求解 6*x + 15*y = 9的解 + int x,y; + int d = exgcd(a,b,x,y); + + if(!linear_equation(a,b,c,x,y))printf("无解\n"); + + printf("%d*x + %d*y = %d的一些解是\n",a,b,c); + printf("%d %d\n",x,y); //第一组 + for(int t = 2; t < 10; t++){ //输出其他的一些解 + int x1 = x + b/d*t; + int y1 = y - a/d*t; + printf("%d %d\n",x1,y1); //其余组 + } + return 0; +} +``` +再给出一个比较简单的[模板题](http://poj.org/problem?id=1061) + +```cpp +//题目链接:http://poj.org/problem?id=1061 +//题目大意:中文题,给出青蛙A,B的起点坐标,以及跳跃距离m,n以及环的长度L,求什么时候能相遇 +/*解题思路: 假设跳了T次以后,青蛙1的坐标便是x+m*T,青蛙2的坐标为y+n*T。它们能够相遇的情况为(x+m*T)-(y+n*T)==P*L,其中P为某一个整数,变形一下设经过T次跳跃相遇,得到(n-m)*T-P*L==x-y 我们设a=(n-m),b=L,c=x-y,T=x,P=y. + 得到ax+by==c,直接利用欧几里得扩展定理可以得到一组x,y但是这组x,y不一定是符合条件的最优解, + 首先,当gcd(a,b)不能整除c的时候是无解的,当c能整除gcd(a,b)时,把x和y都要变为原来的c/gcd(a,b)倍, + 我们知道它的通解为x0+b/gcd(a,b)*t要保证这个解是不小于零的最小的数, + 我们先计算当x0+b/gcd(a,b)*t=0时的t值, + 此时的t记为t0=-x0/b/gcd(a,b)(整数除法),代入t0如果得到的x小于零再加上一个b/gcd(a,b)就可以了*/ +#include +using namespace std; +typedef long long LL; + +LL exgcd(LL a,LL b,LL &x,LL &y){ + if(b == 0){ + x = 1; + y = 0; + return a; + } + LL d = exgcd(b,a%b,x,y); + LL t = x; + x = y; + y = t-(a/b)*y; + return d; +} + +int main(){ + //freopen("in.txt","r",stdin); + LL x,y,m,n,L,T,P; + while(cin>>x>>y>>m>>n>>L){ + LL a,b,c; + a = n-m; + b = L; + c = x-y; + LL d = exgcd(a,b,T,P); + if(c%d!=0){ cout<<"Impossible"<欧拉函数 +欧拉函数只是工具: +* 提供`1`到`N`中与`N`互质的数; +* 对于一个正整数`N`的素数幂分解为`N = P1^q1 * P2^q2 * ... * Pn^qn`,则欧拉函数 `γ(N) = N*(1-1/P1) * (1-1/P2) * ... * (1-1/Pn)`。 + +这里也给出一个[博客讲解](https://blog.csdn.net/sentimental_dog/article/details/52002608)。 + +```cpp +//欧拉函数模板 +//题目连接 : https://vjudge.net/contest/185827#problem/G +//题目意思 : 就是要你求1~n互素的对数(注意1~1不要重复) +#include +#include +const int maxn = 50000; +int phi[maxn+1], phi_psum[maxn+1]; + +int euler_phi(int n){ + int m = sqrt(n+0.5); + int ans = n; + for(int i = 2; i < m; i++)if(n % i == 0){ + ans = ans/i*(i-1); + while(n % i == 0)n /= i; + } + if(n > 1)ans = ans/n*(n-1); + return ans; +} + + +//筛素数的方法,求解1~n所有数的欧拉phi函数值 +void phi_table(int n) { + phi[1] = 0; //这里不计算第一个1,1和1,1是重复的,等下直接2*phi_psum[n] + 1 + for(int i = 2; i <= n; i++) if(phi[i] == 0) + for(int j = i; j <= n; j += i) { + if(phi[j] == 0) phi[j] = j; + phi[j] = phi[j] / i * (i-1); + } +} + +int main() { + int n; + phi_table(maxn); + phi_psum[0] = 0; + for(int i = 1; i <= maxn; i++)phi_psum[i] = phi_psum[i-1] + phi[i]; + while(scanf("%d", &n) == 1 && n) printf("%d\n",2*phi_psum[n] + 1); + + return 0; +} +``` +这里再贴上大牛的模板: + +```cpp +const int MAXN = 10000000; +bool check[MAXN+10]; +int phi[MAXN+10]; +int prime[MAXN+10]; +int tot;//素数的个数 + +// 线性筛(同时得到欧拉函数和素表) +void phi_and_prime_table(int N) { + memset(check,false,sizeof(check)); + phi[1] = 1; + tot = 0; + for(int i = 2; i <= N; i++) { + if( !check[i] ) { + prime[tot++] = i; + phi[i] = i-1; + } + for(int j = 0; j < tot; j++) { + if(i * prime[j] > N)break; + check[i * prime[j]] = true; + if( i % prime[j] == 0) { + phi[i * prime[j]] = phi[i] * prime[j]; + break; + } else { + phi[i * prime[j]] = phi[i] * (prime[j] - 1); + } + } + } +} +``` + +*** +### 快速幂 +**乘法快速幂看[这里](https://blog.csdn.net/zxzxzx0119/article/details/82816131)。** + +```cpp +#include + +//计算(a*b)%c +long long mul(long long a,long long b,long long mod) { + long long res = 0; + while(b > 0){ + if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = ( res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + return res; +} + +//幂取模函数 +long long pow1(long long a,long long n,long long mod){ + long long res = 1; + while(n > 0){ + if(n&1) + res = (res * a)%mod; + a = (a * a)%mod; + n >>= 1; + } + return res; +} + + +// 计算 ret = (a^n)%mod +long long pow2(long long a,long long n,long long mod) { + long long res = 1; + while(n > 0) { + if(n & 1) + res = mul(res,a,mod); + a = mul(a,a,mod); + n >>= 1; + } + return res; +} + +//递归分治法求解 +int pow3(int a,int n,int Mod){ + if(n == 0) + return 1; + int halfRes = pow1(a,n/2,Mod); + long long res = (long long)halfRes * halfRes % Mod; + if(n&1) + res = res * a % Mod; + return (int)res; +} + +int main(){ + printf("%lld\n",mul(3,4,10)); + printf("%lld\n",pow1(2,5,3)); + printf("%lld\n",pow2(2,5,3)); + printf("%d\n",pow3(2,5,3)); + return 0; +} +``` + +*** +### 矩阵快速幂 +这个也是比较常用的,主要还是在于常数矩阵的求解和递推公式的求解,这里给出一个[博客讲解](https://blog.csdn.net/wust_zzwh/article/details/52058209)和自己写过的一道模板题,**以及后面自己的[总结](https://blog.csdn.net/zxzxzx0119/article/details/82822588)。** +![这里写图片描述](https://img-blog.csdn.net/20180408171318555?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +转换之后,直接把问题转化为求矩阵的`n-9`次幂。 + +```cpp +//题目链接:https://vjudge.net/contest/172365#problem/A +//题目大意:函数满足——If x < N f(x) = x. +//If x >= N f(x) = a0 * f(x - 1) + a1 * f(x - 2) + a2 * f(x - 3) + …… + a9 * f(x - N); +//ai(0<=i<=9) can only be 0 or 1 . +//要求f(k) % mod; +//解题思路:构造矩阵求解N*N矩阵 + +#include +#include +const int N = 10; +using namespace std; + +int Mod; +struct Matrix { + int m[N][N]; + Matrix(){} +}; + +void Init(Matrix &matrix){ + for(int i = 0;i < N; i++)scanf("%d",&matrix.m[0][i]); + for(int i = 1;i < N; i++){ + for(int j = 0;j < N; j++){ + if(i == (j+1))matrix.m[i][j] = 1; + else matrix.m[i][j] = 0; + } + } +} + +//矩阵相乘 +Matrix Mul(Matrix &a,Matrix &b){ + Matrix c; + for(int i = 0; i < N; i++){ + for(int j = 0;j < N; j++){ + c.m[i][j] = 0; + for(int k = 0; k < N; k++)c.m[i][j] += a.m[i][k]*b.m[k][j]; + c.m[i][j] %= Mod; + } + } + return c; +} + +//矩阵幂 +Matrix Pow(Matrix& matrix, int k) { + Matrix res; + for (int i = 0; i < N; ++i) + for (int j = 0; j < N; ++j) + if (i == j) res.m[i][j] = 1; + else res.m[i][j] = 0; + while (k) { + if (k & 1) res = Mul(matrix,res); + k >>= 1; + matrix = Mul(matrix,matrix); + } + return res; +} + +int main() { + int k,a[N]; + while (~scanf("%d%d",&k,&Mod)) { + Matrix matrix; + Init(matrix); + if (k < 10) { printf("%d\n",k % Mod); continue; } + matrix = Pow(matrix,k-9); + int sum = 0; + for (int i = 0; i < N; i++)sum += matrix.m[0][i] * (9 - i) % Mod; + printf("%d\n",sum%Mod); + } + return 0; +} +``` +*** + + diff --git "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 1090M. The Pleasant Walk.md" b/Algorithm/Codeforces/DP/Codeforces - 1090M. The Pleasant Walk.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/Codeforces - 1090M. The Pleasant Walk.md" rename to Algorithm/Codeforces/DP/Codeforces - 1090M. The Pleasant Walk.md diff --git "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 1105C. Ayoub and Lost Array(DP).md" b/Algorithm/Codeforces/DP/Codeforces - 1105C. Ayoub and Lost Array(DP).md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/Codeforces - 1105C. Ayoub and Lost Array(DP).md" rename to Algorithm/Codeforces/DP/Codeforces - 1105C. Ayoub and Lost Array(DP).md diff --git "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 118D. Caesar's Legions _ TimusOJ - 2018. The Debut Album (DP).md" b/Algorithm/Codeforces/DP/Codeforces - 118D. Caesar's Legions _ TimusOJ - 2018. The Debut Album (DP).md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/Codeforces - 118D. Caesar's Legions _ TimusOJ - 2018. The Debut Album (DP).md" rename to Algorithm/Codeforces/DP/Codeforces - 118D. Caesar's Legions _ TimusOJ - 2018. The Debut Album (DP).md diff --git "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 607B. Zuma (DP).md" b/Algorithm/Codeforces/DP/Codeforces - 607B. Zuma (DP).md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/Codeforces - 607B. Zuma (DP).md" rename to Algorithm/Codeforces/DP/Codeforces - 607B. Zuma (DP).md diff --git "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" "b/Algorithm/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" similarity index 96% rename from "\345\210\267\351\242\230/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" rename to "Algorithm/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" index 9ca5175c..4cfb9d5e 100644 --- "a/\345\210\267\351\242\230/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" +++ "b/Algorithm/Codeforces/DP/Codeforces - 977F. Consecutive Subsequence( Map + DP) & 1097B. Petr and a Combination Lock(\346\236\232\344\270\276).md" @@ -4,13 +4,13 @@ * Codeforces - 1097B. Petr and a Combination Lock(枚举) *** -### Codeforces - 977F. Consecutive Subsequence( Map + DP) +### Codeforces - 977F. Consecutive Subsequence( Map + DP) #### [题目链接](https://codeforces.com/problemset/problem/977/F) > https://codeforces.com/problemset/problem/977/F #### 题目 -**求最长的连续递增子序列(注意是连续递增(也就是值前后相差为`1`)), 输出长度以及子序列的下标。** +**求最长的连续递增子序列(注意是连续递增(也就是值前后相差为`1`)), 输出长度以及子序列的下标。** ![在这里插入图片描述](images/977F_t.png) ![在这里插入图片描述](images/977F_t2.png) diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/1090M_t.png" b/Algorithm/Codeforces/DP/images/1090M_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/1090M_t.png" rename to Algorithm/Codeforces/DP/images/1090M_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/1097F_t2.png" b/Algorithm/Codeforces/DP/images/1097F_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/1097F_t2.png" rename to Algorithm/Codeforces/DP/images/1097F_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/1097_t.png" b/Algorithm/Codeforces/DP/images/1097_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/1097_t.png" rename to Algorithm/Codeforces/DP/images/1097_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/1105C_t.png" b/Algorithm/Codeforces/DP/images/1105C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/1105C_t.png" rename to Algorithm/Codeforces/DP/images/1105C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/118D_t.png" b/Algorithm/Codeforces/DP/images/118D_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/118D_t.png" rename to Algorithm/Codeforces/DP/images/118D_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/607B_s.png" b/Algorithm/Codeforces/DP/images/607B_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/607B_s.png" rename to Algorithm/Codeforces/DP/images/607B_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/607B_s2.png" b/Algorithm/Codeforces/DP/images/607B_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/607B_s2.png" rename to Algorithm/Codeforces/DP/images/607B_s2.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/607B_t.png" b/Algorithm/Codeforces/DP/images/607B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/607B_t.png" rename to Algorithm/Codeforces/DP/images/607B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/977F_t.png" b/Algorithm/Codeforces/DP/images/977F_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/977F_t.png" rename to Algorithm/Codeforces/DP/images/977F_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/977F_t2.png" b/Algorithm/Codeforces/DP/images/977F_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/977F_t2.png" rename to Algorithm/Codeforces/DP/images/977F_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/DP/images/time2018_t.png" b/Algorithm/Codeforces/DP/images/time2018_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/DP/images/time2018_t.png" rename to Algorithm/Codeforces/DP/images/time2018_t.png diff --git a/Algorithm/Codeforces/DataStructure/Map/Codeforces - 1133D. Zero Quantity Maximization.md b/Algorithm/Codeforces/DataStructure/Map/Codeforces - 1133D. Zero Quantity Maximization.md new file mode 100644 index 00000000..3de5ce68 --- /dev/null +++ b/Algorithm/Codeforces/DataStructure/Map/Codeforces - 1133D. Zero Quantity Maximization.md @@ -0,0 +1,106 @@ +## Codeforces - 1133D. Zero Quantity Maximization(哈希) + +#### [题目链接](https://codeforces.com/problemset/problem/1133/D) + +> https://codeforces.com/problemset/problem/1133/D + +#### 题目 + +就是给你`n`,然后两个数目为`n`个数组`a[]、b[]`,要你随便找一个数`d`(可以是浮点数和整数),使得`d * a[i] + b[i] = 0`的数目数目最多。求出最多的`0`。 + +![1133D_t.png](images/1133D_t.png) + +![1133D_t2.png](images/1133D_t2.png) + +### 解析 + +重点是怎么哈希,这里先求得两个数的最大公约数,然后相除,然后在`map`中的数只要是`a == a2 && b == b2`或者`a == -a2 && b == -b2`的就可以看做同一个。 + +一开始没有注意`if(a[i] == 0 && b[i] != 0) continue;`,在这里wrong了挺多次。这种情况是不可能为`0`的。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static class Pair { + int f, s; + + public Pair(int f, int s) { + this.f = f; + this.s = s; + } + + @Override + public boolean equals(Object o) { + Pair tr = (Pair) o; + return (f == tr.f && s == tr.s) || (f == -tr.f && s == -tr.s); + } + @Override + public int hashCode() { + return 31 * f + s; + } + } + + // 12 16 + static int gcd(int a, int b) { + int r = 0; + while (b != 0) { + r = a % b; + a = b; + b = r; + } + return a; + } + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int[] a = new int[n]; + int[] b = new int[n]; + boolean good = false; + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + if (a[i] != 0) good = true; + } + for (int i = 0; i < n; i++) { + b[i] = in.nextInt(); + if (b[i] == 0) good = true; + } + if (!good) { + System.out.println(0); + return; + } + HashMap map = new HashMap<>(); + int allZero = 0, fiZero = 0; + for (int i = 0; i < n; i++) { + if(a[i] == 0 && b[i] != 0) continue;//在这里wrong了挺多次 + if(a[i] != 0 && b[i] == 0) { + fiZero++; + continue; + } + int d = gcd(a[i], b[i]); + if (d != 0) { + Pair p = new Pair(a[i] / d, b[i] / d); + map.put(p, map.getOrDefault(p, 0) + 1); + } else { + allZero++; + } + } + int res = fiZero; // 有可能是 0 -> X ,就直接取0了 + for (Pair key : map.keySet()) + res = Math.max(res, map.get(key)); + out.println(res + allZero); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + + + diff --git a/Algorithm/Codeforces/DataStructure/Map/images/1133D_t.png b/Algorithm/Codeforces/DataStructure/Map/images/1133D_t.png new file mode 100644 index 00000000..4de181bd Binary files /dev/null and b/Algorithm/Codeforces/DataStructure/Map/images/1133D_t.png differ diff --git a/Algorithm/Codeforces/DataStructure/Map/images/1133D_t2.png b/Algorithm/Codeforces/DataStructure/Map/images/1133D_t2.png new file mode 100644 index 00000000..7c5d2561 Binary files /dev/null and b/Algorithm/Codeforces/DataStructure/Map/images/1133D_t2.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Data Structure/Queue/Codeforces - 1089L. Lazyland.md" b/Algorithm/Codeforces/DataStructure/Queue/Codeforces - 1089L. Lazyland.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Data Structure/Queue/Codeforces - 1089L. Lazyland.md" rename to Algorithm/Codeforces/DataStructure/Queue/Codeforces - 1089L. Lazyland.md diff --git "a/\345\210\267\351\242\230/Codeforces/Data Structure/Queue/images/1089L_t.png" b/Algorithm/Codeforces/DataStructure/Queue/images/1089L_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Data Structure/Queue/images/1089L_t.png" rename to Algorithm/Codeforces/DataStructure/Queue/images/1089L_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" "b/Algorithm/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" similarity index 99% rename from "\345\210\267\351\242\230/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" rename to "Algorithm/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" index 1a51a958..0b3d4e49 100644 --- "a/\345\210\267\351\242\230/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" +++ "b/Algorithm/Codeforces/Graph/Codeforces - 1082A & 1073A & 330B & GYM101502I(\346\234\200\347\237\255\350\267\257).md" @@ -6,7 +6,7 @@ * [Codeforces - 330B - Road Construction](#codeforces---330b---road-construction) * [GYM - 101502I - Move Between Numbers](#gym---101502i---move-between-numbers) *** -### Codeforces - 1082A - Vasya and Book +### Codeforces - 1082A - Vasya and Book #### [题目链接](http://codeforces.com/problemset/problem/1082/A) > http://codeforces.com/problemset/problem/1082/A diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/Codeforces - 1106D. Lunar New Year and a Wander(\345\233\276\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/Codeforces/Graph/Codeforces - 1106D. Lunar New Year and a Wander(\345\233\276\347\256\200\345\215\225\351\242\230).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/Codeforces - 1106D. Lunar New Year and a Wander(\345\233\276\347\256\200\345\215\225\351\242\230).md" rename to "Algorithm/Codeforces/Graph/Codeforces - 1106D. Lunar New Year and a Wander(\345\233\276\347\256\200\345\215\225\351\242\230).md" diff --git a/Algorithm/Codeforces/Graph/Codeforces - 1133F1. Spanning Tree with Maximum Degree.md b/Algorithm/Codeforces/Graph/Codeforces - 1133F1. Spanning Tree with Maximum Degree.md new file mode 100644 index 00000000..38ef99b8 --- /dev/null +++ b/Algorithm/Codeforces/Graph/Codeforces - 1133F1. Spanning Tree with Maximum Degree.md @@ -0,0 +1,90 @@ +## Codeforces - 1133F1. Spanning Tree with Maximum Degree.md(DFS,记录父亲) + +#### [题目链接](https://codeforces.com/problemset/problem/1133/F1) + +> https://codeforces.com/problemset/problem/1133/F1 + +#### 题目 + +给你一个图,要你求必须包含**度最大的点的所有相连边**的生成树。 + +![1133F1_t.png](images/1133F1_t.png) + +![1133F1_t2.png](images/1133F1_t2.png) + +![1133F1_t3.png](images/1133F1_t3.png) + +![1133F1_t4.png](images/1133F1_t4.png) + +![1133F1_t5.png](images/1133F1_t5.png) + +#### 解析 + +思路: + +* 先找出度最大的度的顶点`maxv`。然后先将这个点的邻接边加入结果。 +* 然后从和`maxv`邻接的点开始`dfs`,这个过程既要记录`vis`是否访问,又要记录一个父亲节点,这样才比较好维护; + +![1133F1_s.png](images/1133F1_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static boolean[] vis; + static ArrayList G[]; + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int m = in.nextInt(); + G = new ArrayList[n + 1]; + int[] deg = new int[n + 1]; + for (int i = 1; i <= n; i++) G[i] = new ArrayList<>(); + for (int i = 0; i < m; i++) { + int from = in.nextInt(); + int to = in.nextInt(); + deg[to]++; + deg[from]++; + G[from].add(to); + G[to].add(from); + } + int maxDeg = 0; + int maxv = 0; + for (int i = 1; i <= n; i++) { // 找度最大的 + if (deg[i] > maxDeg) { + maxDeg = deg[i]; + maxv = i; + } + } + vis = new boolean[n + 1]; + vis[maxv] = true; + for(int to : G[maxv]){ // 先将度最大的相连的边连接起来 + vis[to] = true; + out.println(maxv + " " + to); + } + for(int to : G[maxv]) dfs(to, maxv); + } + + static void dfs(int cur, int par) { // 记录当前节点和父亲节点 +// if(vis[cur]) return ; // 不要写这里,不然第一次就不能进去 + vis[cur] = true; + for (int to : G[cur]) { + if(to == par || vis[to]) continue; + out.println(cur + " " + to); + dfs(to, cur); + } + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/1073A_t.png" b/Algorithm/Codeforces/Graph/images/1073A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/1073A_t.png" rename to Algorithm/Codeforces/Graph/images/1073A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/1082A_t.png" b/Algorithm/Codeforces/Graph/images/1082A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/1082A_t.png" rename to Algorithm/Codeforces/Graph/images/1082A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/1106D_t1.png" b/Algorithm/Codeforces/Graph/images/1106D_t1.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/1106D_t1.png" rename to Algorithm/Codeforces/Graph/images/1106D_t1.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/1106D_t2.png" b/Algorithm/Codeforces/Graph/images/1106D_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/1106D_t2.png" rename to Algorithm/Codeforces/Graph/images/1106D_t2.png diff --git a/Algorithm/Codeforces/Graph/images/1133F1_s.png b/Algorithm/Codeforces/Graph/images/1133F1_s.png new file mode 100644 index 00000000..3c579a90 Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_s.png differ diff --git a/Algorithm/Codeforces/Graph/images/1133F1_t.png b/Algorithm/Codeforces/Graph/images/1133F1_t.png new file mode 100644 index 00000000..5da8f688 Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_t.png differ diff --git a/Algorithm/Codeforces/Graph/images/1133F1_t2.png b/Algorithm/Codeforces/Graph/images/1133F1_t2.png new file mode 100644 index 00000000..680ac638 Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_t2.png differ diff --git a/Algorithm/Codeforces/Graph/images/1133F1_t3.png b/Algorithm/Codeforces/Graph/images/1133F1_t3.png new file mode 100644 index 00000000..197513a1 Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_t3.png differ diff --git a/Algorithm/Codeforces/Graph/images/1133F1_t4.png b/Algorithm/Codeforces/Graph/images/1133F1_t4.png new file mode 100644 index 00000000..bb0f68ba Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_t4.png differ diff --git a/Algorithm/Codeforces/Graph/images/1133F1_t5.png b/Algorithm/Codeforces/Graph/images/1133F1_t5.png new file mode 100644 index 00000000..e65b00c2 Binary files /dev/null and b/Algorithm/Codeforces/Graph/images/1133F1_t5.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/330B_t.png" b/Algorithm/Codeforces/Graph/images/330B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/330B_t.png" rename to Algorithm/Codeforces/Graph/images/330B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/330B_t2.png" b/Algorithm/Codeforces/Graph/images/330B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/330B_t2.png" rename to Algorithm/Codeforces/Graph/images/330B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Graph/images/gym101502I_t.png" b/Algorithm/Codeforces/Graph/images/gym101502I_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Graph/images/gym101502I_t.png" rename to Algorithm/Codeforces/Graph/images/gym101502I_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/Codeforces - 1107B. Digital root & 1107C. Brutality(\350\247\204\345\276\213 & \350\264\252\345\277\203).md" "b/Algorithm/Codeforces/Greedy/Codeforces - 1107B. Digital root & 1107C. Brutality(\350\247\204\345\276\213 & \350\264\252\345\277\203).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/Codeforces - 1107B. Digital root & 1107C. Brutality(\350\247\204\345\276\213 & \350\264\252\345\277\203).md" rename to "Algorithm/Codeforces/Greedy/Codeforces - 1107B. Digital root & 1107C. Brutality(\350\247\204\345\276\213 & \350\264\252\345\277\203).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/Codeforces - 1114B. Yet Another Array Partitioning Task(\350\264\252\345\277\203+\347\264\242\345\274\225).md" "b/Algorithm/Codeforces/Greedy/Codeforces - 1114B. Yet Another Array Partitioning Task(\350\264\252\345\277\203+\347\264\242\345\274\225).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/Codeforces - 1114B. Yet Another Array Partitioning Task(\350\264\252\345\277\203+\347\264\242\345\274\225).md" rename to "Algorithm/Codeforces/Greedy/Codeforces - 1114B. Yet Another Array Partitioning Task(\350\264\252\345\277\203+\347\264\242\345\274\225).md" diff --git a/Algorithm/Codeforces/Greedy/Codeforces - 1130B. Two Cakes.md b/Algorithm/Codeforces/Greedy/Codeforces - 1130B. Two Cakes.md new file mode 100644 index 00000000..2246a2e3 --- /dev/null +++ b/Algorithm/Codeforces/Greedy/Codeforces - 1130B. Two Cakes.md @@ -0,0 +1,72 @@ +## Codeforces - 1130B. Two Cakes + +#### [题目链接](https://codeforces.com/problemset/problem/1130/B) + +> https://codeforces.com/problemset/problem/1130/B + +#### 题目 + +给你`n`和`2n`个数。(`1~n`之间的数各两个) + +两个人都同时从最左边的`1`位置开始出发,每次依次去找`1, 2, 3, ..., n`。问你两个人需要走的最小的路程。 + +![1130_B_t.png](images/1130_B_t.png) + +![1130_B_t2.png](images/1130_B_t2.png) + +### 解析 + +贪心的策略就是: + +当前的`abs(id1 - p1[i + 1]) + abs(id2 - p2[i + 1]) `和`abs(id1 - p2[i + 1]) + abs(id2 - p1[i + 1])`哪个更小,就去哪个。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int n = in.nextInt(); + int[] arr = new int[n * 2 + 1]; + int[] p1 = new int[n * 2 + 1]; + int[] p2 = new int[n * 2 + 1]; + for (int i = 1; i <= 2 * n; i++) { + arr[i] = in.nextInt(); + if (p1[arr[i]] == 0) + p1[arr[i]] = i; + else + p2[arr[i]] = i; + } + +// System.out.println(Arrays.toString(p1)); +// System.out.println(Arrays.toString(p2)); + + int id1 = 1, id2 = 1; + long r1 = 0, r2 = 0; + for (int i = 0; i < n; i++) { // 每一个数 + if (abs(id1 - p1[i + 1]) + abs(id2 - p2[i + 1]) < + abs(id1 - p2[i + 1]) + abs(id2 - p1[i + 1])) { //贪心策略 + r1 += abs(p1[i + 1] - id1); + id1 = p1[i + 1]; + r2 += abs(p2[i + 1] - id2); + id2 = p2[i + 1]; + } else { + r1 += abs(p2[i + 1] - id1); + id1 = p2[i + 1]; + r2 += abs(p1[i + 1] - id2); + id2 = p1[i + 1]; + } + } + out.println(r1 + r2); + } + + static int abs(int n) { + return Math.abs(n); + } +} + +``` + diff --git a/Algorithm/Codeforces/Greedy/Codeforces - 1157B. Long Number.md b/Algorithm/Codeforces/Greedy/Codeforces - 1157B. Long Number.md new file mode 100644 index 00000000..0d9cb589 --- /dev/null +++ b/Algorithm/Codeforces/Greedy/Codeforces - 1157B. Long Number.md @@ -0,0 +1,48 @@ +# Codeforces - 1157B. Long Number + +#### [题目链接](https://codeforces.com/problemset/problem/1157/B) + +> https://codeforces.com/problemset/problem/1157/B + +#### 题目 + +![1556624729268](assets/1556624729268.png) + +### 解析 + +模拟即可。注意情况`if(f[s[i] - '0'] == s[i] - '0') continue`。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static void solve(Scanner in, PrintWriter out){ + int n = in.nextInt(); + char[] s = in.next().toCharArray(); + int[] f = new int[10]; + for(int i = 1; i < 10; i++) f[i] = in.nextInt(); + boolean flag = true; + for(int i = 0; i < n; i++){ + if(f[s[i] - '0'] > s[i] - '0'){ + s[i] = (char)(f[s[i] - '0'] + '0'); + flag = false; + }else { + if(f[s[i] - '0'] == s[i] - '0') continue; // 注意这里 + if(!flag) break; + } + } + out.println(s); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + solve(in, out); + out.close(); + } +} + +``` + diff --git a/Algorithm/Codeforces/Greedy/Codeforces - 1157C1. Increasing Subsequence (easy version).md b/Algorithm/Codeforces/Greedy/Codeforces - 1157C1. Increasing Subsequence (easy version).md new file mode 100644 index 00000000..276d54c3 --- /dev/null +++ b/Algorithm/Codeforces/Greedy/Codeforces - 1157C1. Increasing Subsequence (easy version).md @@ -0,0 +1,74 @@ +# Codeforces - 1157C1. Increasing Subsequence + +#### [题目链接](https://codeforces.com/problemset/problem/1157/C1) + +> https://codeforces.com/problemset/problem/1157/C1 + +#### 题目 + +![1556625506830](assets/1556625506830.png) + +### 解析 + +每次取两边对应的两个中满足条件的小的那个即可(贪心)。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static void solve(Scanner in, PrintWriter out){ + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + + int L = 0, R = n-1; + int pre = -1; + int res = 0; + StringBuilder sb = new StringBuilder(); + while(L <= R && Math.max(a[L], a[R]) > pre ){ + if(a[L] > pre && a[R] > pre){ + if(a[L] < a[R]){ + res++; + sb.append("L"); + pre = a[L]; + L++; + if(L == n) break; + }else { + res++; + sb.append("R"); + pre = a[R]; + R--; + if(R == -1)break; + } + }else { + if(a[L] > pre){ + res++; + sb.append("L"); + pre = a[L]; + L++; + if(L == n) break; + }else { + res++; + sb.append("R"); + pre = a[R]; + R--; + if(R == -1)break; + } + } + } + out.println(res); + out.println(sb.toString()); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + solve(in, out); + out.close(); + } +} + +``` + diff --git a/Algorithm/Codeforces/Greedy/Codeforces - 1157C2. Increasing Subsequence (hard version).md b/Algorithm/Codeforces/Greedy/Codeforces - 1157C2. Increasing Subsequence (hard version).md new file mode 100644 index 00000000..4e6ff363 --- /dev/null +++ b/Algorithm/Codeforces/Greedy/Codeforces - 1157C2. Increasing Subsequence (hard version).md @@ -0,0 +1,87 @@ +# Codeforces - 1157C2. Increasing Subsequence + +#### [题目链接](https://codeforces.com/problemset/problem/1157/C2) + +> https://codeforces.com/problemset/problem/1157/C2 + +#### 题目 + +![1556627939953](assets/1556627939953.png) + +### 解析 + +注意一下相等的时候,直接计算一下返回即可。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static void solve(Scanner in, PrintWriter out){ + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + + int L = 0, R = n-1; + int pre = -1; + int res = 0; + StringBuilder sb = new StringBuilder(); + while(L <= R && Math.max(a[L], a[R]) > pre ){ + if(a[L] == a[R]){ //judge + int r1 = 1, r2 = 1; + for(int t = L+1; t < R && a[t] > a[t-1]; t++, r1++); + for(int t = R-1; t > L && a[t] > a[t+1]; t--, r2++); + if(r1 > r2){ + res += r1; + for(int i = 0; i < r1; i++) sb.append("L"); + }else { + res += r2; + for(int i = 0; i < r2; i++) sb.append("R"); + } + break; + } + if(a[L] > pre && a[R] > pre){ + if(a[L] < a[R]){ + res++; + sb.append("L"); + pre = a[L]; + L++; + if(L == n) break; + }else { + res++; + sb.append("R"); + pre = a[R]; + R--; + if(R == -1)break; + } + }else { + if(a[L] > pre){ + res++; + sb.append("L"); + pre = a[L]; + L++; + if(L == n) break; + }else { + res++; + sb.append("R"); + pre = a[R]; + R--; + if(R == -1)break; + } + } + } + out.println(res); + out.println(sb.toString()); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + solve(in, out); + out.close(); + } +} + +``` + diff --git a/Algorithm/Codeforces/Greedy/assets/1556624729268.png b/Algorithm/Codeforces/Greedy/assets/1556624729268.png new file mode 100644 index 00000000..e7b65c05 Binary files /dev/null and b/Algorithm/Codeforces/Greedy/assets/1556624729268.png differ diff --git a/Algorithm/Codeforces/Greedy/assets/1556625506830.png b/Algorithm/Codeforces/Greedy/assets/1556625506830.png new file mode 100644 index 00000000..163aa4fe Binary files /dev/null and b/Algorithm/Codeforces/Greedy/assets/1556625506830.png differ diff --git a/Algorithm/Codeforces/Greedy/assets/1556627939953.png b/Algorithm/Codeforces/Greedy/assets/1556627939953.png new file mode 100644 index 00000000..882da23f Binary files /dev/null and b/Algorithm/Codeforces/Greedy/assets/1556627939953.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107B_s.png" b/Algorithm/Codeforces/Greedy/images/1107B_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107B_s.png" rename to Algorithm/Codeforces/Greedy/images/1107B_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107B_t.png" b/Algorithm/Codeforces/Greedy/images/1107B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107B_t.png" rename to Algorithm/Codeforces/Greedy/images/1107B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_s.png" b/Algorithm/Codeforces/Greedy/images/1107C_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_s.png" rename to Algorithm/Codeforces/Greedy/images/1107C_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t.png" b/Algorithm/Codeforces/Greedy/images/1107C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t.png" rename to Algorithm/Codeforces/Greedy/images/1107C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t2.png" b/Algorithm/Codeforces/Greedy/images/1107C_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t2.png" rename to Algorithm/Codeforces/Greedy/images/1107C_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t3.png" b/Algorithm/Codeforces/Greedy/images/1107C_t3.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1107C_t3.png" rename to Algorithm/Codeforces/Greedy/images/1107C_t3.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1114B_t.png" b/Algorithm/Codeforces/Greedy/images/1114B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1114B_t.png" rename to Algorithm/Codeforces/Greedy/images/1114B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Greedy/images/1114B_t2.png" b/Algorithm/Codeforces/Greedy/images/1114B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Greedy/images/1114B_t2.png" rename to Algorithm/Codeforces/Greedy/images/1114B_t2.png diff --git a/Algorithm/Codeforces/Greedy/images/1130_B_t.png b/Algorithm/Codeforces/Greedy/images/1130_B_t.png new file mode 100644 index 00000000..0e1fbbed Binary files /dev/null and b/Algorithm/Codeforces/Greedy/images/1130_B_t.png differ diff --git a/Algorithm/Codeforces/Greedy/images/1130_B_t2.png b/Algorithm/Codeforces/Greedy/images/1130_B_t2.png new file mode 100644 index 00000000..838bb73a Binary files /dev/null and b/Algorithm/Codeforces/Greedy/images/1130_B_t2.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Math/Codeforces - 1101A. Minimum Integer(\346\225\260\345\255\246\346\216\250\347\220\206).md" "b/Algorithm/Codeforces/Math/Codeforces - 1101A. Minimum Integer(\346\225\260\345\255\246\346\216\250\347\220\206).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Math/Codeforces - 1101A. Minimum Integer(\346\225\260\345\255\246\346\216\250\347\220\206).md" rename to "Algorithm/Codeforces/Math/Codeforces - 1101A. Minimum Integer(\346\225\260\345\255\246\346\216\250\347\220\206).md" diff --git "a/Algorithm/Codeforces/Math/Codeforces - 1117A. Best Subsegment(\346\234\200\345\244\247\345\271\263\345\235\207\345\255\227\346\256\265).md" "b/Algorithm/Codeforces/Math/Codeforces - 1117A. Best Subsegment(\346\234\200\345\244\247\345\271\263\345\235\207\345\255\227\346\256\265).md" new file mode 100644 index 00000000..01314a77 --- /dev/null +++ "b/Algorithm/Codeforces/Math/Codeforces - 1117A. Best Subsegment(\346\234\200\345\244\247\345\271\263\345\235\207\345\255\227\346\256\265).md" @@ -0,0 +1,51 @@ +## Codeforces - 1117A. Best Subsegment(最大平均字段) + +#### [题目链接](https://codeforces.com/problemset/problem/1117/A) + +>https://codeforces.com/problemset/problem/1117/A + +#### 题目 + +给你`n`,以及`n`个数,要你求最大的平均段(即下面的数学表达式),如果平均数相同,求最长的`R - L + 1`。 + +![](images/1117A_t.png) + +### 解析 + +关键的地方在于: + +* 我们要维护数组的一个最大值`max`; +* 平均值最大肯定就是这个最大值。 +* 然后在最大值的基础上去找可不可以有更长的长度`res`。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int n = in.nextInt(); + int num, len = 0, res = 0, max = Integer.MIN_VALUE; + for(int i = 0; i < n; i++){ + num = in.nextInt(); + if(num > max){ + max = num; + res = 1; // find a new + len = 1; + }else if(num == max) + len++; + else + len = 0; + res = Math.max(res, len); + } + out.println(res); + } +} + +``` + diff --git a/Algorithm/Codeforces/Math/Codeforces - 1141C. Polycarp Restores Permutation.md b/Algorithm/Codeforces/Math/Codeforces - 1141C. Polycarp Restores Permutation.md new file mode 100644 index 00000000..6531cdf7 --- /dev/null +++ b/Algorithm/Codeforces/Math/Codeforces - 1141C. Polycarp Restores Permutation.md @@ -0,0 +1,99 @@ +## Codeforces - 1141C. Polycarp Restores Permutation + +#### [题目链接](https://codeforces.com/problemset/problem/1141/C) + +> https://codeforces.com/problemset/problem/1141/C + +#### 题目 + +`permutation`是`n`个数中,恰好`1~n`都出现一次。 + +现在给你`n`和`n-1`个数,这`n-1`个数代表的是`permutation`中相邻两个数的差值。 + +问你能不能通过这`n-1`个数构造出原来的`permutation`。 + +![1141C_t.png](images/1141C_t.png) + +### 解析 + +要构造出一个差的前缀和。 + +然后记录最小的那个前缀和,这样我们就可以得到排列的第一个数是`1 - min`。 + +然后其他数就是第一个数+对应的前缀和即可。 + +**前缀和数组就是每个数和第一个数的差值**。 + +![1141C_s.png](images/1141C_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { +// Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + FR in = new FR(is); + int n = in.nextInt(); + int[] a = new int[n]; + int min = 0; + for(int i = 1; i < n; i++) { + a[i] = in.nextInt() + a[i-1]; // 每个数和第一个数的差值 + min = Math.min(min, a[i]); + } + int first = 1 - min; + boolean ok = true; + boolean[] vis = new boolean[n + 1]; + for(int i = 0; i < n; i++){ + a[i] += first; + if(a[i] < 1 || a[i] > n || vis[a[i]]){ + ok = false; + break; + } + vis[a[i]] = true; + } + if(!ok){ + out.println(-1); + }else { + for(int num : a) out.print(num + " "); + } + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + int nextInt() { + return Integer.parseInt(next()); + } + } +} + +``` + diff --git "a/\345\210\267\351\242\230/Codeforces/Math/images/1101A_t.png" b/Algorithm/Codeforces/Math/images/1101A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Math/images/1101A_t.png" rename to Algorithm/Codeforces/Math/images/1101A_t.png diff --git a/Algorithm/Codeforces/Math/images/1117A_t.png b/Algorithm/Codeforces/Math/images/1117A_t.png new file mode 100644 index 00000000..7de29834 Binary files /dev/null and b/Algorithm/Codeforces/Math/images/1117A_t.png differ diff --git a/Algorithm/Codeforces/Math/images/1141C_s.png b/Algorithm/Codeforces/Math/images/1141C_s.png new file mode 100644 index 00000000..38d1e6cc Binary files /dev/null and b/Algorithm/Codeforces/Math/images/1141C_s.png differ diff --git a/Algorithm/Codeforces/Math/images/1141C_t.png b/Algorithm/Codeforces/Math/images/1141C_t.png new file mode 100644 index 00000000..67a2b537 Binary files /dev/null and b/Algorithm/Codeforces/Math/images/1141C_t.png differ diff --git a/Algorithm/Codeforces/Search/Codeforces - 1118F1. Tree Cutting(Easy Version).md b/Algorithm/Codeforces/Search/Codeforces - 1118F1. Tree Cutting(Easy Version).md new file mode 100644 index 00000000..50e89bcb --- /dev/null +++ b/Algorithm/Codeforces/Search/Codeforces - 1118F1. Tree Cutting(Easy Version).md @@ -0,0 +1,81 @@ +## Codeforces - 1118F1. Tree Cutting(Easy Version) + +#### [题目链接](https://codeforces.com/contest/1118/problem/F1) + +> https://codeforces.com/contest/1118/problem/F1 + +#### 题目 + +给一棵树,有些节点是红色(`1`)或蓝色(`2`),或无色(`0`),**切掉一条边后分成两棵树且红蓝在不同的树**。统计有多少条边可以切; + +![1118F1_t.png](images/1118F1_t.png) + +![1118F2_t2.png](images/1118F2_t2.png) + +#### 解析 + +一个节点的一个子树**如果包含红的全部且不包含蓝的,或包含蓝的全部而不包含红的**,那么这条边可以切。 + +dfs是计算子树(不包括父亲,所以使用一个`pre`表示当前节点的父亲,然后在下面的循环中判断`if(to != pre)`)红蓝个数是否满足条件; + +```java +import java.io.*; +import java.util.*; + +public class Main{ + + static PrintStream out = System.out; + + static int n, red, blue, res; + static int[] a; + static ArrayListG[]; + + static int[] dfs(int cur, int pre){ + int[] p = new int[2]; + int r = 0, b = 0; // cur red num and cur blue num + if(a[cur] == 1) r++; + else if(a[cur] == 2) b++; + for(int i = 0; i < G[cur].size(); i++){ + int to = G[cur].get(i); + if(pre == to) continue; // 不能往回走 (爸爸已经走过了) //只是记录自己的子树 + int[] np = dfs(to, cur); + if(np[0] == red && np[1] == 0) res++; + if(np[0] == 0 && np[1] == blue) res++; + r += np[0]; + b += np[1]; + } + // 左边的 + p[0] = r; + p[1] = b; + return p; + } + + static void solve(Scanner in){ + red = 0; blue = 0; res = 0; + n = in.nextInt(); + a = new int[n+1]; + G = new ArrayList[n+1]; + for(int i = 1; i <= n; i++) { + G[i] = new ArrayList<>(); + a[i] = in.nextInt(); + if(a[i] == 1) red++; + else if(a[i] == 2) blue++; + } + + for(int i = 0; i < n - 1; i++){ + int from = in.nextInt(); + int to = in.nextInt(); + G[from].add(to); + G[to].add(from); + } + dfs(1, -1); + out.println(res); + } + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + solve(in); + } +} +``` + diff --git "a/Algorithm/Codeforces/Search/Codeforces - 1130C. Connect(BFS\346\210\226DFS).md" "b/Algorithm/Codeforces/Search/Codeforces - 1130C. Connect(BFS\346\210\226DFS).md" new file mode 100644 index 00000000..9e5f2b04 --- /dev/null +++ "b/Algorithm/Codeforces/Search/Codeforces - 1130C. Connect(BFS\346\210\226DFS).md" @@ -0,0 +1,178 @@ +## Codeforces - 1130C. Connect(BFS或DFS) + +#### [题目链接](https://codeforces.com/problemset/problem/1130/C) + +> https://codeforces.com/problemset/problem/1130/C + +#### 题目 + +给出`n*n`的矩阵、起点以及终点`(r1, c2)和(r2, c2)`,每个格子有0或者1,0代表陆地,1代表水。 + +现在有个人想从起点走到终点,但他不能沾水。**现在你可以修最多一条管道,连接两块陆地,费用为相应两点间距离的平方**。问最终最小的费用为多少。 + +![1130C_t.png](images/1130C_t.png) + +![1130C_t2.png](images/1130C_t2.png) + +### 解析 + +思路: 找到起点`(r1, c1)`可以直接到达的点的集合。 + +找到终点(`r2, c2`)直接可以到达的点的集合。 + +然后找这两个点的两个集合中两两距离平方最小的就可以了。 + +找的过程可以用`dfs`,也可以用`bfs`。 + +`dfs`版本: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int n; + static char[][] a; + static boolean[][][] vis; + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + + n = in.nextInt(); + a = new char[n][n]; + vis = new boolean[2][n][n]; + int r1 = in.nextInt() - 1; + int c1 = in.nextInt() - 1; + int r2 = in.nextInt() - 1; + int c2 = in.nextInt() - 1; + for (int i = 0; i < n; i++) + a[i] = in.next().toCharArray(); + dfs(0, r1, c1); // 起点可以到达的点 + dfs(1, r2, c2); // 终点可以到达的点 + int res = Integer.MAX_VALUE; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (vis[0][i][j]) { + for (int p = 0; p < n; p++) { + for (int q = 0; q < n; q++) { + if (vis[1][p][q]) { + res = Math.min(res, (p - i) * (p - i) + (q - j) * (q - j)); + } + } + } + } + } + } + out.println(res); + } + + static final int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + static void dfs(int flag, int r, int c) { + if (vis[flag][r][c]) return; + vis[flag][r][c] = true; + for (int i = 0; i < 4; i++) { + int nr = r + dir[i][0]; + int nc = c + dir[i][1]; + if (nr >= 0 && nr < n && nc >= 0 && nc < n && + a[nr][nc] == '0' && !vis[flag][nr][nc]) dfs(flag, nr, nc); + } + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); //must close + } + +} + +``` + +bfs的写法: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int n; + static char[][] a; + static int[][] vis; + + static class Node{ + int x, y; + + public Node(int x, int y) { + this.x = x; + this.y = y; + } + } + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + n = in.nextInt(); + a = new char[n][n]; + vis = new int[n][n]; + int r1 = in.nextInt() - 1; + int c1 = in.nextInt() - 1; + int r2 = in.nextInt() - 1; + int c2 = in.nextInt() - 1; + for (int i = 0; i < n; i++) + a[i] = in.next().toCharArray(); + bfs(r1, c1, 1); + if(vis[r2][c2] == 1){ + out.println(0); + return; + } + bfs(r2, c2, -1); + int res = Integer.MAX_VALUE; + for(int i = 0; i < n; i++){ + for(int j = 0; j < n; j++){ + if(vis[i][j] == 1){ + for(int p = 0; p < n; p++){ + for(int q = 0; q < n; q++){ + if(vis[p][q] == -1){ + res = Math.min(res, (p-i)*(p-i) + (q-j)*(q-j)); + } + } + } + } + } + } + out.println(res); + } + + static final int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + static void bfs(int r, int c, int flag){ + Queue queue = new LinkedList<>(); + queue.add(new Node(r, c)); + vis[r][c] = flag; // 注意bfs一开始要vis = true; + while(!queue.isEmpty()){ + Node cur = queue.poll(); + for(int i = 0; i < 4; i++){ + int nr = cur.x + dir[i][0]; + int nc = cur.y + dir[i][1]; + if(nr >= 0 && nr < n && nc >= 0 && nc < n && a[nr][nc] == '0' && vis[nr][nc] == 0){ + queue.add(new Node(nr, nc)); + vis[nr][nc] = flag; + } + } + } + } + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); //must close + } +} + +``` + diff --git "a/\345\210\267\351\242\230/Codeforces/Search/Codeforces - 217A & 580C & 189A & 368B(DFS).md" b/Algorithm/Codeforces/Search/Codeforces - 217A & 580C & 189A & 368B(DFS).md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/Codeforces - 217A & 580C & 189A & 368B(DFS).md" rename to Algorithm/Codeforces/Search/Codeforces - 217A & 580C & 189A & 368B(DFS).md diff --git a/Algorithm/Codeforces/Search/images/1118F1_t.png b/Algorithm/Codeforces/Search/images/1118F1_t.png new file mode 100644 index 00000000..0966a469 Binary files /dev/null and b/Algorithm/Codeforces/Search/images/1118F1_t.png differ diff --git a/Algorithm/Codeforces/Search/images/1118F2_t2.png b/Algorithm/Codeforces/Search/images/1118F2_t2.png new file mode 100644 index 00000000..7627ed94 Binary files /dev/null and b/Algorithm/Codeforces/Search/images/1118F2_t2.png differ diff --git a/Algorithm/Codeforces/Search/images/1130C_t.png b/Algorithm/Codeforces/Search/images/1130C_t.png new file mode 100644 index 00000000..53af5666 Binary files /dev/null and b/Algorithm/Codeforces/Search/images/1130C_t.png differ diff --git a/Algorithm/Codeforces/Search/images/1130C_t2.png b/Algorithm/Codeforces/Search/images/1130C_t2.png new file mode 100644 index 00000000..fe296051 Binary files /dev/null and b/Algorithm/Codeforces/Search/images/1130C_t2.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Search/images/189A_t.png" b/Algorithm/Codeforces/Search/images/189A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/images/189A_t.png" rename to Algorithm/Codeforces/Search/images/189A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Search/images/217A_t.png" b/Algorithm/Codeforces/Search/images/217A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/images/217A_t.png" rename to Algorithm/Codeforces/Search/images/217A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Search/images/368B_t.png" b/Algorithm/Codeforces/Search/images/368B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/images/368B_t.png" rename to Algorithm/Codeforces/Search/images/368B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Search/images/580C_t.png" b/Algorithm/Codeforces/Search/images/580C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/images/580C_t.png" rename to Algorithm/Codeforces/Search/images/580C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Search/images/580C_t2.png" b/Algorithm/Codeforces/Search/images/580C_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Search/images/580C_t2.png" rename to Algorithm/Codeforces/Search/images/580C_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1037C & 313B & 489C & 474D.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1037C & 313B & 489C & 474D.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1037C & 313B & 489C & 474D.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1037C & 313B & 489C & 474D.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1077C. Good Array.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1077C. Good Array.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1077C. Good Array.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1077C. Good Array.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1080B. Margarite and the best present.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1080B. Margarite and the best present.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1080B. Margarite and the best present.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1080B. Margarite and the best present.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1084B. Kvass and the Fair Nut.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1084B. Kvass and the Fair Nut.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1084B. Kvass and the Fair Nut.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1084B. Kvass and the Fair Nut.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1088B. Ehab and subtraction.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1088B. Ehab and subtraction.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1088B. Ehab and subtraction.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1088B. Ehab and subtraction.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1090A. Company Merging.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1090A. Company Merging.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1090A. Company Merging.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 1090A. Company Merging.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1100B. Build a Contest(\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1100B. Build a Contest(\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1100B. Build a Contest(\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1100B. Build a Contest(\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1103A. Grid game(\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1103A. Grid game(\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1103A. Grid game(\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1103A. Grid game(\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1105A. Salem and Sticks(\347\256\200\345\215\225\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1105A. Salem and Sticks(\347\256\200\345\215\225\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1105A. Salem and Sticks(\347\256\200\345\215\225\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1105A. Salem and Sticks(\347\256\200\345\215\225\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1105B. Zuhair and Strings(\345\255\227\347\254\246\344\270\262\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1105B. Zuhair and Strings(\345\255\227\347\254\246\344\270\262\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1105B. Zuhair and Strings(\345\255\227\347\254\246\344\270\262\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1105B. Zuhair and Strings(\345\255\227\347\254\246\344\270\262\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1106B. Lunar New Year and Food Ordering (\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1106B. Lunar New Year and Food Ordering (\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1106B. Lunar New Year and Food Ordering (\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1106B. Lunar New Year and Food Ordering (\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1108C. Nice Garland & 1108D. Diverse Garland(\346\216\222\345\210\227 _ \346\236\232\344\270\276 ).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1108C. Nice Garland & 1108D. Diverse Garland(\346\216\222\345\210\227 _ \346\236\232\344\270\276 ).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 1108C. Nice Garland & 1108D. Diverse Garland(\346\216\222\345\210\227 _ \346\236\232\344\270\276 ).md" rename to "Algorithm/Codeforces/Simulation/easy/Codeforces - 1108C. Nice Garland & 1108D. Diverse Garland(\346\216\222\345\210\227 _ \346\236\232\344\270\276 ).md" diff --git "a/Algorithm/Codeforces/Simulation/easy/Codeforces - 1131C. Birthday(\350\264\252\345\277\203).md" "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1131C. Birthday(\350\264\252\345\277\203).md" new file mode 100644 index 00000000..60d67221 --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1131C. Birthday(\350\264\252\345\277\203).md" @@ -0,0 +1,43 @@ +## Codeforces - 1131C. Birthday(贪心) + +#### [题目链接](https://codeforces.com/problemset/problem/1131/C) + +> https://codeforces.com/problemset/problem/1131/C + +#### 题目 + +给你`n`和`n`个数,要你重新排列`n`个数,使得这些数的相邻差值中最大的那个值最小。 + +![1131C_t.png](images/1131C_t.png) + +### 解析 + +排序后,从第一个开始隔一个取一个,取到最后。 + +然后从倒数第二个往前取,这样才是最小的。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static void solve(Scanner in) { + int n = in.nextInt(); + int[] arr = new int[n]; + for(int i = 0; i < n; i++) arr[i] = in.nextInt(); + Arrays.sort(arr); + for(int i = 0; i < n; i += 2) out.print(arr[i] + " "); + for(int i = (n % 2 == 0 ? n-1 : n-2); i >= 0; i -= 2) out.print(arr[i] + " "); + out.println(); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + solve(in); + } +} +``` + diff --git a/Algorithm/Codeforces/Simulation/easy/Codeforces - 1136B. Nastya Is Playing Computer Games.md b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1136B. Nastya Is Playing Computer Games.md new file mode 100644 index 00000000..b4c97234 --- /dev/null +++ b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1136B. Nastya Is Playing Computer Games.md @@ -0,0 +1,54 @@ +## Codeforces - 1136B. Nastya Is Playing Computer Games + +#### [题目链接](https://codeforces.com/problemset/problem/1136/B) + +> https://codeforces.com/problemset/problem/1136/B + +#### 题目 + +有n个井盖,每个井盖上有一个小石头。给出n和k,k表示刚开始在第k个井盖上方。 + +有三种操作,左右移动,扔石头到任意一个井盖,下到井盖里拿金币。 + +只有井盖上没有石头才能下井盖。求捡完全部金币的最小步数。 + +![1136B_t.png](images/1136B_t.png) + +![1136B_t2.png](images/1136B_t2.png) + +#### 解析 + +模拟题。 + +* (1)、首先每个点至少要丢掉石头和捡起硬币,需要`2 * n`; +* (2)、可以发现,我们只需要丟额外的一个石头,所以`+1`; +* (3)、然后就是计算步数,不在两边的时候,需要额外多走`min(k-1, n-k)`步; + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int k = in.nextInt(); + int res = 0; + res += 2 * n;//每个点: 扔掉石头和捡起钱的花费 + res += 1; // 只需要额外多扔掉一个石头 + if(k == n || k == 1) res += n - 1; // 如果是左右端点,步行的步数 + else res += (n-1) + Math.min(k-1, n-k); //其他点,需要额外的步数 + out.println(res); + + } + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git a/Algorithm/Codeforces/Simulation/easy/Codeforces - 1153B. Serval and Toy Bricks.md b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1153B. Serval and Toy Bricks.md new file mode 100644 index 00000000..acb65c0d --- /dev/null +++ b/Algorithm/Codeforces/Simulation/easy/Codeforces - 1153B. Serval and Toy Bricks.md @@ -0,0 +1,93 @@ +## Codeforces - 1153B. Serval and Toy Bricks + +#### [题目链接](https://codeforces.com/problemset/problem/1153/B) + +> https://codeforces.com/problemset/problem/1153/B + +#### 题目 + +给出一个立体图形,高度最高为h,第一行给出n、m、h,第二行给出正视图每一列的高度(共m列),第三行给出左视图每一列的高度(共n列),然后有n*m个数,若其值为0,则意味着该处高度为0;若其值为1,则意味着该处高度不为0。求满足题意的立体图形的每一处的高度。(有多个解输出任意一个即可) + +![1555589144732](assets/1555589144732.png) + +![1555589154633](assets/1555589154633.png) + +![1555589162471](assets/1555589162471.png) + +## 解析 + +**设序列A存储正视图m列的高度,序列B存储左视图n列的高度**。 + +二维数组C存储`n*m`的01矩阵,若`C[i][j]=0`,则该处答案为0,(因为高度为0)否则输出`min(A[i],B[j]` ) + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + int n = in.nextInt(); + int m = in.nextInt(); + int h = in.nextInt(); + int[] z = new int[m]; + int[] left = new int[n]; + for(int i = 0; i < m; i++) z[i] = in.nextInt(); + for(int i = 0; i < n; i++) left[i] = in.nextInt(); + for(int i = 0; i < n; i++){ + for(int j = 0; j < m; j++){ + int x = in.nextInt(); + if(x == 0){ + out.print(0 + " "); + }else { + out.print(Math.min(left[i], z[j]) + " "); //去一个最小的即可 + } + } + out.println(); + } + + } + + /*--------------------------------------------------------------------------------------*/ + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } + + static class FR { + + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } + +} + +``` + diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 20C & 115A & 840A & 782C.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 20C & 115A & 840A & 782C.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 20C & 115A & 840A & 782C.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 20C & 115A & 840A & 782C.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 363B. Fence & 466C. Number of Ways.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 363B. Fence & 466C. Number of Ways.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 363B. Fence & 466C. Number of Ways.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 363B. Fence & 466C. Number of Ways.md diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 377A & 476B & 550A & 550C.md" b/Algorithm/Codeforces/Simulation/easy/Codeforces - 377A & 476B & 550A & 550C.md similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/Codeforces - 377A & 476B & 550A & 550C.md" rename to Algorithm/Codeforces/Simulation/easy/Codeforces - 377A & 476B & 550A & 550C.md diff --git a/Algorithm/Codeforces/Simulation/easy/assets/1555589144732.png b/Algorithm/Codeforces/Simulation/easy/assets/1555589144732.png new file mode 100644 index 00000000..57090ee1 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/assets/1555589144732.png differ diff --git a/Algorithm/Codeforces/Simulation/easy/assets/1555589154633.png b/Algorithm/Codeforces/Simulation/easy/assets/1555589154633.png new file mode 100644 index 00000000..37e5e7b1 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/assets/1555589154633.png differ diff --git a/Algorithm/Codeforces/Simulation/easy/assets/1555589162471.png b/Algorithm/Codeforces/Simulation/easy/assets/1555589162471.png new file mode 100644 index 00000000..e75072dd Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/assets/1555589162471.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1037C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1037C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1037C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1037C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1077C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1077C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1077C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1077C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1077C_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/1077C_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1077C_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/1077C_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1080B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1080B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1080B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1080B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1080B_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/1080B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1080B_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/1080B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1084B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1084B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1084B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1084B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1088B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1088B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1088B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1088B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1090A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1090A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1090A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1090A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_s.png" b/Algorithm/Codeforces/Simulation/easy/images/1103A_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_s.png" rename to Algorithm/Codeforces/Simulation/easy/images/1103A_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1103A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1103A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/1103A_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1103A_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/1103A_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1105A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1105A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1105B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1105B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105B_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/1105B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1105B_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/1105B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t1.png" b/Algorithm/Codeforces/Simulation/easy/images/1106B_t1.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t1.png" rename to Algorithm/Codeforces/Simulation/easy/images/1106B_t1.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/1106B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/1106B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t3.png" b/Algorithm/Codeforces/Simulation/easy/images/1106B_t3.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1106B_t3.png" rename to Algorithm/Codeforces/Simulation/easy/images/1106B_t3.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1108C_s.png" b/Algorithm/Codeforces/Simulation/easy/images/1108C_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1108C_s.png" rename to Algorithm/Codeforces/Simulation/easy/images/1108C_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1108C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1108C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1108C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1108C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1110B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/1110B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/1110B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/1110B_t.png diff --git a/Algorithm/Codeforces/Simulation/easy/images/1131C_t.png b/Algorithm/Codeforces/Simulation/easy/images/1131C_t.png new file mode 100644 index 00000000..9bc5b790 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/images/1131C_t.png differ diff --git a/Algorithm/Codeforces/Simulation/easy/images/1136B_t.png b/Algorithm/Codeforces/Simulation/easy/images/1136B_t.png new file mode 100644 index 00000000..c205538c Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/images/1136B_t.png differ diff --git a/Algorithm/Codeforces/Simulation/easy/images/1136B_t2.png b/Algorithm/Codeforces/Simulation/easy/images/1136B_t2.png new file mode 100644 index 00000000..86ac35c0 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/easy/images/1136B_t2.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_s.png" b/Algorithm/Codeforces/Simulation/easy/images/115A_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_s.png" rename to Algorithm/Codeforces/Simulation/easy/images/115A_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_s2.png" b/Algorithm/Codeforces/Simulation/easy/images/115A_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_s2.png" rename to Algorithm/Codeforces/Simulation/easy/images/115A_s2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/115A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/115A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/115A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/20C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/20C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/20C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/20C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/313B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/313B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/313B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/313B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/363B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/363B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/363B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/363B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/377A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/377A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/377A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/377A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/466C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/466C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/466C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/466C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/466_s.png" b/Algorithm/Codeforces/Simulation/easy/images/466_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/466_s.png" rename to Algorithm/Codeforces/Simulation/easy/images/466_s.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/474D_t.png" b/Algorithm/Codeforces/Simulation/easy/images/474D_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/474D_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/474D_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/476B_t.png" b/Algorithm/Codeforces/Simulation/easy/images/476B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/476B_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/476B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/476B_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/476B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/476B_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/476B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/489C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/489C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/489C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/489C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/550A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/550A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/550A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/550A_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/550C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/550C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/550C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/550C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t.png" b/Algorithm/Codeforces/Simulation/easy/images/782C_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/782C_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t2.png" b/Algorithm/Codeforces/Simulation/easy/images/782C_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t2.png" rename to Algorithm/Codeforces/Simulation/easy/images/782C_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t3.png" b/Algorithm/Codeforces/Simulation/easy/images/782C_t3.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/782C_t3.png" rename to Algorithm/Codeforces/Simulation/easy/images/782C_t3.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/easy/images/840A_t.png" b/Algorithm/Codeforces/Simulation/easy/images/840A_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/easy/images/840A_t.png" rename to Algorithm/Codeforces/Simulation/easy/images/840A_t.png diff --git a/Algorithm/Codeforces/Simulation/hard/Codeforces - 558C. Amr and Chemistry.md b/Algorithm/Codeforces/Simulation/hard/Codeforces - 558C. Amr and Chemistry.md new file mode 100644 index 00000000..b9629481 --- /dev/null +++ b/Algorithm/Codeforces/Simulation/hard/Codeforces - 558C. Amr and Chemistry.md @@ -0,0 +1,112 @@ +# Codeforces - 558C. Amr and Chemistry + +#### [题目链接](https://codeforces.com/problemset/problem/558/C) + +> https://codeforces.com/problemset/problem/558/C + +#### 题目 + +就是给你`n`,和`n`个数`a[i]`,每次可以将`a[i]/2`或者`a[i]*2`,问你最少的次数将所有数变成一样的。 + +![1554824823989](assets/1554824823989.png) + +## 解析 + +暴力。 + +参考: + +https://www.cnblogs.com/zhengguiping--9876/p/5162393.html + +https://blog.csdn.net/u014028317/article/details/46897963 + +`cnt`数组统计到这个点的`a[i]`的数目,`num`数组统计到这个点的需要的步数。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + int n = in.nextInt(); + int[] a = new int[n]; + int max = 0; + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + max = Math.max(max, a[i]); + } + int[] cnt = new int[max + 1]; + int[] num = new int[max + 1]; + for (int i = 0; i < n; i++) { + int t = a[i]; + int step = 0; + cnt[a[i]]++; + while(t * 2 <= max){ + t *= 2; + step++; + cnt[t]++; + num[t] += step; + } + + t = a[i]; + step = 0; + while(t > 0){ + if(t != 1 && t%2 == 1){ + int step2 = step + 1; + int t2 = t / 2; + while(t2*2 <= max){ + t2 *= 2; + step2++; + cnt[t2]++; + num[t2] += step2; + } + } + t /= 2; + step++; + cnt[t]++; + num[t] += step; + } + } + int min = Integer.MAX_VALUE; + for(int i = 0 ; i <= max; i++)if(cnt[i] == n) min = Math.min(min, num[i]); + out.println(min); + } + + /**-----------------------------------not code------------------------------------------**/ + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + int nextInt() { + return Integer.parseInt(next()); + } + } +} +``` + diff --git a/Algorithm/Codeforces/Simulation/hard/assets/1554824823989.png b/Algorithm/Codeforces/Simulation/hard/assets/1554824823989.png new file mode 100644 index 00000000..cf61a8d7 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/hard/assets/1554824823989.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/median/Codeforces - 1110B. Tape(\350\264\252\345\277\203+\346\216\222\345\272\217).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1110B. Tape(\350\264\252\345\277\203+\346\216\222\345\272\217).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/median/Codeforces - 1110B. Tape(\350\264\252\345\277\203+\346\216\222\345\272\217).md" rename to "Algorithm/Codeforces/Simulation/median/Codeforces - 1110B. Tape(\350\264\252\345\277\203+\346\216\222\345\272\217).md" diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/median/Codeforces - 1111B. Average Superhero Gang Power(\350\266\205\344\272\272\347\232\204\346\234\200\345\244\247\345\271\263\345\235\207\346\210\230\346\226\227\345\212\233).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1111B. Average Superhero Gang Power(\350\266\205\344\272\272\347\232\204\346\234\200\345\244\247\345\271\263\345\235\207\346\210\230\346\226\227\345\212\233).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/median/Codeforces - 1111B. Average Superhero Gang Power(\350\266\205\344\272\272\347\232\204\346\234\200\345\244\247\345\271\263\345\235\207\346\210\230\346\226\227\345\212\233).md" rename to "Algorithm/Codeforces/Simulation/median/Codeforces - 1111B. Average Superhero Gang Power(\350\266\205\344\272\272\347\232\204\346\234\200\345\244\247\345\271\263\345\235\207\346\210\230\346\226\227\345\212\233).md" diff --git "a/Algorithm/Codeforces/Simulation/median/Codeforces - 1118B. Tanya and Candies(\345\211\215\347\274\200\345\222\214\345\210\206\345\245\207\345\201\266).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1118B. Tanya and Candies(\345\211\215\347\274\200\345\222\214\345\210\206\345\245\207\345\201\266).md" new file mode 100644 index 00000000..c040711f --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1118B. Tanya and Candies(\345\211\215\347\274\200\345\222\214\345\210\206\345\245\207\345\201\266).md" @@ -0,0 +1,78 @@ +## Codeforces - 1118B. Tanya and Candies(前缀和分奇偶) + +#### [题目链接]() + +> https://codeforces.com/problemset/problem/1118/B + +#### 题目 + +给你`n`和`n`个数,要你从`n`个数中拿走一个`arr[i]`,然后剩下的`n-1`个数,如果奇数位置上的数的和 等于 偶数位置上的和,那就是一个好的方案,问你这样的`arr[i]`有多少个。 + +![1118B_t1.png](images/1118B_t1.png) + +![1118B_t2.png](images/1118B_t2.png) + + + +### 解析 + +这题也比较有意思。 + +首先看到数据规模肯定需要`O(N)`左右的时间复杂度。 + +所以我们需要先用类似`sums`数组维护一个前缀和。 + +但是这个前缀和需要分为奇数和偶数来维护,这里奇数的为`ods`数组,偶数的为`evs`数组。 + +然后关键的地方: + +* 当我们删除`arr[i]`之后,怎么求的这个数组的奇数位置上的和 和 偶数位置上的和呢? +* 先看奇数的:删除`arr[i]`之后,`arr[i]`左边的奇数和为`ods[i-1]`即可。而`arr[i]`右边的呢? 可以通过`evs[n] - evs[i]`得到,因为删除`arr[i]`之后,**后面的奇数部分原先应该恰好是偶数部分**; +* 同理。偶数的:删除`arr[i]`之后,`arr[i]`左边的偶数和为`evs[i-1]`即可。而`arr[i]`右边的呢? 可以通过`ods[n] - ods[i]`得到,因为删除`arr[i]`之后,**后面的偶数部分原先应该恰好是奇数部分**; + +举几个例子: + +![1118B_s.png](images/1118B_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int n = in.nextInt(); + int[] arr = new int[n + 1]; + int[] ods = new int[n + 1]; // 其实可以只开 n/2 + 2即可,但是那样下标会很麻烦,所以直接开n+1了 + int[] evs = new int[n + 1]; + for (int i = 1; i <= n; i++) { + arr[i] = in.nextInt(); + if (i % 2 == 1) { + ods[i] = ods[i - 1] + arr[i]; + evs[i] = evs[i - 1]; + } else { + evs[i] = evs[i - 1] + arr[i]; + ods[i] = ods[i - 1]; + } + } + +// out.println("ods : " + Arrays.toString(ods)); +// out.println("evs : " + Arrays.toString(evs)); + + int cnt = 0; + for (int i = 1; i <= n; i++) { + int curOd = ods[i - 1], curEv = evs[i - 1]; + curOd += evs[n] - evs[i]; // 删掉arr[i]之后,后面的变成求偶数的 + curEv += ods[n] - ods[i]; // 同理,变成求奇数的 + if (curOd == curEv) cnt++; + } + out.println(cnt); + } +} + +``` + diff --git a/Algorithm/Codeforces/Simulation/median/Codeforces - 1132C. Painting the Fence.md b/Algorithm/Codeforces/Simulation/median/Codeforces - 1132C. Painting the Fence.md new file mode 100644 index 00000000..ad686f37 --- /dev/null +++ b/Algorithm/Codeforces/Simulation/median/Codeforces - 1132C. Painting the Fence.md @@ -0,0 +1,66 @@ +## Codeforces - 1132C. Painting the Fence + +#### [题目链接](https://codeforces.com/problemset/problem/1132/C) + +> https://codeforces.com/problemset/problem/1132/C + +#### 题目 + +给你`n、Q`,`n`表示`1~n`个点,给你`Q`个画工,每个画工的范围是`L[i] ~ R[i]`的区间,要你从中去掉两个画工,使得能画的点尽可能的多,输出这个数量。 + +![1132C_t.png](images/1132C_t.png) + +#### 解析 + +* 枚举,Q次循环,首先去掉当前线段(去掉一条),最后还要恢复该线段; +* 去掉该线段后我们想要再去掉一条线段取得最大值,先用`tmp`记录去掉一条线段后的可覆盖的点的个数。 +* 我们需要用到**覆盖次数为1**的前缀和了,因为覆盖次数再多一点的话,去不去掉没影响,但是覆盖次数为1的等下枚举去掉第2条的时候就有影响了; +* 看L到R区间内有几个覆盖次数为1的,如果为0,则tmp不需要变,否则tmp需要减去该值,运用前缀和可以O(1)查找。 +* 之后更新最优值即可。 + +代码: + + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int Q = in.nextInt(); + int[] L = new int[Q + 1]; + int[] R = new int[Q + 1]; + int[] c = new int[n + 1]; // 每个位置出现的次数 + int[] sums = new int[n + 1]; // 只出现一次的前缀和 + for (int q = 1; q <= Q; q++) { + L[q] = in.nextInt(); + R[q] = in.nextInt(); + for(int i = L[q]; i <= R[q]; i++) c[i]++;// 记录每个点覆盖的次数 + } + int res = 0; + for(int q = 1; q <= Q; q++){//先枚举去掉 L[q]~R[q] + for(int i = L[q]; i <= R[q]; i++) c[i]--; + int tmp = 0; + for(int i = 1; i <= n; i++){// 求出出现大于1次的: tmp, 顺便求出次数为1的前缀和sums + if(c[i] > 0) tmp++; + if(c[i] == 1) sums[i] = sums[i-1] + 1; // 为1的前缀和,因为下面枚举下面的时候,为1的可以消失 + else sums[i] = sums[i-1]; + } + for(int p = q + 1; p <= Q; p++) //如果 q == Q, 这时虽然只去掉了一个,但是这个循环根本不会进行 + res = Math.max(res, tmp - (sums[R[p]] - sums[L[p]-1])); + for(int i = L[q]; i <= R[q]; i++) c[i]++; // 还原 + } + out.println(res); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git "a/Algorithm/Codeforces/Simulation/median/Codeforces - 1133B. Preparation for International Women's Day(\346\261\202pair\344\271\213\345\222\214\346\225\264\351\231\244k\347\232\204\346\225\260).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1133B. Preparation for International Women's Day(\346\261\202pair\344\271\213\345\222\214\346\225\264\351\231\244k\347\232\204\346\225\260).md" new file mode 100644 index 00000000..01e4c482 --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1133B. Preparation for International Women's Day(\346\261\202pair\344\271\213\345\222\214\346\225\264\351\231\244k\347\232\204\346\225\260).md" @@ -0,0 +1,48 @@ +## Codeforces - 1133B. Preparation for International Women's Day(求pair之和整除k的数) + +#### [题目链接](https://codeforces.com/contest/1133/problem/B) + +https://codeforces.com/contest/1133/problem/B + +#### 题目 + +给你`n、k`,要你从`n`个数中挑出最多的对数,每一对`a[i] + a[j]`要求能整除`k`,问你多少个这样的数(多少对,然后乘2) + +![1133B_t.png](images/1133B_t.png) + +#### 解析 + +很巧妙的解法,先对输入的数取摸,然后计算`min(a[i], a[k-i])`累加个数即可。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + // write code + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int k = in.nextInt(); + int[] c = new int[k+1]; + for(int i = 0; i < n; i++){ + int x = in.nextInt(); + c[x % k]++; + } + int res = c[0] / 2; // (0 + m) / 2 + for(int i = 1; i < (k%2 == 1 ? k/2+1 : k/2); i++) //注意分奇数偶数 + res += Math.min(c[i], c[k-i]); + if( k % 2 == 0) res += c[k/2]/2; + res *= 2; + out.println(res); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git "a/Algorithm/Codeforces/Simulation/median/Codeforces - 1133C. Balanced Team(\346\273\221\345\212\250\347\252\227\345\217\243)(\346\234\211\345\235\221\344\270\215\350\246\201\347\224\250int\347\224\250Integer).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1133C. Balanced Team(\346\273\221\345\212\250\347\252\227\345\217\243)(\346\234\211\345\235\221\344\270\215\350\246\201\347\224\250int\347\224\250Integer).md" new file mode 100644 index 00000000..0b1c33f3 --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1133C. Balanced Team(\346\273\221\345\212\250\347\252\227\345\217\243)(\346\234\211\345\235\221\344\270\215\350\246\201\347\224\250int\347\224\250Integer).md" @@ -0,0 +1,96 @@ +## Codeforces - 1133C. Balanced Team(滑动窗口)(有坑不要用int用Integer) + +#### [题目链接](https://codeforces.com/contest/1133/problem/C) + +https://codeforces.com/contest/1133/problem/C + +#### 题目 + +给你`n`个数,要你从中取最多的个数,但是取出的数的最大值和最小值的差不能超过`5`。 + +![1133C_t.png](images/1133C_t.png) + +#### 解析 + +题目不难,先排序,然后用两个指针滑动即可。 + +但是这题有一个很大的坑,就是数组要用`Integer`,而不是`int`,因为Java内部对于`int`是使用快速排序,而对于`Integer`是使用堆排序,所以堆排序最坏情况要比快速排序更好。 + +具体看[**这里**](https://codeforces.com/blog/entry/46756)。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + // write code + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + // 用int 就会超时,因为Arrays.sort(int)用的是快排,而Arrays.sort(Integer)用的是归并排序 + Integer[] a = new Integer[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + Arrays.sort(a); + int res = 1; + int l = 0; + for(int r = 0; r < n; r++) { + while(a[r] - a[l] > 5) + l++; + res = Math.max(res, r - l + 1); + } + out.println(res); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + +C++也有坑,不能用`cin`:,用不用`qsort`倒无所谓。 + +```c++ +#include +using namespace std; + +const int maxn = 2e5 + 10; + +int cmp(const void * a, const void * b){ + return ( *(int*)a - *(int*)b ); +} + +int main() +{ +#ifndef ONLINE_JUDGE + freopen("in.txt","r",stdin); +#endif +//code start + + int n; + int a[maxn]; + cin >> n; + for(int i = 0; i < n; i++) scanf("%d", &a[i]); + //for(int i = 0; i < n; i++) cin >> a[i]; //用cin就会超时 + sort(a, a + n); + //qsort(a, n, sizeof(a[0]), cmp); + int res = 1; + for(int l = 0, r = 0; r < n; r++){ + while(a[r] - a[l] > 5) l++; + res = max(res, r - l + 1); + } + cout << res << '\n'; + +// code end +#ifndef ONLINE_JUDGE + cerr << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC << " s.\n"; +#endif + return 0; +} + +``` + diff --git "a/Algorithm/Codeforces/Simulation/median/Codeforces - 1136C. Nastya Is Transposing Matrices(\345\210\244\346\226\255a\347\237\251\351\230\265\346\230\257\345\220\246\345\217\257\344\273\245\351\200\232\350\277\207\350\275\254\347\275\256\345\276\227\345\210\260b\347\237\251\351\230\265).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1136C. Nastya Is Transposing Matrices(\345\210\244\346\226\255a\347\237\251\351\230\265\346\230\257\345\220\246\345\217\257\344\273\245\351\200\232\350\277\207\350\275\254\347\275\256\345\276\227\345\210\260b\347\237\251\351\230\265).md" new file mode 100644 index 00000000..d2bb3967 --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1136C. Nastya Is Transposing Matrices(\345\210\244\346\226\255a\347\237\251\351\230\265\346\230\257\345\220\246\345\217\257\344\273\245\351\200\232\350\277\207\350\275\254\347\275\256\345\276\227\345\210\260b\347\237\251\351\230\265).md" @@ -0,0 +1,137 @@ +## Codeforces - 1136C. Nastya Is Transposing Matrices(判断a矩阵是否可以通过转置得到b矩阵) + +#### [题目链接](https://codeforces.com/problemset/problem/1136/C) + +> https://codeforces.com/problemset/problem/1136/C + +#### 题目 + +给你`n`和`m`,以及两个`n`行`m`列的矩阵`a、b`,问你能不能通过**不断的转置`a`矩阵里面的子矩阵**,得到`b`。 + +![1136C_t.png](images/1136C_t.png) + +![1136C_t2.png](images/1136C_t2.png) + +#### 解析 + +将两个矩阵对应的划线区域排序,然后比较是否都相同即可。 + +意思就是只要`b`对应的划线区域(斜线)是`a`对应的斜线区域的元素即可,不需要顺序。 + +这个过程可以用排序解决,也可以用哈希表解决。 + +![1136C_s.png](images/1136C_s.png) + +代码: + +注意下面的`!Object.equals`用法: + +https://www.cnblogs.com/woaixingxing/p/7482215.html + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int m = in.nextInt(); + int[][] a = new int[n][m]; + int[][] b = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + a[i][j] = in.nextInt(); + } + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + b[i][j] = in.nextInt(); + } + } + boolean ok = true; + for (int i = 0, j = 0; i < n && j < m; ) { + List arr1 = new ArrayList<>(); + List arr2 = new ArrayList<>(); + for (int pr = i, pc = j; pr >= 0 && pc < m; pr--, pc++) { + arr1.add(a[pr][pc]); + arr2.add(b[pr][pc]); + } + Collections.sort(arr1); + Collections.sort(arr2); + for (int p = 0; p < arr1.size(); p++) +// if (arr1.get(p) != arr2.get(p)) { // Wrong , 直接这样有可能是null,则会返回false + if (!Objects.equals(arr1.get(p), arr2.get(p))) { //Right + ok = false; + break; + } + if (!ok) break; + if (i == n - 1) + j++; + else + i++; + } + out.println(ok ? "YES" : "NO"); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + +更好的做法: 原理差不多。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + int n = in.nextInt(); + int m = in.nextInt(); + int[][] a = new int[n][m]; + int[][] b = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + a[i][j] = in.nextInt(); + } + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + b[i][j] = in.nextInt(); + } + } + boolean ok = true; + for (int i = 0, j = 0; i < n && j < m; ) { + HashMap map = new HashMap<>(); + for (int pr = i, pc = j; pr >= 0 && pc < m; pr--, pc++) { + map.put(a[pr][pc], map.getOrDefault(a[pr][pc], 0) + 1); + map.put(b[pr][pc], map.getOrDefault(b[pr][pc], 0) - 1); + } + for(int v : map.values()) if(v != 0){ + ok = false; + break; + } + if(!ok) break; + if (i == n - 1) + j++; + else + i++; + } + out.println(ok ? "YES" : "NO"); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git a/Algorithm/Codeforces/Simulation/median/Codeforces - 1153A. Serval and Bus.md b/Algorithm/Codeforces/Simulation/median/Codeforces - 1153A. Serval and Bus.md new file mode 100644 index 00000000..cbb32274 --- /dev/null +++ b/Algorithm/Codeforces/Simulation/median/Codeforces - 1153A. Serval and Bus.md @@ -0,0 +1,100 @@ +# Codeforces - 1153A. Serval and Bus + +#### [题目链接](https://codeforces.com/problemset/problem/1153/A) + +> https://codeforces.com/problemset/problem/1153/A + +#### 题目 + +一个人要等车,他在`t`时刻到达车站,下面有`n`辆列车,每个列车有一个`s[i]、d[i]`表示的是`i`列车在`s[i]`的时候到达,然后每隔`d[i]`就再来一次。这个人会坐最早的列车,问你会坐那一辆。 + +![1555248922838](assets/1555248922838.png) + +![1555248930476](assets/1555248930476.png) + +## 解析 + +如果一开始就有`s[i]`和`t`相等,直接输出这个序号即可。 + +然后直接和求出`t`相近的时间即可。 + +通过`t - s[i] / d[i]`求出每一个隔的次数。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { +// Scanner in = new Scanner(new BufferedInputStream(System.in)); + FR in = new FR(stream); + int n = in.nextInt(); + int t = in.nextInt(); + int[] s = new int[n]; + int[] d = new int[n]; + int resI = 0; + for (int i = 0; i < n; i++) { + s[i] = in.nextInt(); + d[i] = in.nextInt(); + if (s[i] == t) resI = i + 1; + } + if (resI != 0) { + out.println(resI); + return; + } + for (int i = 0; i < n; i++) { + if (t < s[i]) continue; // 直接跳过 + // div 或者 div+1 + int remain = (t - s[i]) % d[i]; + int div = (t - s[i]) / d[i]; + s[i] = remain == 0 ? s[i] + (div * d[i]) : s[i] + (div + 1) * d[i]; + } + int min = Integer.MAX_VALUE; + for (int i = 0; i < n; i++) { + if (s[i] - t < min) { + min = s[i] - t; + resI = i + 1; + } + } + out.println(resI); + } + + /*--------------------------------------------------------------------------------------*/ + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} +``` + diff --git "a/Algorithm/Codeforces/Simulation/median/Codeforces - 1155C. Alarm Clocks Everywhere(\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260).md" "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1155C. Alarm Clocks Everywhere(\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260).md" new file mode 100644 index 00000000..d4ab0d1e --- /dev/null +++ "b/Algorithm/Codeforces/Simulation/median/Codeforces - 1155C. Alarm Clocks Everywhere(\346\234\200\345\244\247\345\205\254\347\272\246\346\225\260).md" @@ -0,0 +1,79 @@ +# Codeforces - 1155C. Alarm Clocks Everywhere(GCD) + +#### [题目链接](https://codeforces.com/problemset/problem/1155/C) + +> https://codeforces.com/problemset/problem/1155/C + +#### 题目 + +给出N个时刻(x数组), M个时间间隔(p数组), 给出一个闹钟可以在任意时刻响起, 使用其中一个间隔(索引), 使得可以覆盖所有的时刻。 + +![1556674496917](assets/1556674496917.png) + +![1556674515762](assets/1556674515762.png) + +### 解析 + +求出`x[i-1] ~ x[i]`间距之间的最大公约数,记为`Gc`,然后在`p[i]`中找到`Gc % p[i]`的即可。 + +代码: + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static long gcd(long a, long b){ + long r; + while(b != 0){ + r = a % b; + a = b; + b = r; + } + return a; + } + + static long ngcd(long[] a, int n){ + if(n == 1) return a[0]; + return gcd(a[n-1], ngcd(a, n-1)); + } + + static void solve(Scanner in, PrintWriter out){ + int n = in.nextInt(); + int m = in.nextInt(); + long[] x = new long[n]; + long[] p = new long[m]; + long[] g = new long[n]; + long pre = 0; + for(int i = 0; i < n; i++){ + x[i] = in.nextLong(); + if(i > 0) g[i-1] = x[i] - pre; + pre = x[i]; + } + for(int i = 0; i < m; i++) p[i] = in.nextLong(); + long Gc = ngcd(g, n-1); + boolean flag = false; + int resi = -1; + for(int i = 0; i < m; i++) if(Gc % p[i] == 0){ // notice not Gc == p[i] + flag = true; + resi = i; + break; + } + if(!flag) out.println("NO"); + else { + out.println("YES"); + out.println(x[0] + " " + (resi+1)); + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + solve(in, out); + out.close(); + } +} + +``` + diff --git a/Algorithm/Codeforces/Simulation/median/Codeforcess - 1154B. Make Them Equal.md b/Algorithm/Codeforces/Simulation/median/Codeforcess - 1154B. Make Them Equal.md new file mode 100644 index 00000000..aa848bff --- /dev/null +++ b/Algorithm/Codeforces/Simulation/median/Codeforcess - 1154B. Make Them Equal.md @@ -0,0 +1,70 @@ +# Codeforcess - 1154B. Make Them Equal + +#### [题目链接](https://codeforces.com/problemset/problem/1154/B) + +> https://codeforces.com/problemset/problem/1154/B + +#### 题目 + +给你一个数组,要你选出一个最小的D,使得将数组中的每一个数+D或者-D或者不变,操作一遍之后能够将数组中的所有数变成同一个数。如果不行输出-1。 + +![1556677734504](assets/1556677734504.png) + +### 解析 + +比较好的方法是用set去重,set中超过三个元素就不满足,然后自己再判断一下就行了。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + + static void solve(Scanner in, PrintWriter out){ + int n = in.nextInt(); + int[] a = new int[n]; + HashSet set = new HashSet<>(); + int mx = Integer.MIN_VALUE; + int mn = Integer.MAX_VALUE; + for(int i = 0; i < n; i++){ + a[i] = in.nextInt(); + set.add(a[i]); + mx = Math.max(mx, a[i]); + mn = Math.min(mn, a[i]); + } + if(set.size() > 3){ + out.println(-1); + return; + } + if(set.size() == 1){ + out.println(0); + return; + } + if(set.size() == 2){ + out.println( (mx - mn)%2 == 0 ? (mx - mn)/2 : mx - mn); + return; + } + // set.size() == 3 + if ((mx - mn) % 2 == 1) { + out.println(-1); + return; + } + int d = (mx - mn) / 2; + for(int i = 0; i < n; i++) if(!(mx == a[i] || mn == a[i] || a[i] + d == mx)){ + out.println(-1); + return; + } + out.println(d); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + solve(in, out); + out.close(); + } +} + +``` + diff --git a/Algorithm/Codeforces/Simulation/median/assets/1555248922838.png b/Algorithm/Codeforces/Simulation/median/assets/1555248922838.png new file mode 100644 index 00000000..54058742 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/assets/1555248922838.png differ diff --git a/Algorithm/Codeforces/Simulation/median/assets/1555248930476.png b/Algorithm/Codeforces/Simulation/median/assets/1555248930476.png new file mode 100644 index 00000000..ee1424ab Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/assets/1555248930476.png differ diff --git a/Algorithm/Codeforces/Simulation/median/assets/1556674496917.png b/Algorithm/Codeforces/Simulation/median/assets/1556674496917.png new file mode 100644 index 00000000..3fec8a2e Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/assets/1556674496917.png differ diff --git a/Algorithm/Codeforces/Simulation/median/assets/1556674515762.png b/Algorithm/Codeforces/Simulation/median/assets/1556674515762.png new file mode 100644 index 00000000..3afd6858 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/assets/1556674515762.png differ diff --git a/Algorithm/Codeforces/Simulation/median/assets/1556677734504.png b/Algorithm/Codeforces/Simulation/median/assets/1556677734504.png new file mode 100644 index 00000000..e0ea7de9 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/assets/1556677734504.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/median/images/1110B_t.png" b/Algorithm/Codeforces/Simulation/median/images/1110B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/median/images/1110B_t.png" rename to Algorithm/Codeforces/Simulation/median/images/1110B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/Simulation/median/images/1111B_t.png" b/Algorithm/Codeforces/Simulation/median/images/1111B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/Simulation/median/images/1111B_t.png" rename to Algorithm/Codeforces/Simulation/median/images/1111B_t.png diff --git a/Algorithm/Codeforces/Simulation/median/images/1118B_s.png b/Algorithm/Codeforces/Simulation/median/images/1118B_s.png new file mode 100644 index 00000000..f7e42b6e Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1118B_s.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1118B_t1.png b/Algorithm/Codeforces/Simulation/median/images/1118B_t1.png new file mode 100644 index 00000000..6488c5ba Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1118B_t1.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1118B_t2.png b/Algorithm/Codeforces/Simulation/median/images/1118B_t2.png new file mode 100644 index 00000000..be799938 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1118B_t2.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1132C_t.png b/Algorithm/Codeforces/Simulation/median/images/1132C_t.png new file mode 100644 index 00000000..763c6d1d Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1132C_t.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1133B_t.png b/Algorithm/Codeforces/Simulation/median/images/1133B_t.png new file mode 100644 index 00000000..55faf5c4 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1133B_t.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1133C_t.png b/Algorithm/Codeforces/Simulation/median/images/1133C_t.png new file mode 100644 index 00000000..98b5ab02 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1133C_t.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1136C_s.png b/Algorithm/Codeforces/Simulation/median/images/1136C_s.png new file mode 100644 index 00000000..efa05688 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1136C_s.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1136C_t.png b/Algorithm/Codeforces/Simulation/median/images/1136C_t.png new file mode 100644 index 00000000..309198c9 Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1136C_t.png differ diff --git a/Algorithm/Codeforces/Simulation/median/images/1136C_t2.png b/Algorithm/Codeforces/Simulation/median/images/1136C_t2.png new file mode 100644 index 00000000..59d9bfdb Binary files /dev/null and b/Algorithm/Codeforces/Simulation/median/images/1136C_t2.png differ diff --git "a/Algorithm/Codeforces/SortOrBinarySearch/Codeforces - 1137A. Skyscrapers(\346\216\222\345\272\217_\344\272\214\345\210\206).md" "b/Algorithm/Codeforces/SortOrBinarySearch/Codeforces - 1137A. Skyscrapers(\346\216\222\345\272\217_\344\272\214\345\210\206).md" new file mode 100644 index 00000000..ae5f452a --- /dev/null +++ "b/Algorithm/Codeforces/SortOrBinarySearch/Codeforces - 1137A. Skyscrapers(\346\216\222\345\272\217_\344\272\214\345\210\206).md" @@ -0,0 +1,126 @@ +## Codeforces - 1137A. Skyscrapers(排序|二分) + +#### [题目链接](https://codeforces.com/problemset/problem/1137/A) + +> https://codeforces.com/problemset/problem/1137/A + +#### 题目 + +有一个 `n×m `的矩阵 `a`。 + +对于每个` (i,j)(1≤i≤n, 1≤i≤n)`,你把第` i`行和第 `j`列单独抽出(`(i, j)`所在行和列),有 `n+m−1` 个数被抽出。 + +你可以对这些数**重新标号**为正整数,但是要满足第` i`行所有数的大小关系(相对大小)不变,第` j`列所有数的大小关系不变(两个限制相互独立)。 + +满足这个限制下,希望最大的标号尽量小,对每个` (i,j) `求出这个最小的最大标号。 + +![1137A_t.png](images/1137A_t.png) + +![1137A_t2.png](images/1137A_t2.png) + +### 解析 + +这题感觉和[NowCoder-发奖金](https://github.com/ZXZxin/ZXNotes/blob/master/%E5%88%B7%E9%A2%98/Other/nowcoder/2016%E6%A0%A1%E6%8B%9B%E7%9C%9F%E9%A2%98/Nowcoder%20-%20%E5%8F%91%E5%A5%96%E9%87%91.md)有点类似。 + +因为行大小关系不变,列大小关系不变,我们考虑分别考虑每行每列,并统计每个数在行内和列内的排名。 + +若 `(i,j)` 在行内排名为第 `x` 小,列内排名为第 `y`小,它最优情况下就被标号为` max(x,y)`,然后行内、列内比它大的数依次往大排。 + +也是用`r1`和`r2`两个分别统计,然后取最大的那个。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static Random rnd = new Random(); + + static void solve(InputStream is, PrintWriter out) { +// Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + FR in = new FR(is); + /**-------------write code-----------------**/ + int n = in.nextInt(); + int m = in.nextInt(); + int[][] a = new int[n][m]; + for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) a[i][j] = in.nextInt(); + int[][] r1 = new int[n][m], r2 = new int[n][m]; + int[] t1 = new int[n]; + for (int j = 0; j < m; j++) { + for (int i = 0; i < n; i++) + t1[i] = a[i][j]; + int cnt = sortUnique(t1); + for (int i = 0; i < n; i++) { + int pos = Arrays.binarySearch(t1, 0, cnt, a[i][j]); + r1[i][j] = pos; + r2[i][j] = cnt - 1 - pos; + } + } + int[] t2 = new int[m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) + t2[j] = a[i][j]; + int cnt = sortUnique(t2); + for (int j = 0; j < m; j++) { + int pos = Arrays.binarySearch(t2, 0, cnt, a[i][j]); + r1[i][j] = Math.max(r1[i][j], pos); + r2[i][j] = Math.max(r2[i][j], cnt - 1 - pos); + out.print((r1[i][j] + r2[i][j] + 1) + " "); + } + out.println(); + } + } + + static int sortUnique(int[] arr) { + for (int i = 0; i < arr.length; i++) { + int j = rnd.nextInt(i + 1); + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } + Arrays.sort(arr); // quick sort + int cnt = 0; + for (int i = 0; i < arr.length; i++) { + if (i == 0 || arr[i] != arr[i - 1]) + arr[cnt++] = arr[i]; + } + return cnt; + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); //must close + } + + // FastReader + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} + +``` + diff --git a/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t.png b/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t.png new file mode 100644 index 00000000..b5fcda39 Binary files /dev/null and b/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t.png differ diff --git a/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t2.png b/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t2.png new file mode 100644 index 00000000..f054c4b8 Binary files /dev/null and b/Algorithm/Codeforces/SortOrBinarySearch/images/1137A_t2.png differ diff --git "a/\345\210\267\351\242\230/Codeforces/String/Codeforces - 1104B. Game with string(\346\200\235\347\273\264\346\250\241\346\213\237).md" "b/Algorithm/Codeforces/String/Codeforces - 1104B. Game with string(\346\200\235\347\273\264\346\250\241\346\213\237).md" similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/String/Codeforces - 1104B. Game with string(\346\200\235\347\273\264\346\250\241\346\213\237).md" rename to "Algorithm/Codeforces/String/Codeforces - 1104B. Game with string(\346\200\235\347\273\264\346\250\241\346\213\237).md" diff --git "a/\345\210\267\351\242\230/Codeforces/String/images/1104B_t.png" b/Algorithm/Codeforces/String/images/1104B_t.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/String/images/1104B_t.png" rename to Algorithm/Codeforces/String/images/1104B_t.png diff --git "a/\345\210\267\351\242\230/Codeforces/String/images/1104B_t2.png" b/Algorithm/Codeforces/String/images/1104B_t2.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/String/images/1104B_t2.png" rename to Algorithm/Codeforces/String/images/1104B_t2.png diff --git "a/\345\210\267\351\242\230/Codeforces/String/images/1104_s.png" b/Algorithm/Codeforces/String/images/1104_s.png similarity index 100% rename from "\345\210\267\351\242\230/Codeforces/String/images/1104_s.png" rename to Algorithm/Codeforces/String/images/1104_s.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs1.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs1.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs2.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs2.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs3.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs3.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs4.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs4.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs5.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs5.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs6.png" b/Algorithm/DataStructure/Algorithm/BinarySearch/images/bs6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/images/bs6.png" rename to Algorithm/DataStructure/Algorithm/BinarySearch/images/bs6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" "b/Algorithm/DataStructure/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" similarity index 99% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" rename to "Algorithm/DataStructure/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" index 7b42cee2..92036555 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" +++ "b/Algorithm/DataStructure/Algorithm/BinarySearch/\344\272\214\345\210\206\346\237\245\346\211\276\347\232\204\346\200\273\347\273\223(6\347\247\215\345\217\230\345\275\242).md" @@ -78,7 +78,7 @@ return -1; } ``` -上面的两种方式一般还是第一种方式用的多一点。 +上面的两种方式一般还是第一种方式用的多一点。 *** ### 第一个`=key`的,不存在返回`-1` 这个和之前的不同是: diff --git a/Algorithm/DataStructure/Algorithm/Bit/BitNotes.md b/Algorithm/DataStructure/Algorithm/Bit/BitNotes.md new file mode 100644 index 00000000..bd5b0345 --- /dev/null +++ b/Algorithm/DataStructure/Algorithm/Bit/BitNotes.md @@ -0,0 +1,30 @@ +## 位运算可以实现 + +1、状态压缩 + + 用二进制的思想,表示某个数存不存在,1表示存在,0表示不存在 + +2、成对的思想 + +```java +0 ^ 1 = 1, 1 ^ 1 = 0 +4 ^ 1 = 5, 5 ^ 1 = 4 +``` + +3、lowbit运算 + +lowbit是得到最后一个1以及后面的数,例如` lowbit(1110010000) = 10000` + +```java + +n : 1110010000 +~n : 0001101111 +~n+1 : 0001110000 +(~n+1)&n: 0000010000 (答案) +而 (~n+1) = -n +所以 +int lowbit(n){ + return (-n) & n ; // (~n + 1) & n; 取反+1 & n +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/10_\345\277\2537.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/10_\345\277\2537.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/10_\345\277\2537.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/10_\345\277\2537.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/11_\345\277\2538.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/11_\345\277\2538.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/11_\345\277\2538.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/11_\345\277\2538.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/12_\345\240\2061.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/12_\345\240\2061.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/12_\345\240\2061.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/12_\345\240\2061.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/13_\345\275\222\345\271\2662.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/13_\345\275\222\345\271\2662.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/13_\345\275\222\345\271\2662.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/13_\345\275\222\345\271\2662.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/14_\345\240\2061.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/14_\345\240\2061.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/14_\345\240\2061.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/14_\345\240\2061.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/15_\350\256\241\346\225\2601.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/15_\350\256\241\346\225\2601.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/15_\350\256\241\346\225\2601.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/15_\350\256\241\346\225\2601.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/16_\345\237\272\346\225\260.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/16_\345\237\272\346\225\260.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/16_\345\237\272\346\225\260.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/16_\345\237\272\346\225\260.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/17_\346\241\266.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/17_\346\241\266.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/17_\346\241\266.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/17_\346\241\266.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/1_\346\217\222\345\205\245.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/1_\346\217\222\345\205\245.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/1_\346\217\222\345\205\245.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/1_\346\217\222\345\205\245.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/2_.png" b/Algorithm/DataStructure/Algorithm/Sort/images/2_.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/2_.png" rename to Algorithm/DataStructure/Algorithm/Sort/images/2_.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/3_\345\270\214\345\260\224.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/3_\345\270\214\345\260\224.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/3_\345\270\214\345\260\224.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/3_\345\270\214\345\260\224.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/4_\345\277\2531.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/4_\345\277\2531.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/4_\345\277\2531.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/4_\345\277\2531.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/5_\345\277\2532.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/5_\345\277\2532.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/5_\345\277\2532.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/5_\345\277\2532.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/6_\345\277\2533.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/6_\345\277\2533.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/6_\345\277\2533.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/6_\345\277\2533.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/7_\345\277\2534.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/7_\345\277\2534.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/7_\345\277\2534.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/7_\345\277\2534.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/8_\345\277\2535.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/8_\345\277\2535.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/8_\345\277\2535.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/8_\345\277\2535.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/9_\345\277\2536.png" "b/Algorithm/DataStructure/Algorithm/Sort/images/9_\345\277\2536.png" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/images/9_\345\277\2536.png" rename to "Algorithm/DataStructure/Algorithm/Sort/images/9_\345\277\2536.png" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" "b/Algorithm/DataStructure/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" similarity index 97% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" rename to "Algorithm/DataStructure/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" index 4ff7331b..cfa1e344 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" +++ "b/Algorithm/DataStructure/Algorithm/Sort/\345\220\204\347\247\215\346\216\222\345\272\217\347\256\227\346\263\225\346\200\273\347\273\223(\345\205\250\351\235\242).md" @@ -2,7 +2,7 @@ - [概括](#概括) - [冒泡排序](#冒泡排序) - - [改进的冒泡排序-鸡尾酒排序](#改进的冒泡排序-鸡尾酒排序) + - [鸡尾酒排序-改进的冒泡排序](#鸡尾酒排序-改进的冒泡排序) - [选择排序](#选择排序) - [插入排序](#插入排序) - [二分插入排序](#二分插入排序) @@ -101,7 +101,7 @@ static void bubbleSort2(int[] arr){ ``` *** -## 鸡尾酒排序-改进的冒泡排序 +## 鸡尾酒排序-改进的冒泡排序 也叫做定向冒泡排序: @@ -300,32 +300,13 @@ static int partition(int[] arr, int L, int R) { 代码: ```java -static void quickSort(int arr[]) { - if (arr == null || arr.length <= 1) - return; - quickProcess(arr, 0, arr.length - 1); -} - static void quickProcess(int[] arr, int L, int R) { - if (L >= R) - return; + if (L >= R) return; swap(arr, L, L + (int) (Math.random() * (R - L + 1))); //随机选取一个pivot int p = partition(arr, L, R); quickProcess(arr, L, p - 1); quickProcess(arr, p + 1, R); } - -static int partition(int[] arr, int L, int R) { - //直接选取 arr[L]作为pivot(中心点) - int key = arr[L]; - int pivot = L; - for (int i = L + 1; i <= R; i++) { - if (arr[i] < key) - swap(arr, i, ++pivot); - } - swap(arr, pivot, L); // 将arr[L]放到pivot位置(中间) --> 完全了按照arr[L]划分数组的目的 - return pivot; -} ``` **第二个优化(双路快速排序)(解决重复元素多的问题)** @@ -550,7 +531,7 @@ static void mergeProcess(int[] arr, int L, int R) { if (L >= R) return; //递归条件判断 int mid = L + ((R - L) >> 1); //这个相当于 (R+L)/2; - mergeProcess(arr, L, mid); //T(n/2) + mergeProcess(arr, L, mid); //T(n/2),注意不是mid-1 mergeProcess(arr, mid + 1, R); //T(n/2) /**这个是一个优化,因为arr[L,mid]和arr[mid+1,R]已经有序,所以如果满足这个条件,就不要排序了,防止一开始数组有序*/ if (arr[mid] > arr[mid + 1]) @@ -577,10 +558,11 @@ static void merge(int[] arr, int L, int mid, int R) { 注意几点: +* `mergeProcess(arr, L, mid)`,不是`mid-1`; + - **注意上面的代码中`if(arr[mid] > arr[mid+1])`防止一开始数组很有序的情况;** - **注意在外排比较的时候,为了保证稳定性,左右相等的时候,先拷贝左边的;** - **另外再补充一个[LintCode532Reverse Pairs归并排序求解逆序数](https://www.lintcode.com/problem/reverse-pairs/description)的问题**: ![在这里插入图片描述](images/11_快8.png) @@ -709,7 +691,7 @@ static void mergeSortBU(int[] arr){ ``` ## 堆排序 -堆的相关介绍看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79890060)。 +堆的相关介绍看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79890060)。 堆排序的过程是一个反复调整堆的过程: @@ -725,8 +707,8 @@ static void heapSort(int[] arr) { for (int i = 0; i < arr.length; i++) { siftUp(arr, i); } - int size = arr.length; - swap(arr, 0, --size); + int size = arr.length - 1; + swap(arr, 0, size); while (size > 0) { siftDown(arr, 0, size); swap(arr, 0, --size); @@ -756,7 +738,7 @@ static void siftDown(int[] arr, int i, int heapSize) { } ``` - 下面是将一个数组建成堆的时候的优化过程: +下面是将一个数组建成堆的时候的优化过程: 下面的写法就是一个下沉的操作(这里建堆的时候没有使用上浮,而是从第一个非叶子结点开始使用下沉的方式建堆): ![在这里插入图片描述](images/14_堆1.png) @@ -768,7 +750,7 @@ static void heapSort(int[] arr) { if (arr == null || arr.length <= 1) return; int size = arr.length - 1; for (int i = (size - 1) / 2; i >= 0; i--) - siftDown(arr, i, size); + siftDown(arr, i, size+1);// 注意这里是size+1,因为这个不是交换了最后一个,所以要考虑arr[size],下面不要考虑arr[size] swap(arr, 0, size); while (size > 0) { siftDown(arr, 0, size); @@ -793,7 +775,7 @@ static void siftDown(int[] arr, int i, int heapSize) { } ``` -**shiftDown的过程也可以递归的换:** +**shiftDown的过程也可以递归的换**: ```java //递归的调整A[i]以下的堆 static void siftDown(int[] arr, int i, int size) { //从A[i] 开始往下调整 @@ -809,9 +791,10 @@ static void siftDown(int[] arr, int i, int size) { //从A[i] 开始往下调整 } static void heapSort2(int[] arr) { + if (arr == null || arr.length <= 1) return; int size = arr.length - 1; for (int i = (size - 1) / 2; i >= 0; i--) - siftDown(arr, i, size); + siftDown(arr, i, size+1);// 注意这里是size+1,因为这个不是交换了最后一个,所以要考虑arr[size],下面不要考虑arr[size] swap(arr, 0, size);//和最后一个数交换 while (size > 0) { siftDown(arr, 0, size); @@ -821,7 +804,7 @@ static void heapSort2(int[] arr) { ``` *** -## 计数排序 +## 计数排序 计数排序是一种非比较排序: - **利用一个`count`数组,统计每个元素`arr[i]`出现的次数`count[arr[i]]`,`count`数组的大小代表的是能排序的元素的最大的值;** @@ -829,9 +812,13 @@ static void heapSort2(int[] arr) { - **最后通过反向填充目标数组`tmp`,将数组元素`arr[i]` 放在数组`tmp`的第`count[arr[i]]`个位置(下标为`count[arr[i]]-1`),每放一个元素就将`count[arr[i]]`递减,可以确保计数排序的稳定性;** 看一个例子 + ![这里写图片描述](https://img-blog.csdn.net/20180405170000881?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + 填充过程: + + ![这里写图片描述](images/15_计数1.png) 代码: diff --git "a/Algorithm/DataStructure/DP/51Nod - 1006. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227LCS \345\222\214 \346\234\200\351\225\277\345\205\254\344\274\227\345\255\220\344\270\262.md" "b/Algorithm/DataStructure/DP/51Nod - 1006. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227LCS \345\222\214 \346\234\200\351\225\277\345\205\254\344\274\227\345\255\220\344\270\262.md" new file mode 100644 index 00000000..0bd96ac8 --- /dev/null +++ "b/Algorithm/DataStructure/DP/51Nod - 1006. \346\234\200\351\225\277\345\205\254\345\205\261\345\255\220\345\272\217\345\210\227LCS \345\222\214 \346\234\200\351\225\277\345\205\254\344\274\227\345\255\220\344\270\262.md" @@ -0,0 +1,232 @@ +## 51Nod - 1006. 最长公共子序列LCS 和 最长公众子串 + +* [51Nod-1006-最长公共子序列LCS](#51nod-1006-最长公共子序列lcs) +* [最长公众子串](#最长公众子串) + +*** +### 51Nod-1006-最长公共子序列LCS +#### [题目链接](http://www.51nod.com/Challenge/Problem.html#!#problemId=1006) + +> http://www.51nod.com/Challenge/Problem.html#!#problemId=1006 + +#### 题目 +就是输入两个字符串`str1`、`str2`,输出任意一个最长公共子序列。 +#### 解析 + +`dp[i][j]`代表的是 : 必须以`str1[i]`、`str2[j]`结尾的最长公共子序列,`dp[i][j]`来源: + +* 可能是`dp[i-1][j]`,代表`str1[0~i-1]`与`str2[0~j]`的最长公共子序列。 +* 可能是`dp[i][j-1]`,代表`str1[0~i]`与`str2[0~j-1]`的最长公共子序列。 +* 如果`str1[i] == str2[j]`,还可能是`dp[i-1][j-1] + 1`。 + +这三种情况中取最大值。 + +![在这里插入图片描述](images/dp1.png) + +构造结果的过程(利用`dp`数组即可) + +* 从矩阵的右下角开始,有三种移动方式:向上、向左、向左上。 +* 如果`dp[i][j] > dp[i-1][j] && dp[i][j] > dp[i][j-1]`,说明之前在计算`dp[i][j]`的时候,一定是选择了`dp[i-1][j-1]+1`,所以可以确定`str1[i] = str2[j]`,并且这个字符一定输入最长公共子序列,把这个字符放进结果字符串,然后向左上方移动; +* 如果`dp[i][j] == dp[i-1][j]`,说明之前计算`dp[i][j]`的时候,`dp[i-1][j-1]+1`不是必须的选择,向上方移动即可; +* 如果`dp[i][j] == dp[i][j-1]`,向左方移动即可; +* 如果`dp[i][j]`同时等于`dp[i-1][j]`和`dp[i][j-1]`,向上向左都可以,选择一个即可,不会错过必须选择的字符; + +```java +import java.io.*; +import java.util.*; + +public class Main { + + /** + * dp[i][j]代表的是 str[0..i]与str[0...j]的最长公共子序列 + */ + static int[][] getDp(char[] s1, char[] s2) { + int n1 = s1.length, n2 = s2.length; + int[][] dp = new int[n1][n2]; + dp[0][0] = s1[0] == s2[0] ? 1 : 0; + for (int i = 1; i < n1; i++) // 一旦dp[i][0]被设置成1,则dp[i~N-1][0]都为1 + dp[i][0] = Math.max(dp[i - 1][0], s1[i] == s2[0] ? 1 : 0); + for (int j = 1; j < n2; j++) + dp[0][j] = Math.max(dp[0][j - 1], s2[j] == s1[0] ? 1 : 0); + + for (int i = 1; i < n1; i++) { + for (int j = 1; j < n2; j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + if (s1[i] == s2[j]) { + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); + } + } + } + return dp; + } + + static String getLCS(char[] s1, char[] s2, int[][] dp) { + if(s1 == null || s1.length == 0 || s2 == null || s2.length == 0) + return ""; + int i = s1.length - 1; + int j = s2.length - 1; + char[] res = new char[dp[i][j]]; //生成答案的数组 + int index = dp[i][j] - 1; + while (index >= 0) { + if (i > 0 && dp[i][j] == dp[i - 1][j]) { + i--; + } else if (j > 0 && dp[i][j] == dp[i][j - 1]) { + j--; + } else { // dp[i][j] = dp[i-1][j-1]+1 + res[index--] = s1[i]; + i--; + j--; + } + } + return String.valueOf(res); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + char[] s1 = in.next().toCharArray(); + char[] s2 = in.next().toCharArray(); + + int[][] dp = getDp(s1, s2); +// System.out.println(dp[s1.length-1][s2.length-1]); //length of lcs + System.out.println(getLCS(s1, s2, dp)); + } +} + +``` + +*** +### 最长公众子串 +#### [题目链接](https://www.nowcoder.com/questionTerminal/02e7cc263f8a49e8b1e1dc9c116f7602?toCommentId=1532408) + +> https://www.nowcoder.com/questionTerminal/02e7cc263f8a49e8b1e1dc9c116f7602?toCommentId=1532408 + +#### 解析 +* `dp`矩阵第一列即`dp[0~N-1][0]`,对某一个位置`(i,0)`来说,如果`str1[i] == str2[0]`,令`dp[i][0] = 1`,否则令`dp[i][0] = 0`; +* 矩阵`dp`第一行,即`dp[0][0~M-1]`,对某个位置`(0,j)`来说,如果`str1[0] == str2[j]`,令`dp[0][j] = 1`,否则令`dp[0][j] = 0`; +* 一般的位置有两种情况,如果`str1[i] != str2[j]`,说明在必须把`str1[i]`和`str2[j]`当做公共子串最后一个字符是不可能的,所以`dp[i][j] = 0`; 如果`str1[i] = str2[j]`,说明可以将`str1[i]`和`str2[j]`作为公共子串的最后一个字符,其长度就是`dp[i-1][j-1] + 1`; + + +![在这里插入图片描述](images/dp2.png) + +```java +import java.util.*; + +public class LongestSubstring { + public int findLongest(String A, int n, String B, int m) { + char[] s1 = A.toCharArray(); + char[] s2 = B.toCharArray(); + int[][] dp = new int[s1.length][s2.length]; + for(int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同 + dp[i][0] = s1[i] == s2[0] ? 1 : 0; + for(int j = 0; j < s2.length; j++) + dp[0][j] = s1[0] == s2[j] ? 1 : 0; + int res = 0; + for(int i = 1; i < s1.length; i++){ + for(int j = 1; j < s2.length; j++){ + if(s1[i] == s2[j]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + res = Math.max(res, dp[i][j]); + } + } + } + return res; //dp数组中的最大值,就是最大公共字串的长度 + } +} +``` + +由`dp`表生成答案字符串也是不难的,找到最大值,然后往左边的`res`个字符就是答案。 + +测试程序: +```java +public class LCSub { + + static int[][] getDp(char[] s1, char[] s2) { + int[][] dp = new int[s1.length][s2.length]; + for (int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同 + dp[i][0] = s1[i] == s2[0] ? 1 : 0; + for (int j = 0; j < s2.length; j++) + dp[0][j] = s1[0] == s2[j] ? 1 : 0; + int res = 0; + for (int i = 1; i < s1.length; i++) { + for (int j = 1; j < s2.length; j++) { + if (s1[i] == s2[j]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + res = Math.max(res, dp[i][j]); + } + } + } + System.out.println(res); //4 + return dp; //dp数组中的最大值,就是最大公共字串的长度 + } + + /** + * 根据dp表得到答案 + */ + static String getLongestSubstring(String s1, String s2, int[][] dp) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) + return ""; + int max = 0, end = 0; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + if (dp[i][j] > max) { + max = dp[i][j]; + end = i; + } + } + } + return s1.substring(end - max + 1, end + 1); + } + + public static void main(String[] args) { + String sa = "abcdefq"; + String sb = "cdefab"; + int[][] dp = getDp(sa.toCharArray(), sb.toCharArray()); + System.out.println(getLongestSubstring(sa, sb, dp)); // cdef + System.out.println(getLongestSubstring(sa, sb, dp).length()); //4 + } +} + +``` +另外,还有一种可以优化空间的做法: +* 因为`dp[i][j]`只依赖于左上角位置的`dp[i-1][j-1]`,所以用一个变量记录左上角的值即可。 + +* 遍历方向从右上角的斜线开始,一直遍历到左下角,中间记录最大值`max`和结束位置`end`即可。 + +![在这里插入图片描述](images/dp3.png) + +代码如下: + +```java + public static String getLongestSubstring2(String sa,String sb){ + if(sa == null || sb == null || sa.length() == 0 || sb.length() == 0) + return ""; + char[] chs1 = sa.toCharArray(); + char[] chs2 = sb.toCharArray(); + int row = 0, col = chs2.length-1; //从右上角开始 + int max = 0, end = 0; //记录最大长度和结束位置 + while(row < chs1.length){ + int i = row, j = col; + int ul = 0; + while(i < chs1.length && j < chs2.length){ //从(i,j)向右下方开始遍历 + if(chs1[i] == chs2[j]) + ul++; + else + ul = 0; + if(ul > max){ //记录最大值以及结尾字符的位置 + max = ul; + end = i; + } + i++; + j++; + } + if(col > 0) // 斜线还没到最左边 --> 往左移动 + col--; + else + row++; //到了最左 --> 往下移动 + } + System.out.println(max); + return sa.substring(end-max+1, end+1); // [end-max+1, end] + } +``` + +*** diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp1.png" b/Algorithm/DataStructure/DP/images/dp1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp1.png" rename to Algorithm/DataStructure/DP/images/dp1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp2.png" b/Algorithm/DataStructure/DP/images/dp2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp2.png" rename to Algorithm/DataStructure/DP/images/dp2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp3.png" b/Algorithm/DataStructure/DP/images/dp3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/DP/images/dp3.png" rename to Algorithm/DataStructure/DP/images/dp3.png diff --git a/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_2.png b/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_2.png new file mode 100644 index 00000000..38e562a0 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_2.png differ diff --git a/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_3.png b/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_3.png new file mode 100644 index 00000000..dedbf893 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/FenwickTree/images/acm_3.png differ diff --git a/Algorithm/DataStructure/DataStructure/FenwickTree/images/fenwick_1.png b/Algorithm/DataStructure/DataStructure/FenwickTree/images/fenwick_1.png new file mode 100644 index 00000000..b58480e7 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/FenwickTree/images/fenwick_1.png differ diff --git "a/Algorithm/DataStructure/DataStructure/FenwickTree/\346\240\221\347\212\266\346\225\260\347\273\204\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/DataStructure/FenwickTree/\346\240\221\347\212\266\346\225\260\347\273\204\346\200\273\347\273\223.md" new file mode 100644 index 00000000..b2d1dba1 --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/FenwickTree/\346\240\221\347\212\266\346\225\260\347\273\204\346\200\273\347\273\223.md" @@ -0,0 +1,153 @@ +## 树状数组总结 + +最容易懂的工具: [**点这里**](https://visualgo.net/en/fenwicktree)。 + +一张图概括: + +![fenwick_1.png](images/fenwick_1.png) + +具体演示可以看上面的链接。 + +[**LeetCode - 307. Range Sum Query - Mutable**](https://leetcode.com/problems/range-sum-query-mutable/)例题: + +题目: + +![acm_3.png](images/acm_3.png) + +树状数组代码: + +```java +class NumArray { + + private int[] sums;// 树状数组中求和的数组 + private int[] data;//真实存放数据的数组 + private int n; + + private int lowbit(int x) {return x & (-x);} + + private int query(int i){ + int s = 0; + while(i > 0){//树状数组中索引是1~n + s += sums[i]; + i -= lowbit(i); + } + return s; + } + + // fenWick update + private void renewal(int i, int delta){// delta是增量,不是新值 + while(i <= n){//树状数组中索引是1~n + sums[i] += delta; + i += lowbit(i); + } + } + + public NumArray(int[] nums) { + n = nums.length; + sums = new int[n+1]; + data = new int[n]; + for(int i = 0; i < n; i++) { + data[i] = nums[i]; + renewal(i+1, nums[i]); + } + } + + public void update(int i, int val) { + renewal(i+1, val - data[i]); + data[i] = val; + } + + public int sumRange(int i, int j) { + return query(j+1) - query(i); + } +} +``` + +再看一个例题[**POJ - 2352. Stars**](http://poj.org/problem?id=2352) + +题目意思就是给你一些星星的坐标,每个星星的级别是他左下方的星星的数量,要你求出各个级别的星星有多少个,看样例就懂了。 + +![acm_2.png](images/acm_2.png) + +题目中一个重要的信息就是输入**是按照`y`递增,如果`y`相同则`x`递增的顺序**给出的,所以,对于第`i`颗星星,它的`level`就是之前出现过的星星中,横坐标小于等于`i`的星星的数量。这里用树状数组来记录所有星星的`x`值。 + +代码中有个小细节就是`x++`这是因为`lowbit`不能传值为`0`,否则会陷入死循环。 + +```cpp +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static class FastReader { + public BufferedReader br; + public StringTokenizer token; + + public FastReader(InputStream in) { + br = new BufferedReader(new InputStreamReader(in), 32768); + token = null; + } + + public String next() { + while (token == null || !token.hasMoreTokens()) { + try { + token = new StringTokenizer(br.readLine()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return token.nextToken(); + } + + public int nextInt() { + return Integer.parseInt(next()); + } + } + + static int[] sums; // 注意树状数组中 索引从1开始 + + static int n; + + static int lowbit(int x) { + return x & (-x); + } + + static int query(int i) { + int res = 0; + while (i > 0) { + res += sums[i]; + i -= lowbit(i); + } + return res; + } + + // delta是增量, 不是新值 + static void update(int i, int delta) { + while (i <= 32010) { //注意这里是最大的x,没有记录所以用 32010,不能用n + sums[i] += delta; + i += lowbit(i); + } + } + + // write code + static void solve(InputStream stream) { + FastReader in = new FastReader(stream); + n = in.nextInt(); + sums = new int[32010]; // 用x作为索引 + int[] level = new int[n]; + for (int i = 1; i <= n; i++) { + int x = in.nextInt() + 1; //防止 x == 0的情况,lowbit会陷入死循环 + int y = in.nextInt(); + level[query(x)]++; // 求的是左边的和, 然后这个和就是一个级别,这个级别多了一个 + update(x, 1);// 从x向上都+1 + } + for (int i = 0; i < n; i++) System.out.println(level[i]); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` \ No newline at end of file diff --git "a/Algorithm/DataStructure/DataStructure/Heap/\344\274\230\345\205\210\351\230\237\345\210\227\345\222\214\345\240\206\347\232\204\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/DataStructure/Heap/\344\274\230\345\205\210\351\230\237\345\210\227\345\222\214\345\240\206\347\232\204\346\200\273\347\273\223.md" new file mode 100644 index 00000000..f003f98b --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/Heap/\344\274\230\345\205\210\351\230\237\345\210\227\345\222\214\345\240\206\347\232\204\346\200\273\347\273\223.md" @@ -0,0 +1,965 @@ +# 优先队列和堆的总结 + + - 堆的基础知识 + - 堆实现(使用动态数组) + - 使用堆实现优先队列 + - `LeetCode-347. Top K Frequent Elements`测试优先队列(`Top K`问题) + +*** +## 一、堆的基础知识 +堆的基本性质: + + - **堆首先是一颗完全二叉树;** + - **堆要满足的性质是或者每一个结点都大于孩子,或者都小于孩子结点的值;** + - 最大堆: 所有结点都比自己的孩子结点大; + - 最小堆: 所有结点都比自己的孩子结点小; + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220205223959.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**堆用数组来表示的话,数组可以从`1`位置开始,也可以从`0`位置开始,一般使用从`0`位置开始,和孩子的下标对应关系如下:** + +从`1`开始的对应关系: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220205640306.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +从`0`开始的对应关系: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220205950485.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +*** +## 二、堆实现(使用动态数组) +这里使用动态数组作为堆中的存储结构,也可以不使用动态数组,但是不能扩容的数组效率相对会比较低,动态数组实现看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79811308)。 + +```java +public class Array { + + private E[] data; + private int size; + + public Array(int capacity){ // user assign size + data = (E[])new Object[capacity]; + size = 0; + } + public Array(){ + this(10); // default size + } + public Array(E[] arr){ + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + size = arr.length; + } + + public int getSize(){ + return size; + } + public int getCapacity(){ + return data.length; + } + public boolean isEmpty(){ + return size == 0; + } + + public void rangeCheck(int index){ + if(index < 0 || index >= size) + throw new IllegalArgumentException("Index is Illegal!"); + } + + public void add(int index,E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + if(size == data.length) + resize(data.length * 2); + for(int i = size - 1; i >= index; i--){ + data[i+1] = data[i]; + } + data[index] = e; + size++; + } + + private void resize(int newCapacity) { + E[] newData = (E[])new Object[newCapacity]; + for(int i = 0; i < size; i++) newData[i] = data[i]; + data = newData; + } + + public void addLast(E e){ //末尾添加 + add(size,e); + } + public void addFirst(E e){ //头部添加 + add(0,e); + } + + + public E get(int index){ + rangeCheck(index); + return data[index]; + } + public E getLast(){ + return get(size-1); + } + public E getFirst(){ + return get(0); + } + + public void set(int index,E e){ + rangeCheck(index); + data[index] = e; + } + + public boolean contains(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return true; + } + return false; + } + public int find(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return i; + } + return -1; + } + + + public E remove(int index){ // remove data[index] and return the value + rangeCheck(index); + E res = data[index]; + for(int i = index; i < size-1; i++){ + data[i] = data[i+1]; + } + size--; + data[size] = null;//loitering objects != memory leak + if(size == data.length / 4 && data.length/2 != 0)resize(data.length / 2); //防止复杂度的震荡 + return res; + } + + public E removeFirst(){ + return remove(0); + } + public E removeLast(){ + return remove(size-1); + } + + + public void removeElement(E e){ //only remove one(may repetition) and user not know whether is deleted. + int index = find(e); + if(index != -1){ + remove(index); + } + } + + // new method + public void swap(int i, int j){ + if(i < 0 || i >= size || j < 0 || j >= size) + throw new IllegalArgumentException("Index is illegal."); + + E t = data[i]; + data[i] = data[j]; + data[j] = t; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + res.append(String.format("Array : size = %d, capacity = %d\n",size,data.length)); + res.append("["); + for(int i = 0; i < size; i++){ + res.append(data[i]); + if(i != size-1){ + res.append(", "); + } + } + res.append("]"); + return res.toString(); + } +} +``` +**`MaxHeap`的实现**: + + - **`add()`操作的逻辑是先把元素加入到动态数组的最后,然后向上浮,注意其中的`siftUp`操作就是将一个元素向上浮使得满足最大堆的操作**; + - **`extractMax()`就是取出堆顶的元素并且返回,其中步骤是先交换堆顶和最后一个元素,然后将新的堆顶往下沉的操作**; + - **`replace()`操作就是取出堆中的最大元素,并且替换成元素`e` **; +```java +/** + * 使用动态数组实现堆 + * @param + */ +public class MaxHeap> { + + private Array data; // 里面存放的是动态的数组 + + public MaxHeap(int capacity){ + data = new Array<>(capacity); + } + public MaxHeap(){ + data = new Array<>(); + } + + public MaxHeap(E[] arr){ + data = new Array<>(arr); + for(int i = (arr.length-1-1)/2; i >= 0; i--) + siftDown(i); + } + + public int size(){ + return data.getSize(); + } + public boolean isEmpty(){ + return data.isEmpty(); + } + + public void add(E e){// 向堆中添加元素 + data.addLast(e); //在数组的末尾添加 + siftUp(data.getSize() - 1); + } + /**上浮操作 */ + private void siftUp(int index) { + while(data.get(index).compareTo(data.get((index-1)/2)) > 0){ //比父亲大 + data.swap(index,(index-1)/2); + index = (index-1)/2; //继续向上 + } + } + + /** 看堆中的最大元素*/ + public E findMax(){ + if(data.getSize() == 0) + throw new IllegalArgumentException("Can not findMax when heap is empty."); + return data.get(0); + } + public E extractMax(){ /** 取出堆中最大元素(堆顶) */ + E ret = findMax(); + data.swap(0, data.getSize() - 1); //堆顶和最后一个元素交换 + data.removeLast(); //把最后一个元素删除 ,也就是把之前的堆顶移出堆 + + siftDown(0); //新的堆顶要下沉 + return ret; + } + + /**下沉操作*/ + private void siftDown(int i) { + int L = 2 * i + 1; //左孩子 + while(L < data.getSize()){ //不越界 + int maxIdx = L + 1 < data.getSize() && data.get(L+1).compareTo(data.get(L)) > 0 ? L+1 : L; + maxIdx = data.get(i).compareTo(data.get(maxIdx)) > 0 ? i : maxIdx; + if(maxIdx == i) break; //自己就是最大的,不需要下沉 + + data.swap(i,maxIdx); + i = maxIdx; + L = 2 * i + 1; + } + } + + public E replace(E e){ // 取出堆中的最大元素,并且替换成元素e 一个O(logn) + E ret = findMax(); + data.set(0, e); + siftDown(0); + return ret; + } +} + +``` + + - 其中一个很优化的地方就是`heapify`的过程,也就是在构造函数中的从`(arr.length - 1 - 1) / 2`的过程开始建堆,这比我们一个个的添加到堆中效率要高,原理就是找到第一个非叶子结点开始调整: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220210647888.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +*** +## 三、使用堆实现优先队列 + +```java +/** + * 优先队列的接口 + */ +public interface Queue { + int getSize(); + boolean isEmpty(); + void enqueue(E e); + E dequeue(); + E getFront(); +} +``` + +```java +public class PriorityQueue> implements Queue { + + private MaxHeapmaxHeap; + + public PriorityQueue(){ + maxHeap = new MaxHeap<>(); + } + + @Override + public int getSize() { + return maxHeap.size(); + } + + @Override + public boolean isEmpty() { + return maxHeap.isEmpty(); + } + + @Override + public void enqueue(E e) { + maxHeap.add(e); + } + + @Override + public E dequeue() { + return maxHeap.extractMax(); //取出最大元素 + } + + @Override + public E getFront() { + return maxHeap.findMax(); + } +} +``` +*** +## 四、LeetCode-347. Top K Frequent Elements测试优先队列(Top K问题) +#### [题目链接](https://leetcode-cn.com/problems/top-k-frequent-elements/description/) +#### 题目 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220194422474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +#### 解析 +**使用上面的优先队列我们就可以维护一个有K个数的优先队列,然后遍历Map中的每一个数,如果堆中还没有K个数,直接入堆,如果有了K个数,就比较堆顶(堆中频次较低的),如果当前元素比堆顶的频次高,就从堆顶中弹出堆顶,并且让这个元素进入堆顶。** +```java + private class Freq implements Comparable{ + + public int e; + public int freq; //元素和频次 + + public Freq(int e, int freq) { + this.e = e; + this.freq = freq; + } + + @Override + public int compareTo(Freq o) {//按照频次升序 频次低的在堆顶 + return this.freq < o.freq ? 1 : (this.freq > o.freq ? -1 : 0); + } + } + + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for(int num : nums){ + if(map.containsKey(num)){ + map.put(num,map.get(num) + 1); + }else { + map.put(num,1); + } + } + + PriorityQueuequeue = new PriorityQueue<>(); + + //维护一个 前 k个频次的堆 + for(int key : map.keySet()){ + if(queue.getSize() < k){ + queue.enqueue(new Freq(key,map.get(key))); + }else{ + if(map.get(key) > queue.getFront().freq){ + queue.dequeue(); + queue.enqueue(new Freq(key,map.get(key))); + } + } + } + + Listres = new ArrayList<>(); + while(!queue.isEmpty()){ + res.add(queue.dequeue().e); + } + return res; + + } +``` +**使用Java中的优先队列,并且基于`Comparator`接口中的`compare()`方法比较,实现方法差不多:** + +```java + private class Freq { + public int e; + public int freq; //元素和频次 + + public Freq(int e, int freq) { + this.e = e; + this.freq = freq; + } + } + + private class FreqComparator implements Comparator{ + @Override + public int compare(Freq o1, Freq o2) { + return o1.freq - o2.freq; + } + } + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for(int num : nums){ + if(map.containsKey(num)){ + map.put(num,map.get(num) + 1); + }else { + map.put(num,1); + } + } + + PriorityQueuequeue = new PriorityQueue<>(new FreqComparator()); + + //维护一个 前 k个频次的堆 + for(int key : map.keySet()){ + if(queue.size() < k){ //堆中个数 queue.peek().freq){ + queue.poll(); + queue.add(new Freq(key,map.get(key))); + } + } + } + + Listres = new ArrayList<>(); + while(!queue.isEmpty()){ + res.add(queue.poll().e); + } + return res; + } +``` + +**再次优化,使用匿名内部类以及省去那个`Freq`类(优先队列中只需要存储对应的值,因为`map`中已经存储了频次),并且使用Lambda表达式:** + +```java + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for(int num : nums){ + if(map.containsKey(num)){ + map.put(num,map.get(num) + 1); + }else { + map.put(num,1); + } + } + + PriorityQueue queue = new PriorityQueue<>((o1, o2) -> map.get(o1) - map.get(o2)); + + //维护一个 前 k个频次的堆 + for(int key : map.keySet()){ + if(queue.size() < k){ //堆中个数 map.get(queue.peek())){ + queue.poll(); + queue.add(key); + } + } + } + + Listres = new ArrayList<>(); + while(!queue.isEmpty()){ + res.add(queue.poll()); + } + return res; + } +``` + +**最后再贴上我们在LeetCode中测试优先队列和堆的程序(较长):** + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2018122113110463.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +具体代码如下: + +```java +class Solution { + + private class Array { + + private E[] data; + private int size; + + public Array(int capacity){ // user assign size + data = (E[])new Object[capacity]; + size = 0; + } + public Array(){ + this(10); // default size + } + public Array(E[] arr){ + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + size = arr.length; + } + + + public int getSize(){ + return size; + } + public int getCapacity(){ + return data.length; + } + public boolean isEmpty(){ + return size == 0; + } + + public void rangeCheck(int index){ + if(index < 0 || index >= size) + throw new IllegalArgumentException("Index is Illegal!"); + } + + public void add(int index,E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + if(size == data.length) + resize(data.length * 2); + for(int i = size - 1; i >= index; i--){ + data[i+1] = data[i]; + } + data[index] = e; + size++; + } + + private void resize(int newCapacity) { + E[] newData = (E[])new Object[newCapacity]; + for(int i = 0; i < size; i++) newData[i] = data[i]; + data = newData; + } + + public void addLast(E e){ //末尾添加 + add(size,e); + } + public void addFirst(E e){ //头部添加 + add(0,e); + } + + + public E get(int index){ + rangeCheck(index); + return data[index]; + } + public E getLast(){ + return get(size-1); + } + public E getFirst(){ + return get(0); + } + + public void set(int index,E e){ + rangeCheck(index); + data[index] = e; + } + + public boolean contains(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return true; + } + return false; + } + public int find(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return i; + } + return -1; + } + + + public E remove(int index){ // remove data[index] and return the value + rangeCheck(index); + E res = data[index]; + for(int i = index; i < size-1; i++){ + data[i] = data[i+1]; + } + size--; + data[size] = null;//loitering objects != memory leak + if(size == data.length / 4 && data.length/2 != 0)resize(data.length / 2); //防止复杂度的震荡 + return res; + } + + public E removeFirst(){ + return remove(0); + } + public E removeLast(){ + return remove(size-1); + } + + + public void removeElement(E e){ //only remove one(may repetition) and user not know whether is deleted. + int index = find(e); + if(index != -1){ + remove(index); + } + } + + // new method + public void swap(int i, int j){ + if(i < 0 || i >= size || j < 0 || j >= size) + throw new IllegalArgumentException("Index is illegal."); + + E t = data[i]; + data[i] = data[j]; + data[j] = t; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + res.append(String.format("Array : size = %d, capacity = %d\n",size,data.length)); + res.append("["); + for(int i = 0; i < size; i++){ + res.append(data[i]); + if(i != size-1){ + res.append(", "); + } + } + res.append("]"); + return res.toString(); + } + } + + private class MaxHeap> { + + private Array data; // 里面存放的是动态的数组 + + public MaxHeap(int capacity){ + data = new Array<>(capacity); + } + public MaxHeap(){ + data = new Array<>(); + } + + public MaxHeap(E[] arr){ + data = new Array<>(arr); + for(int i = (arr.length-1-1)/2; i >= 0; i--) + siftDown(i); + } + + public int size(){ + return data.getSize(); + } + public boolean isEmpty(){ + return data.isEmpty(); + } + + public void add(E e){// 向堆中添加元素 + data.addLast(e); //在数组的末尾添加 + siftUp(data.getSize() - 1); + } + /**上浮操作 */ + private void siftUp(int index) { + while(data.get(index).compareTo(data.get((index-1)/2)) > 0){ //比父亲大 + data.swap(index,(index-1)/2); + index = (index-1)/2; //继续向上 + } + } + + /** 看堆中的最大元素*/ + public E findMax(){ + if(data.getSize() == 0) + throw new IllegalArgumentException("Can not findMax when heap is empty."); + return data.get(0); + } + public E extractMax(){ /** 取出堆中最大元素(堆顶) */ + E ret = findMax(); + data.swap(0, data.getSize() - 1); //堆顶和最后一个元素交换 + data.removeLast(); //把最后一个元素删除 ,也就是把之前的堆顶移出堆 + + siftDown(0); //新的堆顶要下沉 + return ret; + } + + /**下沉操作*/ + private void siftDown(int i) { + int L = 2 * i + 1; //左孩子 + while(L < data.getSize()){ //不越界 + int maxIdx = L + 1 < data.getSize() && data.get(L+1).compareTo(data.get(L)) > 0 ? L+1 : L; + maxIdx = data.get(i).compareTo(data.get(maxIdx)) > 0 ? i : maxIdx; + if(maxIdx == i) break; //自己就是最大的,不需要下沉 + + data.swap(i,maxIdx); + i = maxIdx; + L = 2 * i + 1; + } + } + + public E replace(E e){ // 取出堆中的最大元素,并且替换成元素e 一个O(logn) + E ret = findMax(); + data.set(0, e); + siftDown(0); + return ret; + } + } + + private interface Queue { + + int getSize(); + boolean isEmpty(); + void enqueue(E e); + E dequeue(); + E getFront(); + } + + private class MyPriorityQueue> implements Queue { + + private MaxHeapmaxHeap; + + public MyPriorityQueue(){ + maxHeap = new MaxHeap<>(); + } + + @Override + public int getSize() { + return maxHeap.size(); + } + + @Override + public boolean isEmpty() { + return maxHeap.isEmpty(); + } + + @Override + public void enqueue(E e) { + maxHeap.add(e); + } + + @Override + public E dequeue() { + return maxHeap.extractMax(); //取出最大元素 + } + + @Override + public E getFront() { + return maxHeap.findMax(); + } + } + + private class Freq implements Comparable{ + + public int e; + public int freq; //元素和频次 + + public Freq(int e, int freq) { + this.e = e; + this.freq = freq; + } + + @Override + public int compareTo(Freq o) {//按照频次升序 频次低的在堆顶 + return this.freq < o.freq ? 1 : (this.freq > o.freq ? -1 : 0); + } + } + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for(int num : nums){ + if(map.containsKey(num)){ + map.put(num,map.get(num) + 1); + }else { + map.put(num,1); + } + } + + MyPriorityQueuequeue = new MyPriorityQueue<>(); + + //维护一个 前 k个频次的堆 + for(int key : map.keySet()){ + if(queue.getSize() < k){ + queue.enqueue(new Freq(key,map.get(key))); + }else{ + if(map.get(key) > queue.getFront().freq){ + queue.dequeue(); + queue.enqueue(new Freq(key,map.get(key))); + } + } + } + + Listres = new ArrayList<>(); + while(!queue.isEmpty()){ + res.add(queue.dequeue().e); + } + return res; + + } +} +``` + +也可以将自己写的动态数组`Array`换成`JDK`的`ArrayList`: + +```java +class Solution { + + private class MaxHeap> { + + private ArrayList data; // 里面存放的是动态的数组 + + public MaxHeap(int capacity){ + data = new ArrayList<>(capacity); + } + public MaxHeap(){ + data = new ArrayList<>(); + } + + public MaxHeap(E[] arr){ + data = new ArrayList<>(arr.length); + for(int i = (arr.length-1-1)/2; i >= 0; i--) + siftDown(i); + } + + public int size(){ + return data.size(); + } + public boolean isEmpty(){ + return data.isEmpty(); + } + + public void add(E e){// 向堆中添加元素 + data.add(data.size(),e); + siftUp(data.size() - 1); + } + /**上浮操作 */ + private void siftUp(int index) { + while(data.get(index).compareTo(data.get((index-1)/2)) > 0){ //比父亲大 + swap(index, (index-1)/2); + index = (index-1)/2; //继续向上 + } + } + + /** 看堆中的最大元素*/ + public E findMax(){ + if(data.size() == 0) + throw new IllegalArgumentException("Can not findMax when heap is empty."); + return data.get(0); + } + public E extractMax(){ /** 取出堆中最大元素(堆顶) */ + E ret = findMax(); + swap(0, data.size()-1); + data.remove(data.size()-1); //把最后一个元素删除 ,也就是把之前的堆顶移出堆 + siftDown(0); //新的堆顶要下沉 + return ret; + } + + /**下沉操作*/ + private void siftDown(int i) { + int L = 2 * i + 1; //左孩子 + while(L < data.size()){ //不越界 + int maxIdx = L + 1 < data.size() && data.get(L+1).compareTo(data.get(L)) > 0 ? L+1 : L; + maxIdx = data.get(i).compareTo(data.get(maxIdx)) > 0 ? i : maxIdx; + if(maxIdx == i) break; //自己就是最大的,不需要下沉 + swap(i, maxIdx); + i = maxIdx; + L = 2 * i + 1; + } + } + + public E replace(E e){ // 取出堆中的最大元素,并且替换成元素e 一个O(logn) + E ret = findMax(); + data.set(0, e); + siftDown(0); + return ret; + } + + public void swap(int i, int j){ + E temp = data.get(i); + data.set(i, data.get(j)); + data.set(j, temp); + } + } + + private interface Queue { + + int getSize(); + boolean isEmpty(); + void enqueue(E e); + E dequeue(); + E getFront(); + } + + private class MyPriorityQueue> implements Queue { + + private MaxHeapmaxHeap; + + public MyPriorityQueue(){ + maxHeap = new MaxHeap<>(); + } + + @Override + public int getSize() { + return maxHeap.size(); + } + + @Override + public boolean isEmpty() { + return maxHeap.isEmpty(); + } + + @Override + public void enqueue(E e) { + maxHeap.add(e); + } + + @Override + public E dequeue() { + return maxHeap.extractMax(); //取出最大元素 + } + + @Override + public E getFront() { + return maxHeap.findMax(); + } + } + + private class Freq implements Comparable{ + + public int e; + public int freq; //元素和频次 + + public Freq(int e, int freq) { + this.e = e; + this.freq = freq; + } + + @Override + public int compareTo(Freq o) {//按照频次升序 频次低的在堆顶 + return this.freq < o.freq ? 1 : (this.freq > o.freq ? -1 : 0); + } + } + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + + for(int num : nums){ + if(map.containsKey(num)){ + map.put(num,map.get(num) + 1); + }else { + map.put(num,1); + } + } + + MyPriorityQueuequeue = new MyPriorityQueue<>(); + + //维护一个 前 k个频次的堆 + for(int key : map.keySet()){ + if(queue.getSize() < k){ + queue.enqueue(new Freq(key,map.get(key))); + }else{ + if(map.get(key) > queue.getFront().freq){ + queue.dequeue(); + queue.enqueue(new Freq(key,map.get(key))); + } + } + } + + Listres = new ArrayList<>(); + while(!queue.isEmpty()){ + res.add(queue.dequeue().e); + } + return res; + + } +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/images/l1.png" b/Algorithm/DataStructure/DataStructure/List/images/l1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/images/l1.png" rename to Algorithm/DataStructure/DataStructure/List/images/l1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/images/l2.png" b/Algorithm/DataStructure/DataStructure/List/images/l2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/images/l2.png" rename to Algorithm/DataStructure/DataStructure/List/images/l2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/\345\260\206\345\215\225\351\223\276\350\241\250\346\214\211\346\237\220\345\200\274\345\210\222\345\210\206\346\210\220\345\267\246\350\276\271\345\260\217\357\274\214\344\270\255\351\227\264\347\233\270\347\255\211\357\274\214\345\217\263\350\276\271\345\244\247\347\232\204\345\275\242\345\274\217.md" "b/Algorithm/DataStructure/DataStructure/List/\345\260\206\345\215\225\351\223\276\350\241\250\346\214\211\346\237\220\345\200\274\345\210\222\345\210\206\346\210\220\345\267\246\350\276\271\345\260\217\357\274\214\344\270\255\351\227\264\347\233\270\347\255\211\357\274\214\345\217\263\350\276\271\345\244\247\347\232\204\345\275\242\345\274\217.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/List/\345\260\206\345\215\225\351\223\276\350\241\250\346\214\211\346\237\220\345\200\274\345\210\222\345\210\206\346\210\220\345\267\246\350\276\271\345\260\217\357\274\214\344\270\255\351\227\264\347\233\270\347\255\211\357\274\214\345\217\263\350\276\271\345\244\247\347\232\204\345\275\242\345\274\217.md" rename to "Algorithm/DataStructure/DataStructure/List/\345\260\206\345\215\225\351\223\276\350\241\250\346\214\211\346\237\220\345\200\274\345\210\222\345\210\206\346\210\220\345\267\246\350\276\271\345\260\217\357\274\214\344\270\255\351\227\264\347\233\270\347\255\211\357\274\214\345\217\263\350\276\271\345\244\247\347\232\204\345\275\242\345\274\217.md" diff --git "a/Algorithm/DataStructure/DataStructure/List/\351\223\276\350\241\250\343\200\201\346\240\210\357\274\214\351\230\237\345\210\227\347\232\204\346\200\273\347\273\223\344\270\216\345\256\236\347\216\260.md" "b/Algorithm/DataStructure/DataStructure/List/\351\223\276\350\241\250\343\200\201\346\240\210\357\274\214\351\230\237\345\210\227\347\232\204\346\200\273\347\273\223\344\270\216\345\256\236\347\216\260.md" new file mode 100644 index 00000000..2edcc5c1 --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/List/\351\223\276\350\241\250\343\200\201\346\240\210\357\274\214\351\230\237\345\210\227\347\232\204\346\200\273\347\273\223\344\270\216\345\256\236\347\216\260.md" @@ -0,0 +1,1105 @@ +# 目录 + - 实现动态数组`ArrayList` + - 实现单链表 + - 双向链表 + - 双向循环链表 + - 使用`Array`动态数组和链表实现栈 + - 使用`Array`动态数组和链表实现队列 + - 循环队列(`LoopQueue`)的实现 + +*** + +## 一、实现动态数组`ArrayList` +模拟实现`ArrayList`动态数组,包括的方法:  + + - `rangeCheck()`,检查数组下标是否越界; + - `add()`,在某个下标位置添加元素; + - `resize()`,动态扩容,当数组容量满或者空闲整个数组的3/4时,重新定义容量; + - `get()`,获取某个位置的元素值; + - `set()`,设置某个下标的元素值; + - `contains()`,查找是否包含某个元素; + - `find()`,找到某个元素的下标; + - `remove()`,移除某个下标对应的值; + + +```java +public class Array { + + private E[] data; + private int size; + + public Array(int capacity){ // user assign size + data = (E[])new Object[capacity]; + size = 0; + } + public Array(){ + this(10); // default size + } + + public int getSize(){ + return size; + } + public int getCapacity(){ + return data.length; + } + public boolean isEmpty(){ + return size == 0; + } + + public void rangeCheck(int index){ + if(index < 0 || index >= size) + throw new IllegalArgumentException("Index is Illegal!"); + } + + public void add(int index,E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + if(size == data.length) + resize(data.length * 2); + for(int i = size - 1; i >= index; i--){ + data[i+1] = data[i]; + } + data[index] = e; + size++; + } + + private void resize(int newCapacity) { + E[] newData = (E[])new Object[newCapacity]; + for(int i = 0; i < size; i++) newData[i] = data[i]; + data = newData; + } + + public void addLast(E e){ //末尾添加 + add(size,e); + } + public void addFirst(E e){ //头部添加 + add(0,e); + } + + + public E get(int index){ + rangeCheck(index); + return data[index]; + } + public E getLast(){ + return get(size-1); + } + public E getFirst(){ + return get(0); + } + + public void set(int index,E e){ + rangeCheck(index); + data[index] = e; + } + + public boolean contains(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return true; + } + return false; + } + public int find(E e){ + for(int i = 0; i < size; i++){ + if(data[i].equals(e))return i; + } + return -1; + } + + + public E remove(int index){ // remove data[index] and return the value + rangeCheck(index); + E res = data[index]; + for(int i = index; i < size-1; i++){ + data[i] = data[i+1]; + } + size--; + data[size] = null;//loitering objects != memory leak + if(size == data.length / 4 && data.length/2 != 0)resize(data.length / 2); //防止复杂度的震荡 + return res; + } + + public E removeFirst(){ + return remove(0); + } + public E removeLast(){ + return remove(size-1); + } + + + public void removeElement(E e){ //only remove one(may repetition) and user not know whether is deleted. + int index = find(e); + if(index != -1){ + remove(index); + } + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + res.append(String.format("Array : size = %d, capacity = %d\n",size,data.length)); + res.append("["); + for(int i = 0; i < size; i++){ + res.append(data[i]); + if(i != size-1){ + res.append(", "); + } + } + res.append("]"); + return res.toString(); + } +} + +``` + +## 二、实现单链表 +注意: + + - 我使用了带头结点(`dummyHead`)的,也就是头结点的数据域不起作用; + - 这里我是以`0`开始的索引; + +单链表的操作不是很难: +![这里写图片描述](https://img-blog.csdn.net/20180406213333734?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + +```java +public class SingleList { + + private class Node{ + public E val; + public Node next; + + public Node(E val, Node next) { + this.val = val; + this.next = next; + } + public Node(E val) { + this(val,null); + } + public Node(){ + this(null); + } + + @Override + public String toString() { + return val.toString(); + } + } + + private Node dummyHead; //虚拟的头结点 + private int size; //表示链表当前的元素 + + public SingleList() { + dummyHead = new Node(); + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + + public void rangeCheck(int index){ + if(index < 0 || index >= size) + throw new IllegalArgumentException("Index is Illegal!"); + } + + //insert + public void add(int index,E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + Node prev = dummyHead; //设置虚拟头结点的目的是 为了插入和删除的方便(不需要判断第一个和最后一个位置) + for(int i = 0; i < index; i++) prev = prev.next; + Node node = new Node(e); + node.next = prev.next; + prev.next = node; + size++; + } + public void addFirst(E e){ + add(0,e); + } + public void addLast(E e){ + add(size,e); + } + + + //获取 + public E get(int index) { + rangeCheck(index); + Node cur = dummyHead.next; + for (int i = 0; i < index; i++) cur = cur.next; + return cur.val; + } + public E getFirst(){ + return get(0); + } + public E getLast(){ + return get(size - 1); + } + + + //remove + public E remove(int index){ + rangeCheck(index); + Node prev = dummyHead; + for(int i = 0; i < index; i++) prev = prev.next; + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + return delNode.val; + } + public E removeFirst(){ + return remove(0); + } + public E removeLast(){ + return remove(size - 1); + } + + + public void set(int index,E e){ + rangeCheck(index); + Node cur = dummyHead.next; + for(int i = 0; i < index; i++)cur = cur.next; + cur.val = e; + } + + public boolean contains(E e){ + Node cur = dummyHead.next; + while(cur != null){ + if(cur.val.equals(e)){ + return true; + } + cur = cur.next; + } + return false; + } + + public void removeElement(E e){ + Node prev = dummyHead; + while(prev.next != null){//找到对应e的 prev + if(prev.next.val.equals(e)){ + break; + } + prev = prev.next; + } + + if(prev.next != null){ + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + } + } + + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + Node cur = dummyHead.next; + while(cur != null){ + res.append(cur + "->"); + cur = cur.next; + } + res.append("NULL"); + + return res.toString(); + } + + + + //for test + public static void main(String[] args) { + SingleList singleList = new SingleList<>(); + for(int i = 0 ; i < 5 ; i ++){ + singleList.addFirst(i);//头插 + } + System.out.println(singleList); + + singleList.add(2, 666); + System.out.println(singleList); + + singleList.remove(2); + System.out.println(singleList); + + singleList.removeFirst(); + System.out.println(singleList); + + singleList.removeLast(); + System.out.println(singleList); + + singleList.removeElement(2); + System.out.println(singleList); + + System.out.println(singleList.contains(3)); + + singleList.set(1,999); + System.out.println(singleList); + + singleList.addLast(888); + System.out.println(singleList); + + System.out.println(singleList.getFirst() +" " + singleList.getLast()); + System.out.println(singleList.size()); + } +} + +``` +运行效果如图: + +![这里写图片描述](https://img-blog.csdn.net/2018082919123032?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 三、双向链表 +其实也挺简单的,看一下插入和删除的图示: +* 这里也是使用了一个头节点(`dummyHead`); +* 索引从`0`开始; + + + +![这里写图片描述](https://img-blog.csdn.net/20180829203308397?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![这里写图片描述](https://img-blog.csdn.net/20180829204005378?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```java +/** + * 双向链表 + * @param + */ +public class DoubleList { + + private class Node{ + public E val; + public Node prev; + public Node next; + + public Node(E val, Node prev, Node next) { + this.val = val; + this.prev = prev; + this.next = next; + } + public Node(E val){ + this(val,null,null); + } + public Node(){ + this(null,null,null); + } + + @Override + public String toString() { + return val.toString(); + } + } + + private Node dummyHead; + private int size; + + public DoubleList() { + dummyHead = new Node(); + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + + public void rangeCheck(int index){ + if(index < 0 || index >= size) + throw new IllegalArgumentException("Index is Illegal!"); + } + + public void add(int index,E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + Node pre = dummyHead; + for(int i = 0; i < index; i++) pre = pre.next; + Node node = new Node(e); + Node nxt = pre.next; + + node.prev = pre; + node.next = (nxt == null) ? null : nxt; + pre.next = node; + if(nxt != null)nxt.prev = node; + size++; + } + public void addFirst(E e){ + add(0,e); + } + public void addLast(E e){ + add(size,e); + } + + + public E remove(int index){ + rangeCheck(index); + if(isEmpty())return null; + Node pre = dummyHead; + for(int i = 0; i < index; i++) pre = pre.next; + Node delNode = pre.next; + pre.next = delNode.next; + if(delNode.next != null)delNode.next.prev = pre; + size--; + return delNode.val; + } + public E removeFirst(){ + return remove(0); + } + public E removeLast(){ + return remove(size-1); + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + + Node cur = dummyHead.next; + while(cur != null){ + res.append(cur + "->"); + cur = cur.next; + } + res.append("NULL"); + + return res.toString(); + } + + public static void main(String[] args) { + DoubleList dulList = new DoubleList<>(); + for(int i = 0; i < 5; i++) dulList.addLast(i); + System.out.println(dulList); + + dulList.removeLast(); + System.out.println(dulList); + + dulList.removeFirst(); + System.out.println(dulList); + + dulList.remove(1); + System.out.println(dulList); + + dulList.addFirst(-1); + dulList.add(1,666); + System.out.println(dulList); + + } +} +``` + +效果: + +![这里写图片描述](https://img-blog.csdn.net/20180829201800884?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 四、双向循环链表 +![在这里插入图片描述](https://img-blog.csdnimg.cn/201812160856497.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**注意几点:** + + * 插入的时候要注意如果是在最后插入的话,可以直接通过`dummyHead`(虚拟头结点找到最后的),然后直接插入; + * 在`get`的时候,可以判断一下从哪边开始查找,加快查找速度; + +```java +/** + * 双向循环链表 + * @param + */ +public class MyLinkedList{ + + private class Node{ + public E e; + public Node prev; + public Node next; + + public Node(E e, Node prev, Node next) { + this.e = e; + this.prev = prev; + this.next = next; + } + + public Node(E e){ + this(e,null,null); + } + public Node (){ + this(null,null,null); + } + + @Override + public String toString() { + return e.toString(); + } + } + + private Node dummyHead; + private Node tail; + private int size; + + public MyLinkedList() { + dummyHead = new Node(); + tail = dummyHead; + //dummyHead自成一环 + dummyHead.next = tail; + dummyHead.prev = tail; + tail.next = dummyHead; + tail.prev = dummyHead; + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + + //check index < 0 || index >= size + public void rangeCheck(int index){ + if(index < 0 || index >= size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + } + + //add Last + public void add(E e){ + Node node = new Node(e); + node.prev = tail; + node.next = dummyHead; + tail.next = node; + dummyHead.prev = node; + tail = node; + size++; + } + + public void add(int index, E e){ + if(index < 0 || index > size){ + throw new IllegalArgumentException("Index is Illegal ! "); + } + if(index == size){ //add to Last + add(e); + return; + } + Node pre = dummyHead; + for(int i = 0; i < index; i++)pre = pre.next; + Node nxt = pre.next; + Node node = new Node(e); + node.prev = pre; + node.next = nxt; + pre.next = node; + nxt.prev = node; + size++; + } + + // speed get + public E get(int index){ + rangeCheck(index); + Node cur = dummyHead; + if(index < (size << 1)){ + for(int i = 0; i < index + 1; i++)cur = cur.next; + return cur.e; + }else { + for(int i = 0; i < index + 1; i++)cur = cur.prev; + return cur.e; + } + } + + + public E removeLast(){ + E ret = tail.e; + tail.prev.next = tail.next; + tail.next.prev = tail.prev; + tail = tail.prev;//改变tail + size--; + return ret; + } + + public E remove(int index){ + rangeCheck(index); + if(index == size - 1){ + return removeLast(); + } + Node pre = dummyHead; + for(int i = 0; i < index; i++)pre = pre.next; + Node delNode = pre.next; + + pre.next = delNode.next; + delNode.next.prev = delNode.prev; + size--; + return delNode.e; + } + + @Override + public String toString() { + StringBuilder res = new StringBuilder(); + Node cur = dummyHead.next; + while(cur != dummyHead){ + res.append(cur.e + "->"); + cur = cur.next; + } + res.append("NULL"); + return res.toString(); + } + + + public static void main(String[] args) { + MyLinkedListmyLinkedList = new MyLinkedList<>(); + for(int i = 0; i < 5; i++){ + myLinkedList.add(i); + } + myLinkedList.add(0,-1); + myLinkedList.add(1,999); + myLinkedList.removeLast(); + System.out.println(myLinkedList); + myLinkedList.remove(3); + System.out.println(myLinkedList); + System.out.println(myLinkedList.tail.next.next.e); + System.out.println(myLinkedList.dummyHead.next.next.e); + System.out.println(myLinkedList.tail.prev.e); + System.out.println(myLinkedList.get(4)); + } +} +``` +效果: + +![这里写图片描述](https://img-blog.csdn.net/2018083110142035?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 五、使用Array动态数组和链表实现栈 + +**动态数组实现栈** + +```java +public interface Stack { + int getSize(); + boolean isEmpty(); + void push(E e); + E pop(); + E peek(); +} +``` + +```java +public class ArrayStack implements Stack { + + private Array array; + + public ArrayStack(int capacity){ + array = new Array<>(capacity); + } + + public ArrayStack(){ + array = new Array<>(); + } + + @Override + public int getSize(){ + return array.getSize(); + } + + @Override + public boolean isEmpty(){ + return array.isEmpty(); + } + + public int getCapacity(){ + return array.getCapacity(); + } + + @Override + public void push(E e){ + array.addLast(e); + } + + @Override + public E pop(){ + return array.removeLast(); + } + + @Override + public E peek(){ + return array.getLast(); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append("Stack: "); + res.append('['); + for(int i = 0 ; i < array.getSize() ; i ++){ + res.append(array.get(i)); + if(i != array.getSize() - 1) + res.append(", "); + } + res.append("] top"); + return res.toString(); + } +} +``` +链表实现栈: + +```java +public class SingListStack implements Stack { + + private SingleList list; + + public SingListStack(){ + list = new SingleList<>(); + } + + @Override + public int getSize(){ + return list.size(); + } + + @Override + public boolean isEmpty(){ + return list.isEmpty(); + } + + @Override + public void push(E e){ + list.addFirst(e); + } + + @Override + public E pop(){ + return list.removeFirst(); + } + + @Override + public E peek(){ + return list.getFirst(); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append("Stack: top "); + res.append(list); + return res.toString(); + } +} + +``` + +顺便提一下系统栈的应用以及方法的调用压栈过程: + + - 系统栈中压入了之前的方法的所有信息,包括方法名,参数,运行到了第几行; + - 当后面的方法完全结束之后,从系统栈中弹出之前的方法,然后继续执行; + + + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181216085845248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +*** +## 六、使用Array动态数组和链表实现队列 +**动态数组实现队列** + +```java +public interface Queue { + int getSize(); + boolean isEmpty(); + void enqueue(E e); + E dequeue(); + E getFront(); +} +``` + +```java +public class ArrayQueue implements Queue { + + private Array array; + + public ArrayQueue(int capacity){ + array = new Array<>(capacity); + } + + public ArrayQueue(){ + array = new Array<>(); + } + + @Override + public int getSize(){ + return array.getSize(); + } + + @Override + public boolean isEmpty(){ + return array.isEmpty(); + } + + public int getCapacity(){ + return array.getCapacity(); + } + + @Override + public void enqueue(E e){ + array.addLast(e); + } + + @Override + public E dequeue(){ + return array.removeFirst(); + } + + @Override + public E getFront(){ + return array.getFirst(); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append("Queue: "); + res.append("front ["); + for(int i = 0 ; i < array.getSize() ; i ++){ + res.append(array.get(i)); + if(i != array.getSize() - 1) + res.append(", "); + } + res.append("] tail"); + return res.toString(); + } +} +``` +链表实现队列: + + - 注意为了dequeue的方便,增加了一个tail指针指向尾节点; + - 注意队列为空的情况要特判 + +![这里写图片描述](https://img-blog.csdn.net/20180829220212627?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```java +public class SingleListQueue implements Queue{ + + private class Node{ + public E val; + public Node next; + + public Node(E val, Node next){ + this.val = val; + this.next = next; + } + + public Node(E val){ + this(val, null); + } + + public Node(){ + this(null, null); + } + + @Override + public String toString(){ + return val.toString(); + } + } + + + private Node head,tail; + private int size; + + public SingleListQueue(){ + head = null; + tail = null; + size = 0; + } + + @Override + public int getSize(){ + return size; + } + + @Override + public boolean isEmpty(){ + return size == 0; + } + + @Override + public void enqueue(E e) { + if(tail == null){ + tail = new Node(e); + head = tail; + }else { + tail.next = new Node(e); + tail = tail.next; + } + size++; + } + + @Override + public E dequeue() { + if(isEmpty()) + throw new IllegalArgumentException("Cannot dequeue from an empty queue."); + + Node delNode = head; + head = head.next; + delNode.next = null; + if(head == null) tail = null; //特判一下 维护tail指针 + size--; + return delNode.val; + } + + + @Override + public E getFront(){ + if(isEmpty()) throw new IllegalArgumentException("Queue is empty."); + return head.val; + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append("Queue: front "); + + Node cur = head; + while(cur != null) { + res.append(cur + "->"); + cur = cur.next; + } + res.append("NULL tail"); + return res.toString(); + } + + + public static void main(String[] args) { + SingleListQueuequeue = new SingleListQueue<>(); + for(int i = 0 ; i < 5 ; i ++){ + queue.enqueue(i); + System.out.println(queue); + + if(i % 2 == 0){ // 0 2 4 + queue.dequeue(); + System.out.println(queue); + } + } + } +} + +``` +效果: + +![这里写图片描述](https://img-blog.csdn.net/20180829220251645?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 七、循环队列的实现 + + - 注意循环队列的内置数组中要浪费一个空间用来区分队列空和满; + - 循环队列的出队操作比ArrayQueue要快很多,原因在于ArrayQueue出队要移动大量元素; + +![这里写图片描述](https://img-blog.csdn.net/20180829205754467?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: + +```java +public class LoopQueue implements Queue { + + private E[] data; + private int front,tail; + private int size; + + + public LoopQueue(int capacity) { + data = (E[])new Object[capacity + 1]; //因为要浪费一个空间,所以在内部我们要多开辟一个空间 + front = 0; + tail = 0; + size = 0; + } + + public LoopQueue(){ + this(5); + } + + public int getCapacity(){ + return data.length - 1; //因为我们多开了一个空间,所以返回的是data.length - 1 + } + + @Override + public int getSize() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public void enqueue(E e) { + if((tail + 1) % data.length == front){ + resize(getCapacity() * 2); + } + data[tail] = e; + tail = (tail + 1) % data.length;//尾部指针变化 + size++; + } + + @Override + public E dequeue() { + if(isEmpty()) throw new IllegalArgumentException("Queue is Empty! Can't delete!"); + E res = data[front]; + data[front] = null; + front = (front + 1) % data.length; + size--; + if(size == getCapacity()/4 && getCapacity()/2 != 0){ + resize(getCapacity() / 2); + } + return res; + } + + @Override + public E getFront() { + if(isEmpty()) + throw new IllegalArgumentException("Queue is empty."); + return data[front]; + } + + private void resize(int newCapacity) { + E[] newData = (E[])new Object[newCapacity + 1]; + for(int i = 0; i < size; i++){ + newData[i] = data[(i + front) % data.length]; + } + data = newData; + front = 0; + tail = size; + } + + @Override + public String toString(){ + + StringBuilder res = new StringBuilder(); + res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity())); + res.append("front ["); + for(int i = front ; i != tail ; i = (i + 1) % data.length){ //实现方式二 + res.append(data[i]); + if((i + 1) % data.length != tail) res.append(", "); + } + res.append("] tail"); + return res.toString(); + } + + public static void main(String[] args) { + LoopQueue queue = new LoopQueue<>(); + for(int i = 0 ; i < 5 ; i ++){ + queue.enqueue(i); + System.out.println(queue); + + if(i % 2 == 0){ // 0 2 4 + queue.dequeue(); + System.out.println(queue); + } + } + } +} + +``` +效果: + +![这里写图片描述](https://img-blog.csdn.net/20180829212535196?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** + diff --git "a/Algorithm/DataStructure/DataStructure/Map/Hash\345\222\214Hash\350\241\250\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/DataStructure/Map/Hash\345\222\214Hash\350\241\250\346\200\273\347\273\223.md" new file mode 100644 index 00000000..e0b20b43 --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/Map/Hash\345\222\214Hash\350\241\250\346\200\273\347\273\223.md" @@ -0,0 +1,773 @@ +# Hash和Hash表总结 + - `Hash`基础内容 + - `Hash`函数设计 + - 重写`hashCode()`和`equals()`方法 + - 使用数组+红黑树实现`HashMap` + - 使用数组+链表实现`HashMap` + - 相关时间复杂度分析以及更多处理冲突的方法 + - 用`HashMap`实现一个小栗子(统计单词出现的次数) + - 使用`LeetCode-350. Intersection of Two Arrays II`测试我们实现的`Map` + +## 一、`Hash`基础内容 + + - 哈希表,也称散列表,是实现字典操作的一种有效的数据结构。尽管在最坏的情况下,散列表查找一个元素的时间复杂度与链表中查找的时间相同,达到了O(n),然而实际应用中,散列表查找的性能是极好的,在一些合理的假设下,在散列表中可以查找一个元素的平均时间复杂度是O(1)。 + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2018122022300196.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +`Hash`函数一般的设计,直接取模: + + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220223128604.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +*** +## 二、Hash函数设计 + - **但是如果随便模一个数的话容易导致分布不均匀,所以可以使用摸一个素数的方法来使得散列更加的均匀(数学证明)** + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220223243804.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +于是不管是浮点型数还是字符串数都可以转换成字符串处理: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2018122022355056.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +注意字符串处理的时候: + + - 可以把字符串看作是`26`进制的数,然后来计算它的`hash`值; + - 在运算的过程中,为了防止高次方的运算,可以利用多项式的拆解来处理提高运算效率; + - 为了防止大整数的溢出,取模的时候我们每次运算一次就进行取模,和最后取模的效果是一样的; + +![这里写图片描述](https://img-blog.csdn.net/20180906224215959?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +如果是一个复合的类,也可以进行类似的处理: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220223913202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +综上: `hash`函数设计的原则: + +![这里写图片描述](https://img-blog.csdn.net/20180906224731693?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 三、重写`hashCode()`和`equals()`方法 +按照上面的方式,使用`java`中的`hashCode()`重写,来计算我们的`hash`值,例如下面的`Student`类,我们计算`hash`的值的方法如下: + +```java +public class Student { + + private int grade;//年级 + private int cls; //班级 + private String firstName; + private String lastName; + + public Student(int grade, int cls, String firstName, String lastName) { + this.grade = grade; + this.cls = cls; + this.firstName = firstName; + this.lastName = lastName; + } + + + //复合类型重写 Object类中的hashCode()方法 + // Object类中已经写了,是通过地址比较的  + @Override + public int hashCode() { + int hash = 0; + int B = 31; //这个就是那个进制 + + hash = hash*B + grade; + hash = hash*B + cls; + hash = hash*B + firstName.toLowerCase().hashCode(); + hash = hash*B + lastName.toLowerCase().hashCode(); + + return hash; + } + + /** + 由于hashCode中如果自己重写了hashCode方法,那么有可能导致 不是同一个引用地址的对象是相同的 + 所以要使用equals方法来真的比较对象是否相同 + */ + @Override + public boolean equals(Object obj) { + if(this == obj){ + return true; + } + if(obj == null){ + return false; + } + if(getClass() != obj.getClass()){ + return false; + } + + Student another = (Student)obj; + return this.grade == another.grade && + this.cls == another.cls && + this.firstName.toLowerCase().equals(another.firstName.toLowerCase()) && + this.lastName.toLowerCase().equals(another.lastName.toLowerCase()); + } +} +``` +**为什么要重写`equals()?`** + + - 由于`hashCode`中如果自己重写了`hashCode`方法,那么有可能导致 不是同一个引用地址的对象是相同的(冲突); + + - 所以要使用`equals`方法来真的比较对象是否相同; + + +相关测试: + +```java +public class HashCodeTest { + + public static void main(String[] args) { + + /** + * 测试各个类型的hashCode() 都是使用一个整数映射 + */ + int a = 42; + System.out.println(((Integer)a).hashCode()); + + int b = -42; + System.out.println(((Integer)b).hashCode()); + + double c = 3.1415926; + System.out.println(((Double)c).hashCode()); + + String d = "zxzx"; + System.out.println(d.hashCode()); + + System.out.println(Integer.MAX_VALUE + 1); + System.out.println(); + + + + /** + (1)如果没有重写 Object中的hashCode,那么下面的student和student2是不同的,hashCode按照地址比较 + (2)如果按照自己重写的 hashCode,那么下面的student和student2是相同的 + +   由于不能仅仅只按照hashCode来比较两个对象是否相同,所以就有重写equals方法 + 自己写的hashCode只是计算hash函数的值,但是产生hash冲突的时候(虽然hash函数值相等),还是要比较是否相等 + + */ + Student student = new Student(3, 2, "xinxin", "zheng"); + System.out.println(student.hashCode()); + + Student student2 = new Student(3, 2, "xinxin", "zheng"); + System.out.println(student2.hashCode()); + + System.out.println(student.hashCode() == student2.hashCode()); //true + System.out.println(student == student2); //false + + } +} +``` +测试结果: + +![这里写图片描述](https://img-blog.csdn.net/2018091310481658?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 四、使用数组+红黑树实现HashMap + + - 数组的里面是红黑树实现,红黑树的可以看一下[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79891792)解释。 + - 因为JDK中的红黑树使用的`TreeMap`实现,所以这里直接使用`TreeMap`当做红黑树使用; + +![这里写图片描述](https://img-blog.csdn.net/20180906224646488?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +使用红黑树实现的HashMap + +```java +public class MyHashMap,V> { + + /**为什么要这样的扩容,原因就是这些数都是素数,可以让哈希函数分布均匀,而且都是大致成两倍的关系 */ + private final int[] capacity = { + 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 + }; + + private static final int upperTol = 10; /**每一个TreeMap内部超过这个就要扩容 --> size >= upperTol * M */ + private static final int lowerTol = 2; /** 每一个TreeMap内部小于这个就要缩容 --> size < lowerTol * M */ + private int capacityIndex = 0; /**这个是容量数组的下标,一开始是capacity[0]的容量*/ + + private TreeMap[] hashtable;/** hash数组,每一个数组对应的都是一棵红黑树 */ + private int size; /**总的元素个数*/ + private int M; /**数组大小*/ + + public MyHashMap(){ + this.M = capacity[capacityIndex];//一开始大小为53 + size = 0; + hashtable = new TreeMap[M]; + for(int i = 0; i < M; i++) hashtable[i] = new TreeMap<>(); + } + + public int size(){ + return size; + } + + /** 计算hash值(也就是对应数组的索引) 使用hashCode % M 的方法 注意hashCode()要取绝对值*/ + private int hash(K key){ + return (key.hashCode() & 0x7fffffff) % M; + } + + /** add */ + public void put(K key,V value){ + TreeMapmap = hashtable[hash(key)]; //找到对应的数组index + if(map.containsKey(key)){ + map.put(key,value); + }else { + map.put(key,value); + size++; + + /**判断是否要扩容 */ + if(size >= upperTol * M && capacityIndex + 1 < capacity.length) {//需要扩容且可以扩容 + capacityIndex++; + resize(capacity[capacityIndex]); //扩容到容量数组的下一个值 + } + } + } + + public V remove(K key){ + V ret = null; + TreeMapmap = hashtable[hash(key)]; + + if(map.containsKey(key)){ + ret = map.remove(key); + size--; + + if(size < lowerTol * M && capacityIndex - 1 >= 0){ + capacityIndex--; + resize(capacity[capacityIndex]); + } + } + return ret; + } + + private void resize(int newM) { + TreeMap[] newHashtable = new TreeMap[newM]; + for(int i = 0; i < newM; i++) + newHashtable[i] = new TreeMap<>(); + int oldM = this.M; + this.M = newM; + for(int i = 0; i < oldM; i++){ + TreeMapmap = hashtable[i]; + for(K key : map.keySet()){ + newHashtable[hash(key)].put(key,map.get(key)); + } + } + this.hashtable = newHashtable; + } + + // 相当于put + public void set(K key,V value){ + TreeMapmap = hashtable[hash(key)]; + if(!map.containsKey(key)) + throw new IllegalArgumentException(key + "doesn't exist!"); + map.put(key,value); + } + + public boolean contains(K key){ + return hashtable[hash(key)].containsKey(key); + } + public V get(K key){ + return hashtable[hash(key)].get(key); + } +} +``` +上述代码有几点要注意的: + + - 第一: `capacity`数组是用来`resize(扩容,缩容)`的时候使用的数组,因为我们上面说过,`M`要设计成素数会更好的均匀分布; + - 第二: `upperTol`和`lowerTol`表示平均`TreeMap`数组内的容量达到这两个容量的时候就进行扩容或者缩容; + - 第三: `(key.hashCode() & 0x7fffffff) % M`; 其实就是`Math.abs(key.hashCode()) % M`; + - 第四: `resize()`函数中的 `int oldM = this.M; this.M = newM; `使用`oldM`来保存之前的`M`的做法是为了在下面求`hash(key)`求的是新的`hash`函数的值,不是旧的`hash`的值,这点很容易忽视; + +*** +## 五、使用数组+链表实现HashMap +类似可以使用`LinkedList`来实现链表: + +![这里写图片描述](https://img-blog.csdn.net/20180906224843428?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```java +import java.util.LinkedList; +/** + * 自定义map的升级版,查询效率较高 + * map底层实现 : 数组+链表 + */ +public class LinkHashMap { + + private class Node{ + public K key; + public V value; + + public Node(K key, V value) { + this.key = key; + this.value = value; + } + } + + private final int[] capacity + = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; + + private static final int upperTol = 10; + private static final int lowerTol = 2; + private int capacityIndex = 0; + + private LinkedList[] linkedLists; + private int size; + private int M; + + public int size() { + return size; + } + + public LinkHashMap() { + this.M = capacity[capacityIndex]; + size = 0; + linkedLists = new LinkedList[M]; + for(int i = 0; i < M; i++) + linkedLists[i] = new LinkedList<>(); + } + + private int hash(K key){ + return (key.hashCode() & 0x7fffffff) % M; + } + + public void put(K key, V value) { + Node node = new Node(key, value); + int hash = hash(key); + LinkedListlist = linkedLists[hash]; + if (list == null) { + list = new LinkedList<>(); + linkedLists[hash] = list; + list.add(node); + } else { + Node node2 = null; + for (int i = 0; i < list.size(); i++) { + node2 = list.get(i); + if (node2.key.equals(key)) { + node2.value = value; + return; + } + } + linkedLists[hash].add(node); + } + size++; + if(size >= upperTol * M && capacityIndex + 1 < capacity.length){ + capacityIndex ++; + resize(capacity[capacityIndex]); + } + } + + public V remove(K key) { + int hash = hash(key); + V ret = null; + LinkedListlist = linkedLists[hash]; + + if(list != null){ + Node node2 = null; + for(int i = 0; i < list.size(); i++){ + node2 = list.get(i); + if(node2.key.equals(key)){ + ret = node2.value; + list.remove(i);// list.remove(node2); + size--; + //resize + if(size < lowerTol * M && capacityIndex - 1 >= 0){ + capacityIndex --; + resize(capacity[capacityIndex]); + } + return ret; + } + } + } + return null; + } + + private void resize(int newM) { + LinkedList[]newLinkedLists = new LinkedList[newM]; + for(int i = 0; i < newM; i++) + newLinkedLists[i] = new LinkedList<>(); + int oldM = this.M; + this.M = newM; + Node node = null; + for(int i = 0; i < oldM; i++){ + LinkedListlist = linkedLists[i]; + for(int j = 0; j < list.size(); j++){ + node = list.get(j); + newLinkedLists[hash(node.key)].add(node); + } + } + this.linkedLists = newLinkedLists; + } + + public boolean contains(K key){ + int hash = hash(key); + for(int i = 0; i < linkedLists[hash].size(); i++){ + if(linkedLists[hash].get(i).key.equals(key)) + return true; + } + return false; + } + public V get(K key){ + int hash = hash(key); + Node node = null; + for(int i = 0; i < linkedLists[hash].size(); i++){ + node = linkedLists[hash].get(i); + if(node.key.equals(key)) + return node.value; + } + return null; + } + + public void set(K key,V value){ + int hash = hash(key); + LinkedListlist = linkedLists[hash]; + if(list == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + Node node = null; + for(int i = 0; i < list.size(); i++){ + node = list.get(i); + if(node.key.equals(key)){ + node.value = value; + return; + } + } + throw new IllegalArgumentException(key + " doesn't exist!"); + } +} +``` +*** +## 六、相关时间复杂度分析 +如果我们没有使用`resize()`动态扩容的话: 时间复杂度不是`O(1)`,于是我们进行了扩容,可以达到平均时间复杂度为`O(1)`(是均摊复杂度分析): + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220224428533.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +不过`HashMap`达到了`O(1)`,但是相比搜索树牺牲了有序性: + +![这里写图片描述](https://img-blog.csdn.net/20180907000439273?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +## 七、更多处理冲突的方法 +开放地址法: + + * 线性探测: 遇到哈希冲突`+1`; + * 平方探测: `+ 1` ,`+ 4` ,`+9`,`+16`; + * 二次`hash`:`hash2(key)`; +*** + +## 八、使用LeetCode-350. Intersection of Two Arrays II测试我们实现的Map +最后使用LeetCode-350测试我们实现的Map: +#### [题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/description/) +#### 题目 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220224517577.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +#### 解析 +题目很简单,使用`HashMap`和`TreeMap`来记录`nums1`中的数字的个数,然后对`nums2`进行操作,这里测试我们的`Map; +LinkHashMap:` + +```java +class Solution { + private class LinkHashMap { + + private class Node{ + public K key; + public V value; + + public Node(K key, V value) { + this.key = key; + this.value = value; + } + } + + private final int[] capacity + = {53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741}; + + private static final int upperTol = 10; + private static final int lowerTol = 2; + private int capacityIndex = 0; + + private LinkedList[] linkedLists; + private int size; + private int M; + + public int size() { + return size; + } + + + public LinkHashMap() { + this.M = capacity[capacityIndex]; + size = 0; + linkedLists = new LinkedList[M]; + for(int i = 0; i < M; i++) + linkedLists[i] = new LinkedList<>(); + } + + private int hash(K key){ + return (key.hashCode() & 0x7fffffff) % M; + } + + + public void put(K key, V value) { + Node node = new Node(key, value); + int hash = hash(key); + LinkedListlist = linkedLists[hash]; + if (list == null) { + list = new LinkedList<>(); + linkedLists[hash] = list; + list.add(node); + } else { + Node node2 = null; + for (int i = 0; i < list.size(); i++) { + node2 = list.get(i); + if (node2.key.equals(key)) { + node2.value = value; + return; + } + } + linkedLists[hash].add(node); + } + size++; + if(size >= upperTol * M && capacityIndex + 1 < capacity.length){ + capacityIndex ++; + resize(capacity[capacityIndex]); + } + } + + public V remove(K key) { + int hash = hash(key); + V ret = null; + LinkedListlist = linkedLists[hash]; + + if(list != null){ + Node node2 = null; + for(int i = 0; i < list.size(); i++){ + node2 = list.get(i); + if(node2.key.equals(key)){ + ret = node2.value; + list.remove(i);// list.remove(node2); + size--; + //resize + if(size < lowerTol * M && capacityIndex - 1 >= 0){ + capacityIndex --; + resize(capacity[capacityIndex]); + } + return ret; + } + } + } + return null; + } + + private void resize(int newM) { + LinkedList[]newLinkedLists = new LinkedList[newM]; + for(int i = 0; i < newM; i++) + newLinkedLists[i] = new LinkedList<>(); + int oldM = this.M; + this.M = newM; + Node node = null; + for(int i = 0; i < oldM; i++){ + LinkedListlist = linkedLists[i]; + for(int j = 0; j < list.size(); j++){ + node = list.get(j); + newLinkedLists[hash(node.key)].add(node); + } + } + this.linkedLists = newLinkedLists; + } + + + public boolean contains(K key){ + int hash = hash(key); + for(int i = 0; i < linkedLists[hash].size(); i++){ + if(linkedLists[hash].get(i).key.equals(key)) + return true; + } + return false; + } + public V get(K key){ + int hash = hash(key); + Node node = null; + for(int i = 0; i < linkedLists[hash].size(); i++){ + node = linkedLists[hash].get(i); + if(node.key.equals(key)) + return node.value; + } + return null; + } + public void set(K key,V value){ + int hash = hash(key); + LinkedListlist = linkedLists[hash]; + if(list == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + Node node = null; + for(int i = 0; i < list.size(); i++){ + node = list.get(i); + if(node.key.equals(key)){ + node.value = value; + return; + } + } + throw new IllegalArgumentException(key + " doesn't exist!"); + } + } + + + + public int[] intersect(int[] nums1, int[] nums2) { + + LinkHashMap map = new LinkHashMap<>(); + for(int num: nums1){ + if(!map.contains(num)) + map.put(num, 1); + else + map.set(num, map.get(num) + 1); + } + + ArrayList res = new ArrayList<>(); + for(int num: nums2){ + if(map.contains(num)){ + res.add(num); + map.set(num, map.get(num) - 1); + if(map.get(num) == 0) + map.remove(num); + } + } + + int[] ret = new int[res.size()]; + for(int i = 0 ; i < res.size() ; i ++) + ret[i] = res.get(i); + + return ret; + } +} +``` +MyHashMap: + +```java +class Solution { + private class MyHashMap,V> { + + /**为什么要这样的扩容,原因就是这些数都是素数,可以让哈希函数分布均匀,而且都是大致成两倍的关系 */ + private final int[] capacity = { + 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 + }; + + private static final int upperTol = 10; /**每一个TreeMap内部超过这个就要扩容 --> size >= upperTol * M */ + private static final int lowerTol = 2; /** 每一个TreeMap内部小于这个就要缩容 --> size < lowerTol * M */ + private int capacityIndex = 0; /**这个是容量数组的下标,一开始是capacity[0]的容量*/ + + private TreeMap[] hashtable;/** hash数组,每一个数组对应的都是一棵红黑树 */ + private int size; /**总的元素个数*/ + private int M; /**数组大小*/ + + public MyHashMap(){ + this.M = capacity[capacityIndex];//一开始大小为53 + size = 0; + hashtable = new TreeMap[M]; + for(int i = 0; i < M; i++) hashtable[i] = new TreeMap<>(); + } + + public int size(){ + return size; + } + + /** 计算hash值(也就是对应数组的索引) 使用hashCode % M 的方法 注意hashCode()要取绝对值*/ + private int hash(K key){ + return (key.hashCode() & 0x7fffffff) % M; + } + + /** add */ + public void put(K key,V value){ + TreeMapmap = hashtable[hash(key)]; //找到对应的数组index + if(map.containsKey(key)){ + map.put(key,value); + }else { + map.put(key,value); + size++; + + /**判断是否要扩容 */ + if(size >= upperTol * M && capacityIndex + 1 < capacity.length) {//需要扩容且可以扩容 + capacityIndex++; + resize(capacity[capacityIndex]); //扩容到容量数组的下一个值 + } + } + } + + public V remove(K key){ + V ret = null; + TreeMapmap = hashtable[hash(key)]; + + if(map.containsKey(key)){ + ret = map.remove(key); + size--; + + if(size < lowerTol * M && capacityIndex - 1 >= 0){ + capacityIndex--; + resize(capacity[capacityIndex]); + } + } + return ret; + } + + private void resize(int newM) { + TreeMap[] newHashtable = new TreeMap[newM]; + for(int i = 0; i < newM; i++) + newHashtable[i] = new TreeMap<>(); + int oldM = this.M; + this.M = newM; + for(int i = 0; i < oldM; i++){ + TreeMapmap = hashtable[i]; + for(K key : map.keySet()){ + newHashtable[hash(key)].put(key,map.get(key)); + } + } + this.hashtable = newHashtable; + } + + + public void set(K key,V value){ + TreeMapmap = hashtable[hash(key)]; + if(!map.containsKey(key)) + throw new IllegalArgumentException(key + "doesn't exist!"); + map.put(key,value); + } + public boolean contains(K key){ + return hashtable[hash(key)].containsKey(key); + } + public V get(K key){ + return hashtable[hash(key)].get(key); + } + } + + + + public int[] intersect(int[] nums1, int[] nums2) { + + MyHashMap map = new MyHashMap<>(); + for(int num: nums1){ + if(!map.contains(num)) + map.put(num, 1); + else + map.set(num, map.get(num) + 1); + } + + ArrayList res = new ArrayList<>(); + for(int num: nums2){ + if(map.contains(num)){ + res.add(num); + map.set(num, map.get(num) - 1); + if(map.get(num) == 0) + map.remove(num); + } + } + + int[] ret = new int[res.size()]; + for(int i = 0 ; i < res.size() ; i ++) + ret[i] = res.get(i); + + return ret; + } +} +``` +*** diff --git "a/Algorithm/DataStructure/DataStructure/Map/\351\233\206\345\220\210(Set)\345\222\214\346\230\240\345\260\204(Map)\345\237\272\347\241\200\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/DataStructure/Map/\351\233\206\345\220\210(Set)\345\222\214\346\230\240\345\260\204(Map)\345\237\272\347\241\200\346\200\273\347\273\223.md" new file mode 100644 index 00000000..caa0c6ab --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/Map/\351\233\206\345\220\210(Set)\345\222\214\346\230\240\345\260\204(Map)\345\237\272\347\241\200\346\200\273\347\273\223.md" @@ -0,0 +1,621 @@ +## 集合(Set)和映射(Map)基础总结 + + - `Set`介绍 + - 基于链表实现`Set` + - 基于二叉搜索树实现`Set` + - 基于二叉平衡树实现`Set` + - 使用`Set`解决LeetCode-804. Unique Morse Code Words + - 使用`Set`解决LeetCode-349. Intersection of Two Arrays + - `Map`介绍 + - 基于链表实现`Map` + - 基于二叉搜索树实现`Map` + - 基于二叉平衡树实现`Map` + - 使用`Map`解决LeetCode-350. Intersection of Two Arrays II + - `Set`和`Map`的一些对比和总结 + +*** +### `Set`介绍 + + - `Set`是不允许重复的集合容器; + - `Set`可以分为有序集合和无需集合; + - 有序集合基于搜索树实现,JDK底层是红黑树,即`TreeSet`; + - 无序集合基于`Hash`表实现,JDK底层是`HashMap`包装之后,即`HashSet`; + +*** +### 基于链表实现`Set` +首先看一下[单链表的实现](https://blog.csdn.net/zxzxzx0119/article/details/79811308),下面的实现中使用`SingleList`实现: +先看`Set`接口中需要实现的方法: + +```java +/** + * Set接口 + * @param + */ +public interface Set { + void add(E e); + boolean contains(E e); + void remove(E e); + int getSize(); + boolean isEmpty(); +} +``` +如果有单链表的相关方法,其中的`Set`就很容易实现,唯一要注意的就是 : 在添加元素的时候要注意先判断集合中有没有这个元素: + +```java +/** + * 基于单链表实现 Set + * @param + */ +public class LinkedListSet implements Set { + + private SingleList list; + + public LinkedListSet(){ + list = new SingleList<>(); + } + + @Override + public int getSize(){ + return list.size(); + } + + @Override + public boolean isEmpty(){ + return list.isEmpty(); + } + + @Override + public void add(E e){ + if(!list.contains(e)) //这个就是效率低的原因 + list.addFirst(e); + } + + @Override + public boolean contains(E e){ + return list.contains(e); + } + + @Override + public void remove(E e){ + list.removeElement(e); + } + +} +``` +*** +### 基于二叉搜索树实现`Set` +先看一下[二叉搜索树](https://blog.csdn.net/zxzxzx0119/article/details/80012374)相关方法的实现。注意由于我实现的二叉搜索树没有重复元素,所有`Set`也没有重复元素。 +```java +/** + * 基于二叉搜索树实现的Set 类似JDK的TreeSet (有序集合) 而HashSet是无序集合 + * @param + */ +public class BSTSet> implements Set { + + private BSTree bst; + + public BSTSet(){ + bst = new BSTree<>(); + } + + @Override + public int getSize(){ + return bst.size(); + } + + @Override + public boolean isEmpty(){ + return bst.isEmpty(); + } + + @Override + public void add(E e){ + bst.add(e); + } + + @Override + public boolean contains(E e){ + return bst.contains(e); + } + + @Override + public void remove(E e){ + bst.remove(e); + } +} +``` +*** +### 基于二叉平衡树实现Set +在这之前先看一下[二叉平衡树](https://blog.csdn.net/zxzxzx0119/article/details/80012812)的总结和代码实现。 +```java +public class AVLSet> implements Set { + + private AVLTree avl; + + public AVLSet(){ + avl = new AVLTree<>(); + } + + @Override + public int getSize(){ + return avl.size(); + } + + @Override + public boolean isEmpty(){ + return avl.isEmpty(); + } + + @Override + public void add(E e){ + avl.add(e, null); + } + + @Override + public boolean contains(E e){ + return avl.contains(e); + } + + @Override + public void remove(E e){ + avl.remove(e); + } +} +``` + +*** +### 使用Set解决LeetCode-804. Unique Morse Code Words +#### [题目链接](https://leetcode.com/problems/unique-morse-code-words/description/) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220225038289.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +很简单的题目,转换成对应的解码,然后存到一个不能重复的`set`集合中,返回集合中的元素个数即可; + +```java + public int uniqueMorseRepresentations(String[] words) { + if (words == null || words.length == 0) { + return 0; + } + String[] dict = {".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."}; + HashSetset = new HashSet<>(); + for(String word : words){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < word.length(); i++){ + sb.append(dict[word.charAt(i) - 'a']); + } + set.add(sb.toString()); + } + return set.size(); + } +``` + +*** +### 使用Set解决LeetCode-349. Intersection of Two Arrays +#### [题目链接](https://leetcode.com/problems/intersection-of-two-arrays/description/) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220225119734.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +解题思路: 我们可以使用一个`set`一开始去除`nums1`中的重复元素,遍历`nums2`中的元素,判断每个元素在`nums1`中是否出现,为了不重复,记录一个之后要在`set`中删除这个。 + +```java + public int[] intersection(int[] nums1, int[] nums2) { + HashSetset = new HashSet<>(); + for(int num : nums1) set.add(num); + + ArrayListlist = new ArrayList<>(); + for(int num : nums2) { + if(set.contains(num)){ + list.add(num); + set.remove(num);// important + } + } + int[] res = new int[list.size()]; + for(int i = 0 ; i < list.size() ; i ++) + res[i] = list.get(i); + return res; + } +``` +另外这个题目基于双指针和二分`Ologn`的实现也不难,可以看下[这里](https://leetcode.com/problems/intersection-of-two-arrays/discuss/81969/Three-Java-Solutions)。 +*** +### `Map`介绍 + + * `Map`的键不允许重复,如果重复插入键相同的,则新的`value`覆盖原来的`value`; + * `Map`可以分为有序`Map`和无序`Map`; + * 有序`Map`基于搜索树实现,JDK底层使用红黑树实现,即`TreeMap`; + * 无序`Map`基于`Hash`表实现,JDK底层使用`Hash`表底层实现,即`HashMap`; + +*** +### 基于链表实现Map +和普通的单链表不同的: + + - 结点内部是`key,value`的键值对; + - `getNode()`,返回`key`对应的结点,方便操作; + - 在`add()`操作的时候,如果已经存在`key`对应的结点,就更新`value`即可; + +```java +public class LinkedListMap implements Map { + + //结点结构 + private class Node { + public K key; + public V value; + public Node next; + + public Node(K key, V value, Node next) { + this.key = key; + this.value = value; + this.next = next; + } + + public Node(K key, V value) { + this(key, value, null); + } + + public Node() { + this(null, null, null); + } + + @Override + public String toString() { + return key.toString() + " : " + value.toString(); + } + } + + private Node dummyHead; + private int size; + + public LinkedListMap() { + dummyHead = new Node(); + size = 0; + } + + @Override + public int getSize() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + + // important + private Node getNode(K key) { + Node cur = dummyHead.next; + while (cur != null) { + if (cur.key.equals(key)) + return cur; + cur = cur.next; + } + return null; + } + + @Override + public boolean contains(K key) { + return getNode(key) != null; + } + + @Override + public V get(K key) { + Node node = getNode(key); + return node == null ? null : node.value; + } + + @Override + public void add(K key, V value) { + Node node = getNode(key); + if (node == null) { + /**Node newNode = new Node(key,value); + newNode.next = dummyHead.next; + dummyHead.next = newNode;*/ + dummyHead.next = new Node(key, value, dummyHead.next);// == 上面三行 + size++; + } else //already exist + node.value = value; + } + + @Override + public void set(K key, V newValue) { + Node node = getNode(key); + if (node == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + + node.value = newValue; + } + + @Override + public V remove(K key) { + + Node prev = dummyHead; + while (prev.next != null) { + if (prev.next.key.equals(key)) + break; + prev = prev.next; + } + + if (prev.next != null) { + Node delNode = prev.next; + prev.next = delNode.next; + delNode.next = null; + size--; + return delNode.value; + } + + return null; + } +} +``` +*** +### 基于二叉搜索树实现Map +和[二叉搜索树](https://blog.csdn.net/zxzxzx0119/article/details/80012374)中的操作差不多,注意: + + - 在添加的时候,如果已经存在,也就是`key`相等的话就直接更新`value`即可; + - 增加`getNode()`方法: 返回以`node`为根节点的二分搜索树中,`key`所在的节点; + - 大部分对于`e`(也就是结点值)的操作,就是对`key`的操作; + +```java +public class BSTMap, V> implements Map { + + private class Node { + public K key; + public V value; + public Node left, right; + + public Node(K key, V value) { + this.key = key; + this.value = value; + left = null; + right = null; + } + } + + private Node root; + private int size; + + public BSTMap() { + root = null; + size = 0; + } + + @Override + public int getSize() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + // 向二分搜索树中添加新的元素(key, value) + @Override + public void add(K key, V value) {//相当于JDK的put操作 + root = add(root, key, value); + } + + // 向以node为根的二分搜索树中插入元素(key, value),递归算法 + // 返回插入新节点后二分搜索树的根 + private Node add(Node node, K key, V value) { + + if (node == null) { + size++; + return new Node(key, value); + } + + if (key.compareTo(node.key) < 0) + node.left = add(node.left, key, value); + else if (key.compareTo(node.key) > 0) + node.right = add(node.right, key, value); + else // key.compareTo(node.key) == 0 + node.value = value; + + return node; + } + + // 返回以node为根节点的二分搜索树中,key所在的节点 + private Node getNode(Node node, K key) { + + if (node == null) + return null; + + if (key.equals(node.key)) + return node; + else if (key.compareTo(node.key) < 0) + return getNode(node.left, key); + else // if(key.compareTo(node.key) > 0) + return getNode(node.right, key); + } + + @Override + public boolean contains(K key) { + return getNode(root, key) != null; + } + + @Override + public V get(K key) { + Node node = getNode(root, key); + return node == null ? null : node.value; + } + + @Override + public void set(K key, V newValue) { + Node node = getNode(root, key); + if (node == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + + node.value = newValue; + } + + // 返回以node为根的二分搜索树的最小值所在的节点 + private Node minimum(Node node) { + if (node.left == null) + return node; + return minimum(node.left); + } + + // 删除掉以node为根的二分搜索树中的最小节点 + // 返回删除节点后新的二分搜索树的根 + private Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); + return node; + } + + // 从二分搜索树中删除键为key的节点 + @Override + public V remove(K key) { + + Node node = getNode(root, key); + if (node != null) { + root = remove(root, key); + return node.value; + } + return null; + } + + private Node remove(Node node, K key) { + + if (node == null) + return null; + + if (key.compareTo(node.key) < 0) { + node.left = remove(node.left, key); + return node; + } else if (key.compareTo(node.key) > 0) { + node.right = remove(node.right, key); + return node; + } else { // key.compareTo(node.key) == 0 + + // 待删除节点左子树为空的情况 + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + + // 待删除节点右子树为空的情况 + if (node.right == null) { + Node leftNode = node.left; + node.left = null; + size--; + return leftNode; + } + + /** 待删除节点左右子树均不为空的情况 + 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点 + 用这个节点顶替待删除节点的位置*/ + Node successor = minimum(node.right); + successor.right = removeMin(node.right); + successor.left = node.left; + + node.left = node.right = null; + + return successor; + } + } +} +``` +*** +### 基于二叉平衡树实现Map +在这之前先看[平衡二叉树](https://blog.csdn.net/zxzxzx0119/article/details/80012812)的原理和实现。 +```java +public class AVLMap, V> implements Map { + + private AVLTree avl; + + public AVLMap(){ + avl = new AVLTree<>(); + } + + @Override + public int getSize(){ + return avl.size(); + } + + @Override + public boolean isEmpty(){ + return avl.isEmpty(); + } + + @Override + public void add(K key, V value){ + avl.add(key, value); + } + + @Override + public boolean contains(K key){ + return avl.contains(key); + } + + @Override + public V get(K key){ + return avl.get(key); + } + + @Override + public void set(K key, V newValue){ + avl.set(key, newValue); + } + + @Override + public V remove(K key){ + return avl.remove(key); + } +} +``` + +*** +### 使用Map解决LeetCode-350. Intersection of Two Arrays II +#### [题目链接](https://leetcode.com/problems/intersection-of-two-arrays-ii/description/) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220225410690.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +解题思路: 也很简单,只需要用`map`记录一下次数。然后在`map`中更新维护次数即可。 + +```java + public int[] intersect(int[] nums1, int[] nums2) { + HashMapmap = new HashMap<>(); + + + for(int num : nums1){ + if(!map.containsKey(num)){ + map.put(num,1); + }else { + map.put(num,map.get(num) + 1); + } + } + + ArrayListlist = new ArrayList<>(); + for(int num : nums2){ + if(map.containsKey(num)){ + list.add(num); + map.put(num,map.get(num) - 1); + if(map.get(num) == 0)map.remove(num); + } + } + + int[] res = new int[list.size()]; + for(int i = 0; i < list.size(); i++) res[i] = list.get(i); + return res; + } +``` +### Set和Map的一些对比和总结 +* 集合`Set`和映射`Map`有很多的相似之处,实现都可以使用链表和搜索树实现; +* 其中JDK的`HashSet`也是`HashMap`包装之后的,虽然用的是`Hash`表。 +* **使用搜索树实现的`Set`和`Map`平均时间复杂度为Ologn(Ologh)(Olog2n),但是使用链表是`O(n)`;** + +|`Set< E >`|`Map`| +|-|-| +|`void add(E)`|`void add(K, V)`| +|`void remove(E)`|`V remove(K)`| +|`boolean contains(E)`|`boolean contains(K)`| +|`int getSize()`|`int getSize()`| +|`boolean isEmpty()`|`boolean isEmpty()`| +||`V get(K)`| +||`void set(K, V)`| + + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms1.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms1.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms2.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms2.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms3.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms3.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms4.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms4.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms5.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms5.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms6.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms6.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms7.png" b/Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms7.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/images/ms7.png" rename to Algorithm/DataStructure/DataStructure/MonotoneStack/images/ms7.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" "b/Algorithm/DataStructure/DataStructure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" similarity index 96% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" rename to "Algorithm/DataStructure/DataStructure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" index 2496bb40..e9969037 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" +++ "b/Algorithm/DataStructure/DataStructure/MonotoneStack/\345\215\225\350\260\203\346\240\210\344\273\213\347\273\215\344\273\245\345\217\212\346\236\204\351\200\240\346\225\260\347\273\204\347\232\204MaxTree\351\227\256\351\242\230.md" @@ -8,21 +8,21 @@ #### 题目(构造数组的MaxTree问题) ![在这里插入图片描述](images/ms3.png) - - ### 单调栈介绍 -单调栈最初解决的问题就是**寻找一个数组中 ,每一个数的左右两边离它最近的数**。 +单调栈最初解决的问题就是**寻找一个数组中 ,每一个数的左右两边离它最近的比它大的数**。 - 遍历一个数组,如果**栈为空或者栈顶比当前数大(或者相等,相等的话就多个下标对应一个值),就把当前数入栈**; - 如果栈顶比当前数小,那么就**处理这个栈顶,即这个栈顶右边第一个比它大的数就是当前数,左边第一个比它大的数就是在栈里面它的下面的那个数,也就是它出栈之后的栈顶**; - - 当遍历完所有的数之后,栈中还有数,这时,逐个判断栈中的数,每一个数,**它的右边不存在比它大的**,如果这个数在栈里面它的下面还有数,它左边离他最近的大的数就是它下面的数; + - 当遍历完所有的数之后,栈中还有数,这时,逐个判断栈中的数,每一个数,**它的右边不存在比它大的**,如果这个数在栈里面它的下面还有数,它左边离他最近的大的数就是它下面的数; + +图: ![这里写图片描述](images/ms2.png) ### 单调栈解决构造数组的MaxTree问题 按照下面的方法来建树 - - 每一个树的**父节点是它左边第一个比它大的数和它右边第一个比它大的数中,比较的那个**; + - 每一个树的**父节点是它左边第一个比它大的数和它右边第一个比它大的数中,比较小的那个**; - 如果左边没有没有比它大的数,右边也没有。也就是说,这个数是整个数组的最大值,那么这个数是`MaxTree`的头结点; 按照大根堆和上面的方案放置如下: diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/\346\234\200\345\244\247\345\200\274\345\207\217\345\216\273\346\234\200\345\260\217\345\200\274\345\260\217\344\272\216\347\255\211\344\272\216aim \347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\351\207\217(\345\215\225\350\260\203\351\230\237\345\210\227(\346\234\200\345\244\247\345\200\274\345\222\214\346\234\200\345\260\217\345\200\274\346\233\264\346\226\260\347\273\223\346\236\204)).md" "b/Algorithm/DataStructure/DataStructure/MonotoneStack/\346\234\200\345\244\247\345\200\274\345\207\217\345\216\273\346\234\200\345\260\217\345\200\274\345\260\217\344\272\216\347\255\211\344\272\216aim \347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\351\207\217(\345\215\225\350\260\203\351\230\237\345\210\227(\346\234\200\345\244\247\345\200\274\345\222\214\346\234\200\345\260\217\345\200\274\346\233\264\346\226\260\347\273\223\346\236\204)).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/MonotoneStack/\346\234\200\345\244\247\345\200\274\345\207\217\345\216\273\346\234\200\345\260\217\345\200\274\345\260\217\344\272\216\347\255\211\344\272\216aim \347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\351\207\217(\345\215\225\350\260\203\351\230\237\345\210\227(\346\234\200\345\244\247\345\200\274\345\222\214\346\234\200\345\260\217\345\200\274\346\233\264\346\226\260\347\273\223\346\236\204)).md" rename to "Algorithm/DataStructure/DataStructure/MonotoneStack/\346\234\200\345\244\247\345\200\274\345\207\217\345\216\273\346\234\200\345\260\217\345\200\274\345\260\217\344\272\216\347\255\211\344\272\216aim \347\232\204\345\255\220\346\225\260\347\273\204\346\225\260\351\207\217(\345\215\225\350\260\203\351\230\237\345\210\227(\346\234\200\345\244\247\345\200\274\345\222\214\346\234\200\345\260\217\345\200\274\346\233\264\346\226\260\347\273\223\346\236\204)).md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg1.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg1.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg2.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg2.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg3.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg3.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg4.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg4.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg5.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg5.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg6.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg6.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg7.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg7.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg7.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg7.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg8.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg8.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg8.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg8.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg9.png" b/Algorithm/DataStructure/DataStructure/SegmentTree/images/sg9.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/SegmentTree/images/sg9.png" rename to Algorithm/DataStructure/DataStructure/SegmentTree/images/sg9.png diff --git "a/Algorithm/DataStructure/DataStructure/SegmentTree/\347\272\277\346\256\265\346\240\221\346\200\273\347\273\223\344\273\245\345\217\212LeetCode - 307. Range Sum Query - Mutable.md" "b/Algorithm/DataStructure/DataStructure/SegmentTree/\347\272\277\346\256\265\346\240\221\346\200\273\347\273\223\344\273\245\345\217\212LeetCode - 307. Range Sum Query - Mutable.md" new file mode 100644 index 00000000..ba47011e --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/SegmentTree/\347\272\277\346\256\265\346\240\221\346\200\273\347\273\223\344\273\245\345\217\212LeetCode - 307. Range Sum Query - Mutable.md" @@ -0,0 +1,577 @@ +## 线段树总结以及LeetCode - 307. Range Sum Query - Mutable + + - [线段树介绍](#线段树介绍) + - [线段树创建](#线段树创建) + - [线段树查询](#线段树查询) + - [线段树更新](#线段树更新) + - [完整测试代码](#完整测试代码) + - [LeetCode - 307. Range Sum Query - Mutable](#leetcode---307-range-sum-query---mutable) + +*** +### 线段树介绍 +**线段树 : 它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为`O(logn)`。** + +线段树解决的是类似下面频繁的对一段区间进行查询的问题: + +![在这里插入图片描述](images/sg1.png) + +**线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是`[L,R]`,那么(`m=(L+R)/2`)左儿子的区间是`[L,m]`,右儿子的区间是`[m+1,R]`。** + +### 线段树创建 + + - 线段树的创建是一个递归和类似二分的过程,首先我们将我们的整个区间(也就是整个数组)作为整颗线段的叶子结点; + - 然后我们将区间分为两半,`[L,m] `和` [m+1,R]`,然后递归的去创建各自的线段树; + - 边界条件是直到我们的区间中只有一个元素的时候,我们就可以使用这个这个元素建立出叶子结点; + +![在这里插入图片描述](images/sg2.png) + +那么用数组创建线段树需要多少结点呢? + +(1) 第一种情况: 我们的元素全部落在最后一层,这样的话我们大概只需要`2*n`(`n`是数组,也是最后一层的元素的个数)。 + +![在这里插入图片描述](images/sg3.png) + +(2) 第二种情况: 我们的元素不全部是在最后一层,而是在倒数第二层也有:这样的话我们最多可能需要`4*n`的空间。 + +![在这里插入图片描述](images/sg4.png) + +**上面的第二种情况就是我们的数组元素有在倒数第二层的情况: 例如下面的例子,当区间的划分的个数是奇数个的时候,势必左右两边的个数不同,下面的图是左边比右边的少一个,也就是左边区间是`[L,m-1]`,右边的区间是`[m,R]` (但是我在代码实现的时候,左边是`[L,m]`,右边是`[m+1,R]`,也就是左边多一个)**。 + +![在这里插入图片描述](images/sg5.png) + +**我们保存树的结构是类似和堆一样的使用数组来保存,使用下标来对应左右孩子**: + +```java +public class SegmentTree { + + //操作的方式: 求和 | 查询最大值 | 最小值 + private interface Merger{ + E merge(E a,E b); + } + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr,Merger merger) { + this.merger = merger; + + data = (E[]) new Object[arr.length]; + for(int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = (E[]) new Object[4 * arr.length]; //最多需要4 * n + buildSegmentTree(0, 0, arr.length - 1); + } +} +``` +注意其中: + + - 接口`Merger`表示的处理方式,比如查询区间和,查询最大值,查询最小值。 + - `data`用来保存用户传进来的`arr`值,是它的拷贝。 + - `tree`就是用数组来描述树的结构,注意大小为` 4 * arr.length`。 + - `buildSegmentTree()`函数是创建线段树。 + + +然后就是对线段树的创建: +要注意的是: + + - 我创建的时候二分是左区间`[L,m]`,右区间`[m+1,R]`,当然也可以右区间多一个元素; + +一个例子: + +![这里写图片描述](images/sg6.png) + +建树代码: + +```java + // tree是树的结构(类似堆的存储) + public void buildSegmentTree(int treeIndex,int L,int R){ + if( L == R){//叶子结点  直接创建赋值 + tree[treeIndex] = data[L]; + return; + } + int treeL = treeIndex * 2 + 1; //左孩子对应的下标 + int treeR = treeIndex * 2 + 2; //右孩子下标 + int m = L + (R - L) / 2; // + + // 先把左右子树给我建好 + //[0,4] ---> [0,2](3), [2,4](2) + buildSegmentTree(treeL,L,m); + buildSegmentTree(treeR,m+1,R); + + //然后我再把左右子树合并(sum | max | min) + tree[treeIndex] = merger.merge(tree[treeL],tree[treeR]); + } +``` + +*** +### 线段树查询 +![在这里插入图片描述](images/sg7.png) + +假设查询的区间为`[qL,qR]`分为三种情况: + + - `qR <= m`,说明我们要去左边的区间查询; + - `qL > m `,说明我们要去右边的区间查询; + - 其他情况,说明左右两边都要查询,查完之后,记得合并; + +```java + //查询[qL,qR]的 sum | max | min + public E query(int qL,int qR){ + if(qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR)return null; + return query(0,0,data.length - 1,qL,qR); + } + + // [treeIndex,L,R]表示的是结点为treeIndex的树的左右区间范围(arr的下标) + private E query(int treeIndex,int L,int R,int qL,int qR){ + if(L == qL && R == qR){ + return tree[treeIndex]; + } + int m = L + (R - L) / 2; + + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + + if(qR <= m){ //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeL,L,m,qL,qR); + }else if(qL > m ) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeR,m+1,R,qL,qR); + }else { //在两边都有,查询的结果 合并 + return merger.merge(query(treeL,L,m,qL,m), //注意是查询 [qL,m] + query(treeR,m+1,R,m+1,qR)); //查询[m+1,qR] + } + } +``` +*** +### 线段树更新 +线段树的更新也是类似的,首先修改数组的值,然后递归的查找到叶子,然后沿途修改树中结点的值即可。 +```java + public void update(int index,E e){ + if(index < 0 || index >= data.length )return; + data[index] = e; //首先修改data + update(0,0,data.length-1,index,e); + } + + private void update(int treeIndex,int L,int R,int index,E e){ + if(L == R){ + tree[treeIndex] = e; + return; + } + int m = L + (R - L ) / 2; + int treeL = 2 * treeIndex + 1; + int treeR = 2 * treeIndex + 2; + if(index <= m){ //左边 + update(treeL,L,m,index,e); + }else { + update(treeR,m+1,R,index,e); + } + tree[treeIndex] = merger.merge(tree[treeL],tree[treeR]); //更新完左右子树之后,自己受到影响,重新更新和 + } +``` +*** +### 完整测试代码 + +```java +import java.util.Arrays; + +public class SegmentTree { + + //操作的方式: 求和 | 查询最大值 | 最小值 + private interface Merger{ + E merge(E a,E b); + } + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr,Merger merger) { + this.merger = merger; + + data = (E[]) new Object[arr.length]; + for(int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = (E[]) new Object[4 * arr.length]; //最多需要4 * n + buildSegmentTree(0, 0, arr.length - 1); + } + + // tree是树的结构(类似堆的存储) + public void buildSegmentTree(int treeIndex,int L,int R){ + if( L == R){ + tree[treeIndex] = data[L]; + return; + } + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + int m = L + (R - L) / 2; + + // 先把左右子树给我建好 + //[0,4] ---> [0,2](3), [2,4](2) + buildSegmentTree(treeL,L,m); + buildSegmentTree(treeR,m+1,R); + + //然后我再把左右子树合并(sum | max | min) + tree[treeIndex] = merger.merge(tree[treeL],tree[treeR]); + } + + //查询[qL,qR]的 sum | max | min + public E query(int qL,int qR){ + if(qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR)return null; + return query(0,0,data.length - 1,qL,qR); + } + + // [treeIndex,L,R]表示的是结点为treeIndex的树的左右区间范围(arr的下标) + private E query(int treeIndex,int L,int R,int qL,int qR){ + if(L == qL && R == qR){ + return tree[treeIndex]; + } + int m = L + (R - L) / 2; + + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + + if(qR <= m){ //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeL,L,m,qL,qR); + }else if(qL > m ) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeR,m+1,R,qL,qR); + }else { //在两边都有,查询的结果 合并 + return merger.merge(query(treeL,L,m,qL,m), //注意是查询 [qL,m] + query(treeR,m+1,R,m+1,qR)); //查询[m+1,qR] + } + } + + public void update(int index,E e){ + if(index < 0 || index >= data.length )return; + data[index] = e; //首先修改data + update(0,0,data.length-1,index,e); + } + + private void update(int treeIndex,int L,int R,int index,E e){ + if(L == R){ + tree[treeIndex] = e; + return; + } + int m = L + (R - L ) / 2; + int treeL = 2 * treeIndex + 1; + int treeR = 2 * treeIndex + 2; + if(index <= m){ //左边 + update(treeL,L,m,index,e); + }else { + update(treeR,m+1,R,index,e); + } + tree[treeIndex] = merger.merge(tree[treeL],tree[treeR]); //更新完左右子树之后,自己受到影响,重新更新和 + } + + public static void main(String[] args) { + int[] nums = {-2, 0, 3, -5, 2, -1}; + Integer[] arr = new Integer[nums.length]; + for(int i = 0; i < nums.length; i++) arr[i] = nums[i]; + + SegmentTreesegmentTree = new SegmentTree(arr, new Merger() { + @Override + public Integer merge(Integer a, Integer b) { + return a + b; + } + }); + System.out.println(segmentTree.query(0, 2)); + System.out.println(Arrays.toString(segmentTree.tree)); + + segmentTree.update(1,2); + System.out.println(segmentTree.query(0, 2)); + + System.out.println(Arrays.toString(segmentTree.tree)); + + } +} +``` +上面的例子输出: + +![这里写图片描述](images/sg8.png) + +*** +### LeetCode - 307. Range Sum Query - Mutable +#### [题目链接](https://leetcode.com/problems/range-sum-query-mutable/description/) + +> https://leetcode.com/problems/range-sum-query-mutable/description/ + +#### 题目 +![在这里插入图片描述](images/sg9.png) +#### 解析 +知道了上面的操作,这个题目完全就是上面的操作的结合: + +```java +class NumArray { + private interface Merger { + E merge(E a, E b); + } + + private class SegmentTree { + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger) { + this.merger = merger; + + data = (E[]) new Object[arr.length]; + for (int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = (E[]) new Object[4 * arr.length]; //最多需要4 * n + buildSegmentTree(0, 0, arr.length - 1); + } + + public void buildSegmentTree(int treeIndex, int L, int R) { + if (L == R) { + tree[treeIndex] = data[L]; + return; + } + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + int m = L + (R - L) / 2; + + buildSegmentTree(treeL, L, m); + buildSegmentTree(treeR, m + 1, R); + + tree[treeIndex] = merger.merge(tree[treeL], tree[treeR]); + } + + + public E query(int qL, int qR) { + if (qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR) return null; + return query(0, 0, data.length - 1, qL, qR); + } + + private E query(int treeIndex, int L, int R, int qL, int qR) { + if (L == qL && R == qR) { + return tree[treeIndex]; + } + + int m = L + (R - L) / 2; + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + + if (qR <= m) { //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeL, L, m, qL, qR); + } else if (qL > m) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeR, m + 1, R, qL, qR); + } else { //在两边都有,查询的结果 合并 + return merger.merge(query(treeL, L, m, qL, m), //注意是查询 [qL,m] + query(treeR, m + 1, R, m + 1, qR)); //查询[m+1,qR] + } + } + + public void update(int index, E e) { + if (index < 0 || index >= data.length) return; + data[index] = e; //首先修改data + update(0, 0, data.length - 1, index, e); + } + + private void update(int treeIndex, int L, int R, int index, E e) { + if (L == R) { + tree[treeIndex] = e; + return; + } + int m = L + (R - L) / 2; + int treeL = 2 * treeIndex + 1; + int treeR = 2 * treeIndex + 2; + if (index <= m) { //左边 + update(treeL, L, m, index, e); + } else { + update(treeR, m + 1, R, index, e); + } + tree[treeIndex] = merger.merge(tree[treeL], tree[treeR]); //更新完左右子树之后,自己受到影响,重新更新和 + } + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) return; + Integer[] arr = new Integer[nums.length]; + for (int i = 0; i < nums.length; i++) arr[i] = nums[i]; + segTree = new SegmentTree(arr, new Merger() { + @Override + public Integer merge(Integer a, Integer b) { + return a + b; + } + }); + } + + public void update(int i, int val) { + if (segTree == null) return; + segTree.update(i, val); + } + + public int sumRange(int i, int j) { + return segTree.query(i, j); + } + +} +``` + +将上面的代码改的简单一点(不用泛型): + +```java +class NumArray { + + class SegTree { + + int[] tree; + int[] data; + + public SegTree(int[] arr) { + data = new int[arr.length]; + for (int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = new int[4 * arr.length]; //最多需要4 * n + buildTree(0, 0, arr.length - 1); + } + + public void buildTree(int treeIndex, int start, int end) { + if (start == end) { + tree[treeIndex] = data[start]; + return; + } + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + int m = start + (end - start) / 2; + buildTree(treeLid, start, m); + buildTree(treeRid, m + 1, end); + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //区间求和 + } + + public int query(int qL, int qR) { + if (qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR) return -1; + return query(0, 0, data.length - 1, qL, qR); + } + + private int query(int treeIndex, int start, int end, int qL, int qR) { + if (start == qL && end == qR) { + return tree[treeIndex]; + } + int mid = start + (end - start) / 2; + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + + if (qR <= mid) { //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeLid, start, mid, qL, qR); + } else if (qL > mid) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeRid, mid + 1, end, qL, qR); + } else { //在两边都有,查询的结果 合并 + return query(treeLid, start, mid, qL, mid) + //注意是查询 [qL,m] + query(treeRid, mid + 1, end, mid + 1, qR); //查询[m+1,qR] + } + } + + public void update(int index, int val) { + data[index] = val; //首先修改data + update(0, 0, data.length - 1, index, val); + } + + private void update(int treeIndex, int start, int end, int index, int val) { + if (start == end) { + tree[treeIndex] = val; // 最后更新 + return; + } + int m = start + (end - start) / 2; + int treeLid = 2 * treeIndex + 1; + int treeRid = 2 * treeIndex + 2; + if (index <= m) { //左边 + update(treeLid, start, m, index, val); + } else { + update(treeRid, m + 1, end, index, val); + } + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //更新完左右子树之后,自己受到影响,重新更新和 + } + } + + private SegTree segTree; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) return; + segTree = new SegTree(nums); + } + + public void update(int i, int val) { + segTree.update(i, val); + } + + public int sumRange(int i, int j) { + return segTree.query(i, j); + } +} +``` + +再附上后来写的用树的引用的代码: + +```java +class SegNode{ + int start; // 表示的区间的左端点 + int end; // 表示区间的右端点 , 当start == end的时候就只有一个元素 + int sum; + SegNode left; + SegNode right; + + public SegNode(int start, int end, int sum, SegNode left, SegNode right) { + this.start = start; + this.end = end; + this.sum = sum; + this.left = left; + this.right = right; + } +} + +class NumArray { + + SegNode root; + int[] arr; + + private SegNode buildTree(int s, int e){ + if(s == e) + return new SegNode(s, e, arr[s], null, null); + int mid = s + (e - s) / 2; + SegNode L = buildTree(s, mid); + SegNode R = buildTree(mid+1, e); + return new SegNode(s, e, L.sum + R.sum, L, R); + } + + private void update(SegNode node, int i, int val){ + if(node.start == node.end && node.start == i){ + node.sum = val; + return; + } + int mid = node.start + (node.end - node.start) / 2; + if(i <= mid) + update(node.left, i, val); + else + update(node.right, i, val); + node.sum = node.left.sum + node.right.sum; // 记得下面的更新完之后,更新当前的和 + } + + private int query(SegNode node, int i, int j){ + if(node.start == i && node.end == j) + return node.sum; + int mid = node.start + (node.end - node.start) / 2; + if(j <= mid){ // 区间完全在左边 + return query(node.left, i, j); + }else if(i > mid) { // 区间完全在右边 + return query(node.right, i, j); + }else { + return query(node.left, i, mid) + query(node.right, mid+1, j); + } + } + + public NumArray(int[] nums) { + arr = new int[nums.length]; + for(int i = 0; i < nums.length; i++) arr[i] = nums[i]; + if(nums.length != 0) + root = buildTree(0, nums.length-1); + } + + public void update(int i, int val) { + arr[i] = val; + update(root, i, val); + } + + public int sumRange(int i, int j) { + return query(root, i, j); + } +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" "b/Algorithm/DataStructure/DataStructure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" similarity index 93% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" rename to "Algorithm/DataStructure/DataStructure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" index c24f8beb..93c8ce11 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" +++ "b/Algorithm/DataStructure/DataStructure/Trie/LeetCode - 208. Implement Trie (Prefix Tree)\344\273\245\345\217\212\345\256\236\347\216\260\345\255\227\345\205\270\346\240\221(\345\211\215\347\274\200\346\240\221).md" @@ -1,7 +1,7 @@ ## LeetCode - 208. Implement Trie (Prefix Tree)以及实现字典树(前缀树) - 基础知识和结构 - 字符串的插入 - - 统计某个字符串的数量 || 查询是否有某个字符串 + - 统计某个字符串的数量 || 查询是否有某个字符串 - 统计以某个字符串为前缀的字符串数量 || 是否有某个前缀 - 字符串的删除 - 完整测试代码 @@ -10,22 +10,19 @@ - 更多字典树 *** -### 基础知识和结构 -`Map`和`Trie`的差别,关于映射集合等可以看[**这篇博客**](https://blog.csdn.net/zxzxzx0119/article/details/79891408)。 - -![在这里插入图片描述](images/t1.png) - - - **字典树也叫做前缀树,可以存储一组元素(一般是字符串),可以快速的查找某个字符串在树中的个数以及寻找以某个字符串为前缀的字符串的个数,先看下图为一些字符串插入到字典树中的情形。** +### 基础知识和结构 + - **字典树也叫做前缀树,可以存储一组元素(一般是字符串),可以快速的查找某个字符串在树中的个数以及寻找以某个字符串为前缀的字符串的个数,先看下图为一些字符串插入到字典树中的情形**。 字典树的存储结构: - - **首先字母是画在树的边上的;** - - **而结点中的`path`代表的是经过这个结点的字母的次数;** - - **为了方便前缀的计算,而`end`表示的是以这个结点结尾的字符串的数量,方便统计树中某个字符串的数量。** - - **而`next`表示的是多个儿子结点,因为可以有大小写字母,所以我初始化为`52`(如果是小写字母的就为`26`)(也可以使用`Map`来存储)。** + - **首先字母是画在树的边上的**; + - **而结点中的`path`代表的是经过这个结点的字母的次数**; + - **为了方便前缀的计算,而`end`表示的是以这个结点结尾的字符串的数量,方便统计树中某个字符串的数量**。 + - **而`next`表示的是多个儿子结点,因为可以有大小写字母,所以我初始化为`52`(如果是小写字母的就为`26`)(也可以使用`Map`来存储)**。 +代码: ```java public class Trie { @@ -53,15 +50,10 @@ public class Trie { ![这里写图片描述](images/t2.png) - - -再如: **树中有`"abc","ab","ab","abd","bc","bd","cd","cde","ce"`总共`9`个字符串。如下的结构:** +再如: **树中有`"abc","ab","ab","abd","bc","bd","cd","cde","ce"`总共`9`个字符串。如下的结构**: ![这里写图片描述](images/t3.png) - - -*** ### 字符串的插入 - 遍历每个字符,算出每个`字符-'a'`的值,表示的是在结点儿子中的位置; @@ -148,6 +140,8 @@ public class Trie { ![这里写图片描述](https://img-blog.csdn.net/20180908115421994?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +代码: + ```java public void remove(String word){ if(word == null) @@ -313,8 +307,6 @@ public class Trie { #### 题目 ![在这里插入图片描述](images/t4.png) - - #### 解析 运用上面的操作完全能解决这个问题: @@ -395,9 +387,7 @@ class Trie { ``` *** ### 使用Map来保存next -还有一种写法就是 - - - 使用`Map`来保存`next`数组,这样不只可以存`26`或者`52`个孩子: +还有一种写法就是 : 使用`Map`来保存`next`数组,这样不只可以存`26`或者`52`个孩子: 还是上面那个题目代码: ```java @@ -495,10 +485,10 @@ class Trie { *** ### 更多字典树 - - **压缩字典树;** + - **压缩字典树** ![这里写图片描述](images/t5.png) - - **三分搜索树;** + - **三分搜索树** ![这里写图片描述](images/t6.png) \ No newline at end of file diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t1.png" b/Algorithm/DataStructure/DataStructure/Trie/images/t1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t1.png" rename to Algorithm/DataStructure/DataStructure/Trie/images/t1.png diff --git a/Algorithm/DataStructure/DataStructure/Trie/images/t2.png b/Algorithm/DataStructure/DataStructure/Trie/images/t2.png new file mode 100644 index 00000000..f17f0f33 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/Trie/images/t2.png differ diff --git a/Algorithm/DataStructure/DataStructure/Trie/images/t3.png b/Algorithm/DataStructure/DataStructure/Trie/images/t3.png new file mode 100644 index 00000000..e34d5d30 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/Trie/images/t3.png differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t4.png" b/Algorithm/DataStructure/DataStructure/Trie/images/t4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t4.png" rename to Algorithm/DataStructure/DataStructure/Trie/images/t4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t5.png" b/Algorithm/DataStructure/DataStructure/Trie/images/t5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/Trie/images/t5.png" rename to Algorithm/DataStructure/DataStructure/Trie/images/t5.png diff --git a/Algorithm/DataStructure/DataStructure/Trie/images/t6.png b/Algorithm/DataStructure/DataStructure/Trie/images/t6.png new file mode 100644 index 00000000..52b8c2f0 Binary files /dev/null and b/Algorithm/DataStructure/DataStructure/Trie/images/t6.png differ diff --git "a/Algorithm/DataStructure/DataStructure/UnionFind/POJ - 1611. The Suspects\344\273\245\345\217\212\345\271\266\346\237\245\351\233\206\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/DataStructure/UnionFind/POJ - 1611. The Suspects\344\273\245\345\217\212\345\271\266\346\237\245\351\233\206\346\200\273\347\273\223.md" new file mode 100644 index 00000000..fb012b9b --- /dev/null +++ "b/Algorithm/DataStructure/DataStructure/UnionFind/POJ - 1611. The Suspects\344\273\245\345\217\212\345\271\266\346\237\245\351\233\206\346\200\273\347\273\223.md" @@ -0,0 +1,396 @@ +## POJ - 1611. The Suspects以及并查集总结 + + - 题目 + - 基本并查集 + - Size优化并查集 + - Rank优化并查集 + - 路径压缩优化一(最好) + - 路径压缩优化二(递归) + +#### [题目链接](http://poj.org/problem?id=1611) + +> http://poj.org/problem?id=1611 + +#### 题意 + + 就是告诉你`0`号同学被感染了,他还参加了一些社团,给出一些社团以及里面的人,问总共多少人感染。输入给出`n`表示人数(标号为`0~n-1`),`m`表示社团数目,接下来`m`行每行第一个数`k` ,表示该社团有`k`人,然后是`k`个人的编号。要你输出有多少个人感染了病毒。 + +![在这里插入图片描述](images/uf1.png) + +### 解析 +题目本身并不难: + + - 把每个社团加入到各自的集合中,然后不断的合并相同的集合,最后看哪些和`0`号同学在同一个集合中,使用一个变量记录和`0`号同学在同一个集合中的人数即可; + - 这里主要是总结并查集几种优化的方式; + + +### 基本并查集 +基本并查集,记录一个每个结点p的父亲结点是`parent[p]`,然后是一个不断从孩子找父亲的过程: + + - `find()`操作,` while(p != parent[p])p = parent[p]`,一直往上找根的过程; + - `union()`操作,就是找到两个结点的根节点,然后将其中一个结点的根节点挂到另一个结点的根节点即可; + +例如: `union()`操作合并6和3所在的集合: +![这里写图片描述](https://img-blog.csdn.net/20180909134516253?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +public class Main { + + static class UnionSet { + private int[] parent; + + public UnionSet(int size) { + parent = new int[size]; + /** 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合 */ + for (int i = 0; i < size; i++) + parent[i] = i; + } + + public int size() { + return parent.length; + } + + // 查找过程, 查找元素p所对应的集合编号 + private int find(int p) { + if (p < 0 || p >= parent.length) + throw new IllegalArgumentException("p is out of bound."); + while (p != parent[p]) + p = parent[p]; + return p; + } + + public boolean isSameSet(Integer a, Integer b) { + return find(b) == find(a); + } + + // 合并元素p和元素q所属的集合 + public void union(int a, int b) { + int aRoot = find(a); + int bRoot = find(b); + if (aRoot == bRoot) + return; + parent[aRoot] = bRoot; + } + } + + + public static void main(String[] args) { + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + while (cin.hasNext()) { + int n = cin.nextInt(); + int m = cin.nextInt(); + if (n == 0 && m == 0) + break; + UnionSet unionSet = new UnionSet(n); + + for (int i = 0; i < m; i++) { + int k = cin.nextInt(); + int root = cin.nextInt(); + for (int j = 0; j < k - 1; j++) { //k-1个 + int x = cin.nextInt(); + unionSet.union(root, x); //这个集合的根 + } + } + int sum = 1; //0号已经被感染 + for (int i = 1; i < n; i++) { + if (unionSet.isSameSet(0, i)) { + sum++; + } + } + System.out.println(sum); + } + } +} +``` +*** +### `Size`优化并查集 + + - 在`union()`操作中,有一种情况会使得我们的集合变得深度很深,这对查询来说是会降低效率的; + - **例如下面的`union`,合并`3`和`9`所在的集合,如果我们将`3`的根`8`挂在`9`下面,会使得高度变成`4`:(不好的)**; + + +![这里写图片描述](https://img-blog.csdn.net/20180909134535670?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +于是,我们的解决办法是: + + - 每一个集合记录一个`size`,在`union()`操作的时候,我们将`size`小的挂到`size`大的下面,这样会使得深度稍微小一点; + - 操作完之后记得维护被挂的那个集合的`size()`; + +![这里写图片描述](https://img-blog.csdn.net/20180909134546891?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +代码: +```java +static class UnionSet{ + private int[] parent; + private int[] sz; // sz[i]表示以i为根的集合中元素个数 + + public UnionSet(int size) { + parent = new int[size]; + sz = new int[size]; + /** 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合 */ + for( int i = 0 ; i < size ; i ++ ) { + parent[i] = i; + sz[i] = 1; + } + } + + public int size(){ + return parent.length; + } + + private int find(int p){ + if(p < 0 || p >= parent.length) + throw new IllegalArgumentException("p is out of bound."); + while(p != parent[p]) + p = parent[p]; + return p; + } + + public boolean isSameSet(Integer a, Integer b){ + return find(b) == find(a); + } + + public void union(int a,int b){ + int aRoot = find(a); + int bRoot = find(b); + if(aRoot == bRoot) + return; + + /** + * 根据两个元素所在树的元素个数不同判断合并方向 + * 将元素个数少的集合合并到元素个数多的集合上 + */ + if(sz[aRoot] < sz[bRoot]){ + parent[aRoot] = bRoot; + sz[bRoot] += sz[aRoot]; + }else { + parent[bRoot] = aRoot; + sz[aRoot] += sz[bRoot]; + } + } +} +``` +*** +### `Rank`优化并查集 + +* 基于`rank`的优化,其中`rank[i]`表示的是根节点为`i`的树的高度; + +发现问题: + + - 虽然上面的`size`优化已经很不错,但是如果出现下面的情况,例如合并`0`和`3`所在的集合,如下,这样会使得高度变成`4`,而如果反着合并就只需要变成`3`; + - 于是我们需要记录的不是`size`,而是记录一个高度`rank`即可; + +![这里写图片描述](https://img-blog.csdn.net/20180909134703162?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +下面是改造的做法,我们将高度小的挂在高度大的下面,这样使得深度更低; + +![这里写图片描述](https://img-blog.csdn.net/20180909134731165?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: + +```java +static class UnionSet{ + private int[] parent; + private int[] rank; // rank[i]表示以i为根的集合所表示的树的层数 + + public UnionSet(int size) { + parent = new int[size]; + rank = new int[size]; + for( int i = 0 ; i < size ; i ++ ) { + parent[i] = i; + rank[i] = 1; + } + } + + public int size(){ + return parent.length; + } + + private int find(int p){ + if(p < 0 || p >= parent.length) + throw new IllegalArgumentException("p is out of bound."); + while(p != parent[p]) + p = parent[p]; + return p; + } + + public boolean isSameSet(Integer a, Integer b){ + return find(b) == find(a); + } + + public void union(int a,int b){ + int aRoot = find(a); + int bRoot = find(b); + if(aRoot == bRoot) + return; + + /** + *根据两个元素所在树的rank不同判断合并方向 + *将rank低的集合合并到rank高的集合上 + */ + if(rank[aRoot] < rank[bRoot]) + parent[aRoot] = bRoot; // a 挂在 b 下 + else if(rank[bRoot] < rank[aRoot]) + parent[bRoot] = aRoot; + else { //rank[aRoot] == rank[bRoot] + parent[aRoot] = bRoot; // a 挂在 b 下 + rank[bRoot]++; //此时维护rank的值 + } + } +} +``` +*** +### 路径压缩优化一(最好) +并查集另一个优化就是路径压缩: + + - 例如下面的三个集合是等价的,但是查询的效率确实逐渐的增加的,第一个查询效率最低,第三个查询效率最高; + - 我们需要做的就是在find()的时候,沿途将查找的孩子结点改变他们的父亲parent达到路径压缩的目的; + +![这里写图片描述](images/uf2.png) + +首先来看改造成第二个版本: (使用非递归 ) + + - s这个优化就是对于沿途的结点,我们从底到上,依次更改他们的父亲结点为他们的父亲结点的父亲结点`(parent[p] = parent[parent[p]] )`; + - 例如我们查询`find(4)`,第一步,我们先将`parent[4] = 2`,(`2`就是`4`的父亲(`3`)的父亲); + + +![这里写图片描述](https://img-blog.csdn.net/20180909134753889?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +继续往上,把`2`的父亲结点改为`2`的父亲结点的父亲结点,也就是`0`结点,此时我们的树结构变成了下面的样子; + +![这里写图片描述](https://img-blog.csdn.net/20180909134808127?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**于是我们就完成了从第一种情况到第二种情况的优化:** + +![这里写图片描述](images/uf3.png) + +代码如下: 在代码中的更改只有上一个版本中find()函数中增加了一行代码: `parent[p] = parent[parent[p]]`; + +```java +static class UnionSet{ + private int[] parent; + + /** + * 在后续的代码中, 我们并不会维护rank的语意, 也就是rank的值在路径压缩的过程中, 有可能不在是树的层数值 + * 这也是我们的rank不叫height或者depth的原因, 他只是作为比较的一个标准 + */ + private int[] rank; + + public UnionSet(int size) { + parent = new int[size]; + rank = new int[size]; + for( int i = 0 ; i < size ; i ++ ) { + parent[i] = i; + rank[i] = 1; + } + } + + public int size(){ + return parent.length; + } + + private int find(int p){ + if(p < 0 || p >= parent.length) + throw new IllegalArgumentException("p is out of bound."); + while(p != parent[p]) { + /** p这个结点的父亲设置为它父亲的父亲 , 相对于第三个版本只增加了这一行代码 */ + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public boolean isSameSet(Integer a, Integer b){ + return find(b) == find(a); + } + + public void union(int a,int b){ + int aRoot = find(a); + int bRoot = find(b); + if(aRoot == bRoot) + return; + + if(rank[aRoot] < rank[bRoot]) + parent[aRoot] = bRoot; + else if(rank[bRoot] < rank[aRoot]) + parent[bRoot] = aRoot; + else { + parent[aRoot] = bRoot; + rank[bRoot]++; + } + + } +} +``` +*** +### 路径压缩优化二(递归) +继续完成从第一种情况到第三种情况的优化,其实核心代码只有几行: + +```java +private int find(int p){ + if(p != parent[p]) + parent[p] = find(parent[p]); + return parent[p]; +} +``` + +我们宏观的就是将`parent[p]`执行了最终的那个根节点,并返回了; + +![这里写图片描述](images/uf4.png) +![这里写图片描述](images/uf5.png) + +```java +static class UnionSet{ + private int[] parent; + private int[] rank; + + public UnionSet(int size) { + parent = new int[size]; + rank = new int[size]; + for( int i = 0 ; i < size ; i ++ ) { + parent[i] = i; + rank[i] = 1; + } + } + + public int size(){ + return parent.length; + } + + private int find(int p){ + if(p < 0 || p >= parent.length) + throw new IllegalArgumentException("p is out of bound."); + + /**和上一种路径压缩不同的是 这里使用的是递归 会将所有的孩子都直接挂在根下面*/ + if(p != parent[p]) + parent[p] = find(parent[p]); + return parent[p]; + } + + public boolean isSameSet(Integer a, Integer b){ + return find(b) == find(a); + } + + public void union(int a,int b){ + int aRoot = find(a); + int bRoot = find(b); + if(aRoot == bRoot) + return; + + if(rank[aRoot] < rank[bRoot]) + parent[aRoot] = bRoot; + else if(rank[bRoot] < rank[aRoot]) + parent[bRoot] = aRoot; + else { + parent[aRoot] = bRoot; + rank[bRoot]++; + } + + } +} +``` +*** +POJ上测试效率对比,从下到上,从版本一到版本五的时间: + +![这里写图片描述](images/uf6.png) + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf1.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf1.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf2.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf2.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf3.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf3.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf4.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf4.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf5.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf5.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf6.png" b/Algorithm/DataStructure/DataStructure/UnionFind/images/uf6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Data Structure/UnionFind/images/uf6.png" rename to Algorithm/DataStructure/DataStructure/UnionFind/images/uf6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/Hdu - 1863. \347\225\205\351\200\232\345\267\245\347\250\213(\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\250\241\346\235\277\351\242\230)(Kruskal\347\256\227\346\263\225\345\222\214Prim\347\256\227\346\263\225\345\256\236\347\216\260).md" "b/Algorithm/DataStructure/Graph/Hdu - 1863. \347\225\205\351\200\232\345\267\245\347\250\213(\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\250\241\346\235\277\351\242\230)(Kruskal\347\256\227\346\263\225\345\222\214Prim\347\256\227\346\263\225\345\256\236\347\216\260).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/Hdu - 1863. \347\225\205\351\200\232\345\267\245\347\250\213(\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\250\241\346\235\277\351\242\230)(Kruskal\347\256\227\346\263\225\345\222\214Prim\347\256\227\346\263\225\345\256\236\347\216\260).md" rename to "Algorithm/DataStructure/Graph/Hdu - 1863. \347\225\205\351\200\232\345\267\245\347\250\213(\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\250\241\346\235\277\351\242\230)(Kruskal\347\256\227\346\263\225\345\222\214Prim\347\256\227\346\263\225\345\256\236\347\216\260).md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/Hdu - 1874. \347\225\205\351\200\232\345\267\245\347\250\213\347\273\255(\346\234\200\347\237\255\350\267\257dijkstra\346\250\241\346\235\277).md" "b/Algorithm/DataStructure/Graph/Hdu - 1874. \347\225\205\351\200\232\345\267\245\347\250\213\347\273\255(\346\234\200\347\237\255\350\267\257dijkstra\346\250\241\346\235\277).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/Hdu - 1874. \347\225\205\351\200\232\345\267\245\347\250\213\347\273\255(\346\234\200\347\237\255\350\267\257dijkstra\346\250\241\346\235\277).md" rename to "Algorithm/DataStructure/Graph/Hdu - 1874. \347\225\205\351\200\232\345\267\245\347\250\213\347\273\255(\346\234\200\347\237\255\350\267\257dijkstra\346\250\241\346\235\277).md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/Hdu - 4109. Instrction Arrangement\344\273\245\345\217\212\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243.md" "b/Algorithm/DataStructure/Graph/Hdu - 4109. Instrction Arrangement\344\273\245\345\217\212\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/Hdu - 4109. Instrction Arrangement\344\273\245\345\217\212\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243.md" rename to "Algorithm/DataStructure/Graph/Hdu - 4109. Instrction Arrangement\344\273\245\345\217\212\345\205\263\351\224\256\350\267\257\345\276\204\350\257\246\350\247\243.md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/Primary/\345\233\276\347\232\204\345\237\272\346\234\254\347\273\223\346\236\204\344\273\245\345\217\212BFS\345\222\214DFS(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" "b/Algorithm/DataStructure/Graph/Primary/\345\233\276\347\232\204\345\237\272\346\234\254\347\273\223\346\236\204\344\273\245\345\217\212BFS\345\222\214DFS(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/Primary/\345\233\276\347\232\204\345\237\272\346\234\254\347\273\223\346\236\204\344\273\245\345\217\212BFS\345\222\214DFS(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" rename to "Algorithm/DataStructure/Graph/Primary/\345\233\276\347\232\204\345\237\272\346\234\254\347\273\223\346\236\204\344\273\245\345\217\212BFS\345\222\214DFS(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" diff --git a/Algorithm/DataStructure/Graph/Uva - 10305. Ordering Tasks _ LeetCode - 207. Course Schedule.md b/Algorithm/DataStructure/Graph/Uva - 10305. Ordering Tasks _ LeetCode - 207. Course Schedule.md new file mode 100644 index 00000000..436e7a85 --- /dev/null +++ b/Algorithm/DataStructure/Graph/Uva - 10305. Ordering Tasks _ LeetCode - 207. Course Schedule.md @@ -0,0 +1,336 @@ + +# Uva - 10305. Ordering Tasks | LeetCode - 207. Course Schedule + +* [Uva - 10305. Ordering Tasks](#uva---10305-ordering-tasks) +* [LeetCode - 207. Course Schedule](#leetcode---207-course-schedule) + + + +### Uva - 10305. Ordering Tasks +#### [题目链接](https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1246) + +> https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1246 + +#### 题意 + +![img.png](images/to1.png) + +给你`n、m`,`n`代表点的个数,`m`代表边的条数,然后下面给出`m`条边,都是有向的(有向图),要你建出一个图,并且找出一种序列,这种序列即拓扑序列 。 + +> 拓扑排序 是对有向无环图(`DAG`)进行的一种操作,这种操作是将`DAG`中的所有顶点排成一个线性序列,使得图中的任意一对顶点`u,v`满足如下条件:若边`(u,v) ∈ E(G)`,则`u`在最终的线性序列中出现在`v`的前面; +> 拓扑排序的应用常常和`AOV`网相联系,在一个大型的工程中,某些项目不是独立于其他项目的,这意味着这种非独立的项目的完成必须依赖与其它项目的完成而完成,不妨记为`u,v`,则若边`(u,v)∈E(G)`,代表着必须在项目`u`完成后,`v`才能完成。 + + **所以如果存在有向环,则不存在拓扑排序**,反之则存在。 +### 解析 +拓扑排序可以使用`BFS`或者`DFS`来求解。 + +#### 1、BFS + +拓扑排序使用`BFS`解题的过程: + + * 找出入度为`0`的结点并加入队列; + * 在队列中弹出一个结点,并访问,**并把它的相邻结点的入度`-1`,如果减一之后入度为`0`,则也进队列;** + * 直到队列为空,访问完毕 ; + +通过上述过程即可以得到图的拓扑序列。 +```java +import java.io.*; +import java.util.*; + +public class Main { + + static ArrayList G[]; + static int[] in; + static int n , m; + static PrintStream out; + + static void sortedTopology(){ + Queuequeue = new LinkedList<>(); + for(int i = 1; i <= n; i++){ + if(in[i] == 0) + queue.add(i); + } + boolean flag = true; // for output + while(!queue.isEmpty()){ + int cur = queue.poll(); + if(flag){ + out.print(cur); + flag = false; + }else + out.print(" " + cur); + for(int i = 0; i < G[cur].size(); i++){ + int to = G[cur].get(i); + if(--in[to] == 0) + queue.add(to); + } + } + out.println(); + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + out = System.out; + while(cin.hasNext()){ + n = cin.nextInt(); + m = cin.nextInt(); + if(n == 0 && m == 0) + break; + in = new int[n + 1]; + G = new ArrayList[n + 1]; + for(int i = 0; i <= n; i++) + G[i] = new ArrayList<>(); + for(int i = 0; i < m; i++){ + int from = cin.nextInt(); + int to = cin.nextInt(); + G[from].add(to); + in[to]++; + } + sortedTopology(); + } + } +} +``` +#### 2、DFS +使用`DFS`求解拓扑排序的过程: + + +* 这里的`vis`需要表示三个状态,即: `vis[i] = 0`表示还从未访问过、`vis[i] = 1`表示已经访问过、`vis[i] = 2`表示正在访问; +* 只需要通过上述的设置,即可以判断是否能得到拓扑排序(或者说是否有环); +* 然后我们还需要记录拓扑序列,因为递归不断进行到深层,所以我们需要用栈来记录拓扑序列,这里用一个数组从后往前存即可; + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static ArrayList G[]; + static int[] vis; + static int n , m; + static PrintStream out; + static int[] res; + static int p; + + static boolean dfs(int cur){ + vis[cur] = 2; // now is visiting + for(int to : G[cur]){ + if(vis[to] == 2 || (vis[to] == 0 && !dfs(to))) // exist directed cycle + return false; + } + vis[cur] = 1; // now is visited + res[p--] = cur; + return true; + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + out = System.out; + while(cin.hasNext()){ + n = cin.nextInt(); + m = cin.nextInt(); + if(n == 0 && m == 0) + break; + G = new ArrayList[n + 1]; + vis = new int[n + 1]; + for(int i = 0; i <= n; i++) + G[i] = new ArrayList<>(); + for(int i = 0; i < m; i++) { + int from = cin.nextInt(); + int to = cin.nextInt(); + G[from].add(to); + } + p = n - 1; // back to front + res = new int[n + 1]; + boolean ok = true; + for(int i = 1; i <= n; i++){ + if(vis[i] == 0) + dfs(i); + } + for(int i = 0; i < n-1; i++) + out.print(res[i] + " "); + out.println(res[n-1]); + } + } +} +``` + +*** +## LeetCode - 207. Course Schedule + +#### [题目链接](https://leetcode.com/problems/course-schedule/) + +> https://leetcode.com/problems/course-schedule/ + +#### 题目 +![在这里插入图片描述](images/to2.png) + +### 解析 +和上一个题目差不多,这个题目更加简单,只需要你判断能不能得到拓扑序列。 + +#### 1、BFS + +* 总体过程和上一题差不多; +* 这里增加一个`vis`数组,当我们进行完`BFS`过程之后,如果还有点没有被访问到`vis[i] = false`,则说明不能得到拓扑序列(有环); + +图: + +![在这里插入图片描述](images/to3.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + // topological sorting should be used on directed acyclic graph + public boolean canFinish(int numCourses, int[][] prerequisites) { + int n = numCourses; + ArrayList G[] = new ArrayList[n]; + int[] in = new int[n]; + boolean[] vis = new boolean[n]; + for(int i = 0; i < n; i++) + G[i] = new ArrayList<>(); + for(int i = 0; i < prerequisites.length; i++){ + int to = prerequisites[i][0]; + int from = prerequisites[i][1]; + G[from].add(to); + in[to]++; + } + Queuequeue = new LinkedList<>(); + for(int i = 0; i < n; i++) + if(in[i] == 0) + queue.add(i); + while(!queue.isEmpty()){ + Integer cur = queue.poll(); + vis[cur] = true; + for(int i = 0; i < G[cur].size(); i++){ + int to = G[cur].get(i); + if(--in[to] == 0) + queue.add(to); + } + } + for(int i = 0; i < n; i++) + if(!vis[i]) // 有些点没有访问到 + return false; + return true; + } + + public static void main(String[] args){ + PrintStream out = System.out; + int numCourses = 2; + int[][] prerequisites = { + {1, 0}, + {0, 1} + }; + out.println(new Solution(). + canFinish(numCourses, prerequisites) + ); + } +} +``` +#### 2、DFS + +这个和上面的`DFS`也是一样的,区别就是这里不需要记录拓扑序列了。 + +![在这里插入图片描述](images/to4.png) + + + +`DFS`访问顺序以及记录的拓扑序列(`res`)如下: + +可以看到访问的顺序和结果的顺序正好相反: + +|**vertex**|**visiting**( `vis = 2`)|**visited**(`vis = 1`)|`res`| +|-|-|-|-| +|**0**|**{0}**|**{}**|**{}**| +|**1**|**{0, 1}**|**{}**|**{}**| +|**0**|**{0**}|**{1}**|**{1}**| +|**7**|**{0, 7}**|**{1}**|**{1}**| +|**0**|**{0}**|**{1, 7}**|{**7, 1}**| +|**-**|**{}**|**{1, 7, 0}**|**{0, 7, 1}**| +|**2**|**{2}**|**{1, 7, 0}**|**{0, 7, 1}**| +|**-**|**{}**|**{1, 7, 0, 2}**|**{2, 0, 7, 1}**| +|**3**|**{3}**|**{1, 7, 0, 2}**|**{2, 0, 7, 1}**| +|**-**|**{}**|**{1, 7, 0, 2, 3}**|**{3, 2, 0, 7, 1}**| +|**4**|**{4}**|**{1, 7, 0, 2, 3}**|**{3, 2, 0, 7, 1}**| +|**6**|**{4, 6}**|**{1, 7, 0, 2, 3}**|**{3, 2, 0, 7, 1}**| +|**4**|**{4}**|**{1, 7, 0, 2, 3, 6}**|**{6, 3, 2, 0, 7, 1}**| +|**-**|**{}**|**{1, 7, 0, 2, 3, 6, 4}**|**{4, 6, 3, 2, 0, 7, 1}**| +|**5**|**{5}**|**{1, 7, 0, 2, 3, 6, 4}**|**{4, 6, 3, 2, 0, 7, 1}**| +|**-**|**{}**|**{1, 7, 0, 2, 3, 6, 4, 5}**|**{5, 4, 6, 3, 2, 0, 7, 1}**| + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private ArrayList G[]; + private int[] vis; // 0: not visited, 1 : visited, 2 : visiting + + // topological sorting should be used on directed acyclic graph + public boolean canFinish(int numCourses, int[][] prerequisites) { + int n = numCourses; + G = new ArrayList[n]; + vis = new int[n]; + for(int i = 0; i < n; i++) + G[i] = new ArrayList<>(); + for(int i = 0; i < prerequisites.length; i++){ + int to = prerequisites[i][0]; + int from = prerequisites[i][1]; + G[from].add(to); + } + for(int i = 0; i < n; i++) + if(!dfs(i)) + return false; + return true; + } + + private boolean dfs(int cur){ + vis[cur] = 2; // visiting + for(int to : G[cur]){ + if((vis[to] == 2) || (vis[to] == 0 && !dfs(to))) + return false; + } + vis[cur] = 1; // visited + return true; + } + + public static void main(String[] args){ + PrintStream out = System.out; + int numCourses = 2; + int[][] prerequisites = { + {1, 0}, + {0, 1} + }; + out.println(new Solution(). + canFinish(numCourses, prerequisites) + ); + } +} +``` + + +另外`DFS`函数也可以这样写: +```java +private boolean dfs(int cur){ + if(vis[cur] == 1) + return true; + if(vis[cur] == 2) + return false; + vis[cur] = 2; // visiting + for(int to : G[cur]){ + if(!dfs(to)) + return false; + } + vis[cur] = 1; // visited + return true; +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g1.png" b/Algorithm/DataStructure/Graph/images/g1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g1.png" rename to Algorithm/DataStructure/Graph/images/g1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g2.png" b/Algorithm/DataStructure/Graph/images/g2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g2.png" rename to Algorithm/DataStructure/Graph/images/g2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g3.png" b/Algorithm/DataStructure/Graph/images/g3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g3.png" rename to Algorithm/DataStructure/Graph/images/g3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g4.png" b/Algorithm/DataStructure/Graph/images/g4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g4.png" rename to Algorithm/DataStructure/Graph/images/g4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g5.png" b/Algorithm/DataStructure/Graph/images/g5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g5.png" rename to Algorithm/DataStructure/Graph/images/g5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g6.png" b/Algorithm/DataStructure/Graph/images/g6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g6.png" rename to Algorithm/DataStructure/Graph/images/g6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g7.png" b/Algorithm/DataStructure/Graph/images/g7.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g7.png" rename to Algorithm/DataStructure/Graph/images/g7.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g8.png" b/Algorithm/DataStructure/Graph/images/g8.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\345\205\263\351\224\256\350\267\257\345\276\204/images/g8.png" rename to Algorithm/DataStructure/Graph/images/g8.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/images/path1.png" b/Algorithm/DataStructure/Graph/images/path1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/images/path1.png" rename to Algorithm/DataStructure/Graph/images/path1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/images/path2.png" b/Algorithm/DataStructure/Graph/images/path2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\347\237\255\350\267\257/images/path2.png" rename to Algorithm/DataStructure/Graph/images/path2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to1.png" b/Algorithm/DataStructure/Graph/images/to1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to1.png" rename to Algorithm/DataStructure/Graph/images/to1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to2.png" b/Algorithm/DataStructure/Graph/images/to2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to2.png" rename to Algorithm/DataStructure/Graph/images/to2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to3.png" b/Algorithm/DataStructure/Graph/images/to3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to3.png" rename to Algorithm/DataStructure/Graph/images/to3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to4.png" b/Algorithm/DataStructure/Graph/images/to4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\213\223\346\211\221\346\216\222\345\272\217/images/to4.png" rename to Algorithm/DataStructure/Graph/images/to4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree1.png" b/Algorithm/DataStructure/Graph/images/tree1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree1.png" rename to Algorithm/DataStructure/Graph/images/tree1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree2.png" b/Algorithm/DataStructure/Graph/images/tree2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree2.png" rename to Algorithm/DataStructure/Graph/images/tree2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree3.png" b/Algorithm/DataStructure/Graph/images/tree3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221/images/tree3.png" rename to Algorithm/DataStructure/Graph/images/tree3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" similarity index 83% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" rename to "Algorithm/DataStructure/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" index afb4c358..4d1a33f8 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" +++ "b/Algorithm/DataStructure/Math/Hdu - 1431\347\264\240\346\225\260\345\233\236\346\226\207\344\273\245\345\217\212\347\264\240\346\225\260\347\233\270\345\205\263\346\200\273\347\273\223.md" @@ -229,30 +229,26 @@ public class TestPrime { * 先用素数筛法筛出`0~9989899`之间的素数,然后再遍历一遍,判断一下是不是回文,最后判断是不是在那个区间即可; * 注意这里使用的三种筛法,第一种会超时,第二种和第三种可以通过; ```java -import java.io.BufferedInputStream; import java.util.*; +import java.io.*; -/** - * 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1431 - */ -public class Main { //提交时改成main +public class Main { // 经典筛法,超时 - static ArrayList primary(boolean[] is_prime,int MAX){ - + static ArrayList primary(boolean[] is_prime, int MAX) { ArrayList prime = new ArrayList<>(); is_prime[0] = is_prime[1] = false; // 01 不是素数 boolean flag; - for(int i = 2; i <= MAX; i++){ //范围是1000 我筛选 0~2000内的素数 + for (int i = 2; i <= MAX; i++) { //范围是1000 我筛选 0~2000内的素数 flag = true; - for(int j = 2; j * j <= i; j++){// 根号i的时间复杂度 - if(i % j == 0){ + for (int j = 2; j * j <= i; j++) {// 根号i的时间复杂度 + if (i % j == 0) { is_prime[i] = false; flag = false; break; } } - if(flag){ + if (flag) { prime.add(i); is_prime[i] = true; } @@ -262,18 +258,17 @@ public class Main { //提交时改成main //经典的埃式筛法 - static ArrayList sieve(boolean[] is_prime,int MAX){ + static ArrayList sieve(boolean[] is_prime, int MAX) { ArrayList prime = new ArrayList<>(); - Arrays.fill(is_prime,true); + Arrays.fill(is_prime, true); is_prime[0] = is_prime[0] = false; - for(int i = 2; i <= MAX; i++){ - if(is_prime[i]){ + for (int i = 2; i <= MAX; i++) { + if (is_prime[i]) { prime.add(i); - - for(int j = 2 * i; j <= MAX; j+=i) + for (int j = 2 * i; j <= MAX; j += i) is_prime[j] = false; } } @@ -282,20 +277,16 @@ public class Main { //提交时改成main } //优化筛法 - static ArrayList sieve2(boolean[] is_prime,int MAX){ - + static ArrayList sieve2(boolean[] is_prime, int MAX) { ArrayList prime = new ArrayList<>(); - Arrays.fill(is_prime,true); - + Arrays.fill(is_prime, true); is_prime[0] = is_prime[0] = false; - - for(int i = 2; i <= MAX; i++){ - if(is_prime[i]) + for (int i = 2; i <= MAX; i++) { + if (is_prime[i]) prime.add(i); - - for(int j = 0; j < prime.size() && prime.get(j) <= MAX / i; j++) { + for (int j = 0; j < prime.size() && prime.get(j) <= MAX / i; j++) { is_prime[prime.get(j) * i] = false; //筛掉 (小于等于i的素数 * i) 构成的合数 - if(i % prime.get(j) == 0) //如果 i是 < i的素数的倍数 就不用筛了 + if (i % prime.get(j) == 0) //如果 i是 < i的素数的倍数 就不用筛了 break; } } @@ -303,54 +294,44 @@ public class Main { //提交时改成main return prime; } - - static boolean isPalindrome(int num){ + + static boolean isPalindrome(int num) { int oldNum = num; int newNum = 0; //反过来计算 - while(num > 0){ + while (num > 0) { newNum = newNum * 10 + num % 10; num /= 10; } return newNum == oldNum; } - static final int maxn = 9989899; //题目中最大的回文素数 public static void main(String[] args) { - - Scanner cin = new Scanner(new BufferedInputStream(System.in)); + Scanner in = new Scanner(new BufferedInputStream(System.in)); boolean[] is_prime = new boolean[maxn + 1]; + // primary(is_prime,maxn); //超时 // sieve(is_prime,maxn); // ok - sieve2(is_prime,maxn); // ok fast + sieve2(is_prime, maxn); // ok fast ArrayList res = new ArrayList<>(); - - for(int i = 0; i <= maxn; i++ ){ - if(is_prime[i] && isPalindrome(i)) + for (int i = 0; i <= maxn; i++) { + if (is_prime[i] && isPalindrome(i)) res.add(i); } - - while(cin.hasNext()){ - int a = cin.nextInt(); - int b = cin.nextInt(); - + while (in.hasNext()) { + int a = in.nextInt(); + int b = in.nextInt(); int num = 0; - for(int i = 0; i < res.size(); i++){ + for (int i = 0; i < res.size(); i++) { num = res.get(i); - if(num < a) - continue; - else if(num > b) - break; // 直接退出 - else - System.out.println(num); + if(num >= a && num <= b) System.out.println(num); } System.out.println(); } } } - ``` diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m1.png" b/Algorithm/DataStructure/Math/images/m1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m1.png" rename to Algorithm/DataStructure/Math/images/m1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m10.png" b/Algorithm/DataStructure/Math/images/m10.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m10.png" rename to Algorithm/DataStructure/Math/images/m10.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m11.png" b/Algorithm/DataStructure/Math/images/m11.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m11.png" rename to Algorithm/DataStructure/Math/images/m11.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m12.png" b/Algorithm/DataStructure/Math/images/m12.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m12.png" rename to Algorithm/DataStructure/Math/images/m12.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m13.png" b/Algorithm/DataStructure/Math/images/m13.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m13.png" rename to Algorithm/DataStructure/Math/images/m13.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m14.png" b/Algorithm/DataStructure/Math/images/m14.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m14.png" rename to Algorithm/DataStructure/Math/images/m14.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m15.png" b/Algorithm/DataStructure/Math/images/m15.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m15.png" rename to Algorithm/DataStructure/Math/images/m15.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m16.png" b/Algorithm/DataStructure/Math/images/m16.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m16.png" rename to Algorithm/DataStructure/Math/images/m16.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m17.png" b/Algorithm/DataStructure/Math/images/m17.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m17.png" rename to Algorithm/DataStructure/Math/images/m17.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m18.png" b/Algorithm/DataStructure/Math/images/m18.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m18.png" rename to Algorithm/DataStructure/Math/images/m18.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m19.png" b/Algorithm/DataStructure/Math/images/m19.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m19.png" rename to Algorithm/DataStructure/Math/images/m19.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m2.png" b/Algorithm/DataStructure/Math/images/m2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m2.png" rename to Algorithm/DataStructure/Math/images/m2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m20.png" b/Algorithm/DataStructure/Math/images/m20.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m20.png" rename to Algorithm/DataStructure/Math/images/m20.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m21.png" b/Algorithm/DataStructure/Math/images/m21.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m21.png" rename to Algorithm/DataStructure/Math/images/m21.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m22.png" b/Algorithm/DataStructure/Math/images/m22.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m22.png" rename to Algorithm/DataStructure/Math/images/m22.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m23.png" b/Algorithm/DataStructure/Math/images/m23.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m23.png" rename to Algorithm/DataStructure/Math/images/m23.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m24.png" b/Algorithm/DataStructure/Math/images/m24.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m24.png" rename to Algorithm/DataStructure/Math/images/m24.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m25.png" b/Algorithm/DataStructure/Math/images/m25.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m25.png" rename to Algorithm/DataStructure/Math/images/m25.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m26.png" b/Algorithm/DataStructure/Math/images/m26.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m26.png" rename to Algorithm/DataStructure/Math/images/m26.png diff --git a/Algorithm/DataStructure/Math/images/m27_.png b/Algorithm/DataStructure/Math/images/m27_.png new file mode 100644 index 00000000..ce6e652c Binary files /dev/null and b/Algorithm/DataStructure/Math/images/m27_.png differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m3.png" b/Algorithm/DataStructure/Math/images/m3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m3.png" rename to Algorithm/DataStructure/Math/images/m3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m4.png" b/Algorithm/DataStructure/Math/images/m4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m4.png" rename to Algorithm/DataStructure/Math/images/m4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m5.png" b/Algorithm/DataStructure/Math/images/m5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m5.png" rename to Algorithm/DataStructure/Math/images/m5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m6.png" b/Algorithm/DataStructure/Math/images/m6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m6.png" rename to Algorithm/DataStructure/Math/images/m6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m7.png" b/Algorithm/DataStructure/Math/images/m7.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m7.png" rename to Algorithm/DataStructure/Math/images/m7.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m8.png" b/Algorithm/DataStructure/Math/images/m8.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m8.png" rename to Algorithm/DataStructure/Math/images/m8.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m9.png" b/Algorithm/DataStructure/Math/images/m9.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/images/m9.png" rename to Algorithm/DataStructure/Math/images/m9.png diff --git "a/Algorithm/DataStructure/Math/\344\271\230\346\263\225\345\277\253\351\200\237\345\271\202\347\233\270\345\205\263\346\200\273\347\273\223 & LeetCode - 50. Pow.md" "b/Algorithm/DataStructure/Math/\344\271\230\346\263\225\345\277\253\351\200\237\345\271\202\347\233\270\345\205\263\346\200\273\347\273\223 & LeetCode - 50. Pow.md" new file mode 100644 index 00000000..63e07f26 --- /dev/null +++ "b/Algorithm/DataStructure/Math/\344\271\230\346\263\225\345\277\253\351\200\237\345\271\202\347\233\270\345\205\263\346\200\273\347\273\223 & LeetCode - 50. Pow.md" @@ -0,0 +1,341 @@ +# 乘法快速幂相关总结 & LeetCode - 50. Pow(x, n) +* 递归计算 (a n) % mod +* 非递归计算 (a n) % mod +* 计算 ( a * b ) % mod +* 配合 ( a * b ) % mod和乘法快速幂 +* XYNUOJ - 1872. 次方求模题解 +* LeetCode - 50. Pow(x, n)题解 + + +## 1、递归计算 (a n) % mod +递归计算其实是更容易理解的: +* 为了求an,我们先递归去求出an/2,得到结果记录为`halfRes`; +* 然后如果`n`为偶数,很好办,再乘以一个`halfRes`就可以了(再取模一下),也就是可以返回`halfRes*halfRes`; +* 但是如果`n`为奇数的话,就需要再乘以一个`a`,然后再返回; + +![幂分解](images/m14.png) + +![在这里插入图片描述](images/m26.png) + +代码: + +```java +static long pow_mod(long a, long n, long mod) { + if (n == 0) // a^0 = 1 + return 1; + // 先求一半的 --> 你先给我求出 a ^ (n/2) 的结果给我 + long halfRes = pow_mod(a, n >> 1, mod); // n >> 1 --> n/2 + + long res = halfRes * halfRes % mod; + + if ((n & 1) != 0) // odd num + res = res * a % mod; + return res; +} +``` +*** +## 2、非递归计算 (a n) % mod +![在这里插入图片描述](images/m27_.png) +![在这里插入图片描述](images/m13.png)![在这里插入图片描述](images/m18.png) + + +假设一个整数是`10`,如何最快的求解`10`的`75`次方。 + +* ① `75`的二进制形式为`1001011`; +* ②`10`的`75`次方 = **1064 × 108 × 102 × 101**; +* 在这个过程中,我们先求出**101**,然后根据**101**求出**102**,再根据**102**求出**104**。。。。,最后根据**1032**求出**1064**,**即`75`的二进制数形式总共有多少位,我们就使用了多少次乘法;** +* ③ 在步骤②进行的过程中,把应该累乘的值相乘即可,比如**1064**、**108**、**102**、**101**应该累乘,因为`64、8、2、1`对应到`75`的二进制数中,相应位上是`1`;而**1032**、**1016**、104不应该累乘,因为`32、16、 4`对应到`75`的二进制数中,相应位是`0`; + +```java + static long pow_mod2(long a, long n, long mod) { + long res = 1; + while (n > 0) { + if ((n & 1) != 0) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = res * a % mod; + a = a * a % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + return res; + } +``` +使用`a ^ 11`来模拟一下计算过程: + +![在这里插入图片描述](images/m17.png) + + + +## 3、计算 ( a * b ) % mod + + + +![在这里插入图片描述](images/m16.png) + + + +```java + // 计算 (a * b) % mod + static long mul_mod(long a, long b, long mod){ + long res = 0; + + while(b > 0){ + if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = ( res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + + return res; + } +``` +*** +## 4、配合 ( a * b ) % mod和乘法快速幂 +可以使用非递归的乘法快速幂和上面的` (a*b) % mod` 来计算快速幂,差别不大: +```java + // 计算 (a * b) % mod + static long mul_mod(long a,long b,long mod){ + long res = 0; + while(b > 0){ + if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = ( res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + return res; + } + + //非递归 计算 (a^n) % mod 配合 mul + static long pow_mod3(long a,long n,long mod){ + long res = 1; + while(n > 0) { + if( (n&1) != 0 ) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = mul_mod(res,a,mod) % mod; + a = mul_mod(a,a,mod) % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + return res; + } + +``` +*** +## 5、XYNUOJ - 1872. 次方求模题解 +#### [题目链接](http://xyoj.xynu.edu.cn/problem.php?id=1872) + +> http://xyoj.xynu.edu.cn/problem.php?id=1872 + +#### 题目 +![在这里插入图片描述](images/m12.png) + +完全的模板题,三种方法都可以通过: + +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +/** + * 题目链接: http://xyoj.xynu.edu.cn/problem.php?id=1872&csrf=mmofuzhUWGip3c6WlmhiFY6bLxeVHZta + */ +public class Main { //提交时改成Main + + //递归 计算 (a^n) % mod + static long pow_mod(long a,long n,long mod){ + if(n == 0) // a^0 = 1 + return 1; + // 先求一半的 --> 你先给我求出 a ^ (n/2) 的结果给我 + long halfRes = pow_mod(a, n >> 1, mod); // n >> 1 --> n/2 + + long res = halfRes * halfRes % mod; + + if( (n&1) != 0) // odd num + res = res * a % mod; + return res; + } + + //非递归 计算 (a^n) % mod + static long pow_mod2(long a,long n,long mod){ + long res = 1; + + while(n > 0) { + if( (n&1) != 0 ) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = res * a % mod; + a = a * a % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + + return res; + } + + // 计算 (a * b) % mod + static long mul_mod(long a,long b,long mod){ + long res = 0; + + while(b > 0){ + if( (b&1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = ( res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + + return res; + } + + //非递归 计算 (a^n) % mod 配合 mul + static long pow_mod3(long a,long n,long mod){ + long res = 1; + + while(n > 0) { + if( (n&1) != 0 ) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = mul_mod(res,a,mod) % mod; + a = mul_mod(a,a,mod) % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + + return res; + } + + + public static void main(String[] args) { + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + int T = cin.nextInt(); + while(T-- > 0){ + int a = cin.nextInt(); + int n = cin.nextInt(); + int mod = cin.nextInt(); +// System.out.println(pow_mod(a,n,mod)); +// System.out.println(pow_mod2(a,n,mod)); + System.out.println(pow_mod3(a,n,mod)); + } + } +} + +``` +*** +## 6、LeetCode - 50. Pow(x, n)题解 +#### [题目链接](https://leetcode.com/problems/powx-n/description/) + +> https://leetcode.com/problems/powx-n/description/ + +#### 题目 +![ddd](images/m15.png) +#### 解析 +这个题目和普通的求幂不同的是: +* **其中`x`(底数)是`double`类型的,且`n`是范围是`Integer`范围的(可正可负) :** +* **要注意的就是当`n`为负数的时候,我们可以转换成求` 1.0 / pow(x,-n)`;** +* 还一个很重要的地方就是当`n = Integer.MIN_VALUE`的时候要特殊处理,因为整形范围是-231到231-1,所以或者我们使用`long`来存转换的数,或者特殊判断一下; +##### 递归求解: +两种写法意思一样,第二种写法更加简洁: +```java +class Solution { + public double myPow(double x, int n) { + if (n > 0) { + return pow(x, n); + } else { + if (n == Integer.MIN_VALUE) { + return 1.0 / (pow(x, -(Integer.MIN_VALUE + 1)) * x); // MAX_VALUE = -(Integer.MIN_VALUE + 1) + } + return 1.0 / pow(x, -n); + } + } + + public double pow(double x, int n) { + if (n == 0) + return 1; + double half = pow(x, n / 2); + if (n % 2 == 0) + return half * half; + else + return x * half * half; + } +} +``` + +```java +class Solution { + public double myPow(double x, int n) { + if (n == 0) + return 1.0; + if (n < 0) { + if (n == Integer.MIN_VALUE) { + // return 1.0 / (myPow(x,-(Integer.MIN_VALUE+1)) * x); + return 1.0 / (myPow(x, Integer.MAX_VALUE) * x); + } + return 1.0 / myPow(x, -n); + } + double half = myPow(x, n / 2); + if (n % 2 == 0) + return half * half; + else + return x * half * half; + } +} +``` +##### 非递归求解: +三种写法的意思都是一样,只不过处理`Integer.MIN_VALUE`的方式不同而已。 +```java +class Solution { + public double myPow(double x, int n) { + if (n == 0) + return 1.0; + if (n < 0) { + if (n == Integer.MIN_VALUE) { + return 1.0 / (myPow(x, Integer.MAX_VALUE) * x); + } else { + return 1.0 / myPow(x, -n); + } + } + double res = 1.0; + while (n > 0) { + if ((n & 1) != 0) + res *= x; + x = x * x; + n >>= 1; + } + return res; + } +} +``` + +```java +class Solution { + public double myPow(double x, int n) { + if (n == 0) + return 1.0; + double res = 1.0; + if (n < 0) { + x = 1 / x; + n = -(1 + n); // for Integer.MIN_VALUE + res *= x; // x is 1/x because n is -(n+1) so should do this + } + while (n > 0) { + if ((n & 1) != 0) + res *= x; + x = x * x; + n >>= 1; + } + + return res; + } +} +``` + +```java +class Solution { + public double myPow(double x, int n) { + if (n == 0) + return 1.0; + + long abs = Math.abs((long) n); // also for Integer.MIN_VALUE + + double res = 1.0; + while (abs > 0) { + if ((abs & 1) != 0) + res *= x; + x = x * x; + abs >>= 1; + } + if (n < 0) + return 1.0 / res; + return res; + } +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/\347\237\251\351\230\265\347\233\270\345\205\263\346\223\215\344\275\234\345\222\214\347\237\251\351\230\265\345\277\253\351\200\237\345\271\202.md" "b/Algorithm/DataStructure/Math/\347\237\251\351\230\265\347\233\270\345\205\263\346\223\215\344\275\234\345\222\214\347\237\251\351\230\265\345\277\253\351\200\237\345\271\202.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Math/\347\237\251\351\230\265\347\233\270\345\205\263\346\223\215\344\275\234\345\222\214\347\237\251\351\230\265\345\277\253\351\200\237\345\271\202.md" rename to "Algorithm/DataStructure/Math/\347\237\251\351\230\265\347\233\270\345\205\263\346\223\215\344\275\234\345\222\214\347\237\251\351\230\265\345\277\253\351\200\237\345\271\202.md" diff --git "a/Algorithm/DataStructure/String/KMP/Hdu - 1711. Number Sequence\344\273\245\345\217\212KMP\347\256\227\346\263\225\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/String/KMP/Hdu - 1711. Number Sequence\344\273\245\345\217\212KMP\347\256\227\346\263\225\346\200\273\347\273\223.md" new file mode 100644 index 00000000..bc49e7ba --- /dev/null +++ "b/Algorithm/DataStructure/String/KMP/Hdu - 1711. Number Sequence\344\273\245\345\217\212KMP\347\256\227\346\263\225\346\200\273\347\273\223.md" @@ -0,0 +1,163 @@ +# Hdu - 1711. Number Sequence以及KMP算法总结 + + - KMP求解流程 + - KMP next数组求解 + - Hdu - 1711. Number Sequence模板题 +*** +#### [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=1711) + +> http://acm.hdu.edu.cn/showproblem.php?pid=1711 + +#### 题目 + +就是求`str2`有没有在`str1`中出现,如果出现就返回第一个出现的位置。 + +![](images/1711_t.png) + +### KMP求解流程 + +**这里先说明`next`数组的含义。** + + - `next[i]`的含义是在`str[i]`之前的字符串`str[0...i]`中,必须以`str[i-1]`结尾的后缀子串(不能包含`str[0]`)与必须以`str[0]`开头的前缀子串(不能包含`str[i-1]`)的最大匹配长度; + - 这里我们先认为已经求得了匹配串的这个`next`数组(后面讲`next`数组求解)。 + +然后再看KMP求解流程: + + - 当主串的指针`i1`和匹配串的指针`i2`所在位置`str1[i1] == str2[i2]`的时候,`i1++,i2++`; + - 否则,让`i2`直接跳到`i2`位置的next数组的值的位置,也就是`i2 = next[i2]`,这个过程相当于`str2`向右推动,看下图的两个失配的做法; + - 当然,如果`next[i2] = -1`了,说明`i2`到了`0`位置,开头都配不上,那`i1++`吧; + +![在这里插入图片描述](images/1711_s.png) + +按照上面流程写出的代码: +```java +static int kmp(String s,String p){ + if(s == null || p == null || p.length() < 1 || s.length() < p.length() )return -1; + char[] str1 = s.toCharArray(); + char[] str2 = p.toCharArray(); + + int i1 = 0,i2 = 0; //甲乙 + int[] next = getNext(str2); + while(i1 < str1.length && i2 < str2.length){ + if(str1[i1] == str2[i2]){ //能配上,继续 + i1++; i2++; + }else { + if(next[i2] == -1) { //我str2到了第一个你都配不上(第一个位置都配不上),那你str1就下一个吧 + i1++; + }else {//逻辑概念是str2往右边推 + i2 = next[i2]; //来到next数组指示(最长公共前缀后缀) + } + } + } + return i2 == str2.length ? i1 - i2 : -1;//返回匹配的第一个位置 +} +``` + +*** +### KMP next数组求解 + + `next`数组使用的数学归纳法,假设我们已经求得了`next[0]、next[1]、....next[i-1]`,现在要求`next[i]`。 + +这个过程是不断的分开往前面匹配的过程: + +一个`cn`位置表示的是`next[i-1]`的值(`0 ~ i-2`的最大前缀后缀匹配长度的后一个位置) + +看几个例子: + +当前判断`X`,比较前一个位置`a == str[cn]`。相等就是`cn+1`。 + +

+ +连续不匹配的情况: + +

+ +两次能匹配的情况: + +

+ +根据上面的分析写出代码 + +```java +static int[] getNext(char[] str2) { + if(str2.length == 1)return new int[]{-1}; + int[] next = new int[str2.length]; + next[0] = -1; + next[1] = 0; + int cn = 0; + for(int i = 2; i < str2.length;){ + if(str2[i-1] == str2[cn]){ + next[i++] = ++cn; //就是cn+1 + }else { + if(cn > 0) cn = next[cn];//往前面跳 + else next[i++] = 0; + } + } + return next; +} +``` +### Hdu - 1711. Number Sequence模板题 + +这个就是一个KMP最简单的模板题,将字符串改成数组就ok了。 +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +public class Main { + + static int kmp(int[] s,int[] p){ + if(s == null || p == null || s.length < p.length || p.length == 0) + return -1; + int[] next = getNext(p); + int i1 = 0,i2 = 0; + while(i1 < s.length && i2 < p.length){ + if(s[i1] == p[i2]) { + i1++; i2++; + }else { + if(next[i2] == -1){ + i1++; + }else { + i2 = next[i2]; + } + } + } + return i2 == p.length ? i1 - i2 : -1; + + } + + static int[] getNext(int[] arr){ + if(arr.length == 1)return new int[]{-1}; + int[] next = new int[arr.length]; + next[0] = -1; + next[1] = 0; + int cn = 0; + for(int i = 2; i < arr.length; ){ + if( arr[i-1] == arr[cn]){ + next[i++] = ++cn; + }else { + if(cn > 0){ + cn = next[cn]; + }else { + next[i++] = 0; + } + } + } + return next; + } + + public static void main(String[] args) { + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + int k = cin.nextInt(); + while (k-- > 0) { + int n = cin.nextInt(); + int m = cin.nextInt(); + int[] s = new int[n], p = new int[m];//切记ACM题中这个长度是很重要的,不能随便new int[n+1]因为后面用length代替了n + for(int i = 0; i < n; i++)s[i] = cin.nextInt(); + for(int i = 0; i < m; i++)p[i] = cin.nextInt(); + int res = kmp(s,p); + System.out.println(res == -1 ? -1 : res+1); + } + } +} +``` + diff --git a/Algorithm/DataStructure/String/KMP/images/1711_s.png b/Algorithm/DataStructure/String/KMP/images/1711_s.png new file mode 100644 index 00000000..b63dac04 Binary files /dev/null and b/Algorithm/DataStructure/String/KMP/images/1711_s.png differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_s2.png" b/Algorithm/DataStructure/String/KMP/images/1711_s2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_s2.png" rename to Algorithm/DataStructure/String/KMP/images/1711_s2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_s3.png" b/Algorithm/DataStructure/String/KMP/images/1711_s3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_s3.png" rename to Algorithm/DataStructure/String/KMP/images/1711_s3.png diff --git a/Algorithm/DataStructure/String/KMP/images/1711_s_1.png b/Algorithm/DataStructure/String/KMP/images/1711_s_1.png new file mode 100644 index 00000000..d4c93d03 Binary files /dev/null and b/Algorithm/DataStructure/String/KMP/images/1711_s_1.png differ diff --git a/Algorithm/DataStructure/String/KMP/images/1711_s_2.png b/Algorithm/DataStructure/String/KMP/images/1711_s_2.png new file mode 100644 index 00000000..39e4e7d6 Binary files /dev/null and b/Algorithm/DataStructure/String/KMP/images/1711_s_2.png differ diff --git a/Algorithm/DataStructure/String/KMP/images/1711_s_3.png b/Algorithm/DataStructure/String/KMP/images/1711_s_3.png new file mode 100644 index 00000000..58ee581f Binary files /dev/null and b/Algorithm/DataStructure/String/KMP/images/1711_s_3.png differ diff --git a/Algorithm/DataStructure/String/KMP/images/1711_s_copy.png b/Algorithm/DataStructure/String/KMP/images/1711_s_copy.png new file mode 100644 index 00000000..fb1d8c1b Binary files /dev/null and b/Algorithm/DataStructure/String/KMP/images/1711_s_copy.png differ diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_t.png" b/Algorithm/DataStructure/String/KMP/images/1711_t.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/String/KMP/images/1711_t.png" rename to Algorithm/DataStructure/String/KMP/images/1711_t.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all1.png" b/Algorithm/DataStructure/Tree/images/all1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all1.png" rename to Algorithm/DataStructure/Tree/images/all1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all2.png" b/Algorithm/DataStructure/Tree/images/all2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all2.png" rename to Algorithm/DataStructure/Tree/images/all2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all3.png" b/Algorithm/DataStructure/Tree/images/all3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all3.png" rename to Algorithm/DataStructure/Tree/images/all3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all4.png" b/Algorithm/DataStructure/Tree/images/all4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/all4.png" rename to Algorithm/DataStructure/Tree/images/all4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji1.png" b/Algorithm/DataStructure/Tree/images/houji1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji1.png" rename to Algorithm/DataStructure/Tree/images/houji1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji2.png" b/Algorithm/DataStructure/Tree/images/houji2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji2.png" rename to Algorithm/DataStructure/Tree/images/houji2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji3.png" b/Algorithm/DataStructure/Tree/images/houji3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji3.png" rename to Algorithm/DataStructure/Tree/images/houji3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji4.png" b/Algorithm/DataStructure/Tree/images/houji4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/houji4.png" rename to Algorithm/DataStructure/Tree/images/houji4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris1.png" b/Algorithm/DataStructure/Tree/images/morris1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris1.png" rename to Algorithm/DataStructure/Tree/images/morris1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris2.png" b/Algorithm/DataStructure/Tree/images/morris2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris2.png" rename to Algorithm/DataStructure/Tree/images/morris2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris3.png" b/Algorithm/DataStructure/Tree/images/morris3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris3.png" rename to Algorithm/DataStructure/Tree/images/morris3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris4.png" b/Algorithm/DataStructure/Tree/images/morris4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris4.png" rename to Algorithm/DataStructure/Tree/images/morris4.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris5.png" b/Algorithm/DataStructure/Tree/images/morris5.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris5.png" rename to Algorithm/DataStructure/Tree/images/morris5.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris6.png" b/Algorithm/DataStructure/Tree/images/morris6.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/morris6.png" rename to Algorithm/DataStructure/Tree/images/morris6.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/print1.png" b/Algorithm/DataStructure/Tree/images/print1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/print1.png" rename to Algorithm/DataStructure/Tree/images/print1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/print2.png" b/Algorithm/DataStructure/Tree/images/print2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/print2.png" rename to Algorithm/DataStructure/Tree/images/print2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree1.png" b/Algorithm/DataStructure/Tree/images/tree1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree1.png" rename to Algorithm/DataStructure/Tree/images/tree1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree2.png" b/Algorithm/DataStructure/Tree/images/tree2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree2.png" rename to Algorithm/DataStructure/Tree/images/tree2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree3.png" b/Algorithm/DataStructure/Tree/images/tree3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree3.png" rename to Algorithm/DataStructure/Tree/images/tree3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree4.png" b/Algorithm/DataStructure/Tree/images/tree4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/images/tree4.png" rename to Algorithm/DataStructure/Tree/images/tree4.png diff --git "a/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\216\222\345\272\217\346\240\221\347\233\270\345\205\263\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\216\222\345\272\217\346\240\221\347\233\270\345\205\263\346\200\273\347\273\223.md" new file mode 100644 index 00000000..9bfb88bf --- /dev/null +++ "b/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\216\222\345\272\217\346\240\221\347\233\270\345\205\263\346\200\273\347\273\223.md" @@ -0,0 +1,543 @@ +# 二叉排序树相关总结 + + - 二叉排序树相关概念以及性质 + - 在二叉搜索树中添加元素: `add()` + - 查看二叉搜索树中是否存在某个元素 : `contains()` + - 找出二叉搜索树中的最小值和最小值 :`minimum()`、`maximum()` + - 删除二叉搜索树中的最小结点和最大结点: `removeMin()`、`removeMax()` + - 删除二叉搜索树中的任意结点: `remove()` + - 完整测试源代码 +*** +## 一、 二叉排序树相关概念以及性质 + +二叉查找树(`Binary Search Tree`),也称**二叉搜索树**,是指一棵空树或者具有下列性质的二叉树: + + - 任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值; + - 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值 + - 任意节点的左、右子树也分别为二叉查找树; + - 没有键值相等的节点(一般)。 + +*** + 相关的性质: + + + - 二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为**O(log n)**。 + - 二叉排序树要求所有的项都能排序,要写出一个一般的类,我们需要实现`Comparable`接口,使得可以使用**泛型**来比较任意的数据类型。注意这里不是使用`equals`方法来实现,根据两项相等当且仅当`compareTo`方法返回`0`。 +这里给出`BSTreeNode` 的定义结构类 + +```java +public class BSTree> { + + private class Node{ + public E e; + public Node left,right; + + public Node(E e, Node left, Node right) { + this.e = e; + this.left = left; + this.right = right; + } + + public Node(E e) { + this.e = e; + left = null; + right = null; + } + } + + private Node root; + private int size; + + public BSTree() { + root = null; + size = 0; + } + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } +} +``` +*** +## 二、在二叉搜索树中添加元素: `add()` + + - 如果根节点为`null`,就新建一个根节点; + - **如果要插入的元素比当前访问结点`node`小,而且`node.left == null`,就可以插入到`node`的左孩子那里**; + - **如果要插入的元素比当前访问结点`node`大,而且`node.right == null`,就可以插入到`node`的右孩子那里**; + - **如果要插入的元素比当前访问结点`node`小,而且`node.left != null`,就递归的去左子树插入这个值**; + - **如果要插入的元素比当前访问结点`node`大,而且`node.right != null`,就递归的去右子树插入这个值**; + +![这里写图片描述](https://img-blog.csdn.net/20180419223559883?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +```java + public void add(E e){ + if(root == null){ + root = new Node(e); + size++; + }else { + add(root,e); + } + } + + // 向以node为根的二分搜索树中插入元素,递归算法 + private void add(Node node, E e) { + if(node.e.equals(e))return; + + if(e.compareTo(node.e) < 0 && node.left == null){ //比node.e小 + node.left = new Node(e); + size++; + return; + }else if(e.compareTo(node.e) > 0 && node.right == null){ + node.right = new Node(e); + size++; + return; + } + + if(e.compareTo(node.e) < 0)// e < node.e && node.left != null + add(node.left,e); + else //e > node.e && node.right != null + add(node.right,e); + } +``` + +* 更加简单的写法,也就是当我们递归插入的时候,再多递归一层,当递归到空结点的时候,就插入这个结点,注意`add(Node node,E e)`方法的作用: +* 向以`node`为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根,我们在递归的时候要得到返回值和上一层的结点进行连接: + +```java + public void add(E e){ + root = add(root,e); + } + private Node add(Node node, E e) { /**向以node为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根 */ + if(node == null) {//在这里插入 + size++; + return new Node(e); + } + if(e.compareTo(node.e) < 0){ + node.left = add(node.left,e); //node.left是变化的 + }else if(e.compareTo(node.e) > 0){ + node.right = add(node.right,e); + } + return node; + } +``` + +## 三、查看二叉搜索树中是否存在某个元素 : `contains()` +在二叉搜索树`bsTree`中查找`x`的过程为: + + - 若`bsTree`是空树,则返回`false`; + - 若`x`等于`bsTree`的根节点的数据域之值,则查找成功; + - 若`x`小于`bsTree`的根节点的数据域之值,则搜索左子树; + - 否则搜索右子树; + + +![这里写图片描述](https://img-blog.csdn.net/20180419222035657?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +代码如下: + +```java + //query + public boolean contains(E e){ + return contains(root,e); + } + private boolean contains(Node node, E e) { + if (node == null) return false; + if (e.compareTo(node.e) == 0) { + return true; + } else if (e.compareTo(node.e) < 0) { + return contains(node.left, e); + } else { + return contains(node.right, e); + } + } +``` +*** +## 四、找出二叉搜索树中的最小值和最小值 : `minimum()`、`maximum()` +实现很简单就是一直往左边,或者一直忘右边走,递归和非递归实现都很简单; +```java + //min + public E minimum(){ + if(size == 0) + throw new IllegalArgumentException("BST is empty!"); + return minimum(root).e; + } + private Node minimum(Node node) { + if(node.left == null)return node; + return minimum(node.left); + } + + //max + public E maximum(){ + if(size == 0) + throw new IllegalArgumentException("BST is empty!"); + return maximum(root).e; + } + private Node maximum(Node node){ + if(node.right == null)return node; + return maximum(node.right); + } +``` +*** +## 五、删除二叉搜索树中的最小结点和最大结点: `removeMin()`、`removeMax()` +先看删除最小结点: + + - 如果最小结点是叶子结点,很简单直接删除即可; + + - 如果不是叶子结点,就删除这个结点,并且将它的父亲和它的右孩子连接起来即可,看下图: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220234321816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + + 删除之后就是下面的样子: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220234428530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**注意这里的连接就是按照返回值的形式,父亲的左孩子连接上删除之后的树的根即**可。 +```java + // 删除二叉搜索树中最小值所在的结点,返回最小值 + public E removeMin(){ + E ret = minimum(); + root = removeMin(root); //remove the min node and then connect the tree + return ret; + } + private Node removeMin(Node node){/**删除掉以node为根的二叉搜索树中的最小结点,返回删除节点后新的结点的二分搜索树的根 */ + if(node.left == null){ //node is min + Node rightNode = node.right; + node.right = null; //remove from the tree + size--; + return rightNode; /** 返回删除节点后新的结点的二分搜索树的根 然后上一层就可以连接上*/ + } + node.left = removeMin(node.left); /** 去删除左子树, 然后我要连上你删除之后返回的新的根*/ + return node; /** 返回删除之后的根 ,上一层还要连接*/ + } +``` +删除最大结点同理: +```java + public E removeMax(){ + E ret = maximum(); + root = removeMax(root); + return ret; + } + private Node removeMax(Node node){ + if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + return leftNode; + } + node.right = removeMax(node.right); + return node; + } +``` +*** +## 六、删除二叉搜索树中的任意结点: `remove()` +删除结点比较复杂,分为四种情况,前三种情况很简单,最后一种情况稍微复杂一点: + + - 如果是叶子结点,直接删除; + - 如果`node`只有右孩子,和之前`removeMin()`的处理一样,删除之后,返回右子树的根节点; + - 如果`node`只有左孩子,和之前`removeMax()`的处理一样,删除之后,返回左子树的根节点; + - **如果`node`既有左孩子又有右孩子,就找到`node`的右子树最小的结点 (后继结点)`successor`结点,然后用这个结点来顶替`node`,具体过程和实现看图片和代码**; + + + +![这里写图片描述](https://img-blog.csdn.net/20180831153504969?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220234558702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220234632174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220234648517.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +代码: + +```java + //remove any node + public void remove(E e){ + root = remove(root,e); + } + private Node remove(Node node,E e){/**删除以node为根的二分搜索树中值为e的节点,返回删除节点后新的二叉搜索树的根*/ + if(node == null) return null; // 树中没有这个结点 + if(e.compareTo(node.e) < 0){ + node.left = remove(node.left,e); + return node; + }else if(e.compareTo(node.e) > 0){ + node.right = remove(node.right,e); + return node; + }else { //e == node.e ---> should remove + + if(node.left == null){ // only have rightchild or leaf + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + return leftNode; + } + + /** 左右子树都不为空 : 找到比待删除结点大的最小结点(后继), 用这个结点顶替待删除结点的位置*/ + Node successor = minimum(node.right); //找到右子树的最小结点 + + successor.right = removeMin(node.right); //将successor.right设置成 原先结点node的右子树移除 successor之后的树 + successor.left = node.left; // + + //remove node + node.left = node.right = null; + //size--; //这个不需要,因为在removeMin(node.right)中已经减了一次 + return successor; //返回新树的根 + } + } +``` +还有一个要注意的就是删除之后不需要`size--`,因为我们在`node`的右子树中删除那个最小的(`successor`)结点并顶替`node`的时候,在`removeMin()`中已经`size- -`了,所以不要再减去。 +*** +## 七、完整测试代码 + +```java +package DataStructure.Tree.BST; + +public class BSTree> { + + private class Node{ + public E e; + public Node left,right; + + public Node(E e, Node left, Node right) { + this.e = e; + this.left = left; + this.right = right; + } + + public Node(E e) { + this.e = e; + left = null; + right = null; + } + } + + private Node root; + private int size; + + public BSTree() { + root = null; + size = 0; + } + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + + +/** + //easy understand version + public void add(E e){ + if(root == null){ + root = new Node(e); + size++; + }else { + add(root,e); + } + } + + // 向以node为根的二分搜索树中插入元素,递归算法 + private void add(Node node, E e) { + if(node.e.equals(e))return; + + if(e.compareTo(node.e) < 0 && node.left == null){ //比node.e小 + node.left = new Node(e); + size++; + return; + }else if(e.compareTo(node.e) > 0 && node.right == null){ + node.right = new Node(e); + size++; + return; + } + + if(e.compareTo(node.e) < 0)// e < node.e && node.left != null + add(node.left,e); + else //e > node.e && node.right != null + add(node.right,e); + } +*/ + //add + public void add(E e){ + root = add(root,e); + } + private Node add(Node node, E e) { /**向以node为根的二分搜索树中插入元素,返回插入新结点之后二分搜索树的根 */ + if(node == null) {//在这里插入 + size++; + return new Node(e); + } + if(e.compareTo(node.e) < 0){ + node.left = add(node.left,e); //node.left是变化的 + }else if(e.compareTo(node.e) > 0){ + node.right = add(node.right,e); + } + return node; + } + + + //query + public boolean contains(E e){ + return contains(root,e); + } + private boolean contains(Node node, E e) { + if (node == null) return false; + if (e.compareTo(node.e) == 0) { + return true; + } else if (e.compareTo(node.e) < 0) { + return contains(node.left, e); + } else { + return contains(node.right, e); + } + } + + public E minimum(){ + if(size == 0) + throw new IllegalArgumentException("BST is empty!"); + return minimum(root).e; + } + private Node minimum(Node node) { + if(node.left == null)return node; + return minimum(node.left); + } + + public E maximum(){ + if(size == 0) + throw new IllegalArgumentException("BST is empty!"); + return maximum(root).e; + } + private Node maximum(Node node){ + if(node.right == null)return node; + return maximum(node.right); + } + + + // 删除二叉搜索树中最小值所在的结点,返回最小值 + public E removeMin(){ + E ret = minimum(); + root = removeMin(root); //remove the min node and then connect the tree + return ret; + } + private Node removeMin(Node node){/**删除掉以node为根的二叉搜索树中的最小结点,返回删除节点后新的结点的二分搜索树的根 */ + if(node.left == null){ //node is min + Node rightNode = node.right; + node.right = null; //remove from the tree + size--; + return rightNode; /** 返回删除节点后新的结点的二分搜索树的根 然后上一层就可以连接上*/ + } + node.left = removeMin(node.left); /** 去删除左子树, 然后我要连上你删除之后返回的新的根*/ + return node; /** 返回删除之后的根 ,上一层还要连接*/ + } + + public E removeMax(){ + E ret = maximum(); + root = removeMax(root); + return ret; + } + private Node removeMax(Node node){ + if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + return leftNode; + } + node.right = removeMax(node.right); + return node; + } + + + //remove any node + public void remove(E e){ + root = remove(root,e); + } + private Node remove(Node node,E e){/**删除以node为根的二分搜索树中值为e的节点,返回删除节点后新的二叉搜索树的根*/ + if(node == null) return null; // 树中没有这个结点 + if(e.compareTo(node.e) < 0){ + node.left = remove(node.left,e); + return node; + }else if(e.compareTo(node.e) > 0){ + node.right = remove(node.right,e); + return node; + }else { //e == node.e ---> should remove + + if(node.left == null){ // only have rightchild or leaf + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + return leftNode; + } + + /** 左右子树都不为空 : 找到比待删除结点大的最小结点(后继), 用这个结点顶替待删除结点的位置*/ + Node successor = minimum(node.right); //找到右子树的最小结点 + + successor.right = removeMin(node.right); //将successor.right设置成 原先结点node的右子树移除 successor之后的树 + successor.left = node.left; // + + //remove node + node.left = node.right = null; + //size--; //这个不需要,因为在removeMin(node.right)中已经减了一次 + return successor; //返回新树的根 + } + } + + + public void printTree(){ + printTree(root,0,"H",8); + } + public void printTree(Node head,int height,String to,int len){ + if(head == null)return; + printTree(head.right,height + 1,"v",len); + + String val = to + head.e + to; //两边指示的字符 + int lenV = val.length(); + int lenL = (len - lenV)/2; //左边的空格(分一半) + int lenR = len - lenV - lenL; // 右边的空格 + System.out.println( getSpace(len * height) + getSpace(lenL) + val + getSpace(lenR)); + + printTree(head.left,height + 1,"^",len); + } + public static String getSpace(int len){ + StringBuffer str = new StringBuffer(); + for(int i = 0; i < len; i++) str.append(" "); + return str.toString(); + } + + + + public static void main(String[] args) { + Integer[] arr = {21,14,28,11,18,25,32,5,12,15,19,23,27,30,37}; + // Arrays.sort(arr); //退化成链表 + BSTreebsTree = new BSTree<>(); + for(int i = 0; i < arr.length; i++) bsTree.add(arr[i]); + bsTree.printTree(); + System.out.println("--------------华丽分割线-------------"); + + System.out.println(bsTree.contains(27)); + System.out.println(bsTree.contains(99)); + System.out.println(bsTree.minimum()); + System.out.println(bsTree.maximum()); + System.out.println("--------------华丽分割线-------------"); + +// bsTree.removeMin(); +// bsTree.removeMax(); +// bsTree.printTree(); + bsTree.remove(25); + bsTree.printTree(); + } +} +``` +效果: + +![这里写图片描述](https://img-blog.csdn.net/20180831155523882?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - 打印二叉树见[这个博客](https://blog.csdn.net/zxzxzx0119/article/details/81096554)。 + - 注意到我在`main`中测试时注释了`Arrays.sort(arr);`这一行,因为如果是这样插入的话就会退化成链表,这个进阶的问题就是[平衡二叉树](https://blog.csdn.net/zxzxzx0119/article/details/80012812)。 + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\344\271\213Morris\351\201\215\345\216\206.md" "b/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\344\271\213Morris\351\201\215\345\216\206.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\344\271\213Morris\351\201\215\345\216\206.md" rename to "Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\344\271\213Morris\351\201\215\345\216\206.md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\204\347\247\215\346\223\215\344\275\234(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222\351\201\215\345\216\206,\346\240\221\346\267\261\345\272\246,\347\273\223\347\202\271\344\270\252\346\225\260\347\255\211\347\255\211).md" "b/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\204\347\247\215\346\223\215\344\275\234(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222\351\201\215\345\216\206,\346\240\221\346\267\261\345\272\246,\347\273\223\347\202\271\344\270\252\346\225\260\347\255\211\347\255\211).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\204\347\247\215\346\223\215\344\275\234(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222\351\201\215\345\216\206,\346\240\221\346\267\261\345\272\246,\347\273\223\347\202\271\344\270\252\346\225\260\347\255\211\347\255\211).md" rename to "Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\347\232\204\345\220\204\347\247\215\346\223\215\344\275\234(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222\351\201\215\345\216\206,\346\240\221\346\267\261\345\272\246,\347\273\223\347\202\271\344\270\252\346\225\260\347\255\211\347\255\211).md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\347\273\223\347\202\271\351\227\264\347\232\204\346\234\200\345\244\247\350\267\235\347\246\273\351\227\256\351\242\230.md" "b/Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\347\273\223\347\202\271\351\227\264\347\232\204\346\234\200\345\244\247\350\267\235\347\246\273\351\227\256\351\242\230.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\344\272\214\345\217\211\346\240\221\347\273\223\347\202\271\351\227\264\347\232\204\346\234\200\345\244\247\350\267\235\347\246\273\351\227\256\351\242\230.md" rename to "Algorithm/DataStructure/Tree/\344\272\214\345\217\211\346\240\221\347\273\223\347\202\271\351\227\264\347\232\204\346\234\200\345\244\247\350\267\235\347\246\273\351\227\256\351\242\230.md" diff --git "a/Algorithm/DataStructure/Tree/\345\223\210\345\244\253\346\233\274\346\240\221\345\222\214\345\223\210\345\244\253\346\233\274\347\274\226\347\240\201\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Tree/\345\223\210\345\244\253\346\233\274\346\240\221\345\222\214\345\223\210\345\244\253\346\233\274\347\274\226\347\240\201\346\200\273\347\273\223.md" new file mode 100644 index 00000000..01a9c532 --- /dev/null +++ "b/Algorithm/DataStructure/Tree/\345\223\210\345\244\253\346\233\274\346\240\221\345\222\214\345\223\210\345\244\253\346\233\274\347\274\226\347\240\201\346\200\273\347\273\223.md" @@ -0,0 +1,304 @@ +# 目录 + - 哈夫曼树的构造 + - 哈夫曼编码的构造 +*** +## 一、哈夫曼树的构造 +首先给出树的几个概念: + + - 路径:从树种一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称作**路径长度**。 + - 树的路径长度:从树根到**每一个结点**的路径长度之和。 + - 结点的带权路径长度:为从该结点到**树根**的之间的路径长度与结点上权的成绩。 + - 树的带权路径长度:为数中**所有叶子结点**的带权路径长度之和。 + +![这里写图片描述](https://img-blog.csdn.net/20180917162949689?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + +![这里写图片描述](https://img-blog.csdn.net/20180917163338269?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - **构建哈夫曼树的思想其实就是贪心的思想,每次在目前可选的树结点中,选取两个最小的结点,构造成一颗新的二叉树,直到只有一棵树为止**。 + + +## 二、哈夫曼编码的构造 +![这里写图片描述](https://img-blog.csdn.net/20180917164635355?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**那么如何构造出最优的前缀编码,也就是赫夫曼编码呢?** + +![这里写图片描述](https://img-blog.csdn.net/20180917164844271?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**注意这里的huffCodes存储的哈夫曼编码的技巧:** + + - 首先我是从每一个叶子**从下往上**去构造哈夫曼编码; + - 然后我的数组也是从每一行的最后一个开始构造`(idx = n - 1) `; + - 也就是说每一个一维数组内,我是从最后开始存,存的是从叶子到根的,存完之后,加上一个-1,然后我输出的时候,从数组的前面开始,碰到-1,就说明编码的开始(也就是说译码其实是从根到叶子的过程,但是这里是从叶子到根构建的); + +看下图: + +![这里写图片描述](https://img-blog.csdn.net/20180917181139872?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + +代码如下: + +```java +import java.util.LinkedList; +import java.util.Queue; + +public class HuffmanTree { + + private static class Node { + private int val; + private boolean flag; //标记是否已经被选 + private Node parent; // 父亲结点 + private Node left; + private Node right; + + public Node(int val) { + this(val, false , null, null, null); + } + public Node(int val, boolean flag, Node parent, Node left, Node right) { + super(); + this.val = val; + this.flag = flag; + this.parent = parent; + this.left = left; + this.right = right; + } + } + + //每次选出一个最小的结点的函数 + public static Node selectMin(Node[] HN,int end){ //找到最小的一个结点 + Node minNode = HN[end]; + for(int i = 0; i <= end; i++){ + if(HN[i].flag == false && HN[i].val < minNode.val){ + minNode = HN[i]; + } + } + return minNode; + } + + //建立赫夫曼树 + public static Node[] build(int[] w){ + int m = w.length * 2 - 1; + Node[] HN = new Node[m+1]; + + for(int i = 0; i < w.length; i++) //先建立好前面的叶子结点 + HN[i] = new Node(w[i]); + + /** + * 已经有w.length - 1个叶子结点,现在要建立 从w.length ~ (2*w.length-1) 的结点 + */ + for(int i = w.length; i < m; i++){ + Node firstNode = selectMin(HN, i-1); //从前面的结点中找到第一个最小的 + firstNode.flag = true; + + Node secondNode = selectMin(HN, i-1); //从前面的结点中找到第二个最小的 + secondNode.flag = true; + + HN[i] = new Node(firstNode.val + secondNode.val); //新结点的值是两个最小结点的和 + + //设置好孩子和孩子的父亲 + HN[i].left = firstNode; + HN[i].right = secondNode; + firstNode.parent = HN[i]; + secondNode.parent = HN[i]; + } + + return HN; //返回结点数组 + } + + + public static void leverOrder(Node root){ + if(root == null) + return; + Queue que = new LinkedList(); + que.add(root); + while(!que.isEmpty()){ + Node cur = que.poll(); + System.out.print(cur.val + " "); + if(cur.left != null) + que.add(cur.left); + if(cur.right != null) + que.add(cur.right); + } + System.out.println(); + } + + + //哈夫曼编码 : 从叶子到根的一个路径 + public static int[][] huffmanCoding(Node[] HN, int n){ + int[][] huffCodes = new int[n][n]; + for(int i = 0; i < n; i++){ + int idx = n-1; //逆向存储 + + for(Node cur = HN[i], pa = cur.parent ; pa != null; cur = pa,pa = pa.parent){ + if(pa.left == cur) //左孩子为0 + huffCodes[i][idx--] = 0; + else //右孩子为1 + huffCodes[i][idx--] = 1; + } + huffCodes[i][idx--] = -1; //标记一下 从-1往后的是真的编码 前面的不足 + + } + return huffCodes; + } + + + public static void main(String[] args) { + int[] w = {7,5,2,4}; // 给出叶子的权值数组 + Node[] HN = build(w); + System.out.println("-----哈夫曼树-----"); + leverOrder(HN[2 * w.length - 2]); //最后的根 的下标 + + + int[][] huffCodes = huffmanCoding(HN,w.length); //开始从叶子到根构建编码 + System.out.println("----哈夫曼编码-----"); + for(int i = 0; i < w.length; i++){ + + System.out.print(w[i] + ": "); + for(int j = 0; j < huffCodes[i].length; j++){ + if(huffCodes[i][j] == -1){ //从-1标记的开始往后的才是编码 + for(int k = j + 1; k < huffCodes[i].length; k++) + System.out.print(huffCodes[i][k]); + break; + } + } + System.out.println(); + + } + } +} +``` + +运行效果: + +![这里写图片描述](https://img-blog.csdn.net/20180917180525113?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +对照上面的例子,可以看出来是对的。 + +**另一种写法: 使用ArrayList存储哈夫曼编码:** + +```java +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +public class HuffmanTree2 { + + private static class Node { + private int val; + private boolean flag; //标记是否已经被选 + private Node parent; // 父亲结点 + private Node left; + private Node right; + + public Node(int val) { + this(val, false , null, null, null); + } + public Node(int val, boolean flag, Node parent, Node left, Node right) { + super(); + this.val = val; + this.flag = flag; + this.parent = parent; + this.left = left; + this.right = right; + } + } + + //每次选出一个最小的结点的函数 + public static Node selectMin(Node[] HN,int end){ //找到最小的一个结点 + Node minNode = HN[end]; + for(int i = 0; i <= end; i++){ + if(HN[i].flag == false && HN[i].val < minNode.val){ + minNode = HN[i]; + } + } + return minNode; + } + + //建立赫夫曼树 + public static Node[] build(int[] w){ + int m = w.length * 2 - 1; + Node[] HN = new Node[m+1]; + + for(int i = 0; i < w.length; i++) //先建立好前面的叶子结点 + HN[i] = new Node(w[i]); + + /** + * 已经有w.length - 1个叶子结点,现在要建立 从w.length ~ (2*w.length-1) 的结点 + */ + for(int i = w.length; i < m; i++){ + Node firstNode = selectMin(HN, i-1); //从前面的结点中找到第一个最小的 + firstNode.flag = true; + + Node secondNode = selectMin(HN, i-1); //从前面的结点中找到第二个最小的 + secondNode.flag = true; + + HN[i] = new Node(firstNode.val + secondNode.val); //新结点的值是两个最小结点的和 + + //设置好孩子和孩子的父亲 + HN[i].left = firstNode; + HN[i].right = secondNode; + firstNode.parent = HN[i]; + secondNode.parent = HN[i]; + } + + return HN; //返回结点数组 + } + + + public static void leverOrder(Node root){ + if(root == null) + return; + Queue que = new LinkedList(); + que.add(root); + while(!que.isEmpty()){ + Node cur = que.poll(); + System.out.print(cur.val + " "); + if(cur.left != null) + que.add(cur.left); + if(cur.right != null) + que.add(cur.right); + } + System.out.println(); + } + + + //哈夫曼编码 : 从叶子到根的一个路径 + public static ArrayList[] huffmanCoding(Node[] HN, int n){ + ArrayList[] huffCodes = new ArrayList[n]; + for(int i = 0; i < huffCodes.length; i++) + huffCodes[i] = new ArrayList(); + + for(int i = 0; i < n; i++){ + for(Node cur = HN[i], pa = cur.parent ; pa != null; cur = pa,pa = pa.parent){ + if(pa.left == cur) //左孩子为0 + huffCodes[i].add(0); + else //右孩子为1 + huffCodes[i].add(1); + } + } + return huffCodes; + } + + + public static void main(String[] args) { + int[] w = {7,5,2,4}; // 给出叶子的权值数组 + Node[] HN = build(w); + System.out.println("-----哈夫曼树-----"); + leverOrder(HN[2 * w.length - 2]); //最后的根 的下标 + + + ArrayList[] huffCodes = huffmanCoding(HN,w.length); //开始从叶子到根构建编码 + System.out.println("----哈夫曼编码-----"); + for(int i = 0; i < w.length; i++){ + System.out.print(w[i] + ": "); + + for(int j = huffCodes[i].size() - 1; j >= 0; j--) + System.out.print(huffCodes[i].get(j)); + + System.out.println(); + + } + } +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\345\234\250\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221\344\270\255\345\257\273\346\211\276\344\270\200\344\270\252\347\273\223\347\202\271\347\232\204\345\220\216\347\273\247\347\273\223\347\202\271(\345\211\215\351\251\261\347\273\223\347\202\271).md" "b/Algorithm/DataStructure/Tree/\345\234\250\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221\344\270\255\345\257\273\346\211\276\344\270\200\344\270\252\347\273\223\347\202\271\347\232\204\345\220\216\347\273\247\347\273\223\347\202\271(\345\211\215\351\251\261\347\273\223\347\202\271).md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\345\234\250\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221\344\270\255\345\257\273\346\211\276\344\270\200\344\270\252\347\273\223\347\202\271\347\232\204\345\220\216\347\273\247\347\273\223\347\202\271(\345\211\215\351\251\261\347\273\223\347\202\271).md" rename to "Algorithm/DataStructure/Tree/\345\234\250\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221\344\270\255\345\257\273\346\211\276\344\270\200\344\270\252\347\273\223\347\202\271\347\232\204\345\220\216\347\273\247\347\273\223\347\202\271(\345\211\215\351\251\261\347\273\223\347\202\271).md" diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\345\246\202\344\275\225\347\233\264\350\247\202\347\232\204\346\211\223\345\215\260\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/DataStructure/Tree/\345\246\202\344\275\225\347\233\264\350\247\202\347\232\204\346\211\223\345\215\260\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\345\246\202\344\275\225\347\233\264\350\247\202\347\232\204\346\211\223\345\215\260\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221.md" rename to "Algorithm/DataStructure/Tree/\345\246\202\344\275\225\347\233\264\350\247\202\347\232\204\346\211\223\345\215\260\344\270\200\351\242\227\344\272\214\345\217\211\346\240\221.md" diff --git "a/Algorithm/DataStructure/Tree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Tree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" new file mode 100644 index 00000000..4da69a01 --- /dev/null +++ "b/Algorithm/DataStructure/Tree/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221\346\200\273\347\273\223.md" @@ -0,0 +1,1049 @@ +# 平衡二叉树总结 + - 平衡二叉树相关概念以及性质 + - 平衡二叉树的类结构以及简单处理方法 + - 获取树的高度以及计算平衡因子 + - 判断树是不是`BST`和`AVL` + - `LL`型失衡以及右旋转 + - `RR`型失衡以及左旋转 + - `LR`型失衡以及处理方法 + - `RL`型失衡以及处理方法 + - `add()`和`remove()`中失衡处理调整 + - 完整源码测试 + - 使用LeetCode-350. Intersection of Two Arrays II测试代码 +*** +## 一、前言 + + - 在学二叉平衡树之前,可以先学一下[二叉排序树](https://blog.csdn.net/zxzxzx0119/article/details/80012374)。 + - 如果对于四种旋转,实在想不清的可以看一下这个[动态视频](http://www.iqiyi.com/w_19ru8hixdx.html)。 + +## 二、平衡二叉树相关概念以及性质 +相关基本概念: + + - 平衡二叉树是一种**二叉排序树**,:要么是一棵空树,要么左右都是平衡二叉树,且左子树和右子树**深度之绝对值**不超过`1`. 将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子`BF`,那么平衡二叉树上的所有结点的平衡因子只可能是`-1`、`0`和`1`。只要二叉树上有一个结点的平衡因子的绝对值大于`1`,则该二叉树就是不平衡的。 + - 距离插入结点最近的,且平衡因子的绝对值大于`1`的结点为根的子树,称为最小不平衡子树。其中我们可以记住两个名词,** 发现者**:即第一个发现这个结点两边的高度之差大于1(我们也叫做**最小不平衡结点** : **距离插入节点最近**的不平衡节点)。**破坏者**:即插入这个结点之后使得树不平衡的那个点。(看等下旋转的例子就知道)。 +*** +## 三、平衡二叉树的类结构以及简单处理方法 +二叉平衡树和二叉排序树的结构定义差不多,这里增加了一个`hegiht`属性,表示每一个结点为根的子树的高度。 +其中大部分方法已经在[二叉搜索树](https://blog.csdn.net/zxzxzx0119/article/details/80012374)和[集合和映射](https://blog.csdn.net/zxzxzx0119/article/details/79891408#comments)中解释和实现过。 +```java +/** + * 基于BST实现的AVL + */ +public class AVLTree,V> { + + private class Node{ + public K key; + public V value; + public Node left,right; + public int height; //每一个结点都要记录一下高度 --> 为了求出每个结点的平衡因子 + + public Node(K key, V value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; + height = 1; //默认的每个新结点(叶子结点)高度都是1 + } + } + + private Node root; + private int size; + + public AVLTree(){ + root = null; + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + //返回以node为根节点的二分搜索树中,key所在的结点 + public Node getNode(Node node,K key){ + if(node == null) + return null; + if(key.compareTo(node.key) == 0){ + return node; + }else if(key.compareTo(node.key) < 0) { + return getNode(node.left,key); + }else { + return getNode(node.right,key); + } + } + + public boolean contains(K key){ + return getNode(root,key) != null; + } + + public V get(K key){ + Node node = getNode(root,key); + return node == null ? null : node.value; + } + + public void set(K key,V newValue){ + Node node = getNode(root,key); + if(node == null) + throw new IllegalArgumentException(key + " doesn't exist !"); + node.value = newValue; + } + + // 返回以node 为根的二分搜索树 最小值所在的结点 + private Node minumum(Node node){ + if(node.left == null) + return node; + return minumum(node.left); + } + + //移除以node为根的二叉搜索树中最小值的结点,返回删除结点之后新的二叉搜索树的根 + private Node removeMin(Node node){ + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); //递归 and connect 例如下面返回一个rightNode, 我的left就连接上它 + return node; + } +} +``` +## 三、获取树的高度以及计算平衡因子 +这两个方法很简单,平衡因子就是 左子树高度 - 右子树高度。 +```java +  private int getHeight(Node node){ + if(node == null)return 0; //空树的高度是0 + return node.height; + } + //计算平衡因子 : 左子树高度-右子树高度 + private int getBalanceFactor(Node node){ + if(node == null)return 0; + return getHeight(node.left) - getHeight(node.right); + } + +``` +*** +## 四、判断树是不是BST和AVL +这两个方法我在[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81112061)和[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/81108640)中也写过,这里判断`BST`使用的是递归,原理都是利用了中序遍历和`BST`的性质。判断平衡二叉树就更简单了。 +```java + //判断一棵树是不是二叉搜索树 + private boolean isBST(){ + ArrayListkeys = new ArrayList<>(); + inOrder(root,keys); + for(int i = 1; i < keys.size(); i++){ + if(keys.get(i-1).compareTo(keys.get(i)) > 0)return false; + } + return true; + } + //递归中序 + private void inOrder(Node node, ArrayList keys) { + if(node == null )return; + inOrder(node.left,keys); + keys.add(node.key); + inOrder(node.right,keys); + } +``` + +```java + //判断这颗二叉树是不是平衡二叉树 + private boolean isBalanced(){ + return isBalanced(root); + } + private boolean isBalanced(Node node) { // self is balance and child is balance + if(node == null) + return true; // empty tree is a balance tree + if(Math.abs(getBalanceFactor(node)) > 1) + return false; + return isBalanced(node.left) && isBalanced(node.right); + } +``` + +## 五、LL型失衡以及右旋转 +**LL失衡就是破坏者是发现者的左孩子的左孩子** : + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220230845431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**解决的办法:** + + - ① 先将`T3`(`x`的右孩子结点或者子树)扔到一边; + - ② 将以`y`为根的树顺时针旋转下来,接到`x`的右孩子; + - ③ 然后将`T3`放到`y`的左孩子地方; + - ④ 调整完之后记得更新高度; + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220231115403.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**① 先将`T3`(`x`的右孩子结点或者子树)扔到一边:** + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220231417880.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**② 将以`y`为根的树顺时针旋转下来,接到`x`的右孩子**: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220231516180.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +**③ 然后将`T3`放到`y`的左孩子地方:** + + ![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220231608925.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + + + +更新的前后关系如下: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220231854127.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +代码很简单: +```java + /** 对节点y进行向右旋转操作,返回旋转后新的根节点x + 右旋转 y x + / \ / \ + x T4 向右旋转 (y) z y + / \ - - - - - - - -> / \ / \ + z T3 T1 T2 T3 T4 + / \ + T1 T2 + */ + private Node rightRotate(Node y){ // y是失衡点 + Node x = y.left; + Node T3 = x.right; + x.right = y; + y.left = T3; + + //调整之后需要更新height 注意要先更新y的height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } +``` +*** +## 六、`RR`型失衡以及左旋转 +**`RR`型失衡和`LL`型失衡对应,破坏者是发现者的右孩子的右孩子:** + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232006629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232126131.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232232610.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +代码: + +```java + /** 对节点y进行向左旋转操作,返回旋转后新的根节点x + y x + / \ / \ + T4 x 向左旋转 (y) y z + / \ - - - - - - - -> / \ / \ + T3 z T4 T3 T1 T2 + / \ + T1 T2 + */ + private Node leftRotate(Node y){ + Node x = y.right; + Node T3 = x.left; + x.left = y; + y.right = T3; + + //更新height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } +``` + +*** +## 七、`LR`型失衡以及处理方法 +`LR`型就是破坏者是`node`的左孩子的右孩子。处理方法要分为两部: + + - **先对`node`的左孩子进行左旋转**,变成`LL`型; + - **然后对`node`自己进行右旋转即可**。 + + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232401854.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232434781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232604279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +*** +## 八、`RL`型失衡以及处理方法 + 同理,和`LR`型对称的,`RL`型就是破坏者是`node`的右孩子的左孩子。处理方法: + + - 先对`node.right`进行右旋转(`rightRotate`)变成`RR`型; + - 然后对自己进行左旋转即可。 + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232725452.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220232814964.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +*** +## 九、`add()`和`remove()`中失衡处理调整 +我们在插入或者移除元素时有可能会破坏二叉树的平衡性,所以需要调整,调整步骤: + + - **先更新`height`**; + - **计算平衡因子**; + - **判断失衡类型**; + +其中判断失衡类型总共有四种 : + +假设`int balanceFactor = getBalanceFactor(node); `也就是`balanceFactor`是`node`的平衡因子。 + + - **`LL型,balanceFactor > 1 && getBalanceFactor(node.left) >= 0;`** + - **`RR型,balanceFactor < -1 && getBalanceFactor(node.right) <= 0;`** + - **`LR型,balanceFactor > 1 && getBalanceFactor(node.left) < 0;`** + - **`RL型,balanceFactor < -1 && getBalanceFactor(node.right) > 0;`** + +其中`add()`添加元素和二叉搜索树区别不大,就是这里存储的是键值对``映射,而之前的二叉搜索树存储的是集合,相当于JDK中`TreeSet`和`TreeMap`一样的区别。 + +```java + //向AVL树中添加新的元素(key,valu) + public void add(K key,V value){ + root = add(root,key,value); + } + private Node add(Node node,K key,V value){ + if(node == null){ + size++; + return new Node(key, value); //新建默认高度是height = 1 + } + if(key.compareTo(node.key) < 0){ + node.left = add(node.left,key,value); + }else if(key.compareTo(node.key) > 0){ + node.right = add(node.right,key,value); + }else { // update + node.value = value; + } + + /** 1) 更新height */ + node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(node); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0) + return rightRotate(node); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0){ + return leftRotate(node); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) < 0){ + node.left = leftRotate(node.left); //左孩子先进行左旋转 + return rightRotate(node); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) > 0){ + node.right = rightRotate(node.right); //右孩子先进行右旋转 + return leftRotate(node); //自己再进行左旋转 + } + return node; + } +``` +`remove`方法和之前的[二叉搜索树](二叉排序树相关总结.md)中实现的remove方法稍微有点不同: + + - **因为我们在`remove`之后要调整平衡,所以使用了一个`retNode`来存储各种操作之后要返回的`node`,最后统一调整**; + - **在调整之前判断一下`retNode`是否为`null`,如果是`null`的话不能操作,否则抛出空指针异常**; + - **有一个`bug`,如果还是使用之前的`removeMin`来处理在删除一个左右孩子都全的`node`的时候,在`removeMin`中没有进行平衡的调整,所以我们在`node.right`中删除`successor`的时候使用的是递归的删除,也就是调用自己的`remove`方法**。 + - **在`key.compareTo(node.key) == 0`的时候,也就是`else` 中要删除`node`的三个逻辑要互斥的处理,不然会重复,因为不是直接返回,和二叉搜索树的处理不同,要注意**。 + +```java + public V remove(K key){ + Node node = getNode(root,key); + if(node != null){ + root = remove(root,key); + return node.value; + } + return null; + } + private Node remove(Node node, K key){ + if(node == null)return null; + + Node retNode = null; + if(key.compareTo(node.key) < 0){ + node.left = remove(node.left,key); + retNode = node; + }else if(key.compareTo(node.key) > 0){ + node.right = remove(node.right,key); + retNode = node; + }else { + //和BST不同 三种情况是一个互斥的关系 + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + retNode = rightNode; + }else if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + retNode = leftNode; + }else { + Node successor = minumum(node.right); +// successor.right = removeMin(node.right); //这里和BST 不同,这里有可能会破坏平衡,所以不用这个 + successor.right = remove(node.right,successor.key); /**这里自己可以维护平衡*/ + successor.left = node.left; + node.right = node.left = null; + retNode = successor; + } + } + + /**特判 和 BST不同*/ + if(retNode == null)return null; + + /** 1) 更新height */ + retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(retNode); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) + return rightRotate(retNode); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0){ + return leftRotate(retNode); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){ + retNode.left = leftRotate(retNode.left); //左孩子先进行左旋转 + return rightRotate(retNode); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){ + retNode.right = rightRotate(retNode.right); //右孩子先进行右旋转 + return leftRotate(retNode); //自己再进行左旋转 + } + return retNode; + } +``` +*** +## 十、完整源码测试 + +```java +import java.util.ArrayList; +import java.util.Arrays; + +/** + * 基于BST实现的AVL + */ +public class AVLTree,V> { + + private class Node{ + public K key; + public V value; + public Node left,right; + public int height; //每一个结点都要记录一下高度 --> 为了求出每个结点的平衡因子 + + public Node(K key, V value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; + height = 1; //默认的每个新结点(叶子结点)高度都是1 + } + } + + private Node root; + private int size; + + public AVLTree(){ + root = null; + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + private int getHeight(Node node){ + if(node == null)return 0; //空树的高度是0 + return node.height; + } + //计算平衡因子 : 左子树高度-右子树高度 + private int getBalanceFactor(Node node){ + if(node == null)return 0; + return getHeight(node.left) - getHeight(node.right); + } + + //判断一棵树是不是二叉搜索树 + private boolean isBST(){ + ArrayListkeys = new ArrayList<>(); + inOrder(root,keys); + for(int i = 1; i < keys.size(); i++){ + if(keys.get(i-1).compareTo(keys.get(i)) > 0)return false; + } + return true; + } + private void inOrder(Node node, ArrayList keys) { + if(node == null )return; + inOrder(node.left,keys); + keys.add(node.key); + inOrder(node.right,keys); + } + + //判断这颗二叉树是不是平衡二叉树 + private boolean isBalanced(){ + return isBalanced(root); + } + private boolean isBalanced(Node node) { // self is balance and child is balance + if(node == null)return true; // empty tree is a balance tree + if(Math.abs(getBalanceFactor(node)) > 1)return false; + return isBalanced(node.left) && isBalanced(node.right); + } + /** 对节点y进行向右旋转操作,返回旋转后新的根节点x + 右旋转 y x + / \ / \ + x T4 向右旋转 (y) z y + / \ - - - - - - - -> / \ / \ + z T3 T1 T2 T3 T4 + / \ + T1 T2 + */ + private Node rightRotate(Node y){ // y是失衡点 + Node x = y.left; + Node T3 = x.right; + x.right = y; + y.left = T3; + + //调整之后需要更新height 注意要先更新y的height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } + + /** 对节点y进行向左旋转操作,返回旋转后新的根节点x + y x + / \ / \ + T4 x 向左旋转 (y) y z + / \ - - - - - - - -> / \ / \ + T3 z T4 T3 T1 T2 + / \ + T1 T2 + */ + private Node leftRotate(Node y){ + Node x = y.right; + Node T3 = x.left; + x.left = y; + y.right = T3; + + //更新height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } + + //向AVL树中添加新的元素(key,valu) + public void add(K key,V value){ + root = add(root,key,value); + } + private Node add(Node node,K key,V value){ + if(node == null){ + size++; + return new Node(key, value); //新建默认高度是height = 1 + } + if(key.compareTo(node.key) < 0){ + node.left = add(node.left,key,value); + }else if(key.compareTo(node.key) > 0){ + node.right = add(node.right,key,value); + }else { // update + node.value = value; + } + + /** 1) 更新height */ + node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(node); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0) + return rightRotate(node); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0){ + return leftRotate(node); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) < 0){ + node.left = leftRotate(node.left); //左孩子先进行左旋转 + return rightRotate(node); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) > 0){ + node.right = rightRotate(node.right); //右孩子先进行右旋转 + return leftRotate(node); //自己再进行左旋转 + } + return node; + } + + //返回以node为根节点的二分搜索树中,key所在的结点 + public Node getNode(Node node,K key){ + if(node == null) + return null; + if(key.compareTo(node.key) == 0){ + return node; + }else if(key.compareTo(node.key) < 0) { + return getNode(node.left,key); + }else { + return getNode(node.right,key); + } + } + + public boolean contains(K key){ + return getNode(root,key) != null; + } + + public V get(K key){ + Node node = getNode(root,key); + return node == null ? null : node.value; + } + + public void set(K key,V newValue){ + Node node = getNode(root,key); + if(node == null) + throw new IllegalArgumentException(key + " doesn't exist !"); + node.value = newValue; + } + + // 返回以node 为根的二分搜索树 最小值所在的结点 + private Node minumum(Node node){ + if(node.left == null) + return node; + return minumum(node.left); + } + + //移除以node为根的二叉搜索树中最小值的结点,返回删除结点之后新的二叉搜索树的根 + private Node removeMin(Node node){ + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); //递归 and connect 例如下面返回一个rightNode, 我的left就连接上它 + return node; + } + + public V remove(K key){ + Node node = getNode(root,key); + if(node != null){ + root = remove(root,key); + return node.value; + } + return null; + } + private Node remove(Node node, K key){ + if(node == null)return null; + + Node retNode = null; + if(key.compareTo(node.key) < 0){ + node.left = remove(node.left,key); + retNode = node; + }else if(key.compareTo(node.key) > 0){ + node.right = remove(node.right,key); + retNode = node; + }else { + //和BST不同 三种情况是一个互斥的关系 + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + retNode = rightNode; + }else if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + retNode = leftNode; + }else { + Node successor = minumum(node.right); +// successor.right = removeMin(node.right); //这里和BST 不同,这里有可能会破坏平衡,所以不用这个 + successor.right = remove(node.right,successor.key); /**这里自己可以维护平衡*/ + successor.left = node.left; + node.right = node.left = null; + retNode = successor; + } + } + + /**特判 和 BST不同*/ + if(retNode == null)return null; + + /** 1) 更新height */ + retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(retNode); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) + return rightRotate(retNode); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0){ + return leftRotate(retNode); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){ + retNode.left = leftRotate(retNode.left); //左孩子先进行左旋转 + return rightRotate(retNode); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){ + retNode.right = rightRotate(retNode.right); //右孩子先进行右旋转 + return leftRotate(retNode); //自己再进行左旋转 + } + return retNode; + } + + + public void printTree(){ + printTree(root,0,"H",8); + } + public void printTree(Node head,int height,String to,int len){ + if(head == null)return; + printTree(head.right,height + 1,"v",len); + + String val = to + head.key + to; //两边指示的字符 + int lenV = val.length(); + int lenL = (len - lenV)/2; //左边的空格(分一半) + int lenR = len - lenV - lenL; // 右边的空格 + System.out.println( getSpace(len * height) + getSpace(lenL) + val + getSpace(lenR)); + + printTree(head.left,height + 1,"^",len); + } + public static String getSpace(int len){ + StringBuffer str = new StringBuffer(); + for(int i = 0; i < len; i++) str.append(" "); + return str.toString(); + } + + /** + * for test + */ + public static void main(String[] args) { + Integer[] arr = {21,14,28,11,18,25,32,5,12,15,19,23,27,30,37}; + Arrays.sort(arr); + AVLTreeavlTree = new AVLTree<>(); + for(int i = 0; i < arr.length; i++) avlTree.add(arr[i],null); + avlTree.printTree(); + System.out.println(avlTree.isBalanced()); + System.out.println(avlTree.isBST()); + } +} +``` +效果 +![这里写图片描述](https://img-blog.csdn.net/20180901201929616?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + * 打印二叉树见[这个博客](https://blog.csdn.net/zxzxzx0119/article/details/81096554) + * 值得注意的是,我在插入之前对arr进行了排序,如果是BST会退化成链表,如下图所示,但是这里的AVL不会; + +![这里写图片描述](https://img-blog.csdn.net/20180901202220755?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +*** +## 十一、使用LeetCode-350. Intersection of Two Arrays II测试代码 +为了保证AVLTree的正确性,使用LeetCode-350测试: +#### [题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/description/) +#### 题目 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220233034145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +#### 解析 +使用 `HashMap`和`TreeMap`都能很简单的做出来,这里使用自己写的`AVLTree`测试一下: + +```java +import java.util.ArrayList; +class Solution { + + public int[] intersect(int[] nums1, int[] nums2) { + AVLTree map = new AVLTree<>(); + + for(int num : nums1){ + if(!map.contains(num)) { + map.add(num,1); + }else { + map.add(num, map.get(num) + 1); + } + } + + ArrayListlist = new ArrayList<>(); + for(int num : nums2){ + if(map.contains(num)){ + list.add(num); + map.add(num,map.get(num) - 1); + if(map.get(num) == 0) + map.remove(num); + } + } + int[] res = new int[list.size()]; + for(int i = 0; i < list.size(); i++){ + res[i] = list.get(i); + } + return res; + } + + /** + * 基于BST实现的AVL + */ + private class AVLTree,V> { + + private class Node{ + public K key; + public V value; + public Node left,right; + public int height; //每一个结点都要记录一下高度 --> 为了求出每个结点的平衡因子 + + public Node(K key, V value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; + height = 1; //默认的每个新结点(叶子结点)高度都是1 + } + } + + private Node root; + private int size; + + public AVLTree(){ + root = null; + size = 0; + } + + public int size(){ + return size; + } + public boolean isEmpty(){ + return size == 0; + } + private int getHeight(Node node){ + if(node == null)return 0; //空树的高度是0 + return node.height; + } + //计算平衡因子 : 左子树高度-右子树高度 + private int getBalanceFactor(Node node){ + if(node == null)return 0; + return getHeight(node.left) - getHeight(node.right); + } + + //判断一棵树是不是二叉搜索树 + private boolean isBST(){ + ArrayListkeys = new ArrayList<>(); + inOrder(root,keys); + for(int i = 1; i < keys.size(); i++){ + if(keys.get(i-1).compareTo(keys.get(i)) > 0)return false; + } + return true; + } + private void inOrder(Node node, ArrayList keys) { + if(node == null )return; + inOrder(node.left,keys); + keys.add(node.key); + inOrder(node.right,keys); + } + + //判断这颗二叉树是不是平衡二叉树 + private boolean isBalanced(){ + return isBalanced(root); + } + private boolean isBalanced(Node node) { // self is balance and child is balance + if(node == null)return true; // empty tree is a balance tree + if(Math.abs(getBalanceFactor(node)) > 1)return false; + return isBalanced(node.left) && isBalanced(node.right); + } + /** 对节点y进行向右旋转操作,返回旋转后新的根节点x + 右旋转 y x + / \ / \ + x T4 向右旋转 (y) z y + / \ - - - - - - - -> / \ / \ + z T3 T1 T2 T3 T4 + / \ + T1 T2 + */ + private Node rightRotate(Node y){ // y是失衡点 + Node x = y.left; + Node T3 = x.right; + x.right = y; + y.left = T3; + + //调整之后需要更新height 注意要先更新y的height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } + + /** 对节点y进行向左旋转操作,返回旋转后新的根节点x + y x + / \ / \ + T4 x 向左旋转 (y) y z + / \ - - - - - - - -> / \ / \ + T3 z T4 T3 T1 T2 + / \ + T1 T2 + */ + private Node leftRotate(Node y){ + Node x = y.right; + Node T3 = x.left; + x.left = y; + y.right = T3; + + //更新height + y.height = 1 + Math.max(getHeight(y.left),getHeight(y.right)); + x.height = 1 + Math.max(getHeight(x.left),getHeight(x.right)); + + return x; + } + + //向AVL树中添加新的元素(key,valu) + public void add(K key,V value){ + root = add(root,key,value); + } + private Node add(Node node,K key,V value){ + if(node == null){ + size++; + return new Node(key, value); //新建默认高度是height = 1 + } + if(key.compareTo(node.key) < 0){ + node.left = add(node.left,key,value); + }else if(key.compareTo(node.key) > 0){ + node.right = add(node.right,key,value); + }else { // update + node.value = value; + } + + /** 1) 更新height */ + node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(node); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) >= 0) + return rightRotate(node); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) <= 0){ + return leftRotate(node); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(node.left) < 0){ + node.left = leftRotate(node.left); //左孩子先进行左旋转 + return rightRotate(node); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(node.right) > 0){ + node.right = rightRotate(node.right); //右孩子先进行右旋转 + return leftRotate(node); //自己再进行左旋转 + } + return node; + } + + //返回以node为根节点的二分搜索树中,key所在的结点 + public Node getNode(Node node,K key){ + if(node == null) + return null; + if(key.compareTo(node.key) == 0){ + return node; + }else if(key.compareTo(node.key) < 0) { + return getNode(node.left,key); + }else { + return getNode(node.right,key); + } + } + + public boolean contains(K key){ + return getNode(root,key) != null; + } + + public V get(K key){ + Node node = getNode(root,key); + return node == null ? null : node.value; + } + + public void set(K key,V newValue){ + Node node = getNode(root,key); + if(node == null) + throw new IllegalArgumentException(key + " doesn't exist !"); + node.value = newValue; + } + + // 返回以node 为根的二分搜索树 最小值所在的结点 + private Node minumum(Node node){ + if(node.left == null) + return node; + return minumum(node.left); + } + + //移除以node为根的二叉搜索树中最小值的结点,返回删除结点之后新的二叉搜索树的根 + private Node removeMin(Node node){ + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); //递归 and connect 例如下面返回一个rightNode, 我的left就连接上它 + return node; + } + + public V remove(K key){ + Node node = getNode(root,key); + if(node != null){ + root = remove(root,key); + return node.value; + } + return null; + } + private Node remove(Node node, K key){ + if(node == null)return null; + + Node retNode = null; + if(key.compareTo(node.key) < 0){ + node.left = remove(node.left,key); + retNode = node; + }else if(key.compareTo(node.key) > 0){ + node.right = remove(node.right,key); + retNode = node; + }else { + //和BST不同 三种情况是一个互斥的关系 + if(node.left == null){ + Node rightNode = node.right; + node.right = null; + size--; + retNode = rightNode; + }else if(node.right == null){ + Node leftNode = node.left; + node.left = null; + size--; + retNode = leftNode; + }else { + Node successor = minumum(node.right); + // successor.right = removeMin(node.right); //这里和BST 不同,这里有可能会破坏平衡,所以不用这个 + successor.right = remove(node.right,successor.key); /**这里自己可以维护平衡*/ + successor.left = node.left; + node.right = node.left = null; + retNode = successor; + } + } + + /**特判 和 BST不同*/ + if(retNode == null)return null; + + /** 1) 更新height */ + retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); + /** 2)计算平衡因子 */ + int balanceFactor = getBalanceFactor(retNode); + /** 3)调整 : 分为四种情况下的调整*/ + + /** LL型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) + return rightRotate(retNode); + /** RR型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0){ + return leftRotate(retNode); + } + /** LR型 */ + if(balanceFactor > 1 && getBalanceFactor(retNode.left) < 0){ + retNode.left = leftRotate(retNode.left); //左孩子先进行左旋转 + return rightRotate(retNode); //自己再进行右旋转 + } + /** RL型 */ + if(balanceFactor < -1 && getBalanceFactor(retNode.right) > 0){ + retNode.right = rightRotate(retNode.right); //右孩子先进行右旋转 + return leftRotate(retNode); //自己再进行左旋转 + } + return retNode; + } + } +} +``` diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\346\211\276\345\210\260\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\346\220\234\347\264\242\344\272\214\345\217\211\345\255\220\346\240\221.md" "b/Algorithm/DataStructure/Tree/\346\211\276\345\210\260\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\346\220\234\347\264\242\344\272\214\345\217\211\345\255\220\346\240\221.md" similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/Tree/\346\211\276\345\210\260\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\346\220\234\347\264\242\344\272\214\345\217\211\345\255\220\346\240\221.md" rename to "Algorithm/DataStructure/Tree/\346\211\276\345\210\260\344\272\214\345\217\211\346\240\221\344\270\255\347\232\204\346\234\200\345\244\247\346\220\234\347\264\242\344\272\214\345\217\211\345\255\220\346\240\221.md" diff --git "a/Algorithm/DataStructure/Tree/\347\272\242\351\273\221\346\240\221\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Tree/\347\272\242\351\273\221\346\240\221\346\200\273\347\273\223.md" new file mode 100644 index 00000000..40938f17 --- /dev/null +++ "b/Algorithm/DataStructure/Tree/\347\272\242\351\273\221\346\240\221\346\200\273\347\273\223.md" @@ -0,0 +1,746 @@ +# 红黑树总结 + + - `2-3`树介绍 + - `2-3`树插入元素 + - 红黑树由来(和`2-3`树等价) + - 红黑树性质的理解 + - 红黑树中添加元素 + - 添加中维护红黑树的代码 + - 使用LeetCode804. Unique Morse Code Words测试红黑树 +*** +## 一、前言 +红黑树之前,可以先看一下[二叉搜索树](二叉排序树相关总结.md)和[平衡二叉树](二叉平衡树总结.md)。 +*** +## 二、`2-3`树介绍 + + - `2`节点: 容纳`1`个元素,有`2`个孩子; + - `3`节点: 容纳`2`个元素,有`3`个孩子; + - `4`节点: 容纳`3`个元素,有`4`个孩子; + - ......... + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220235119167.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220235158838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +绝对平衡表示的是任意子树左右高度之差为`0`。 + +## 三、`2-3`树插入元素 +一个原则: 添加节点将永远不会添加到一个空的位置(和二叉搜索树不同)、只会和最后找到的叶子节点做融合。 +细分为四种情况: + + - 向`2`节点(`2`个孩子的节点)中添加`1`个元素,融合成一个`3`节点; + - 向`3`节点(`3`个孩子的节点)中添加`1`个元素,先融合成为一个`4`节点、如果这个`4`节点是根节点,直接**分裂成含有三个`2`节点的一棵树;** + - 如果这个`4`节点不是根节点,就**不能分裂成含有三个`2`节点的一棵树;因为这样会破坏`2-3`树的绝对平衡性**。正确的做法是向上和父亲节点做新的融合,这里又分为两种情况: + - 第一种情况: 父亲为`2`节点,直接融合到父亲,变成`3`节点; + - 第二种情况: 父亲为`3`节点,先融合到父亲,变成`4`节点,然后父亲分散,全部变成2节点; + +下图的层次关系可能会比较清楚: + +![这里写图片描述](https://img-blog.csdn.net/20180902080859140?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + 看具体的细分的四种情况: +1)、最简单的情况: +![这里写图片描述](https://img-blog.csdn.net/20180902001550797?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +2)、插入3节点且是根: + +![这里写图片描述](https://img-blog.csdn.net/20180902001558770?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +3)、插入3节点且不是根且父亲是2节点: + + +![这里写图片描述](https://img-blog.csdn.net/20180902001607241?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![这里写图片描述](https://img-blog.csdn.net/20180902002008524?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +4)、插入3节点且不是根且父亲是3节点: + +![这里写图片描述](https://img-blog.csdn.net/20180902001614410?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![这里写图片描述](https://img-blog.csdn.net/20180902002114154?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![这里写图片描述](https://img-blog.csdn.net/20180902002132695?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 四、红黑树由来(和`2-3`树等价) +`<<算法4>>`中提到`2-3`树和红黑树是等价的,则其中`2`节点和`3`节点对应红黑树的关系有两种: +**1)、第一种 : 2节点转换成红黑树节点** : + +![这里写图片描述](https://img-blog.csdn.net/20180902081249596?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) +**2)、第二种 : 3节点转换成红黑树节点** + + - **由于一般树结构一个节点只表示一个值,所以要将3节点用一种特殊的方式转换,即转换成红黑树节点;** +![这里写图片描述](https://img-blog.csdn.net/20180902082153310?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + - **上面的摆放只是直观的摆放,我们需要将b节点作为c节点的左孩子,但是又需要去标识b和c的对应关系(是一个3节点而且b < c) 所以我们将b、c中间的线绘制成红色;并且还原成二分搜索树的样子:** +![这里写图片描述](https://img-blog.csdn.net/20180902083002561?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + - **但是在二叉树的实现中没有使用相应的类来表示那条边是红色的,所以我们使用节点来表示这个特性,由于每一个节点只有一个父亲,也就是每一个节点和它父亲相连的边只有一条边,所以我们可以把这条边(红色)的信息存放在结点上,也就是把b描绘成红色,所以这就是红色节点的由来**:这个红色的节点b表示的是这个节点之前在2-3树中是在一个3节点中,而且和它的父亲是存放在一起的(并列),而且它的值比它父亲小。 +![这里写图片描述](https://img-blog.csdn.net/20180902083615414?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**然后再看一个2-3树和红黑树转换的例子**: + +![这里写图片描述](https://img-blog.csdn.net/20180902083648615?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +更加直观的看: + +![这里写图片描述](https://img-blog.csdn.net/20180902083701332?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** + +## 五、红黑树性质的理解 + +![这里写图片描述](https://img-blog.csdn.net/2018090209101486?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - 第一条: 这个很简单,是红黑树的定义; + - **第二条: 由于在2-3树中根节点或者是2节点,或者是3节点,这两种节点转换成红黑树节点之后根节点都是黑色的**; + ![这里写图片描述](https://img-blog.csdn.net/20180902091313806?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + - **第三条: 与其说是性质,不如说是定义,不过要注意这里的叶子节点指的是最后的空节点,都指定为黑色的节点,如果是空树,则同时满足2、3条性质**; + - **第四条: 也是和2-3树密切关系,一个红色节点,在2-3树中是3节点的左边节点,它的左右孩子在2-3树中都要么是2节点,要么是3节点,但是他们的根都是黑色的**; +![这里写图片描述](https://img-blog.csdn.net/20180902092216863?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + - 第五条: 是最重要的一条性质,最主要的原因是**2-3树是绝对平衡的**,如果途中经过的是一个3节点也不要紧,反正都是有一个黑色节点,所以经过的黑色节点的数量是一样多的;所以红黑树是保持"黑平衡"的二叉树,本质就是2-3树是绝对平衡的; + + ![这里写图片描述](https://img-blog.csdn.net/20180902093426896?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +*** +## 六、红黑树中添加元素 +一个原则: 新插入的结点永远是红色 + +![这里写图片描述](https://img-blog.csdn.net/20180902104455256?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**有几个子过程步骤以及相关需要维护的地方** + + - 第一个:维护根节点为黑色,因为我们第二条性质是根节点永远为黑色,所以我们在插入完节点之后,要维护根节点为黑色; + 1)、插入: ![这里写图片描述](https://img-blog.csdn.net/20180902105149505?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + 2)、更一般的情况对于根的维护: + ![这里写图片描述](https://img-blog.csdn.net/20180902105623240?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - 第二个:也是插入的第二种情况,这个很简单,也不需要维护,直接插入就好 : + ![这里写图片描述](https://img-blog.csdn.net/20180902110556933?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - 第三个:也是插入的第三种情况,也就是待插入的红色结点比根节点大(插在右边),这个需要维护,和AVL中一样,类似RR型,需要进行左旋转: + + ![这里写图片描述](https://img-blog.csdn.net/20180902111938546?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + 过程如下: + + ![这里写图片描述](https://img-blog.csdn.net/20180902111820822?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +但是这样还不够,我们还需要维护颜色: 此时`x`成为了新的根节点,所以`x.color = node.color`(`x`颜色变成之前根节点的颜色); 而`node.color = RED`,因为形成的还是`3`节点:(注意之前的node.color有可能是红色,但是后续还要处理 ) + +![这里写图片描述](https://img-blog.csdn.net/20180902113027326?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: +```java + /**左旋转 + + node x + / \ --> / \ + T1 x node T3 + / \ / \ + T2 T3 T1 T2 + */ + private Node leftRotate(Node node){ + Node x = node.right; + Node T2 = x.left; + + x.left = node; + node.right = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } +``` + + - 第四个: 也是插入的第四种情况,也是另一种维护情况:>颜色反转, 在根节点的右侧插入: 其实相当于在2-3树中的`3`节点中插入元素,然后分散: + ![这里写图片描述](https://img-blog.csdn.net/20180902134534127?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + 在2-3树中散开之后都成为了2节点,所以需要**变成全都是黑色节点:** + + ![这里写图片描述](https://img-blog.csdn.net/20180902134653624?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + 最后融合,根节点又需要变回红色,因为这个根节点要向上去和它的父亲节点去融合: + + ![这里写图片描述](https://img-blog.csdn.net/20180902134708100?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + **整体如下图 (需要经过两次换色)** + + ![这里写图片描述](https://img-blog.csdn.net/20180902153339421?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: +```java + //三个节点的颜色反转 根->RED 两个孩子->BLACK + private void flipColors(Node node){ + node.color = RED; + node.left.color = BLACK; + node.right.color = BLACK; + } +``` + - 第五个:也是插入的第五种情况,在根节点的左边的左边,也就是向`2-3`树中的`3`结点的左边添加元素: + +![这里写图片描述](https://img-blog.csdn.net/20180902154544786?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + + +等价于在2-3树中这样插入: +![这里写图片描述](https://img-blog.csdn.net/2018090215504771?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + + +然后需要进行右旋转: +![这里写图片描述](https://img-blog.csdn.net/20180902160124467?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + + +```java + /**右旋转 + node x + / \ / \ + x T1 --> y node + / \ / \ / \ + y T2 T4 T3 T2 T1 + / \ + T4 T3 + */ + private Node rightRotate(Node node){ + Node x = node.left; + Node T2 = x.right; + x.right = node; + node.left = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } +``` + +然后又需要维护颜色(前两部同左旋转,最后一步是颜色翻转(`filpColors`)) + +![这里写图片描述](https://img-blog.csdn.net/20180902170933671?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + - 第六个: 在根节点的左孩子的右边添加,类似2-3树中添加在3节点的中间,这个只需要运用前面的那些步骤综合即可完成: + + ![这里写图片描述](https://img-blog.csdn.net/20180902172237160?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + a)、先对`37`进行左旋转 : + + ![这里写图片描述](https://img-blog.csdn.net/2018090217255165?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + b)、对上面的结果进行右旋转,并且调整颜色 : + + ![这里写图片描述](https://img-blog.csdn.net/2018090217283619?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + c)、最后进行之前的颜色翻转(`filpColors`): + + ![这里写图片描述](https://img-blog.csdn.net/20180902172940741?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + - 总结 : 所以我们发现,其实就是三种基本操作: 左旋转,右旋转,颜色翻转的结合 + +![这里写图片描述](https://img-blog.csdn.net/20180902173433625?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + + +所以这是一个**维护链**,放在`add`中代码如下: + +```java + /** 向红黑树中添加新的元素(key, value) */ + public void add(K key, V value) {//相当于JDK的put操作 + root = add(root, key, value); + root.color = BLACK; /**子过程一: 保持最终根节点为黑色 --> 第二条性质 */ + } + + // 向以node为根的红黑树树中插入元素(key, value),递归算法,返回插入新节点后红黑树树的根 + private Node add(Node node, K key, V value) { + + if (node == null) { + size++; + return new Node(key, value); + } + + if (key.compareTo(node.key) < 0) + node.left = add(node.left, key, value); + else if (key.compareTo(node.key) > 0) + node.right = add(node.right, key, value); + else // key.compareTo(node.key) == 0 + node.value = value; + + /**维护链: 注意三个if不是互斥的 就是一个维护链的关系 */ + if(isRed(node.right) && !isRed(node.left)){ //右红,左黑 -> 左旋 + node = leftRotate(node); + } + if(isRed(node.left) && isRed(node.left.left)){ //左红 左左红 -> 右旋 + node = rightRotate(node); + } + if(isRed(node.left) && isRed(node.right)){//左红右红自己黑 -->颜色翻转 + flipColors(node); + } + return node; + } +``` +重点在这段代码,就是三种基本的维护组成的维护链: + +```java + /**维护链: 注意三个if不是互斥的 就是一个维护链的关系 */ + if(isRed(node.right) && !isRed(node.left)){ //右红,左黑 -> 左旋 + node = leftRotate(node); + } + if(isRed(node.left) && isRed(node.left.left)){ //左红 左左红 -> 右旋 + node = rightRotate(node); + } + if(isRed(node.left) && isRed(node.right)){//左红右红自己黑 -->颜色翻转 + flipColors(node); + } +``` + +*** +## 七、添加中维护红黑树的代码 +这里完成了左倾红黑树的`add`操作 : ,没有实现`remove()`,完整测试源码如下: + +```java +package DataStructure.Tree.RB; + + +import java.util.Arrays; + +/** + * 红黑树实现 BSTMap中拷贝过来的 + */ +public class RBTree, V> { + + private final static boolean RED = true; + private final static boolean BLACK = false; + + private class Node { + public K key; + public V value; + public Node left, right; + public boolean color; + + public Node(K key, V value) { + this.key = key; + this.value = value; + left = null; + right = null; + // 一开始添加的时候都是红结点 + color = RED; //为什么一开始是红色,因为在2-3结点中添加一个结点,永远是和叶子结点进行融合,所以是3节点,所以设置成红色 + } + } + + private Node root; + private int size; + + public RBTree() { + root = null; + size = 0; + } + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + /** 判断节点node的颜色 */ + private boolean isRed(Node node){ + if(node == null) return BLACK; + return node.color; + } + + + /**左旋转 + + node x + / \ --> / \ + T1 x node T3 + / \ / \ + T2 T3 T1 T2 + */ + private Node leftRotate(Node node){ + Node x = node.right; + Node T2 = x.left; + + x.left = node; + node.right = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } + + /**右旋转 + node x + / \ / \ + x T1 --> y node + / \ / \ / \ + y T2 T4 T3 T2 T1 + / \ + T4 T3 + */ + private Node rightRotate(Node node){ + Node x = node.left; + Node T2 = x.right; + x.right = node; + node.left = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } + + /**三个节点的颜色反转 根->RED 两个孩子->BLACK */ + private void flipColors(Node node){ + node.color = RED; + node.left.color = BLACK; + node.right.color = BLACK; + } + + /** 向红黑树中添加新的元素(key, value) */ + public void add(K key, V value) {//相当于JDK的put操作 + root = add(root, key, value); + root.color = BLACK; /**子过程一: 保持最终根节点为黑色 --> 第二条性质 */ + } + + // 向以node为根的红黑树树中插入元素(key, value),递归算法,返回插入新节点后红黑树树的根 + private Node add(Node node, K key, V value) { + + if (node == null) { + size++; + return new Node(key, value); + } + + if (key.compareTo(node.key) < 0) + node.left = add(node.left, key, value); + else if (key.compareTo(node.key) > 0) + node.right = add(node.right, key, value); + else // key.compareTo(node.key) == 0 + node.value = value; + + /**维护链: 注意三个if不是互斥的 就是一个维护链的关系 */ + if(isRed(node.right) && !isRed(node.left)){ //右红,左黑 -> 左旋 + node = leftRotate(node); + } + if(isRed(node.left) && isRed(node.left.left)){ //左红 左左红 -> 右旋 + node = rightRotate(node); + } + if(isRed(node.left) && isRed(node.right)){ + flipColors(node); + } + return node; + } + + // 返回以node为根节点的红黑树中,key所在的节点 + private Node getNode(Node node, K key) { + + if (node == null) + return null; + + if (key.equals(node.key)) + return node; + else if (key.compareTo(node.key) < 0) + return getNode(node.left, key); + else // if(key.compareTo(node.key) > 0) + return getNode(node.right, key); + } + + public boolean contains(K key) { + return getNode(root, key) != null; + } + + public V get(K key) { + Node node = getNode(root, key); + return node == null ? null : node.value; + } + + public void set(K key, V newValue) { + Node node = getNode(root, key); + if (node == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + + node.value = newValue; + } + + // 返回以node为根的红黑树的最小值所在的节点 + private Node minimum(Node node) { + if (node.left == null) + return node; + return minimum(node.left); + } + + // 删除掉以node为根的红黑树中的最小节点,返回删除节点后新的红黑树的根 + private Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); + return node; + } + + //没有实现remove中的调整 + public V remove(K key) { + throw new UnsupportedOperationException("No remove in RBTree!"); + } + + public void printTree(){ + printTree(root,0,"H",8); + } + public void printTree(Node head,int height,String to,int len){ + if(head == null)return; + printTree(head.right,height + 1,"v",len); + + String val = to + head.key + to; //两边指示的字符 + int lenV = val.length(); + int lenL = (len - lenV)/2; //左边的空格(分一半) + int lenR = len - lenV - lenL; // 右边的空格 + System.out.println( getSpace(len * height) + getSpace(lenL) + val + getSpace(lenR)); + + printTree(head.left,height + 1,"^",len); + } + public static String getSpace(int len){ + StringBuffer str = new StringBuffer(); + for(int i = 0; i < len; i++) str.append(" "); + return str.toString(); + } + + public static void main(String[] args) { + Integer[] arr = {42,33,50,17,37,48,88,12,18,66,6}; +// Arrays.sort(arr); //排序后插入也会保持平衡 但是BST会退化成链表 + RBTreerbTree = new RBTree<>(); + for(int i = 0; i < arr.length; i++){ + rbTree.add(arr[i],null); + } + rbTree.printTree(); + } + +} +``` +测试结果: +![这里写图片描述](https://img-blog.csdn.net/20180902183027522?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +和这颗红黑树是一样的: + +![这里写图片描述](https://img-blog.csdn.net/20180902083648615?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +二叉树打印见[这里](https://blog.csdn.net/zxzxzx0119/article/details/81096554)。 + +*** +## 八、使用LeetCode804. Unique Morse Code Words测试红黑树 +#### [题目链接](https://leetcode.com/problems/unique-morse-code-words/description/) +#### 题目 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181220235729983.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +#### 解析 +题目很简单,就是要你解析字符串之后看有多少种不同的翻译,使用HashSet和TreeSet都可以做,这里用RBTree改装的Set来测试(类似JDK中的TreeSet): + +```java +class Solution { + public int uniqueMorseRepresentations(String[] words) { + + String[] codes = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."}; + RBTree set = new RBTree<>(); + for(String word: words){ + StringBuilder res = new StringBuilder(); + for(int i = 0 ; i < word.length() ; i ++) + res.append(codes[word.charAt(i) - 'a']); + + set.add(res.toString(), null); + } + + return set.getSize(); + } + + + private class RBTree, V> { + + + private final static boolean RED = true; + private final static boolean BLACK = false; + + private class Node { + public K key; + public V value; + public Node left, right; + public boolean color; + + public Node(K key, V value) { + this.key = key; + this.value = value; + left = null; + right = null; + // 一开始添加的时候都是红结点 + color = RED; //为什么一开始是红色,因为在2-3结点中添加一个结点,永远是和叶子结点进行融合,所以是3节点,所以设置成红色 + } + } + + private Node root; + private int size; + + public RBTree() { + root = null; + size = 0; + } + + public int getSize() { + return size; + } + + public boolean isEmpty() { + return size == 0; + } + + /** 判断节点node的颜色 */ + private boolean isRed(Node node){ + if(node == null) return BLACK; + return node.color; + } + + + /**左旋转 + + node x + / \ --> / \ + T1 x node T3 + / \ / \ + T2 T3 T1 T2 + */ + private Node leftRotate(Node node){ + Node x = node.right; + Node T2 = x.left; + + x.left = node; + node.right = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } + + /**右旋转 + node x + / \ / \ + x T1 --> y node + / \ / \ / \ + y T2 T4 T3 T2 T1 + / \ + T4 T3 + */ + private Node rightRotate(Node node){ + Node x = node.left; + Node T2 = x.right; + x.right = node; + node.left = T2; + + //update color + x.color = node.color; + node.color = RED; + + return x; + } + + /**三个节点的颜色反转 根->RED 两个孩子->BLACK */ + private void flipColors(Node node){ + node.color = RED; + node.left.color = BLACK; + node.right.color = BLACK; + } + + /** 向红黑树中添加新的元素(key, value) */ + public void add(K key, V value) {//相当于JDK的put操作 + root = add(root, key, value); + root.color = BLACK; /**子过程一: 保持最终根节点为黑色 --> 第二条性质 */ + } + + // 向以node为根的红黑树树中插入元素(key, value),递归算法,返回插入新节点后红黑树树的根 + private Node add(Node node, K key, V value) { + + if (node == null) { + size++; + return new Node(key, value); + } + + if (key.compareTo(node.key) < 0) + node.left = add(node.left, key, value); + else if (key.compareTo(node.key) > 0) + node.right = add(node.right, key, value); + else // key.compareTo(node.key) == 0 + node.value = value; + + /**维护链: 注意三个if不是互斥的 就是一个维护链的关系 */ + if(isRed(node.right) && !isRed(node.left)){ //右红,左黑 -> 左旋 + node = leftRotate(node); + } + if(isRed(node.left) && isRed(node.left.left)){ //左红 左左红 -> 右旋 + node = rightRotate(node); + } + if(isRed(node.left) && isRed(node.right)){ + flipColors(node); + } + return node; + } + + // 返回以node为根节点的红黑树中,key所在的节点 + private Node getNode(Node node, K key) { + + if (node == null) + return null; + + if (key.equals(node.key)) + return node; + else if (key.compareTo(node.key) < 0) + return getNode(node.left, key); + else // if(key.compareTo(node.key) > 0) + return getNode(node.right, key); + } + + public boolean contains(K key) { + return getNode(root, key) != null; + } + + public V get(K key) { + Node node = getNode(root, key); + return node == null ? null : node.value; + } + + public void set(K key, V newValue) { + Node node = getNode(root, key); + if (node == null) + throw new IllegalArgumentException(key + " doesn't exist!"); + + node.value = newValue; + } + + // 返回以node为根的红黑树的最小值所在的节点 + private Node minimum(Node node) { + if (node.left == null) + return node; + return minimum(node.left); + } + + // 删除掉以node为根的红黑树中的最小节点,返回删除节点后新的红黑树的根 + private Node removeMin(Node node) { + if (node.left == null) { + Node rightNode = node.right; + node.right = null; + size--; + return rightNode; + } + node.left = removeMin(node.left); + return node; + } + + //没有实现remove中的调整 + public V remove(K key) { + throw new UnsupportedOperationException("No remove in RBTree!"); + } + } +} +``` diff --git "a/Algorithm/DataStructure/Tree/\347\272\277\347\264\242\344\272\214\345\217\211\346\240\221\345\255\246\344\271\240\346\200\273\347\273\223.md" "b/Algorithm/DataStructure/Tree/\347\272\277\347\264\242\344\272\214\345\217\211\346\240\221\345\255\246\344\271\240\346\200\273\347\273\223.md" new file mode 100644 index 00000000..f59cf902 --- /dev/null +++ "b/Algorithm/DataStructure/Tree/\347\272\277\347\264\242\344\272\214\345\217\211\346\240\221\345\255\246\344\271\240\346\200\273\347\273\223.md" @@ -0,0 +1,280 @@ +# 目录 + + - 线索二叉树相关由来和总结 + - 中序线索二叉树构造(带头结点) +*** +## 一、线索二叉树由来 +**两点由来:** + + - 空间的浪费 : 在使用二叉链表的存储结构的过程中,会存在大量的空指针域,为了充分利用这些空指针域,引申出了“线索二叉树”。对于一个有`n`个节点的二叉链表,每个节点有指向左右节点的`2`个指针域,整个二叉链表存在`2n`个指针域。而`n`个节点的二叉链表有`n-1`条分支线,那么空指针域的个数`=2n-(n-1) = n+1`个空指针域,从存储空间的角度来看,这`n+1`个空指针域浪费了内存资源。 + - 想知道前驱和后继:如果我们想知道按中序方式遍历二叉链表时某一个节点的**前驱节点或者后继节点**时,必须要按中序方式遍历二叉链表才能够知道结果,每次需要结果时**都需要进行一次遍历**,所以可以考虑提前存储这种前驱和后继的关系来提高时间效率。 + +**综合上面两点分析,我们可以通过充分利用二叉链表中的空指针域,存放节点在某种遍历方式下的前驱和后继节点的指针。我们把这种指向前驱和后继的指针称为**线索(`Thread`)**,加上线索的二叉链表成为线索链表,对应的二叉树就成为“线索二叉树**(`Threaded Binary Tree`)”。 + + + - 我们按照上面的规定,若一个结点有左子树,则其`lchild`域指示其左孩子,否则令`lchild`域指示其前驱;若结点有右子树,则其`rchild`域指示其右孩子,否则令`rchild`域指示其后继。 + - 为了避免混淆,需要改变普通的二叉树结构,增加两个标记域`ltag`,`rtag`**,当**`ltag`为`true`时,表示左边是前驱,**为`false`时,表示是左儿子**;同理,当`rtag`为`true`时,表示右边是后驱,为`false`时,表示是右儿子; + +![在这里插入图片描述](https://img-blog.csdn.net/20180917224541548?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +![在这里插入图片描述](https://img-blog.csdn.net/20180917224754330?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做**线索链表**,其中指向结点前驱和后继的指针,叫做**线索**,加上线索的二叉树成为线索二叉树。例如下面的树进行线索化: + +![这里写图片描述](https://img-blog.csdn.net/20180902200355975?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +这里给出结点结构: + +```java +public class BiThrTree{ + + private static final boolean LINK = false; //指针 + private static final boolean THREAD = true; //线索 + + private class Node{ + public E e; + public Node left,right; + public boolean ltag; //false表示left指向左孩子 true表示left指向前驱 + public boolean rtag; //false表示right指向右孩子 true表示right指向后继 + + public Node(E e, Node left, Node right, boolean ltag, boolean rtag) { + this.e = e; + this.left = left; + this.right = right; + this.ltag = ltag; + this.rtag = rtag; + } + + public Node(E e) { + this(e,null,null,LINK,LINK); //默认指向左右孩子 + } + public Node() { + this(null,null,null,LINK,LINK); //默认指向左右孩子 + } + } + + private Node dummyHead; //虚拟头结点 + private Node root; //根结点 + private Node pre; //pre指针 指向前一个结点 +} +``` + +*** +## 二、线索二叉树的构造 +我们这里先讨论中序线索二叉树,看下图的构建过程,实现为指向左右儿子,虚线为线索(指向前驱和后继),对二叉树以某种次序遍历使得其变为线索二叉树的过程叫做**线索化**。 + +![在这里插入图片描述](https://img-blog.csdn.net/20180917224206116?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)![在这里插入图片描述](https://img-blog.csdn.net/20180917225706503?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +遍历的方法 : + + - 寻找一个后继的方法是:若其右标志是`true(THREAD)`,则右边为线索,指示其后继;否则,遍历右子树最先**访问的结点**(也就是**右子树最左下的结点**); + - 同理,所以寻找一个前驱的方法是:若其左标志是`true`,则左边为线索,指示其前驱;否则,遍历左子树**最后访问的结点**(也就是**左子树最右下的结点**)。 + +然后就是注意设置**双向链表头结点**的问题: + +![在这里插入图片描述](https://img-blog.csdn.net/20180917230154974?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +线索化的过程: + +![在这里插入图片描述](https://img-blog.csdn.net/20180917230436298?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**实现代码如下,其中测试用例就是用的上面图的例子,建树使用下标对应,不懂可以看看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79808127#t13)**: + +```java +import java.util.LinkedList; +import java.util.Queue; + +public class BiThrTree{ + + private static final boolean LINK = false; //指针 + private static final boolean THREAD = true; //线索 + + private class Node{ + public E e; + public Node left,right; + public boolean ltag; //false表示left指向左孩子 true表示left指向前驱 + public boolean rtag; //false表示right指向右孩子 true表示right指向后继 + + public Node(E e, Node left, Node right, boolean ltag, boolean rtag) { + this.e = e; + this.left = left; + this.right = right; + this.ltag = ltag; + this.rtag = rtag; + } + + public Node(E e) { + this(e,null,null,LINK,LINK); //默认指向左右孩子 + } + public Node() { + this(null,null,null,LINK,LINK); //默认指向左右孩子 + } + } + + private Node dummyHead; //虚拟头结点 + private Node root; //根结点 + private Node pre; //pre指针 指向前一个结点 + + public BiThrTree(E[] arr){ + this.dummyHead = null; + this.pre = null; + this.root = createTree(arr, 0); // 创建二叉树,返回创建的结点作为自己的根 + } + + public Node createTree(E arr[],int i) { + if(i >= arr.length || arr[i] == null) + return null; + Node T = new Node(arr[i]); + T.left = createTree(arr,2*i + 1); + T.right = createTree(arr,2*i + 2); + return T; + } + + /** 中序线索化二叉树 */ + public void inThreading(){ + initPreNode(); + } + + //先初始化pre结点 并开始线索化 + public void initPreNode(){ + dummyHead = new Node(null); //一开始是虚拟的头结点 + + dummyHead.ltag = LINK; //左边是连着 root + dummyHead.rtag = THREAD; //右边当做线索 + + dummyHead.right = dummyHead; //右指针指向自己 + + if(root == null) //根节点为null 就自己指向自己吧 (此时就只有一个虚拟的头结点,啥也没有) + dummyHead.left = dummyHead; + else { + dummyHead.left = root; //蓝色矩形的(1) + + pre = dummyHead; //初始化第一个pre 为dummyHead + + /**此时pre结点已经初始化完毕,可以开始线索化了*/ + process(root); //开始 中序线索化 + + /**此时差不多 已经线索化完毕了, 只需要 连成一个双向的链表 ,后续的一些小的操作*/ + pre.right = dummyHead; //蓝色矩形的 (3) + pre.rtag = THREAD; + + dummyHead.right = pre; // 虚拟头结点的 right = pre 也就是蓝色矩形的(4) + + } + } + + //递归线索化 + public void process(Node node){ + if(node == null) + return; + process(node.left); //中序 先左 + /**此时已经来到 这次中序的第一个结点, 开始改*/ + + //改自己的前驱 + if(node.left == null){ // no left child + node.ltag = THREAD; + node.left = pre; // as a thread --> 左孩子指向前序 + } + //同时改pre的后继 --> 指向当前node + if(pre.right == null){ + pre.rtag = THREAD; + pre.right = node; + } + pre = node; // 继续下一个 + + process(node.right); //递归右子树 + } + + // 通过 线索化 来 中序遍历 + public void inOrderByThread(){ + Node cur = root; //从根节点开始 + + while(cur != dummyHead){ //转一圈 + while(cur.ltag == LINK) //如果 LINK就一直往左走 (找到最左下角的开始) + cur = cur.left; + + //访问这个结点 + System.out.print(cur.e + " "); + + while(cur.rtag == THREAD && cur.right != dummyHead){ //说明有后继,而且不是最后一个 + cur = cur.right; //指向后继 + System.out.print(cur.e + " "); + } + + cur = cur.right; + } + System.out.println(); + } + + + public void leverOrder(){ + if(root == null) + return; + Queue que = new LinkedList<>(); + que.add(root); + while(!que.isEmpty()){ + Node cur = que.poll(); + System.out.print(cur.e + " "); + if(cur.left != null) + que.add(cur.left); + if(cur.right != null) + que.add(cur.right); + } + System.out.println(); + } + + + public void inOrder(){ + inOrder(root); + } + //递归中序遍历 + public void inOrder(Node node ){ + if(node == null) + return; + inOrder(node.left); + System.out.print(node.e + " "); + inOrder(node.right); + } + + + public static void main(String[] args) { + Character[] arrStr = {'-','+','/','a','*','e','f', + null,null,'b','-',null,null,null,null, //第四层 + + //最后一层 + null,null,null,null,null,null,'c','d', + null,null,null,null, null,null,null,null, + }; + + + BiThrTree biThrTree = new BiThrTree(arrStr); + + //test + System.out.println("-------层序-------"); + biThrTree.leverOrder(); + + System.out.println("-------递归中序-------"); + biThrTree.inOrder(); + System.out.println(); + + biThrTree.inThreading(); // 线索化 + + System.out.println("-------线索化中序-------"); + // biThrTree.inOrder(); //注意此时不能递归中序遍历了,因为已经线索化了 更改了结构 + biThrTree.inOrderByThread(); //线索中序遍历 + } + +} + +``` + +运行结果: + +![在这里插入图片描述](https://img-blog.csdn.net/20180917230939523?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**另外**: + +![在这里插入图片描述](https://img-blog.csdn.net/20180917230852403?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +**没有设置头结点的前,中,后续二叉线索树构造 可以看看[这个博客](https://blog.csdn.net/UncleMing5371/article/details/54291221)。** + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp1.png" b/Algorithm/DataStructure/TwoPointer/images/tp1.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp1.png" rename to Algorithm/DataStructure/TwoPointer/images/tp1.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp2.png" b/Algorithm/DataStructure/TwoPointer/images/tp2.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp2.png" rename to Algorithm/DataStructure/TwoPointer/images/tp2.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp3.png" b/Algorithm/DataStructure/TwoPointer/images/tp3.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp3.png" rename to Algorithm/DataStructure/TwoPointer/images/tp3.png diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp4.png" b/Algorithm/DataStructure/TwoPointer/images/tp4.png similarity index 100% rename from "\346\225\260\346\215\256\347\273\223\346\236\204\347\256\227\346\263\225/TwoPointer/images/tp4.png" rename to Algorithm/DataStructure/TwoPointer/images/tp4.png diff --git "a/Algorithm/DataStructure/TwoPointer/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" "b/Algorithm/DataStructure/TwoPointer/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" new file mode 100644 index 00000000..018ef4e1 --- /dev/null +++ "b/Algorithm/DataStructure/TwoPointer/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" @@ -0,0 +1,157 @@ +### 子数组累加和为aim(小于等于aim)的三个问题 + + - 累加和` = aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); + - 累加和` = aim`的最长子数组的长度(**数组`+`**)(只有正数); + - 累加和` <= aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); + +*** +### 累加和` = aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); +这个题目使用`HashMap`来存储前面出现过的累加和的下标,具体过程如下: + + - 使用变量`sum`表示从`0`位置开始一直加到i位置所有元素的累加和; + - `HashMap`中`key`表示从`arr`最左边开始累加过程中出现过的`sum`值,`value`表示的是`sum`值出现最早的位置;、 + - 假设当前元素为`arr[i]`,则`sum += arr[i]`,之前所有累加和为`sum` ,查看`map`中是否有`sum - aim`这个值,如果有,且对应`value`为`j`,那么就找到一个子数组累加和为`aim`的,且长度为 `i - j + 1`; + - 检查现在的`sum `是否在`map`中出现,如果不存在,说明此时是第一次出现的,把`(sum,i)`加入到`map`中; + - 继续遍历数组; + + 很重要的一个地方就是一开始`map`中要存`(0,-1)`这个值,直观理解是一个数也没有的时候也可以累加出`0` +看下面例子: +`[1,2,3,3] , aim = 6`; +如果没有存`(0,-1)`,累加到下标为`2`的时候,`sum = 6` 此时,`sum - aim = 6 - 6 = 0`,但是没有`0`这个累加和,就会忽略; + +```java + /** O(n)时间 O(n)空间 */ + static int getMaxLength(int[] arr,int aim){ + if(arr == null || arr.length== 0 )return 0; + HashMapmap = new HashMap(); //表示key这个累加和最早出现在value位置 + map.put(0,-1); //这个很重要 + int sum = 0; + int res = 0; + for(int i = 0; i < arr.length; i++){ + sum += arr[i]; + if(map.containsKey(sum - aim)){ //如果之前出现了 + res = Math.max(res,i - map.get(sum - aim)); + } + if(!map.containsKey(sum)){ + map.put(sum,i); + } + } + return res; + } +``` +问题变式: 给定一个数组,求正数和负数个数相等最长子数组长度。 +解: 把正数变成`1`,负数变成`-1`即可。 + + +还有一个扩展问题: + +题目: +> 定义数组的异或和的概念: +> 数组中所有的数异或起来,得到的结果叫做数组的异或和,比如数组`{3, 2, 1}`的异或和是: `3 ^ 2 ^ 1 = 0 `。 +> 给定一个数组`arr`,你可以任意把`arr`分成很多不相容的子数组,你的目的是: 分出来的子数组中,异或和为`0`的子数组最多。 +> 请返回: 分出来的子数组中,异或和为`0`的子数组最多是多少? + +解析: 可以利用这个思想找到**最晚出现和`0~i`内异或和(假设为`xor`)同样异或和的更小的范围内最晚出现的位置,因为最后一个部分是异或和为`0`,且`xor^0 = xor`。** +![这里写图片描述](images/tp1.png) + +```java + /** + * dp[i] = max(dp[i-1],dp[k] + 1) k表示的是i 如果是最优划分中的最后一个部分的最后一个数的话,k是那个部分的开始的地方的前一个 + * 从 0~i-1中异或还是 xor的最晚的位置 : + * @param arr + * @return + */ + static int maxEor(int[] arr){ + HashMapmap = new HashMap<>();//存放某个异或和最晚出现的位置 + int res = 0,xor = 0; + int[] dp = new int[arr.length]; + map.put(0,-1); + for(int i = 0; i < arr.length; i++){ + xor ^= arr[i]; + if(map.containsKey(xor)){// 找到上一个异或和为xor的最晚出现的位置   因为xor^0 = xor + int k = map.get(xor); //k + dp[i] = k == -1 ? 1 : dp[k] + 1; + } + if(i > 0){ + dp[i] = Math.max(dp[i],dp[i-1]); + } + map.put(xor,i); //每次都要put进去 + res = Math.max(dp[i],res); + } + return res; + } +``` + +*** +### 累加和` = aim`的最长子数组的长度(**数组`+`**)(只有正数); +这个和上面唯一的不同就是数组中只有正数,这里使用类似窗口移动的做法,给出两个指针,`L、R`表示窗口的左右边界 ,`sum`表示的是`arr[L,R]`之间的累加和,`L`,`R`一直往右动。 + + - 如果窗口内`sum < aim`,`R`就往右扩,并且`sum += arr[R]`; + - 如果窗口内`sum > aim`,`L` 就往右扩,并且`sum -= arr[L]`; + - 如果窗口内`sum = aim`, 就说明这个窗口内累加和为`sum` ,此时记录最大值即可; + +![这里写图片描述](images/tp2.png) + + + +```java + static int getMax(int[] arr,int aim){ + if(arr == null || arr.length == 0 || aim < 0)return 0; + int L = 0,R = 0; + int res = 0, sum = arr[0]; + while(R < arr.length){ + if(sum == aim){ + res = Math.max(res,R - L + 1); + sum -= arr[L++]; + }else if(sum < aim){//小于等于就往右边扩 + if(++R == arr.length) break; + sum += arr[R]; + }else { // 大于就往左边扩 sum > aim + sum -= arr[L++]; + } + } + return res; + } +``` +*** +### 累加和` <= aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); +两个数组`sum`和`ends`,`sum[i]`表示的是以`arr[i]`开头(必须包含`arr[i]`)的所有子数组的最小累加和,对应的`ends[i]`表示的是取得这个最小累加和的右边界。 一开始先求出`sums`数组和`ends[]`数组。 + +![这里写图片描述](images/tp3.png) + +这个题目最精华的是左右边界不回退,就是说,如果从`0`位置扩到`T`区间,`T+1`区间不能扩了,此时不是回到`1`位置开始扩,而是舍弃`0`位置,看能不能由于舍弃`0`位置把`T+1`位置加进来: + +![这里写图片描述](images/tp4.png) + +```java + static int getMaxLength2(int[] arr,int aim){ + if(arr == null || arr.length == 0)return 0; + int[] sums = new int[arr.length]; //以arr[i]开头所有子数组的最小累加和 + int[] ends = new int[arr.length]; //取得最小累加和的最右边界 + sums[arr.length-1] = arr[arr.length-1]; + ends[arr.length-1] = arr.length-1; + for(int i = arr.length - 2; i >= 0; i--){ //求出sums数组和ends数组 + if(sums[i+1] < 0){ + sums[i] = arr[i] + sums[i+1]; + ends[i] = ends[i+1]; + }else { + sums[i] = arr[i]; + ends[i] = i; + } + } + int sum = 0; //目前的累加和 sum -> R + int R = 0;//每一次扩到的右边界 + int res = 0; //答案 + for(int start = 0; start < arr.length; start++){//每一次开头 + while(R < arr.length && sum + sums[R] <= aim){//一整块一整块的扩 + sum += sums[R]; + R = ends[R] + 1; + } + sum -= R > start ? arr[start] : 0;//如果R>start,下面start要++了,窗口内减去arr[start] + res = Math.max(res,R - start);//窗口是start ~ R-1 ,所以是长度为R-start + R = Math.max(R,start + 1); //有没有可能扩不出去 + } + return res; + } +``` + diff --git a/Algorithm/InterviewAlgorithm.md b/Algorithm/InterviewAlgorithm.md new file mode 100644 index 00000000..298f9473 --- /dev/null +++ b/Algorithm/InterviewAlgorithm.md @@ -0,0 +1,3346 @@ +# 面试常见手撕模板题以及笔试模板总结(附带少数ACM入门算法) + +现在大部分大厂的笔试面试越来越重视算法。特别是笔试题里面,大部分题型和ACM几乎一样。 + +所以我在这里总结了一些(30个)常见面试和ACM**入门级别**的算法模板(其实也不只是ACM要求,大部分模板都可以在`LeetCode`中找到对应的题型),有一部分也是面试时候面试官经常要你手撕的经典算法。例如排序、二叉树非递归遍历、二分、大数等。 + + + +**可能这些算法有些对初学者有点难度,但是不要慌~...~,我在每个算法代码的上方都给了一个链接,每个算法几乎都有很详细的解释(大部分带图,有些是我写的,有些是我看到的很好的文章),不懂的可以点链接进行学习,另外有些模板配了原题,可以验证算法正确性。祝大家在笔试面试手撕算法顺利。。^_^**。 + +当然这些只是我们刷题的基础和模板,**更多的还是需要我们多多刷题**,锻炼逻辑思维和感觉~...~。 + + + +语言用的Java,但是大部分代码和C++基本上没啥很大区别~...~。 + +**如果觉得总结的还不错的话,给个`star`哦*╯3╰ .** + +> 仓库会不断更新和完善新的笔试面试中出现的模板^_^。 +> +> 由于这段时间非常忙,C++版本没有跟上,有兴趣的可以先看我之前写的[ACMC++模板](https://blog.csdn.net/zxzxzx0119/article/details/79838261),后面有时间我会补上^_^。 + +## 目录 + +* [一、排序](#一排序) +* [二、二分](#二二分) +* [三、二叉树非递归遍历](#三二叉树非递归遍历) +* [四、01背包](#四01背包) +* [五、最长递增子序列](#五最长递增子序列) +* [六、最长公共子序列](#六最长公共子序列) +* [七、最长公共子串](#七最长公共子串) +* [八、大数加法](#八大数加法) +* [九、大数乘法](#九大数乘法) +* [十、大数阶乘](#十大数阶乘) +* [十一、全排列](#十一全排列) +* [十二、子集](#十二子集) +* [十三、N皇后](#十三n皇后) +* [十四、并查集](#十四并查集) +* [十五、树状数组](#十五树状数组) +* [十六、线段树](#十六线段树) +* [十七、字典树](#十七字典树) +* [十八、单调栈](#十八单调栈) +* [十九、单调队列](#十九单调队列) +* [二十、KMP](#二十kmp) +* [二十一、Manacher算法](#二十一manacher算法) +* [二十二、拓扑排序](#二十二拓扑排序) +* [二十三、最小生成树](#二十三最小生成树) +* [二十四、最短路](#二十四最短路) +* [二十五、欧拉回路](#二十五欧拉回路) +* [二十六、GCD和LCM](#二十六gcd和lcm) +* [二十七、素数筛法](#二十七素数筛法) +* [二十八、唯一分解定理](#二十八唯一分解定理) +* [二十九、乘法快速幂](#二十九乘法快速幂) +* [三十、矩阵快速幂](#三十矩阵快速幂) + +## Java快速输入 + +先给一个干货,可能有些题用Java会超时(很少),下面是[Petr](https://en.wikipedia.org/wiki/Petr_Mitrichev)刷题时的模板,一般用了这个就不会出现C++能过Java不能过的情况了。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + + // start code..... + + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // 不关闭就没有输出 + } +} +``` + + +## 一、排序 + +先给出一个swap函数,代表交换数组两个位置的值,很多排序用到这个函数: + +```java +static void swap(int[] arr, int a, int b){ + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; +} +``` + +面试主要考察**比较排序**(`O(N^2)、O(NlogN)`)排序(非比较排序可以看下面的详细总结)。给出两篇博客: + +* [各种排序算法总结](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Algorithm/Sort/%E5%90%84%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93(%E5%85%A8%E9%9D%A2).md); +* [动图搞定排序算法](https://mp.weixin.qq.com/s/vn3KiV-ez79FmbZ36SX9lg); + +### 1、冒泡 + +```java +static void bubbleSort(int[] arr){ + for(int end = arr.length - 1; end > 0; end--){ + boolean isSort = true; + for(int i = 0; i < end; i++){ + if(arr[i] > arr[i+1]) { + swap(arr, i, i + 1); + isSort = false; + } + } + if(isSort) break; + } +} +``` + +### 2、选择 + +```java +static void selectSort(int[] arr){ + for(int i = 0; i < arr.length; i++){ + int minIdx = i; + for(int j = i + 1; j < arr.length; j++) minIdx = arr[j] < arr[minIdx] ? j : minIdx; + swap(arr, minIdx, i); + } +} +``` + +### 3、插入 + +```java +// 几个边界: i=1开始(不是必须)、j >= 0, arr[j+1] = key注意一下 +static void insertSort(int[] arr) { + for (int i = 1; i < arr.length; i++) { + int key = arr[i], j; + for (j = i - 1; j >= 0 && arr[j] > key; j--) arr[j + 1] = arr[j]; + arr[j + 1] = key; + } +} +``` + +第二种写法: + +```java +// 边界 j > 0 +static void insertSort2(int[] arr) { + for (int i = 1; i < arr.length; i++) { + for (int j = i; j > 0 && arr[j - 1] > arr[j]; j--) swap(arr, j, j - 1); + } +} +``` + +二分插入排序: + +```java +// 注意 R = i-1,注意找第一个>=key的,注意arr[i]先用key保存 +static void binaryInsertSort(int[] arr) { + for (int i = 1; i < arr.length; i++) { + int L = 0, R = i - 1; + // 找第一个大于的 二分边界搞不清的看下面的二分链接 + int key = arr[i]; + while (L <= R) { + int m = L + (R - L) / 2; + if (arr[m] > arr[i]) { + R = m - 1; + } else { + L = m + 1; + } + } + for (int j = i - 1; j >= L; j--) arr[j + 1] = arr[j]; + arr[L] = key; + } +} +``` + +### 4、希尔排序 + +采取的是**增量序列每次减半**的策略。 + +```java +static void shellSort(int[] arr) { + for (int g = arr.length; g > 0; g /= 2) { // 增量序列 gap + for (int end = g; end < arr.length; end++) { // 每一个组的结束元素, 从数组第gap个元素开始 + // 每组做插入排序 + int key = arr[end], i; + for (i = end - g; i >= 0 && key < arr[i]; i -= g) arr[i + g] = arr[i]; + arr[i + g] = key; + } + } +} +``` + +### 5、快排 + +给出的是三路快排,其他的看我给的博客。 + +```java +static void quickSort(int[] arr){ + if(arr == null || arr.length == 0) return; + quickRec(arr, 0, arr.length - 1); +} + +static void quickRec(int[] arr, int L, int R) { + if (L >= R) return; + swap(arr, L, L + (int) (Math.random() * (R - L + 1))); + int[] p = partition(arr, L, R); + quickRec(arr, L, p[0] - 1); + quickRec(arr, p[1] + 1, R); +} + +// 用arr[L]作为划分点 +static int[] partition(int[] arr, int L, int R) { + int key = arr[L]; + int less = L, more = R + 1; + int cur = L + 1; + while (cur < more) { + if (arr[cur] < key) { + swap(arr, ++less, cur++); + } else if (arr[cur] > key) { + swap(arr, --more, cur); + } else { + cur++; + } + } + swap(arr, L, less); + // 返回相等的两个下标, less位置是我最后交换过来的划分值,more位置是>的,所以返回more-1 + return new int[]{less, more - 1}; +} +``` + +### 6、归并排序 + +```java +static void mergeSort(int[] arr){ + if(arr == null || arr.length == 0) return; + mergeRec(arr, 0, arr.length - 1); +} + +//注意是mergeSort(arr, L, m); 不是mergeSort(arr, L, m-1) +static void mergeRec(int[] arr, int L, int R) { + if (L >= R) return; + int m = L + (R - L) / 2; + mergeRec(arr, L, m); + mergeRec(arr, m + 1, R); + merge(arr, L, m, R); +} + +static void merge(int[] arr, int L, int mid, int R) { + int[] h = new int[R - L + 1]; + int p1 = L, p2 = mid + 1; + int k = 0; + while (p1 <= mid && p2 <= R) + h[k++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; // 注意保证稳定性 + while (p1 <= mid) h[k++] = arr[p1++]; + while (p2 <= R) h[k++] = arr[p2++]; + for (int i = 0; i < k; i++) arr[L + i] = h[i]; +} +``` + +非递归归并排序: + +```java +static void mergeSortBU(int[] arr) { + for (int sz = 1; sz <= arr.length; sz += sz) { // 区间的个数,1..2..4..8 + for (int i = 0; sz + i < arr.length; i += sz + sz) { // 对[i...i+sz-1]和[i+sz...i+2*sz-1]内归并 + merge(arr, i, i + sz - 1, Math.min(arr.length - 1, i + 2 * sz - 1)); // min防止越界 + } + } +} +``` + +### 7、堆排 + +```java +// if(arr == null || arr.length <= 1) return; 是必须的 +static void heapSort(int[] arr) { + if (arr == null || arr.length <= 1) return; + for (int i = 0; i < arr.length; i++) siftUp(arr, i);//上浮方式建堆 + int size = arr.length - 1; + swap(arr, 0, size); + while (size > 0) { + siftDown(arr, 0, size); + swap(arr, 0, --size); + } +} + +// 上浮 +static void siftUp(int[] arr, int i) { + while (arr[i] > arr[(i - 1) / 2]) { + swap(arr, i, (i - 1) / 2); + i = (i - 1) / 2; + } +} + +// 下沉 +static void siftDown(int[] arr, int i, int heapSize) { + int L = 2 * i + 1; + while (L < heapSize) { + int maxIndex = L + 1 < heapSize && arr[L + 1] > arr[L] ? L + 1 : L; + maxIndex = arr[i] > arr[maxIndex] ? i : maxIndex; + if (maxIndex == i) break; + swap(arr, i, maxIndex); + i = maxIndex; + L = 2 * i + 1; + } +} +``` + +第二种方式,使用`heapfiy`的优化,只需要使用`siftDown`过程即可。 + +```java +// 注意这里是size+1,因为这个不是交换了最后一个,所以要考虑arr[size],下面不要考虑arr[size] +// if (arr == null || arr.length <= 1) return; 是必须的 +static void heapSort2(int[] arr) { + if (arr == null || arr.length <= 1) return; + int size = arr.length - 1; + for (int i = (size - 1) / 2; i >= 0; i--) + siftDown(arr, i, size + 1); + swap(arr, 0, size); + while (size > 0) { + siftDown(arr, 0, size); + swap(arr, 0, --size); + } +} +``` + +其中`siftDown`过程也可以使用递归的写法: + +```java +static void siftDown(int[] arr, int i, int heapSize) { //从arr[i] 开始往下调整 + int L = 2 * i + 1; + int R = 2 * i + 2; + int maxIdx = i; + if (L < heapSize && arr[L] > arr[maxIdx]) maxIdx = L; + if (R < heapSize && arr[R] > arr[maxIdx]) maxIdx = R; + if (maxIdx != i) { + swap(arr, i, maxIdx); + siftDown(arr, maxIdx, heapSize); + } +} +``` + +## 二、二分 + +给出我的[另一篇文章](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Algorithm/BinarySearch/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%9A%84%E6%80%BB%E7%BB%93(6%E7%A7%8D%E5%8F%98%E5%BD%A2).md),有关于二分的详细讲解。 + +二分最主要的就是边界问题: + +* 第一个`=key`的,不存在返回`-1`; +* 第一个`>=key`的; +* 第一个`>key`的; +* 最后一个`=key`的; +* 最后一个`<=key`的; +* 最后一个` key) + R = mid - 1; + else + L = mid + 1; + } + return -1; +} +``` + +**口诀: 左边的先R(`if`后的),右边的先L**。 + +查找第一个`=key`的,不存在返回`-1`: + +```java +// 左边的三个, 注意是L < arr.length +static int firstEqual(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] >= key) + R = mid - 1; + else + L = mid + 1; + } + if(L < arr.length && arr[L] == key) return L; + return -1; +} +``` + +第一个`>=key`的: + +```java +static int firstLargeEqual(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] >= key) + R = mid - 1; + else + L = mid + 1; + } + return L; +} +``` + +第一个`>key`的: + +```java +static int firstLarge(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] > key) // 因为是第一个> 的,所以> + R = mid - 1; + else + L = mid + 1; + } + return L; +} +``` + +最后一个`=key`的: + +```java +// 右边的三个 注意是 R>=0 +static int lastEqual(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] <= key) + L = mid + 1; + else + R = mid - 1; + } + if(R >= 0 && arr[R] == key) + return R; + return -1; +} +``` + +最后一个`<=key`的: + +```java +static int lastEqualSmall(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] <= key) + L = mid + 1; + else + R = mid - 1; + } + return R; +} +``` + +最后一个`= arr.length || arr[i] == -1) return null; + Node root = new Node(arr[i]); + root.left = buildTree(arr, i * 2 + 1); + root.right = buildTree(arr, i * 2 + 2); + return root; + } + + static Node buildTree(Scanner in){ + Node root = null; + int val = in.nextInt(); + if(val != -1){ + root = new Node(val); + root.left = buildTree(in); + root.right = buildTree(in); + } + return root; + } + + //前序 + static void preOrder(Node root){ + if(root == null) return; + Stack stack = new Stack<>(); + Node p = root; +// stack.push(root); // wrong + while(p != null || !stack.isEmpty()){ + while(p != null){ + out.print(p.val + " "); + stack.push(p); // 注意先推入 + p = p.left; + } + p = stack.pop(); + p = p.right; + } + out.println(); + } + + //中序 + static void inOrder(Node root){ + if(root == null) return; + Stack stack = new Stack<>(); + Node p = root; + while(p != null || !stack.isEmpty()){ + while(p != null){ + stack.push(p); + p = p.left; + } + p = stack.pop(); + out.print(p.val + " "); + p = p.right; + } + out.println(); + } + + + //后序第一种: 双栈: 可以实现 中-> 右-> 左, 然后再用一个栈逆转即可 + static void postOrder(Node root){ + if(root == null) return; + Node p = root; + Stack s1 = new Stack<>(); + Stack s2 = new Stack<>(); + s1.push(root); + while(!s1.isEmpty()){ + Node cur = s1.pop(); + s2.push(cur); + if(cur.left != null ) s1.push(cur.left); + if(cur.right != null ) s1.push(cur.right); + } + while(!s2.isEmpty()) out.print(s2.pop().val + " "); + out.println(); + } + + + // 后序第二种pre + static void postOrder2(Node root){ + Stack s = new Stack<>(); + s.push(root); + Node pre = null; + while(!s.isEmpty()){ + Node cur = s.peek(); + if((cur.left == null && cur.right == null) || + (pre != null && (pre == cur.left || pre == cur.right))){ + out.print(cur.val + " "); + s.pop(); + pre = cur; + }else { + if(cur.right != null) s.push(cur.right); + if(cur.left != null) s.push(cur.left); + } + } + out.println(); + } + + public static void main(String[] args){ + //int[] arr = {1,2,3,4,5,6,7,8,-1,9,-1,10,-1,11,-1, -1,-1,-1,-1,-1,-1,-1,-1}; // 和下面一样 + int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, -1, 9, -1, 10, -1, 11, -1}; + Node root = buildTree(arr, 0); + + preOrder(root); + inOrder(root); + postOrder(root); + postOrder2(root); + +// Scanner in = new Scanner(new BufferedInputStream(System.in)); +// 树结构和上面相同,输入: 1 2 4 8 -1 -1 -1 5 9 -1 -1 -1 3 6 10 -1 -1 -1 7 11 -1 -1 -1 +// Node root2 = buildTree(in); + } +} +``` + +## 四、01背包 + +这个在笔试题中可能会出现,有时候只是出题场景不同。 + +同样也给出我的[另一篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/Hdu/DP/Hdu%20-%202602.%20Bone%20Collector(01%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98).md)的总结。 + +```java +import java.io.*; +import java.util.*; + +/** + * 01背包 + * 题目: http://acm.hdu.edu.cn/showproblem.php?pid=2602 + */ +public class M4_Knapsack { + + static int n, C; + static int[] w, v; + static int[][] dp; + + //记忆化 + static int rec(int p, int curW) { + if (p == n) + return 0; + if (dp[p][curW] != -1) return dp[p][curW]; + if (curW + w[p] > C) + return dp[p][curW] = rec(p + 1, curW); + else + return dp[p][curW] = Math.max(rec(p + 1, curW + w[p]) + v[p], + rec(p + 1, curW)); + } + + // 普通 + static int dp(){ + int[][] dp = new int[n+1][C+1]; + for(int i = n - 1; i >= 0; i--){ + for(int j = 0; j <= C; j++){ + dp[i][j] = j + w[i] > C ? dp[i+1][j] : + Math.max(dp[i+1][j], dp[i+1][j+w[i]]+v[i]); + } + } + return dp[0][0]; + } + + // 二维滚动 + static int dp2(){ + int[][] dp = new int[2][C+1]; + for(int i = n - 1; i >= 0; i--){ + for(int j = 0; j <= C; j++){ + dp[i&1][j] = j + w[i] > C ? dp[(i+1)&1][j] : + Math.max(dp[(i+1)&1][j], dp[(i+1)&1][j+w[i]]+v[i]); + } + } + return dp[0][0]; + } + + // 一维dp + static int dp3(){ + int[] dp = new int[C + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int j = 0; j <= C; j++) { // 注意顺序一定要这样 + dp[j] = j + w[i] > C ? dp[j] : Math.max(dp[j], dp[j + w[i]] + v[i]); + } + } + return dp[0]; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + int T = in.nextInt(); + for (int t = 0; t < T; t++) { + n = in.nextInt(); + C = in.nextInt(); + w = new int[n]; + v = new int[n]; + for (int i = 0; i < n; i++) v[i] = in.nextInt(); + for (int i = 0; i < n; i++) w[i] = in.nextInt(); + dp = new int[n][C + 1]; + for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1); +// out.println(rec(0, 0)); +// out.println(dp()); +// out.println(dp2()); + out.println(dp3()); + out.flush(); + } + out.close(); + } +} +``` + +## 五、最长递增子序列 + +这个也是笔试中可能出现变种的题目的。 + +[详细讲解博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20354.%20Russian%20Doll%20Envelopes%E5%8F%8A%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93.md)。 + +```java +import java.io.PrintWriter; +import java.util.Arrays; + +/** + * 最长公共子序列 + * 题目: https://leetcode-cn.com/problems/longest-increasing-subsequence/ + */ +public class M5_LIS { + + // O(N^2) + public int lengthOfLIS(int[] nums){ + if(nums == null || nums.length == 0) return 0; + int[] dp = new int[nums.length]; + int res = 1; + for(int i = 0; i < nums.length; i++){ + dp[i] = 1; + for(int j = 0; j < i; j++){ + if(nums[j] < nums[i]) + dp[i] = Math.max(dp[i], dp[j] + 1); + } + res = Math.max(res, dp[i]); + } + return res; + } + + // O(N^2) + static int[] getDp(int[] nums){ + if(nums == null || nums.length == 0) return new int[]{}; + int[] dp = new int[nums.length]; + for(int i = 0; i < nums.length; i++){ + dp[i] = 1; + for(int j = 0; j < i; j++){ + if(nums[j] < nums[i]) + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + return dp; + } + + static int[] getLIS(int[] arr, int[] dp){ + int maxLen = 0, end = 0; + for(int i = 0; i < dp.length; i++) if(dp[i] > maxLen){ + maxLen = dp[i]; + end = i; + } + int[] lis = new int[maxLen]; + lis[--maxLen] = arr[end]; + for(int i = end - 1; i >= 0; i--){ + if(dp[i] == dp[end] - 1 && arr[i] < arr[end]){ + lis[--maxLen] = arr[i]; + end = i; + } + } + return lis; + } + + // O(N * logN) + public int lengthOfLIS2(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + int[] ends = new int[nums.length + 1]; + dp[0] = 1; + ends[1] = nums[0]; + int right = 1; // [1~right]为有效区 ends数组是有序的(升序), right是右边界 + int L, mid, R; + for (int i = 1; i < nums.length; i++) { + L = 1; + R = right; + // 找到第一个>=arr[i]的,返回结果是 L + while (L <= R) { + mid = L + (R - L) / 2; + if (ends[mid] >= nums[i]) + R = mid - 1; + else + L = mid + 1; + } + // 说明以arr[i]以arr[i]结尾的最长递增子序列=ends区有效长度+1 + if (L > right) { //没有找到arr[i]是最长的 (因为下标从1开始,所以判断是>right), + dp[i] = right + 1; + ends[right + 1] = nums[i]; // 扩大ends数组 + right += 1; //扩大有效区 + } else { // 找到了arr[l] > arr[i], 更新end[l] = arr[i] ,表示l长度的最长子序列结尾可以更新为arr[i] + dp[i] = right; // dp[i]还是没有加长 + ends[L] = nums[i]; + } + } + return right; + } + + + public static void main(String[] args){ + PrintWriter out = new PrintWriter(System.out); + int[] arr = {10,9,2,5,3,7,101,18}; + out.println(Arrays.toString(getLIS(arr, getDp(arr)))); + + out.close(); + } +} +``` + +## 六、最长公共子序列 + +也是笔试中可能出现的经典DP题目。 + +[详细讲解博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/DP/51Nod%20-%201006.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97LCS%20%E5%92%8C%20%E6%9C%80%E9%95%BF%E5%85%AC%E4%BC%97%E5%AD%90%E4%B8%B2.md) + +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +/** + * 最长公共子串 + * 题目: http://www.51nod.com/Challenge/Problem.html#!#problemId=1006 + */ +public class M6_LCS { + + /** + * dp[i][j]代表的是 str[0..i]与str[0...j]的最长公共子序列 + */ + static int[][] getDp(char[] s1, char[] s2) { + int n1 = s1.length, n2 = s2.length; + int[][] dp = new int[n1][n2]; + dp[0][0] = s1[0] == s2[0] ? 1 : 0; + for (int i = 1; i < n1; i++) // 一旦dp[i][0]被设置成1,则dp[i~N-1][0]都为1 + dp[i][0] = Math.max(dp[i - 1][0], s1[i] == s2[0] ? 1 : 0); + for (int j = 1; j < n2; j++) + dp[0][j] = Math.max(dp[0][j - 1], s2[j] == s1[0] ? 1 : 0); + + for (int i = 1; i < n1; i++) { + for (int j = 1; j < n2; j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + if (s1[i] == s2[j]) { + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); + } + } + } + return dp; + } + + static String getLCS(char[] s1, char[] s2, int[][] dp) { + if(s1 == null || s1.length == 0 || s2 == null || s2.length == 0) + return ""; + int i = s1.length - 1; + int j = s2.length - 1; + char[] res = new char[dp[i][j]]; //生成答案的数组 + int index = dp[i][j] - 1; + while (index >= 0) { + if (i > 0 && dp[i][j] == dp[i - 1][j]) { + i--; + } else if (j > 0 && dp[i][j] == dp[i][j - 1]) { + j--; + } else { // dp[i][j] = dp[i-1][j-1]+1 + res[index--] = s1[i]; + i--; + j--; + } + } + return String.valueOf(res); + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + char[] s1 = in.next().toCharArray(); + char[] s2 = in.next().toCharArray(); + + int[][] dp = getDp(s1, s2); +// System.out.println(dp[s1.length-1][s2.length-1]); //length of lcs + System.out.println(getLCS(s1, s2, dp)); + } +} +``` + +## 七、最长公共子串 + +同理,也是可能出现的。 + +[详细讲解博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/DP/51Nod%20-%201006.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97LCS%20%E5%92%8C%20%E6%9C%80%E9%95%BF%E5%85%AC%E4%BC%97%E5%AD%90%E4%B8%B2.md)。 + +```java +/** + * 最长公共子串问题 + * 题目: https://www.nowcoder.com/questionTerminal/02e7cc263f8a49e8b1e1dc9c116f7602?toCommentId=1532408 + */ +public class M7_LSS { + + public int findLongest(String A, int n, String B, int m) { + char[] s1 = A.toCharArray(); + char[] s2 = B.toCharArray(); + int[][] dp = new int[s1.length][s2.length]; + for (int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同 + dp[i][0] = s1[i] == s2[0] ? 1 : 0; + for (int j = 0; j < s2.length; j++) + dp[0][j] = s1[0] == s2[j] ? 1 : 0; + int res = 0; + for (int i = 1; i < s1.length; i++) { + for (int j = 1; j < s2.length; j++) { + if (s1[i] == s2[j]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + res = Math.max(res, dp[i][j]); + } + } + } + return res; //dp数组中的最大值,就是最大公共字串的长度 + } + + static int[][] getDp(char[] s1, char[] s2) { + int[][] dp = new int[s1.length][s2.length]; + for (int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同 + dp[i][0] = s1[i] == s2[0] ? 1 : 0; + for (int j = 0; j < s2.length; j++) + dp[0][j] = s1[0] == s2[j] ? 1 : 0; + int res = 0; + for (int i = 1; i < s1.length; i++) { + for (int j = 1; j < s2.length; j++) { + if (s1[i] == s2[j]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + res = Math.max(res, dp[i][j]); + } + } + } + System.out.println(res); //4 + return dp; //dp数组中的最大值,就是最大公共字串的长度 + } + + /** + * 根据dp表得到答案 + */ + static String getLongestSubstring(String s1, String s2, int[][] dp) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) + return ""; + int max = 0, end = 0; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + if (dp[i][j] > max) { + max = dp[i][j]; + end = i; + } + } + } + return s1.substring(end - max + 1, end + 1); + } + + // 空间优化 + public int findLongest(String A, String B) { + char[] s1 = A.toCharArray(); + char[] s2 = B.toCharArray(); + int row = 0, col = s2.length - 1; //从右上角开始 + int max = 0, end = 0; //记录最大长度和结束位置 + while (row < s1.length) { + int i = row, j = col; + int ul = 0; + while (i < s1.length && j < s2.length) { + if (s1[i] == s2[j]) + ul++; + else + ul = 0; + if (ul > max) { + max = ul; + end = i; + } + i++; + j++; + } + if (col > 0) // 还没到最左边 --> 往左移动 + col--; + else + row++; //到了最左 --> 往下移动 + } + return max; + //return sa.substring(end-max+1, end+1); // [end-max+1, end] 返回公共子串 + } +} +``` + +## 八、大数加法 + +这个在拼多多的笔试中就出现过。。。 + +可以看[这篇博客](https://www.cnblogs.com/wuqianling/p/5387099.html)吧,比较简单,就没有自己写了。 + +```java +public class M8_BigAdd { + + //大数加法 + static String add(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int maxL = Math.max(n1, n2); + int[] a = new int[maxL + 1];//注意a,b的数组大小都必须是maxL+1 + int[] b = new int[maxL + 1]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < maxL; i++){ + if(a[i] + b[i] >= 10){ + int tmp = a[i] + b[i];//注意一定要先抽取出来 + a[i] = tmp%10; + a[i+1] += tmp/10; + }else + a[i] += b[i]; + } + StringBuilder sb = new StringBuilder(); + if(a[maxL] != 0) sb.append((char)(a[maxL] + '0')); + for(int i = maxL-1; i >= 0; i--) sb.append((char)(a[i] + '0')); + return sb.toString(); + } +} +``` + +## 九、大数乘法 + +也不难。 + +```java +public class M9_BigMul { + + // 大数乘法 + static String mul(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int[] a = new int[n1]; + int[] b = new int[n2]; + int[] c = new int[n1 + n2]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < n1; i++){ + for(int j = 0; j < n2; j++){ + c[i+j] += a[i] * b[j]; + } + } + for(int i = 0; i < n1 + n2 - 1; i++){ + if(c[i] >= 10){ + c[i+1] += c[i]/10; + c[i] %= 10; + } + } + int i; + for(i = n1 + n2 - 1; i >= 0; i--) if(c[i] != 0) break; + StringBuilder sb = new StringBuilder(); + for(; i >= 0; i--) sb.append( (char)(c[i] + '0')); + return sb.toString(); + } +} + +``` + +## 十、大数阶乘 + +这个稍微特殊一点。我这里简单讲一下,举个例子结合代码就懂了。 + +比如算`50`的阶乘: + +* 我们要先从1开始乘:`1*2=2`,将2存到`a[0]`中; +* 接下来是用`a[0]*3`;`2*3=6`,将`6`储存在`a[0]`中; +* 接下来是用`a[0]*4`;`6*4=24`,是两位数,那么`24%10==4`存到`a[0]`中,`24/10==2`存到`a[1]`中; +* 接下来是用`a[0]*5`;`a[1]*5+num`(如果前一位相乘结果位数是两位数,那么`num`就等于十位上的那个数字;如果是一位数,`num==0`);`24*5=120`,是三位数,那么`120%10==0`存到`a[0]`中,`120/10%10==2`存到`a[1]`中,`120/100==1`存到`a[2]`中; +* 接下来是用`a[0]*6`、`a[1]*6+num`、`a[2]*6+num`、`120*6=720`,那么`720%10==0`存到`a[0]`中,`720/10%10==2`存到`a[1]`中,`720/100==7`存到`a[2]`中。。。 + +代码: + + +```java +/** + * 题目链接: + * http://nyoj.top/problem/28 + */ +public class M10_BigPow { + + //大数计算阶乘位数,可以自己在网上找一下博客 + //lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1; + static int factorialDigit(int n) { + double sum = 0; + for (int i = 1; i <= n; i++) + sum += Math.log10(i); + return (int) sum + 1; + } + + static String bigFactorial(int n) { + int[] res = new int[100001]; + int digit = 1; + res[0] = 1; + for (int i = 2; i <= n; i++) { + int carry = 0; + for (int j = 0; j < digit; j++) { + int temp = res[j] * i + carry; //每一位的运算结果 + res[j] = temp % 10; //将最低位保留在原位置 + carry = temp / 10; //计算进位, 等下这个进位会累加到j+1 + } + while (carry != 0) { + res[digit] = carry % 10; + carry /= 10; + digit++; + } + } + StringBuilder sb = new StringBuilder(); + for (int i = digit - 1; i >= 0; i--) sb.append( (char)(res[i] + '0')); + return sb.toString(); + } + + public static void main(String[] args){ + System.out.println(bigFactorial(5)); + } +} + +``` + +## 十一、全排列 + +总共有四种,前两种是刘汝佳的书中的,后面是经典的和`dfs`的。给出三个博客(后两个我自己的): + +* [枚举生成全排列](https://www.cnblogs.com/Kohinur/p/8523856.html)。 +* [非去重全排列](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2046.%20Permutations(%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95).md)。 +* [去重全排列](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2047.%20Permutations%20II(%E4%B8%8D%E9%87%8D%E5%A4%8D%E5%85%A8%E6%8E%92%E5%88%97)(%E5%9B%9B%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%AE%9E%E7%8E%B0).md)。 + +```java +public class M11_Permutation { + + static PrintStream out = System.out; + + // 1 ~ n 的排列 + static void permutation(int[] tmp, int cur, int n) { + if (cur == n) { // 边界 + for (int i = 0; i < n; i++) + out.print(tmp[i] + " "); + out.println(); + } else for (int i = 1; i <= n; i++) { //尝试在arr[cur]中填充各种整数 (1~n) + boolean flag = true; + for (int j = 0; j < cur; j++) + if (i == tmp[j]) { // 如果i已经在arr[0]~arr[cur-1]中出现过,则不能选 + flag = false; + break; + } + if (flag) { + tmp[cur] = i; //把i填充到当前位置 + permutation(tmp, cur + 1, n); + } + } + } + + // 数组的去重全排列 + // tmp存放排列,arr是原数组 + static void permutation(int[] tmp, int[] arr, int cur, int n) { + if (cur == n) { + for (int i = 0; i < n; i++) + out.print(tmp[i] + " "); + out.println(); + } else for (int i = 0; i < n; i++) if (i == 0 || arr[i] != arr[i - 1]) { + int c1 = 0, c2 = 0; + for (int j = 0; j < n; j++) + if (arr[j] == arr[i]) // 重复元素的个数 + c1++; + for (int j = 0; j < cur; j++) + if (tmp[j] == arr[i]) // 前面已经排列的重复元素的个数 + c2++; + if (c2 < c1) { + tmp[cur] = arr[i]; + permutation(tmp, arr, cur + 1, n); + } + } + } + + //非去重 经典全排列 + static void permutation_2(int[] arr, int cur, int n){ + if(cur == n){ + for(int i = 0; i < n; i++) out.print(arr[i] + " "); + out.println(); + return; + } + for(int i = cur; i < n; i++){ + swap(arr, i, cur); + permutation_2(arr, cur + 1, n); + swap(arr, i, cur); + } + } + + static void swap(int[] arr, int a, int b){ + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } + + // 用一个used数组来求得全排列 + static void dfs(int[] arr, ArrayList list, boolean[] used){ + if(list.size() == arr.length){ + for(int num : list) out.print(num + " "); + out.println(); + return; + } + for(int i = 0; i < arr.length; i++){ + // if (used[i] || (i > 0 && !used[i - 1] && arr[i] == arr[i - 1])) continue; // 去重的写法,去重要先排序 Arrays.sort(arr); + if(used[i]) continue; + used[i] = true; + list.add(arr[i]); + + dfs(arr, list, used); + + list.remove(list.size() - 1); + used[i] = false; + } + } + + public static void main(String[] args) { + int n = 5; + permutation(new int[n], 0, n); + + out.println("--------------"); + + int[] arr = {1, 1, 1}; + // 需要先排序 , 上面的排列只会输出 1,1,1因为我们去重了 + Arrays.sort(arr); + permutation(new int[arr.length], arr, 0, arr.length); + + out.println("--------------"); + permutation_2(new int[]{1, 1}, 0, 2); // 输出两个{1, 1} + + out.println("---------------"); + + dfs(new int[]{1, 3, 2}, new ArrayList<>(), new boolean[3]); + } +} +``` + +输出: + +```java +1 2 3 +1 3 2 +2 1 3 +2 3 1 +3 1 2 +3 2 1 +-------------- +1 1 1 +-------------- +1 1 +1 1 +--------------- +1 3 2 +1 2 3 +3 1 2 +3 2 1 +2 1 3 +2 3 1 +``` + +## 十二、子集 + +这个模板在`LeetCode`上面也是有原题的。例如[LeetCode - 78. Subsets](https://leetcode-cn.com/problems/subsets/)。 + +关于第三种方法(二进制枚举),这里做简单解释: + + `n = 3`;则要枚举`0 - 7` 对应的是有`7`个子集,每个子集去找有哪些元素`print_subset`中的 `1<< i `,也就是对应的那个位置是有元素的,例如`1`的二进制是`0001`也就是代表`0`位置有元素,`0010`是`2`,代表第一个位置是`1`,`0100`代表第`2`个位置上有元素,相应的`1000 = 8`对应第`3`个位置上有元素。 总结来说也就是对应`1<< i`对应` i`上是`1`(从`0`开始),其余位置是`0`。看图容易理解: + +

+ +代码: + +```java +import java.io.PrintStream; + +public class M12_Subset { + + static PrintStream out = System.out; + + //打印0~n-1的所有子集 + //按照递增顺序就行构造子集 防止子集的重复 + static void print_subset(int[] arr, int cur, int n){ + for(int i = 0; i < cur; i++) + out.print(arr[i] + " "); + out.println(); + int s = cur != 0 ? arr[cur-1] + 1 : 0; //确定当前元素的最小可能值 + for(int i = s; i < n; i++){ + arr[cur] = i; + print_subset(arr, cur+1, n); + } + } + + // 1~n 的所有子集:位向量法 + static void print_subset(int cur, boolean[] bits, int n) { + if (cur == n+1) { + for (int i = 1; i < cur; i++) + if (bits[i]) + out.print(i + " "); + out.println(); + return; + } + bits[cur] = true; + print_subset(cur + 1, bits, n); + bits[cur] = false; + print_subset(cur + 1, bits, n); + } + + // 0 ~ n-1的所有子集:二进制法枚举0 ~ n-1的所有子集 + static void print_subset(int n){ + for(int mask = 0; mask < (1 << n); mask++){ + for(int i = 0; i < n; i++) + if( ((mask >> i) & 1) == 1) //和下面一样 +// if( ((1 << i) & mask) != 0) + out.print(i + " "); + out.println(); + } + } + + public static void main(String[] args){ + int n = 3; + // 0~n-1的子集 + print_subset(new int[n], 0, n); + out.println("---------------"); + + // 1 ~ n 的子集 + print_subset(1, new boolean[n+1], n); + out.println("---------------"); + + // 1~n的子集 + print_subset(n); + } +} + +``` + +输出: + +```java +0 +0 1 +0 1 2 +0 2 +1 +1 2 +2 +--------------- +1 2 3 +1 2 +1 3 +1 +2 3 +2 +3 + +--------------- + +0 +1 +0 1 +2 +0 2 +1 2 +0 1 2 +``` + +给出`LeetCode-78`的代码: + +```java +import java.util.*; + +class Solution { + + private boolean[] bit; + private List> res; + + public List> subsets(int[] nums) { + res = new ArrayList<>(); + bit = new boolean[nums.length]; + dfs(0, nums); + return res; + } + + //用一个bit数组记录 + private void dfs(int cur, int[] arr) { + if (cur == arr.length) { + List tmp = new ArrayList<>(); + for (int i = 0; i < cur; i++) if (bit[i]) tmp.add(arr[i]); + res.add(new ArrayList<>(tmp)); + return; + } + bit[cur] = true; + dfs(cur + 1, arr); + bit[cur] = false; + dfs(cur + 1, arr); + } + + public static void main(String[] args){ + System.out.println(new Solution().subsets(new int[]{1, 2, 3})); + } +} +``` + +第二种使用二进制枚举: + +```java +class Solution { + public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + for(int mask = 0; mask < (1 << nums.length); mask++){ + List tmp = new ArrayList<>(); + for(int i = 0; i < nums.length; i++) if( ( (mask >> i) & 1) == 1) tmp.add(nums[i]); + res.add(tmp); + } + return res; + } +} +``` + +## 十三、N皇后 + +N皇后问题也是经典的回溯问题。[详细解释博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2051.%20N-Queens(N%E7%9A%87%E5%90%8E%E9%97%AE%E9%A2%98).md)。 + +```java +public class M13_NQueue { + + static PrintStream out = System.out; + + static int count; + + // 第一种解法 + static void dfs(int r, int n, int[] cols) { // 当前是r行 + if (r == n) { + count++; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (cols[i] == j) + out.print("0 "); + else + out.print(". "); + } + out.println(); + } + out.println("-------------------"); + return; + } + for (int c = 0; c < n; c++) { // 考察的是每一列 + cols[r] = c; // 尝试将 r行的皇后放在第c列 + boolean ok = true; + for (int i = 0; i < r; i++) { //检查是否和已经放置的冲突 + //检查列,"副对角线","主对角线" + if (cols[r] == cols[i] || r - i == cols[r] - cols[i] || r - i == cols[i] - cols[r]) { + ok = false; + break; + } + } + if (ok) dfs(r + 1, n, cols); + } + } + + // 第二种解法: 使用cs三个数组记录,这里只是统计数目,没有保留解 + static void dfs(int r, boolean[] cs, boolean[] d1, boolean[] d2, int n) { // 当前是r行 + if (r == n) { + count++; + return; + } + for (int c = 0; c < n; c++) { //考察的是每一列 + int id1 = r + c; //主对角线 + int id2 = r - c + n - 1; // 副对角线 + if (cs[c] || d1[id1] || d2[id2]) continue; + cs[c] = d1[id1] = d2[id2] = true; + + dfs(r + 1, cs, d1, d2, n); + + cs[c] = d1[id1] = d2[id2] = false; + } + } + + + // 第二种解法的升级: 上一个版本的升级,使用cols数组 保留解 + static void dfs(int r, boolean[][] vis, int[] cols, int n) { //逐行放置皇后 + if (r == n) { + count++; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (cols[i] == j) + out.print("0 "); + else + out.print(". "); + } + out.println(); + } + out.println("--------------"); + return; + } + for (int c = 0; c < n; c++) { //尝试在 cur行的 各 列 放置皇后 + if (vis[0][c] || vis[1][r + c] || vis[2][r - c + n - 1]) continue;//判断当前尝试的皇后的列、主对角线 + vis[0][c] = vis[1][r + c] = vis[2][r - c + n - 1] = true; + cols[r] = c; //r行的列是 c + dfs(r + 1, vis, cols, n); + vis[0][c] = vis[1][r + c] = vis[2][r - c + n - 1] = false;//切记!一定要改回来 + } + } + + public static void main(String[] args) { + + int n = 8; // 8皇后 + + count = 0; + dfs(0, n, new int[n]); // 8皇后 + out.println(count); + out.println("====================="); + + count = 0; + dfs(0, new boolean[n], new boolean[2 * n - 1], new boolean[2 * n - 1], n); + out.println(count); + out.println("====================="); + + count = 0; + dfs(0, new boolean[3][2*n-1], new int[n], n); + out.println(count); + } +} +``` + +## 十四、并查集 + +并查集简单又好用,具体看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/UnionFind/POJ%20-%201611.%20The%20Suspects%E4%BB%A5%E5%8F%8A%E5%B9%B6%E6%9F%A5%E9%9B%86%E6%80%BB%E7%BB%93.md)。 + +```java +import java.io.*; +import java.util.Scanner; + +/** + * 基于rank的并查集 + * 题目链接:http://poj.org/problem?id=1611 + * 题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染 + */ + +public class M14_UnionFind_1 { + + static int[] f; + static int[] rank; + + static int findRoot(int p) { + while (p != f[p]) { + f[p] = f[f[p]]; + p = f[p]; + } + return p; + } + + static void union(int a, int b) { + int aR = findRoot(a); + int bR = findRoot(b); + if (aR == bR) return; + if (rank[aR] < rank[bR]) { + f[aR] = f[bR]; + } else if (rank[aR] > rank[bR]) { + f[bR] = f[aR]; + } else { + f[aR] = f[bR]; + rank[bR]++; + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + while (in.hasNext()) { + int n = in.nextInt(); + int m = in.nextInt(); + if(n == 0 && m == 0) break; + f = new int[n]; + rank = new int[n]; + for(int i = 0; i < n; i++) { + f[i] = i; + rank[i] = 1; + } + for(int i = 0; i < m; i++){ + int c = in.nextInt(); + int root = in.nextInt(); + for(int j = 0; j < c - 1; j++) { + int num = in.nextInt(); + union(root, num); + } + } + int res = 1; // 0已经感染 + for(int i = 1; i < n; i++) + if(findRoot(0) == findRoot(i)) res++; + out.println(res); + } + } +} +``` + +第二种基于size的 + +```java +import java.io.*; +import java.util.Scanner; + +/** + * 基于size的并查集 + * 题目链接:http://poj.org/problem?id=1611 + * 题目大意 : 病毒传染,可以通过一些社团接触给出一些社团(0号人物是被感染的)问有多少人(0~n-1个人)被感染 + */ + +public class M14_UnionFind_2 { + + static int[] f; + static int[] sz; // size + + static int findRoot(int p) { + while (p != f[p]) { + f[p] = f[f[p]]; + p = f[p]; + } + return p; + } + + // 将元素个数少的集合合并到元素个数多的集合上 + static void union(int a, int b) { + int aR = findRoot(a); + int bR = findRoot(b); + if (aR == bR) return; + if (sz[aR] < sz[bR]) { + f[aR] = f[bR]; + sz[bR] += sz[aR]; // 更新集合元素个数 + }else{ + f[bR] = f[aR]; + sz[aR] += sz[bR]; + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + while (in.hasNext()) { + int n = in.nextInt(); + int m = in.nextInt(); + if(n == 0 && m == 0) break; + f = new int[n]; + sz = new int[n]; + for(int i = 0; i < n; i++) { + f[i] = i; + sz[i] = 1; + } + for(int i = 0; i < m; i++){ + int c = in.nextInt(); + int root = in.nextInt(); + for(int j = 0; j < c - 1; j++) { + int num = in.nextInt(); + union(root, num); + } + } + int res = 1; // 0已经感染 + for(int i = 1; i < n; i++) if(findRoot(0) == findRoot(i)) res++; + out.println(res); + } + } +} +``` + +## 十五、树状数组 + +树状数组,最好的解释就是看[这个网站](https://visualgo.net/en/fenwicktree?slide=1)的动图。 + +```java +public class M15_FenWick { + + // 原题: https://leetcode.com/problems/range-sum-query-mutable/ + class NumArray { + + private int[] sums;// 树状数组中求和的数组 + private int[] data;//真实存放数据的数组 + private int n; + + private int lowbit(int x) {return x & (-x);} + + private int query(int i){ + int s = 0; + while(i > 0){//树状数组中索引是1~n + s += sums[i]; + i -= lowbit(i); + } + return s; + } + + // fenWick update + private void renewal(int i, int delta){// delta是增量,不是新值 + while(i <= n){//树状数组中索引是1~n + sums[i] += delta; + i += lowbit(i); + } + } + + public NumArray(int[] nums) { + n = nums.length; + sums = new int[n+1]; + data = new int[n]; + for(int i = 0; i < n; i++) { + data[i] = nums[i]; + renewal(i+1, nums[i]); + } + } + + public void update(int i, int val) { + renewal(i+1, val - data[i]); + data[i] = val; + } + + public int sumRange(int i, int j) { + return query(j+1) - query(i); + } + } + + public static void main(String[] args){ + + } +} +``` + +## 十六、线段树 + +线段树出现的少,可以选择性的学习。具体可看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/SegmentTree/%E7%BA%BF%E6%AE%B5%E6%A0%91%E6%80%BB%E7%BB%93%E4%BB%A5%E5%8F%8ALeetCode%20-%20307.%20Range%20Sum%20Query%20-%20Mutable.md)。 + +```java +public class M16_SegmentTree_1 { + + // 原题(和树状数组出自同一个): https://leetcode.com/problems/range-sum-query-mutable/ + class NumArray { + + class SegTree { + + int[] tree; + int[] data; + + public SegTree(int[] arr) { + data = new int[arr.length]; + for (int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = new int[4 * arr.length]; //最多需要4 * n + buildTree(0, 0, arr.length - 1); + } + + public void buildTree(int treeIndex, int start, int end) { + if (start == end) { + tree[treeIndex] = data[start]; + return; + } + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + int m = start + (end - start) / 2; + buildTree(treeLid, start, m); + buildTree(treeRid, m + 1, end); + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //区间求和 + } + + public int query(int qL, int qR) { + if (qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR) return -1; + return query(0, 0, data.length - 1, qL, qR); + } + + private int query(int treeIndex, int start, int end, int qL, int qR) { + if (start == qL && end == qR) { + return tree[treeIndex]; + } + int mid = start + (end - start) / 2; + int treeLid = treeIndex * 2 + 1; + int treeRid = treeIndex * 2 + 2; + + if (qR <= mid) { //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeLid, start, mid, qL, qR); + } else if (qL > mid) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeRid, mid + 1, end, qL, qR); + } else { //在两边都有,查询的结果 合并 + return query(treeLid, start, mid, qL, mid) + //注意是查询 [qL,m] + query(treeRid, mid + 1, end, mid + 1, qR); //查询[m+1,qR] + } + } + + public void update(int index, int val) { + data[index] = val; //首先修改data + update(0, 0, data.length - 1, index, val); + } + + private void update(int treeIndex, int start, int end, int index, int val) { + if (start == end) { + tree[treeIndex] = val; // 最后更新 + return; + } + int m = start + (end - start) / 2; + int treeLid = 2 * treeIndex + 1; + int treeRid = 2 * treeIndex + 2; + if (index <= m) { //左边 + update(treeLid, start, m, index, val); + } else { + update(treeRid, m + 1, end, index, val); + } + tree[treeIndex] = tree[treeLid] + tree[treeRid]; //更新完左右子树之后,自己受到影响,重新更新和 + } + } + + private SegTree segTree; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) return; + segTree = new SegTree(nums); + } + + public void update(int i, int val) { + segTree.update(i, val); + } + + public int sumRange(int i, int j) { + return segTree.query(i, j); + } + } +} + +``` + +第二种写法,基于树的引用: + +```java +public class M16_SegmentTree_2 { + + // 线段树第二种写法 + // 题目: https://leetcode.com/problems/range-sum-query-mutable/ + + class SegNode{ + int start; // 表示的区间的左端点 + int end; // 表示区间的右端点 , 当start == end的时候就只有一个元素 + int sum; + SegNode left; + SegNode right; + + public SegNode(int start, int end, int sum, SegNode left, SegNode right) { + this.start = start; + this.end = end; + this.sum = sum; + this.left = left; + this.right = right; + } + } + + class NumArray { + + SegNode root; + int[] arr; + + private SegNode buildTree(int s, int e){ + if(s == e) + return new SegNode(s, e, arr[s], null, null); + int mid = s + (e - s) / 2; + SegNode L = buildTree(s, mid); + SegNode R = buildTree(mid+1, e); + return new SegNode(s, e, L.sum + R.sum, L, R); + } + + private void update(SegNode node, int i, int val){ + if(node.start == node.end && node.start == i){ + node.sum = val; + return; + } + int mid = node.start + (node.end - node.start) / 2; + if(i <= mid) + update(node.left, i, val); + else + update(node.right, i, val); + node.sum = node.left.sum + node.right.sum; // 记得下面的更新完之后,更新当前的和 + } + + private int query(SegNode node, int i, int j){ + if(node.start == i && node.end == j) + return node.sum; + int mid = node.start + (node.end - node.start) / 2; + if(j <= mid){ // 区间完全在左边 + return query(node.left, i, j); + }else if(i > mid) { // 区间完全在右边 + return query(node.right, i, j); + }else { + return query(node.left, i, mid) + query(node.right, mid+1, j); + } + } + + public NumArray(int[] nums) { + arr = new int[nums.length]; + for(int i = 0; i < nums.length; i++) arr[i] = nums[i]; + if(nums.length != 0) + root = buildTree(0, nums.length-1); + } + + public void update(int i, int val) { + arr[i] = val; + update(root, i, val); + } + + public int sumRange(int i, int j) { + return query(root, i, j); + } + } +} +``` + +## 十七、字典树 + +具体看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/Trie/LeetCode%20-%20208.%20Implement%20Trie%20(Prefix%20Tree)%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%AD%97%E5%85%B8%E6%A0%91(%E5%89%8D%E7%BC%80%E6%A0%91).md)。 + +下面测试的例子: + +树中有`"abc","ab","ab","abd","bc","bd","cd","cde","ce"`总共9个字符串。如下的结构: + +![images.png](images/acm_4.png) + +代码: + +```java +public class M17_Trie { + + // Trie + static class Trie { + + private class Node { + int path; + int end; + Node[] next;//使用整数表示字符 c - 'a' + + public Node() { + path = 0; + end = 0; + next = new Node[26]; + } + } + + private Node root; + + public Trie() { + root = new Node(); + } + + //插入一个字符串 + public void insert(String word) { + if (word == null) + return; + Node cur = root; + int index = 0; + for (int i = 0; i < word.length(); i++) { + index = word.charAt(i) - 'a'; + if (cur.next[index] == null) { //没有就新建 + cur.next[index] = new Node(); + } + cur = cur.next[index]; + cur.path++; //经过这里 + } + cur.end++; + } + + //统计某个字符串的数量 + public int count(String word) { + if (word == null) + return 0; + Node cur = root; + int index = 0; + for (int i = 0; i < word.length(); i++) { + index = word.charAt(i) - 'a'; + if (cur.next[index] == null) + return 0; + cur = cur.next[index]; + } + return cur.end; + } + + public boolean search(String word) { + return count(word) > 0; + } + + // 求前缀是prefix的数量 + public int prefixNum(String prefix) { + if (prefix == null) + return 0; + Node cur = root; + int index = 0; + for (int i = 0; i < prefix.length(); i++) { + index = prefix.charAt(i) - 'a'; + if (cur.next[index] == null) + return 0; + cur = cur.next[index]; + } + return cur.path; //返回这个经过的  也就是以这个为前驱的 + } + + public boolean startsWith(String prefix) { + return prefixNum(prefix) > 0; + } + + // 在trie中删除word + public void remove(String word) { + if (word == null) + return; + if (!search(word)) //不包含这个字符串 + return; + Node cur = root; + int index = 0; + for (int i = 0; i < word.length(); i++) { + index = word.charAt(i) - 'a'; + if (--cur.next[index].path == 0) { + cur.next[index] = null; //释放掉下面的这棵树 + return; + } + cur = cur.next[index]; + } + cur.end--; //最后这个字符串也要-- + } + } + + public static void main(String[] args) { + + // 简单测试 + + Trie trie = new Trie(); + + trie.insert("abc"); + trie.insert("ab"); + trie.insert("ab"); + trie.insert("abd"); + trie.insert("bc"); + trie.insert("bd"); + trie.insert("cd"); + trie.insert("cde"); + trie.insert("ce"); + + System.out.println(trie.count("ab")); + trie.remove("ab"); + System.out.println(trie.count("ab")); + + System.out.println(trie.count("abd")); + trie.remove("ab"); + System.out.println(trie.count("ab")); + System.out.println(trie.count("abd")); + + trie.remove("abd"); + System.out.println(trie.count("abd")); + } +} +``` + +## 十八、单调栈 + +解决的是: **快速寻找一个数组中每一个元素 左右两边离它`arr[i]`最近的比它大/小的数**。 + +具体看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/MonotoneStack/%E5%8D%95%E8%B0%83%E6%A0%88%E4%BB%8B%E7%BB%8D%E4%BB%A5%E5%8F%8A%E6%9E%84%E9%80%A0%E6%95%B0%E7%BB%84%E7%9A%84MaxTree%E9%97%AE%E9%A2%98.md)。 + +测试中的例子: + +![acm_5.png](images/acm_5.png) + +代码: + +```java +import java.io.PrintStream; +import java.util.Stack; + +/** + * 单调栈: 寻找一个数组中每一个元素 左右两边离它arr[i]最近的比它大的数 + * 栈底到栈顶: 由大到小 (也可以自定义从小到大) + */ +public class M18_MonotoneStack { + + static PrintStream out = System.out; + + public static void main(String[] args) { + + int[] arr = {3, 4, 5, 1, 2}; + int n = arr.length; + + /**--------------找左边的第一个比arr[i]大的-------------------*/ + int[] LL = new int[n]; //LL[i]存的是左边第一个比arr[i]大的数的下标 + Stack stack = new Stack<>(); + for(int i = 0; i < n; i++){ + while(!stack.isEmpty() && arr[i] > arr[stack.peek()]){ + int top = stack.pop(); + if(stack.isEmpty()){ + LL[top] = -1; //左边没有比arr[i]大的数 + }else { + LL[top] = stack.peek(); + } + } + stack.push(i); //注意是下标入栈 + } + + // 如果栈不空 //处理剩下的 + while(!stack.isEmpty()){ + int top = stack.pop(); + if(stack.isEmpty()) LL[top] = -1; + else LL[top] = stack.peek(); + } + + for(int i = 0; i < n; i++) out.print(LL[i] + " "); // -1 -1 -1 2 2 + out.println(); + + /**--------------找右边的第一个比arr[i]大的-------------------*/ + + int[] RR = new int[n];//RR[i]存的是右边边第一个比arr[i]大的数的下标 + stack = new Stack<>(); + + // 反过来即可 + for(int i = n-1; i >= 0; i--){ + while(!stack.isEmpty() && arr[i] > arr[stack.peek()]){ + int top = stack.pop(); + if(stack.isEmpty()){ + RR[top] = -1; //左边没有比arr[i]大的数 + }else { + RR[top] = stack.peek(); + } + } + stack.push(i); //注意是下标入栈 + } + + // 如果栈不空, 处理剩下的 + while(!stack.isEmpty()){ + int top = stack.pop(); + if(stack.isEmpty()) RR[top] = -1; + else RR[top] = stack.peek(); + } + + for(int i = 0; i < n; i++) out.print(RR[i] + " "); // 输出 1 2 -1 4 -1 + out.println(); + } +} +``` + +## 十九、单调队列 + +解决的问题: **用来求出在数组的某个区间范围内求出最大值**。 + +最经典的就是滑动窗口问题。[详细解释博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/LintCode/TwoPointer/LintCode%20-%20362.%20Sliding%20Window%20Maximum%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md)。 + +```java +import java.io.PrintStream; +import java.util.*; + +/** + * 单调队列: 用来求出在数组的某个区间范围内求出最大值 + * 最经典的问题: 滑动窗口的最大值: + * 题目链接: https://www.lintcode.com/problem/sliding-window-maximum/description + */ +public class M19_MonotoneQueue { + + static PrintStream out = System.out; + + //单调双向队列(窗口内最大值), 某一时刻窗口内的最大值是对头 arr[queue.peekFirst()] + public ArrayList maxSlidingWindow(int[] nums, int k) { + if (nums == null || k < 1 || nums.length < k) + return null; + ArrayList res = new ArrayList<>(); + LinkedList queue = new LinkedList<>();//保存的是下标 + for (int i = 0; i < nums.length; i++) { + while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) //要队尾满足条件 + queue.pollLast(); + queue.addLast(i); // 注意添加的是下标 + if (i - k == queue.peekFirst()) + queue.pollFirst();//向左弹出过期的数据 + if (i >= k - 1) // 达到了k个数,这个窗口内的最大值是队列的头部 + res.add(nums[queue.peekFirst()]); + } + return res; + } + + public static void main(String[] args) { + + int[] arr = {1, 2, 7, 7, 8}; + out.println(new M19_MonotoneQueue().maxSlidingWindow(arr, 3)); // 7, 7, 8 + } +} + +``` + +## 二十、KMP + +给出两个博客: + +* [极客学院-KMP算法](http://wiki.jikexueyuan.com/project/kmp-algorithm/define.html)。 +* [Hdu - 1711. KMP总结](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/String/KMP/Hdu%20-%201711.%20Number%20Sequence%E4%BB%A5%E5%8F%8AKMP%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93.md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * KMP: 模式串匹配问题 + * 题目: http://acm.hdu.edu.cn/showproblem.php?pid=1711 + */ +public class M20_KMP { + + static int kmp(int[] s, int[] p) { + if (s == null || p == null || s.length < p.length || p.length == 0) + return -1; + int[] next = getNext(p); + int i1 = 0, i2 = 0; + while (i1 < s.length && i2 < p.length) { + if (s[i1] == p[i2]) { + i1++; + i2++; + } else { + if (next[i2] == -1) { + i1++; + } else { + i2 = next[i2]; + } + } + } + return i2 == p.length ? i1 - i2 : -1; // 返回i2在i1匹配到的第一个位置 + } + + /** + * next数组含义: + * next[i]的含义是在str[i]之前的字符串也就是: str[0...i)中, + * 必须以str[i-1]结尾的后缀子串(不能包含str[0]) 和 + * 必须以str[0]开头的前缀子串(不能包含str[i-1])的最大匹配长度 + */ + static int[] getNext(int[] arr) { + if (arr.length == 1) return new int[]{-1}; + int[] next = new int[arr.length + 1]; + next[0] = -1; + next[1] = 0; + int cn = 0; + for (int i = 2; i <= arr.length; ) { + if (arr[i - 1] == arr[cn]) { + next[i++] = ++cn; + } else { + if (cn > 0) { + cn = next[cn]; + } else { + next[i++] = 0; + } + } + } + return next; + } + + public static void main(String[] args) { +// System.out.println(Arrays.toString(getNext(new int[]{1, 2, 3, 1, 2}))); + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + int k = cin.nextInt(); + while (k-- > 0) { + int n = cin.nextInt(); + int m = cin.nextInt(); + int[] s = new int[n]; + int[] p = new int[m]; //切记不能随便new int[n+1]因为后面用length代替了n + for (int i = 0; i < n; i++) s[i] = cin.nextInt(); + for (int i = 0; i < m; i++) p[i] = cin.nextInt(); + int res = kmp(s, p); + System.out.println(res == -1 ? -1 : res + 1); + } + } +} +``` + +## 二十一、Manacher算法 + +解决的是求最长回文子串的问题。 + +具体看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/String/Manacher/LeetCode%20-%205.%20Longest%20Palindromic%20Substring(%E4%B8%89%E7%A7%8D%E8%A7%A3%E6%B3%95%E5%8F%8AManacher%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3).md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * 马拉车算法: 解决O(N)求最长回文串问题 + * 题目:http://acm.hdu.edu.cn/showproblem.php?pid=3068 + * + * 备注: 如果要求出最长回文,就记录一下取得最长回文的时候最长半径的位置即可 + */ +public class M21_Manacher { + + /** + * 获取指定格式的字符串(中间和两边都带有#) 这样可以处理偶回文 + * 例如 : 如果是abc -->#a#b#c# + * 如果是abcd -->#a#b#c#d# + */ + static char[] manacherString(String str) { + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i < res.length; i++) + res[i] = ((i & 1) == 0) ? '#' : str.charAt(index++); + return res; + } + + static int manacher(String s) { + if (s == null || s.length() == 0) return 0; + char[] chs = manacherString(s); + int[] r = new int[chs.length]; //记录每个位置的最长回文半径,注意是chs的长度 + int R = -1, C = -1; //分别代表目前的最长回文右边界,和它的中心 + int max = Integer.MIN_VALUE; //记录结果 + for (int i = 0; i < chs.length; i++) { + r[i] = R > i ? Math.min(r[2 * C - i], R - i) : 1; //这句代码包含三种情况 第一种大情况,和二种中的(1)(2)情况 + while (i + r[i] < chs.length && i - r[i] >= 0) { //不越界 //注意这包括了四种情况,都要扩一下,为了节省代码 + if (chs[i + r[i]] == chs[i - r[i]]) { // 往前面扩一下 + r[i]++; + } else { //扩不动了 + break; + } + } + if (i + r[i] > R) { //更新最右边界和它的中心 + R = i + r[i]; + C = i; + } + max = Math.max(max, r[i]); //取最大的r[i] (r[i]记录的是每个位置的最长回文半径) + } + return max - 1; //求出来的是加了'#'的 + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + while (in.hasNext()) { + String s = in.next(); + System.out.println(manacher(s)); + } + } +} +``` + +## 二十二、拓扑排序 + +具体可以看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Graph/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/Uva%20-%2010305.%20Ordering%20Tasks%20_%20LeetCode%20-%20207.%20Course%20Schedule%20(%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F).md)。 + +```java +import java.io.*; +import java.util.*; + +/** + * BFS拓扑排序 + * 题目: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1246 + */ +public class M22_TopologySort_1 { + + static ArrayList G[]; + static int[] in; + static int n, m; + static PrintStream out; + + static void topologySort() { + Queue queue = new LinkedList<>(); + for (int i = 1; i <= n; i++) if (in[i] == 0) queue.add(i); + boolean flag = true; // for output + while (!queue.isEmpty()) { + int cur = queue.poll(); + if (flag) { + out.print(cur); + flag = false; + } else + out.print(" " + cur); + for (int i = 0; i < G[cur].size(); i++) { + int to = G[cur].get(i); + if (--in[to] == 0) + queue.add(to); + } + } + out.println(); + } + + public static void main(String[] args) { + Scanner sc = new Scanner(new BufferedInputStream(System.in)); + out = System.out; + while (sc.hasNext()) { + n = sc.nextInt(); + m = sc.nextInt(); + if (n == 0 && m == 0) + break; + in = new int[n + 1]; + G = new ArrayList[n + 1]; + for (int i = 0; i <= n; i++) + G[i] = new ArrayList<>(); + for (int i = 0; i < m; i++) { + int from = sc.nextInt(); + int to = sc.nextInt(); + G[from].add(to); + in[to]++; + } + topologySort(); + } + } +} +``` + +第二种利用dfs记录三个状态: + +```java +import java.io.*; +import java.util.*; + +/** + * DFS拓扑排序 + * 题目: https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1246 + */ +public class M22_TopologySort_2 { + + static ArrayList G[]; + static int[] vis; + static int n, m; + static int[] res; + static int p; + + static boolean dfs(int cur) { + vis[cur] = 2; // now is visiting + for (int to : G[cur]) { + if (vis[to] == 2 || (vis[to] == 0 && !dfs(to))) // exist directed cycle + return false; + } + vis[cur] = 1; // now is visited + res[p--] = cur; + return true; + } + + public static void main(String[] args) { + Scanner sc = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + while (sc.hasNext()) { + n = sc.nextInt(); + m = sc.nextInt(); + if (n == 0 && m == 0) + break; + G = new ArrayList[n + 1]; + vis = new int[n + 1]; + for (int i = 0; i <= n; i++) + G[i] = new ArrayList<>(); + for (int i = 0; i < m; i++) { + int from = sc.nextInt(); + int to = sc.nextInt(); + G[from].add(to); + } + p = n - 1; // back to front + res = new int[n + 1]; + boolean ok = true; + for (int i = 1; i <= n; i++) { + if (vis[i] == 0) + dfs(i); + } + for (int i = 0; i < n - 1; i++) + out.print(res[i] + " "); + out.println(res[n - 1]); + out.flush(); + } + out.close(); + } +} +``` + +## 二十三、最小生成树 + +两种算法,`Kruskal`和`Prim`。具体看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Graph/%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91/Hdu%20-%201863.%20%E7%95%85%E9%80%9A%E5%B7%A5%E7%A8%8B(%E6%9C%80%E5%B0%8F%E7%94%9F%E6%88%90%E6%A0%91%E6%A8%A1%E6%9D%BF%E9%A2%98)(Kruskal%E7%AE%97%E6%B3%95%E5%92%8CPrim%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0).md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * 最小生成树 Kruskal + * 题目: http://acm.hdu.edu.cn/showproblem.php?pid=1863 + */ +public class M23_MST_Kruskal { + + static int n; + static int m; + static ArrayList edges; + + static class Edge implements Comparable { + public int from; + public int to; + public int w; + + public Edge(int from, int to, int w) { + this.from = from; + this.to = to; + this.w = w; + } + + @Override + public int compareTo(Edge o) { + return w - o.w; + } + } + + static class UF { + + int[] parent; + int[] rank; + + public UF(int n) { + parent = new int[n + 1]; + rank = new int[n + 1]; + for (int i = 0; i <= n; i++) { + parent[i] = i; + rank[i] = 0; + } + } + + public boolean isSameSet(int x, int y) { + return find(x) == find(y); + } + + public int find(int v) { + while (parent[v] != v) { + parent[v] = parent[parent[v]]; // 路径压缩优化 + v = parent[v]; + } + return v; + } + + public void union(int a, int b) { + int aR = find(a); + int bR = find(b); + if (aR == bR) + return; + if (rank[aR] < rank[bR]) { // a更矮,所以挂到b更好 + parent[aR] = bR; + } else if (rank[aR] > rank[bR]) { + parent[bR] = aR; + } else { + parent[aR] = bR; + rank[bR]++; + } + } + } + + static int kruskal() { + Collections.sort(edges); // 对边集排序 + UF uf = new UF(n); + int res = 0; + int count = 0; + for (int i = 0; i < edges.size(); i++) { + int from = edges.get(i).from; + int to = edges.get(i).to; + int w = edges.get(i).w; + if (!uf.isSameSet(from, to)) { //两个顶点不属于同一个集合 + res += w; + count++; + if (count == n - 1) + break; + uf.union(from, to); + } + + } + return count == n - 1 ? res : -1; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + while (in.hasNext()) { + m = in.nextInt(); // 先输入道路条数 + n = in.nextInt(); + if (m == 0) + break; + edges = new ArrayList<>(); + for (int i = 0; i < m; i++) { + int from = in.nextInt(); + int to = in.nextInt(); + int w = in.nextInt(); + edges.add(new Edge(from, to, w)); + edges.add(new Edge(to, from, w)); + } + int res = kruskal(); + System.out.println(res == -1 ? "?" : res); + } + } +} +``` + +`Prim`算法 + +```java +import java.util.*; +import java.io.*; + +/** + * 最小生成树 Prim + * 题目:http://acm.hdu.edu.cn/showproblem.php?pid=1863 + */ +public class M23_MST_Prim { + + static int n, m; + static boolean[] vis; + static ArrayList[] G; + + static class Edge implements Comparable { + public int to; + public int w; + + public Edge(int to, int w) { + this.to = to; + this.w = w; + } + + @Override + public int compareTo(Edge o) { + return w - o.w; + } + } + + private static int prim(int start) { + PriorityQueue pq = new PriorityQueue<>(); + for (int i = 0; i < G[start].size(); i++) + pq.add(G[start].get(i)); + int count = 0; + int res = 0; + vis[start] = true; // 起始节点已经在集合中 + while (!pq.isEmpty()) { + Edge curEdge = pq.poll(); + int to = curEdge.to; + if (!vis[to]) { + vis[to] = true; + count++; + res += curEdge.w; + if (count == n - 1) + break; + for (int i = 0; i < G[to].size(); i++) { + int nxtNode = G[to].get(i).to; + if (!vis[nxtNode]) // to -> nxtNode 没有加入过 + pq.add(G[to].get(i)); // 将to-> nxtNode的边加入优先队列 + } + } + } + if (count != n - 1) + return -1; + return res; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + while (in.hasNext()) { + m = in.nextInt(); // 先输入道路条数 + n = in.nextInt(); + if (m == 0) + break; + G = new ArrayList[n + 1]; // 1~n + vis = new boolean[n + 1]; + for (int i = 0; i <= n; i++) + G[i] = new ArrayList<>(); + for (int i = 0; i < m; i++) { + int from = in.nextInt(); + int to = in.nextInt(); + int w = in.nextInt(); + G[from].add(new Edge(to, w)); + G[to].add(new Edge(from, w)); + } + int res = prim(1); + System.out.println(res == -1 ? "?" : res); + } + } +} + +``` + +## 二十四、最短路 + +这里只给出最经典的`Dijstra`。其他很少考。 + +详解请看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Graph/%E6%9C%80%E7%9F%AD%E8%B7%AF/Hdu%20-%201874.%20%E7%95%85%E9%80%9A%E5%B7%A5%E7%A8%8B%E7%BB%AD(%E6%9C%80%E7%9F%AD%E8%B7%AFdijkstra%E6%A8%A1%E6%9D%BF).md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * 最短路 + * 例题: http://acm.hdu.edu.cn/showproblem.php?pid=1874 + */ +public class M24_Dijkstra { + + static int n; + static int m; + static boolean[] vis; + static ArrayList G[]; + + static class Edge implements Comparable { + public int to; + public int w; + + public Edge(int to, int w) { + this.to = to; + this.w = w; + } + + @Override + public int compareTo(Edge o) { + return w - o.w; + } + } + + static int[] dijkstra(int start) { + PriorityQueue pq = new PriorityQueue<>(); + int[] dis = new int[n]; + for (int i = 0; i < n; i++) dis[i] = Integer.MAX_VALUE; //初始标记(不是-1(因为是求最小的)) + dis[start] = 0; +// G.vis[start] = true; //第一个访问 start, 不能将start标记为true + pq.add(new Edge(start, 0)); //将第一条边加入 pq, 自环边 + while (!pq.isEmpty()) { + Edge curEdge = pq.poll(); + int to = curEdge.to; + if (vis[to]) + continue; + vis[to] = true; + for (int i = 0; i < G[to].size(); i++) { //更新相邻的边 + int nxtNode = G[to].get(i).to; + int nxtW = G[to].get(i).w; + if (!vis[nxtNode] && dis[nxtNode] > dis[to] + nxtW) { + dis[nxtNode] = dis[to] + nxtW; + pq.add(new Edge(nxtNode, dis[nxtNode])); //将这个新的dis[nxtNode]加入优先队列,没准它是下一个(很小) + } + } + } + return dis; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + while (in.hasNext()) { + n = in.nextInt(); + m = in.nextInt(); + G = new ArrayList[n]; // 0~n-1 + vis = new boolean[n]; + for (int i = 0; i < n; i++) { + G[i] = new ArrayList<>(); + vis[i] = false; + } + for (int i = 0; i < m; i++) { + int from = in.nextInt(); + int to = in.nextInt(); + int w = in.nextInt(); + G[from].add(new Edge(to, w)); + G[to].add(new Edge(from, w)); + } + int s = in.nextInt(); + int e = in.nextInt(); + int[] dis = dijkstra(s); + System.out.println(dis[e] == Integer.MAX_VALUE ? -1 : dis[e]); + } + } +} +``` + +## 二十五、欧拉回路 + +欧拉回路: + +* 1)、图G是连通的,不能有孤立点存在。 +* 2) 对于无向图来说度数为奇数的点个数为0,对于有向图来说每个点的入度必须等于出度。 + + 欧拉路径: + +* 1)、图G是连通的,无孤立点存在。 + +* 2) 、分情况: + + * 对于无向图来说:度数为奇数的的点可以有2个或者0个,并且这两个奇点其中一个为起点另外一个为终点。 + + * 对于有向图来说:可以存在两个点,其入度不等于出度,其中一个入度比出度大1,为路径的起点; + + 另外一个出度比入度大1,为路径的终点。 + +判断连通可以用`DFS`或者并查集。 +```java +import java.io.*; +import java.util.*; + +/** + * 欧拉回路和路径 (1) 用dfs判连通 + * 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1878 + */ +public class M25_EulerCircuit_1 { + + static ArrayList G[]; + static int n, m; + static boolean[] vis; + + static void dfs(int cur){ + if(vis[cur]) return; + vis[cur] = true; + for(int to : G[cur]){ + if(!vis[to]) + dfs(to); + } + } + + public static void main(String[] args){ + Scanner sc = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + while(sc.hasNext()){ + n = sc.nextInt(); + if(n == 0) break; + m = sc.nextInt(); + int[] in = new int[n+1]; + vis = new boolean[n+1]; + G = new ArrayList[n+1]; + for(int i = 0; i <= n; i++) G[i] = new ArrayList<>(); + for(int i = 0; i < m; i++){ + int from = sc.nextInt(); + int to = sc.nextInt(); + G[from].add(to); + G[to].add(from); + in[from]++; + in[to]++; + } + dfs(1); + boolean ok = true; + for(int i = 1; i <= n; i++) if(in[i] % 2 != 0 || !vis[i]){ + ok = false; + break; + } + out.println(ok ? "1" : "0"); + out.flush(); + } + out.close(); + } +} +``` + +使用并查集判断连通: + +```java +import java.io.*; +import java.util.*; + +public class M25_EulerCircuit_2 { + + static int n, m; + static int[] parent, rank; + + static int findRoot(int p){ + while(p != parent[p]){ + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + static void union(int a, int b){ + int aR = findRoot(a); + int bR = findRoot(b); + if(aR == bR) return; + if(rank[aR] < rank[bR]){ + parent[aR] = bR; + }else if(rank[aR] > rank[bR]){ + parent[bR] = aR; + }else { + parent[aR] = bR; + rank[bR]++; + } + } + + public static void main(String[] args){ + Scanner sc = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + while(sc.hasNext()){ + n = sc.nextInt(); + if(n == 0) break; + m = sc.nextInt(); + int[] in = new int[n+1]; + parent = new int[n+1]; + rank = new int[n+1]; + for(int i = 0; i <= n; i++) { + parent[i] = i; + rank[i] = 1; + } + for(int i = 0; i < m; i++){ + int from = sc.nextInt(); + int to = sc.nextInt(); + union(from, to); + in[from]++; + in[to]++; + } + int oneRoot = findRoot(1); + boolean ok = in[1] % 2 == 0; + for(int i = 2; i <= n; i++) if(in[i] % 2 != 0 || findRoot(i) != oneRoot){ + ok = false; + break; + } + out.println(ok ? "1" : "0"); + out.flush(); + } + out.close(); + } +} + +``` + +## 二十六、GCD和LCM + +求最大公约数和最小公倍数,很经典了,网上搜一下很简单。 + +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +/** + * 最大公约数和最小公倍数问题 + * 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1019 + */ +public class M26_GCD_LCM { + + // 非递归gcd + static int gcdIter(int a, int b){ + int r; + while(b != 0){ + r = a % b; + a = b; + b = r; + } + return a; + } + + static int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + + static int lcm(int a, int b) { + return a / gcd(a, b) * b; + } + + static int ngcd(int arr[], int n) { + if (n == 1) return arr[0]; + return gcd(arr[n - 1], ngcd(arr, n - 1)); + } + + static int nlcm(int arr[], int n) { + if (n == 1) return arr[0]; + return lcm(arr[n - 1], nlcm(arr, n - 1)); + } + + public static void main(String[] args){ + + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int T = in.nextInt(); + for(int t = 0; t < T; t++){ + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + System.out.println(nlcm(a, n)); + } + } +} +``` + +## 二十七、素数筛法 + +主要是埃式筛法。详解看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Math/Hdu%20-%201431%E7%B4%A0%E6%95%B0%E5%9B%9E%E6%96%87%E4%BB%A5%E5%8F%8A%E7%B4%A0%E6%95%B0%E7%9B%B8%E5%85%B3%E6%80%BB%E7%BB%93.md)。 + +```java +import java.util.*; +import java.io.*; + +public class M27_PrimeSieve { + + // 经典筛法,超时 + static ArrayList primary(boolean[] is_prime, int MAX) { + ArrayList prime = new ArrayList<>(); + is_prime[0] = is_prime[1] = false; // 01 不是素数 + boolean flag; + for (int i = 2; i <= MAX; i++) { //范围是1000 我筛选 0~2000内的素数 + flag = true; + for (int j = 2; j * j <= i; j++) {// 根号i的时间复杂度 + if (i % j == 0) { + is_prime[i] = false; + flag = false; + break; + } + } + if (flag) { + prime.add(i); + is_prime[i] = true; + } + } + return prime; + } + + + //经典的埃式筛法 + static ArrayList sieve(boolean[] is_prime, int MAX) { + + ArrayList prime = new ArrayList<>(); + Arrays.fill(is_prime, true); + + is_prime[0] = is_prime[0] = false; + + for (int i = 2; i <= MAX; i++) { + if (is_prime[i]) { + prime.add(i); + for (int j = 2 * i; j <= MAX; j += i) + is_prime[j] = false; + } + } + + return prime; + } + + //优化筛法 + static ArrayList sieve2(boolean[] is_prime, int MAX) { + ArrayList prime = new ArrayList<>(); + Arrays.fill(is_prime, true); + is_prime[0] = is_prime[0] = false; + for (int i = 2; i <= MAX; i++) { + if (is_prime[i]) + prime.add(i); + for (int j = 0; j < prime.size() && prime.get(j) <= MAX / i; j++) { + is_prime[prime.get(j) * i] = false; //筛掉 (小于等于i的素数 * i) 构成的合数 + if (i % prime.get(j) == 0) //如果 i是 < i的素数的倍数 就不用筛了 + break; + } + } + + return prime; + } + + + static boolean isPalindrome(int num) { + int oldNum = num; + int newNum = 0; + //反过来计算 + while (num > 0) { + newNum = newNum * 10 + num % 10; + num /= 10; + } + return newNum == oldNum; + } + + static final int maxn = 9989899; //题目中最大的回文素数 + + public static void main(String[] args) { + + Scanner in = new Scanner(new BufferedInputStream(System.in)); + boolean[] is_prime = new boolean[maxn + 1]; + + // 三种筛法对比 +// primary(is_prime,maxn); //超时 +// sieve(is_prime,maxn); // ok + sieve2(is_prime, maxn); // ok fast + + ArrayList res = new ArrayList<>(); + for (int i = 0; i <= maxn; i++) { + if (is_prime[i] && isPalindrome(i)) + res.add(i); + } + while (in.hasNext()) { + int a = in.nextInt(); + int b = in.nextInt(); + int num = 0; + for (int i = 0; i < res.size(); i++) { + num = res.get(i); + if(num >= a && num <= b) System.out.println(num); + } + System.out.println(); + } + } +} +``` + +## 二十八、唯一分解定理 + +解决的问题: **任何一个大于1的自然数 N,如果N不为质数,那么N可以唯一分解成有限个质数的乘积**。 + +![images.png](images/acm_6.png) + +代码: + +```java +import java.util.*; +import java.io.*; + +/** + * 唯一分解定理 + * 题目链接: https://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/1634.html + */ +public class M28_UniqueDecomposition { + + static ArrayList sieve(int MAX) { + ArrayList prime = new ArrayList<>(); + boolean[] is_prime = new boolean[MAX + 1]; + + Arrays.fill(is_prime, true); + is_prime[0] = is_prime[1] = false; + + for (int i = 2; i <= MAX; i++) { + if (is_prime[i]) { + prime.add(i); + + for (int j = 2 * i; j <= MAX; j += i) { + is_prime[j] = false; + } + } + } + return prime; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + int T = in.nextInt(); + for (int t = 0; t < T; t++) { + int n = in.nextInt(); + ArrayList prime = sieve(n); + ArrayList res = new ArrayList<>(); + //筛选 + for (int i = 0; i < prime.size(); i++) { + int p = prime.get(i); + while (n % p == 0) { + res.add(p); + n /= p; + } + } + out.print(res.get(0)); + for(int i = 1; i < res.size(); i++) out.print("*" + res.get(i)); + out.println(); + out.flush(); + } + out.close(); + } +} +``` + +这里再附上一个约数枚举和整数分解的代码: + +```java +import java.util.*; + +/** + * 约数枚举, 整数分解 + */ +public class M28_ApproximateNumberEnum { + + //约数枚举 + static ArrayList divisor(int n) { + ArrayList res = new ArrayList<>(); + for (int i = 1; i * i <= n; i++) { + if (n % i == 0) { + res.add(i); + if (i != n / i) + res.add(n / i); + } + } + return res; + } + + //整数分解 + static HashMap prime_factor(int n) { + HashMap map = new HashMap<>(); + + for (int i = 2; i * i <= n; i++) { + while (n % i == 0) { + if (map.containsKey(i)) { + map.put(i, map.get(i) + 1); + } else { + map.put(i, 1); + } + n /= i; + } + } + if (n != 1) map.put(n, 1); + return map; + } + + public static void main(String[] args) { + System.out.println(divisor(12)); + + System.out.println("----测试分解素因子(唯一分解定理)-----"); + + HashMap map = prime_factor(12); + for (Integer num : map.keySet()) + System.out.println(num + " " + map.get(num)); + } +} +``` + +## 二十九、乘法快速幂 + +具体看这篇博客,LeetCode上也出现过,也是要掌握的。 + +[详解博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Math/%E4%B9%98%E6%B3%95%E5%BF%AB%E9%80%9F%E5%B9%82%E7%9B%B8%E5%85%B3%E6%80%BB%E7%BB%93%20%20%26%20LeetCode%20-%2050.%20Pow(x%2C%20n).md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * 乘法和乘方快速幂 + * 题目链接: http://xyoj.xynu.edu.cn/problem.php?id=1872&csrf=mmofuzhUWGip3c6WlmhiFY6bLxeVHZta + */ +public class M29_QuickPow { + //递归 计算 (a^n) % mod + static long pow_mod(long a, long n, long mod) { + if (n == 0) // a^0 = 1 + return 1; + // 先求一半的 --> 你先给我求出 a ^ (n/2) 的结果给我 + long halfRes = pow_mod(a, n >> 1, mod); // n >> 1 --> n/2 + + long res = halfRes * halfRes % mod; + + if ((n & 1) != 0) // odd num + res = res * a % mod; + return res; + } + + //非递归 计算 (a^n) % mod + static long pow_mod2(long a, long n, long mod) { + long res = 1; + while (n > 0) { + if ((n & 1) != 0) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = res * a % mod; + a = a * a % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + return res; + } + + // 计算 (a * b) % mod + static long mul_mod(long a, long b, long mod) { + long res = 0; + while (b > 0) { + if ((b & 1) != 0) // 二进制最低位是1 --> 加上 a的 2^i 倍, 快速幂是乘上a的2^i ) + res = (res + a) % mod; + a = (a << 1) % mod; // a = a * 2 a随着b中二进制位数而扩大 每次 扩大两倍。 + b >>= 1; // b -> b/2 右移 去掉最后一位 因为当前最后一位我们用完了, + } + return res; + } + + //非递归 计算 (a^n) % mod 配合 mul + static long pow_mod3(long a, long n, long mod) { + long res = 1; + while (n > 0) { + if ((n & 1) != 0) // 二进制最低位 是 1 --> (n&1) != 0 --> 乘上 x ^ (2^i) (i从0开始) + res = mul_mod(res, a, mod) % mod; + a = mul_mod(a, a, mod) % mod; // a = a^2 + n >>= 1; // n -> n/2 往右边移一位 + } + return res; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int T = in.nextInt(); + while (T-- > 0) { + int a = in.nextInt(); + int n = in.nextInt(); + int mod = in.nextInt(); +// System.out.println(pow_mod(a,n,mod)); +// System.out.println(pow_mod2(a,n,mod)); + System.out.println(pow_mod3(a, n, mod)); + } + } +} +``` + +## 三十、矩阵快速幂 + +也是很好用的模板,详解看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Math/%E7%9F%A9%E9%98%B5%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C%E5%92%8C%E7%9F%A9%E9%98%B5%E5%BF%AB%E9%80%9F%E5%B9%82.md)。 + +```java +import java.util.*; +import java.io.*; + +/** + * 题目:http://poj.org/problem?id=3070 + * 经典的斐波那契数列问题 f[n] = f[n-1] + f[n-2] + */ +public class M30_MatrixQuickPow { + + static class Matrix { + public int row; + public int col; + public int[][] m; + + public Matrix(int row, int col) { + this.row = row; + this.col = col; + m = new int[row][col]; + } + } + + static final int MOD = 10000; + + static Matrix mul(Matrix a, Matrix b) { + Matrix c = new Matrix(a.row, b.col); //注意这里 + + for (int i = 0; i < a.row; i++) { + for (int j = 0; j < b.col; j++) { + for (int k = 0; k < a.col; k++) + c.m[i][j] = (c.m[i][j] + a.m[i][k] * b.m[k][j]) % MOD; + } + } + return c; + } + + static Matrix pow(Matrix a, int k) { + Matrix res = new Matrix(a.row, a.col); // 方阵 + for (int i = 0; i < a.row; i++) + res.m[i][i] = 1; + while (k > 0) { + if ((k & 1) != 0) + res = mul(res, a); + a = mul(a, a); + k >>= 1; + } + return res; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + + while (in.hasNext()) { + int n = in.nextInt(); + if (n == -1) break; + if (n == 0) { + System.out.println(0); + continue; + } + + Matrix matrix = new Matrix(2, 2); + matrix.m[0][0] = matrix.m[0][1] = matrix.m[1][0] = 1; + matrix.m[1][1] = 0; + + Matrix res = pow(matrix, n - 1); + + System.out.println(res.m[0][0] % MOD); + } + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/LeetCode - 153. Find Minimum in Rotated Sorted Array (\346\227\213\350\275\254\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274).md" "b/Algorithm/LeetCode/BinarySearch/LeetCode-153.Find Minimum in Rotated Sorted Array (\346\227\213\350\275\254\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274).md" similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/LeetCode - 153. Find Minimum in Rotated Sorted Array (\346\227\213\350\275\254\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274).md" rename to "Algorithm/LeetCode/BinarySearch/LeetCode-153.Find Minimum in Rotated Sorted Array (\346\227\213\350\275\254\346\225\260\347\273\204\344\270\255\347\232\204\346\234\200\345\260\217\345\200\274).md" diff --git a/Algorithm/LeetCode/BinarySearch/LeetCode-34.Find First and Last Position of Element in Sorted Array.md b/Algorithm/LeetCode/BinarySearch/LeetCode-34.Find First and Last Position of Element in Sorted Array.md new file mode 100644 index 00000000..0175a095 --- /dev/null +++ b/Algorithm/LeetCode/BinarySearch/LeetCode-34.Find First and Last Position of Element in Sorted Array.md @@ -0,0 +1,55 @@ +# LeetCode - 34. Find First and Last Position of Element in Sorted Array + +#### [题目链接](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/) + +> https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/ + +#### 题目 + +![34_t.png](images/34_t.png) + +## 解析 + +这个题目包含了二分的两种情况,我的[**另一篇文章**](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Algorithm/BinarySearch/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%9A%84%E6%80%BB%E7%BB%93(6%E7%A7%8D%E5%8F%98%E5%BD%A2).md)对二分的六种情况做了很详细的总结,具体看那篇文章,二分很简单,主要是边界问题要注意。 + +

+ +代码: + +```java +class Solution { + public int[] searchRange(int[] nums, int target) { + int first = firstEqual(nums, target); + if(first == -1) return new int[]{-1, -1}; + return new int[]{first, lastEqual(nums, target)}; + } + + private int firstEqual(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] >= key) + R = mid - 1; + else + L = mid + 1; + } + if(L < arr.length && arr[L] == key) return L; + return -1; + } + + private int lastEqual(int[] arr, int key){ + int L = 0, R = arr.length - 1; + while(L <= R){ + int mid = L + (R - L) / 2; + if(arr[mid] <= key) + L = mid + 1; + else + R = mid - 1; + } + if(R >= 0 && arr[R] == key) + return R; + return -1; + } +} +``` + diff --git a/Algorithm/LeetCode/BinarySearch/LeetCode-4.Median of Two Sorted Arrays.md b/Algorithm/LeetCode/BinarySearch/LeetCode-4.Median of Two Sorted Arrays.md new file mode 100644 index 00000000..35f0971a --- /dev/null +++ b/Algorithm/LeetCode/BinarySearch/LeetCode-4.Median of Two Sorted Arrays.md @@ -0,0 +1,138 @@ +# LeetCode - 4. Median of Two Sorted Arrays(二分) +#### [题目链接](https://leetcode.com/problems/median-of-two-sorted-arrays/) + +> https://leetcode.com/problems/median-of-two-sorted-arrays/ + +#### 题目 +![在这里插入图片描述](images/4_t.png) +## 解析 +假设两个数组的中间位置为`k`,其中` k=(n1 + n2 + 1)/2`,只要找到中间位置这个值,也就找到了中位数,所以我们可以把问题转换成查找两个数组中第 `k` 大的数。 + +* 如果是总数是偶数,那么如下图,我们的中位数肯定是**(Ck-1 + CK) / 2**;而**Ck-1 = max(Am1 - 1,Bm2 - 1)****Ck = min(Am1 ,Bm2)**; +* 如果总数是奇数,那么中位数就是**Ck-1**,而**Ck-1 = max(Am1 - 1,Bm2 - 1)**; + +看下面的例子: + +![在这里插入图片描述](images/4_s1.png) + + 求解的过程就是: + +* 对`nums1`数组进行二分查找那样的`m1`,然后得到对应的`m2`,比较`nums1[m1]`和`nums2[m2-1]`的大小(不能比较`nums2[m2]`,可能会越界); +* 二分边界是`L > R`,二分完毕就可以找到`m1`的位置,然后对应`m2 = k - m1`;然后确定**Ck-1****Ck**; + + +下面分别看奇数和偶数的两个例子: + +**偶数的例子:** + +![在这里插入图片描述](images/4_s2.png) +* 一开始`L = 0、R = 5`,`k = (6+8+1)/2 = 7`; +* 二分开始,`m1 = (0+5)/2 = 2`,`m2 = 7 - 2 = 5`,因为`nums1[2] = 3 < num2[5-1] = 10`,所以`L = m1 + 1 = 3、R = 5`; +* 因为`L = 3 < R = 5`,所以继续二分,`m1 = (3 + 5)/2 = 4`,`m2 = 7 - 4 = 3`,因为`nums1[4] = 7 > nums2[3 - 1] = 6`,所以`R = m1 - 1 = 3、L = 3`; +* 因为`L = 3 == R = 3`,继续二分,`m1 = (3 + 3)/2 = 3`,`m2 = 7 - 3 = 4`,因为`nums1[3] = 5 < nums2[4 - 1] = 8`,所以`L = m1 + 1 = 4、R = 3`; +* 此时`L > R`,退出二分,此时`m1 = L = 4`,`m2 = (7 - m1) = (7 - 4) = 3`; +* 然后此时**Ck-1 = max(nums1[m1 - 1],nums2[m2 - 1])**;**Ck = max(nums1[m1],nums2[m2])**;最后结果就是**(Ck-1 + CK) / 2**; + + +**奇数的例子:** + +![在这里插入图片描述](images/4_s3.png) + +过程: + +* 一开始`L = 0、R = 4`,`k = (5+8+1)/2 = 7`; +* 二分开始,`m1 = (0+4)/2 = 2`,`m2 = 7 - 2 = 5`,因为`nums1[2] = 3 < num2[5-1] = 10`,所以`L = m1 + 1 = 3、R = 4`; +* 因为`L = 3 < R = 4`,所以继续二分,`m1 = (3 + 4)/2 = 3`,`m2 = 7 - 3 = 4`,因为`nums1[3] = 5 < nums2[4 - 1] = 6`,所以`L = m1 + 1 = 4、R = 4`; +* 因为`L = 4 == R = 4`,继续二分,`m1 = (4 + 4)/2 = 4`,`m2 = 7 - 4 = 3`,因为`nums1[4] = 7 < nums2[3 - 1] = 8`,所以`L = m1 + 1 = 5、R = 4`; +* 此时`L > R`,退出二分,此时`m1 = L = 5`,`m2 = (7 - m1) = (7 - 5) = 2`; +* 因为是奇数,所以直接返回**Ck-1 = max(nums1[m1 - 1],nums2[m2 - 1])即可**; + + + +时间复杂度`O(log(min(m,n)))`。 + + +```java +import java.io.*; +import java.util.*; + +class Solution { + + public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int n1 = nums1.length; + int n2 = nums2.length; + if(n1 > n2) + return findMedianSortedArrays(nums2, nums1); + int L = 0, R = n1 - 1, m1, m2; + int k = (n1 + n2 + 1) / 2; // if even --> right median, else odd --> median + // m1 = k - m2, 注意边界和越界问题 + while(L <= R){ + m1 = L + (R - L) / 2; + m2 = k - m1; + if(nums1[m1] < nums2[m2 - 1]) // 不能和nums[m2]比较,因为m2可能 == n2(越界) + L = m1 + 1; + else + R = m1 - 1; + } + m1 = L; + m2 = k - m1; + int c1 = Math.max(m1 <= 0 ? Integer.MIN_VALUE : nums1[m1-1] + , m2 <= 0 ? Integer.MIN_VALUE : nums2[m2-1]); + if( (n1 + n2)%2 == 1) + return c1; + int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1] + , m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]); + return (c1 + c2)/2.0; + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + //int[] nums1 = {-1, 1, 3, 5, 7, 9}; // 6 numbers + //int[] nums1 = {-1, 1, 3, 5, 7}; // 5 numbers + //int[] nums2 = {2, 4, 6, 8, 10, 12, 14, 16}; // 8 numbers --> even + //int[] nums1 = {1, 3}; + //int[] nums2 = {2}; + int[] nums1 = { 1, 2 }; + int[] nums2 = { 3, 4 }; + out.println(new Solution(). + findMedianSortedArrays(nums1, nums2) + ); + } +} +``` + + +二分查找改成在`[L, R)`区间查找也是可以的,最终`m1`的位置还是在`L`,可以自己写个例子就知道了。 + + +```java +class Solution { + + public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int n1 = nums1.length; + int n2 = nums2.length; + if(n1 > n2) + return findMedianSortedArrays(nums2, nums1); + int L = 0, R = n1, m1, m2; + int k = (n1 + n2 + 1) / 2; + while(L < R){ + m1 = L + (R - L) / 2; + m2 = k - m1; + if(nums1[m1] < nums2[m2 - 1]) + L = m1 + 1; + else + R = m1; + } + m1 = L; + m2 = k - m1; + int c1 = Math.max(m1 <= 0 ? Integer.MIN_VALUE : nums1[m1-1] + , m2 <= 0 ? Integer.MIN_VALUE : nums2[m2-1]); + if( (n1 + n2)%2 == 1) + return c1; + int c2 = Math.min(m1 >= n1 ? Integer.MAX_VALUE : nums1[m1] + , m2 >= n2 ? Integer.MAX_VALUE : nums2[m2]); + return (c1 + c2)/2.0; + } +} +``` diff --git "a/Algorithm/LeetCode/BinarySearch/LeetCode-719.Find K-th Smallest Pair Distance(\346\232\264\345\212\233 _ \344\272\214\345\210\206).md" "b/Algorithm/LeetCode/BinarySearch/LeetCode-719.Find K-th Smallest Pair Distance(\346\232\264\345\212\233 _ \344\272\214\345\210\206).md" new file mode 100644 index 00000000..ec537f2a --- /dev/null +++ "b/Algorithm/LeetCode/BinarySearch/LeetCode-719.Find K-th Smallest Pair Distance(\346\232\264\345\212\233 _ \344\272\214\345\210\206).md" @@ -0,0 +1,110 @@ +# LeetCode - 719. Find K-th Smallest Pair Distance(暴力 | 二分) +#### [题目链接](https://leetcode.com/problems/find-k-th-smallest-pair-distance/) + +> https://leetcode.com/problems/find-k-th-smallest-pair-distance/ + +#### 题目 + +![在这里插入图片描述](images/719_t.png) +#### 解析 + +第一种方法的思想: + +* 先将`nums`数组排序,然后暴力枚举所有的`distance`(也就是`len * (len - 1 ) / 2`种),算出每一种`distance`出现的频率; +* 然后利用类似桶排序的过程,每个频率看做一个桶,我们从小频率到大频率遍历,每次累加频率到一个`count`值,如果这个`count`值`>=k`,即说明这个就是第`k`小的距离对了; + +例子: + +![在这里插入图片描述](images/719_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + // find K-th Smallest Pair Distance + public int smallestDistancePair(int[] nums, int k) { + Arrays.sort(nums); + int n = nums.length; + int[] freqs = new int[nums[n-1] + 1]; //注意nums[i]可能为0,所以要+1 + for(int i = 0; i < n; i++){ + for(int j = i + 1; j < n; j++){ + freqs[nums[j] - nums[i]]++; + } + } + int cnt = 0; + for(int d = 0; d < freqs.length; d++){ + cnt += freqs[d]; + if(cnt >= k) + return d; + } + return 0; + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int[] nums = {1,3,1}; + int k = 1; + out.println(new Solution(). + smallestDistancePair(nums, k) + ); + } +} +``` + +这题更快的方法是用`二分+类似DP`来解: + +* 也是需要先对`nums`数组排序,然后我们需要**在最大距离值和最小距离值中找到一个值`key`,使得恰好它前面有`k`对`pairs`他们**的`distance <= key` ; +* 而我们需要找的恰好是最小的那样的`key`,所以需要利用二分查找的找到第一个`>=key`的写法,具体可以看[**这篇博客**](https://blog.csdn.net/zxzxzx0119/article/details/82670761#t4); +* 这里每次二分里面我们需要去遍历数组(`i`),看似每次需要用另一个索引`j`来逐个统计这种`pair`的`distance`是否`<=mid`,但是这个过程是一个递增的顺序,也就是说我们已经对数组排序了,然后有点类似两个指针不断往后推动的情况,也就是说原本需要`O(N^2)`的时间复杂度可以降低到`2 * O(N)`,所以总的时间复杂度是`O( 2 * N * log(N))`; + +例子: 当二分寻找距离`<=3`的`pair`数。 + +![在这里插入图片描述](images/719_s2.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + // find K-th Smallest Pair Distance + public int smallestDistancePair(int[] nums, int k) { + Arrays.sort(nums); + int n = nums.length; + int L = 0; + int R = nums[n-1] - nums[0]; + while(L <= R){ + int mid = L + (R - L) / 2; + int count = 0; + int j = 0; + for(int i = 0; i < n; i++){ + while(j < n && nums[j] - nums[i] <= mid) j++; + count += j - i - 1; // j not in it, [i, i+1]、[i, i+2]....[i, j-1]'s dist <= m + } + if(count >= k) // 因为需要寻找第一个>=key的, 所以不能当count == k的时候返回 + R = mid - 1; + else // 没有这么多对数的dist <= m, 需要增加 + L = mid + 1; + } + return L; // 返回第一个>=key的 + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int[] nums = {1,3,1}; + int k = 1; + out.println(new Solution(). + smallestDistancePair(nums, k) + ); + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_s.png" b/Algorithm/LeetCode/BinarySearch/images/153_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_s.png" rename to Algorithm/LeetCode/BinarySearch/images/153_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_s2.png" b/Algorithm/LeetCode/BinarySearch/images/153_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_s2.png" rename to Algorithm/LeetCode/BinarySearch/images/153_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_t.png" b/Algorithm/LeetCode/BinarySearch/images/153_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/153_t.png" rename to Algorithm/LeetCode/BinarySearch/images/153_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/154_t.png" b/Algorithm/LeetCode/BinarySearch/images/154_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/154_t.png" rename to Algorithm/LeetCode/BinarySearch/images/154_t.png diff --git a/Algorithm/LeetCode/BinarySearch/images/34_s.png b/Algorithm/LeetCode/BinarySearch/images/34_s.png new file mode 100644 index 00000000..92381a59 Binary files /dev/null and b/Algorithm/LeetCode/BinarySearch/images/34_s.png differ diff --git a/Algorithm/LeetCode/BinarySearch/images/34_t.png b/Algorithm/LeetCode/BinarySearch/images/34_t.png new file mode 100644 index 00000000..8fb7c932 Binary files /dev/null and b/Algorithm/LeetCode/BinarySearch/images/34_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s1.png" b/Algorithm/LeetCode/BinarySearch/images/4_s1.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s1.png" rename to Algorithm/LeetCode/BinarySearch/images/4_s1.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s2.png" b/Algorithm/LeetCode/BinarySearch/images/4_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s2.png" rename to Algorithm/LeetCode/BinarySearch/images/4_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s3.png" b/Algorithm/LeetCode/BinarySearch/images/4_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_s3.png" rename to Algorithm/LeetCode/BinarySearch/images/4_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_t.png" b/Algorithm/LeetCode/BinarySearch/images/4_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/4_t.png" rename to Algorithm/LeetCode/BinarySearch/images/4_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_s.png" b/Algorithm/LeetCode/BinarySearch/images/719_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_s.png" rename to Algorithm/LeetCode/BinarySearch/images/719_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_s2.png" b/Algorithm/LeetCode/BinarySearch/images/719_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_s2.png" rename to Algorithm/LeetCode/BinarySearch/images/719_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_t.png" b/Algorithm/LeetCode/BinarySearch/images/719_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/BinarySearch/images/719_t.png" rename to Algorithm/LeetCode/BinarySearch/images/719_t.png diff --git a/Algorithm/LeetCode/Bit/LeetCode-136.Single Number.md b/Algorithm/LeetCode/Bit/LeetCode-136.Single Number.md new file mode 100644 index 00000000..31b2cc14 --- /dev/null +++ b/Algorithm/LeetCode/Bit/LeetCode-136.Single Number.md @@ -0,0 +1,32 @@ +# LeetCode - 136. Single Number + +#### [题目链接](https://leetcode.com/problems/single-number/) + +> https://leetcode.com/problems/single-number/ + +#### 题目 + +![1554786134064](assets/1554786134064.png) + +## 解析 + +这题很简单。利用**异或运算**的一条性质: `n ^ n = 0`。哈希和排序就不说了,更简单。 + +异或的几条性质: + +* 交换律:`a ^ b ^ c <=> a ^ c ^ b`; +* 任何数于0异或为任何数` 0 ^ n => n`; +* `n ^ n = 0`; + +代码: + +```java +class Solution { + public int singleNumber(int[] nums) { + int res = 0; + for(int num : nums) res ^= num; + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/Bit/LeetCode-137.Single Number II.md b/Algorithm/LeetCode/Bit/LeetCode-137.Single Number II.md new file mode 100644 index 00000000..1c11a0e2 --- /dev/null +++ b/Algorithm/LeetCode/Bit/LeetCode-137.Single Number II.md @@ -0,0 +1,71 @@ +# LeetCode - 137. Single Numer II + +#### [题目链接](https://leetcode.com/problems/single-number-ii/) + +> https://leetcode.com/problems/single-number-ii/ + +#### 题目 + +![1554786922547](assets/1554786922547.png) + +这里只讨论位运算的方法。排序、hash都挺简单,不多说。 + +## 方法一 + +方法: 对每个数的32位都进行一下统计,统计所有数的每一位中`1`出现的次数,累加然后`%3`,然后送回原来对应的位。 + +![1554787966000](assets/1554787966000.png) + +看题目中`[2, 2, 3, 2]`的例子: + +

+ +代码: + +```java +class Solution { + public int singleNumber(int[] nums) { + int res = 0; + for(int i = 0; i < 32; i++){ + int sum = 0; + for(int num : nums) { + sum += (num >> i) & 1; // 第i位的1的个数 + sum %= 3; + } + res |= (sum << i); // 将sum的结果返回给第i位(赋值) + } + return res; + } +} +``` + +## 方法二 + +第二种解法感觉把位运算用到了极致,一般人是想不出来了。。。 + +* 我们用one记录当前位置之前(包括当前位置)所有数字的各个二进制位的总数模3等于1 。 +* 同样用two记录当前位置之前(包括当前位置)所有数字的各个二进制位的总数模3等于2。 +* 比如当前数字是3,前面有1与4,那么one会记录`[3,1,4]`第1至32位bit中1出现的次数`%3 == 1`的结果,two会记录`%3 == 2`的结果。 +* one&two的结果的某个比特位`==1`,说明one中的该比特位与two中的该比特位都是1,那么说明该比特位出现了3次,需要从当前结果中消掉这些比特位。 +* 很简单,one&two**按位取反**表明不需要去掉的比特位。 +* 那么`one&(~(one&two))`将one中的出现3次的比特位去掉。 +* 最后剩下只出现一次的数的所有的比特位都在one中记录。那么one肯定就是那个只出现一次的数。 + +代码: + +```java +class Solution { + public int singleNumber(int[] nums) { + int one = 0, two = 0, three = 0; + for(int num : nums){ + two |= (one & num); + one ^= num; + three = ~(one & two); + one &= three; + two &= three; + } + return one; + } +} +``` + diff --git a/Algorithm/LeetCode/Bit/LeetCode-29.Divide Two Integers.md b/Algorithm/LeetCode/Bit/LeetCode-29.Divide Two Integers.md new file mode 100644 index 00000000..e910d4f1 --- /dev/null +++ b/Algorithm/LeetCode/Bit/LeetCode-29.Divide Two Integers.md @@ -0,0 +1,82 @@ +# LeetCode - 29. Divide Two Integers + +#### [题目链接](https://leetcode.com/problems/divide-two-integers/) + +> https://leetcode.com/problems/divide-two-integers/ + +#### 题目 + +![1558095704230](assets/1558095704230.png) + +### 解析 + +利用位运算,比如`32/3 = 10`。 + +先用`3`不断左移(`3 << 1`),直到结果`>32`。 + +即 + +```java +3 << 1 --> 6 +6 << 1 --> 12 +12 << 1 --> 24 +24 << 1 --> 48 (此时48 > 32了,退出) +``` + +此时我们需要到`24`停下来,所以还余下`32-24 = 8`,继续进行: + +```java +3 << 1 --> 6 +6 << 1 --> 12 (此时12 > 8) +``` + +此时我们需要到`6`停下来,所以还余下`8-6 =2 `,因为`2 < 3`,所以结束。 + +我们的答案就是`24/3 + 6/3 = 10`。 + +图: + +![1558107070062](assets/1558107070062.png) + +还需要注意正负号,我们将`dividend`和`divisor`都化为正数。 + +且要注意最小值和最大值问题。 + +代码: + +```java +class Solution { + + // 要求不使用乘法、除法和 mod 运算符 + public int divide(int dividend, int divisor) { + if(divisor == 0) return Integer.MAX_VALUE; + // 必须加上这个特殊判断,不然就会错 + if(dividend == Integer.MIN_VALUE) { // 注意 [-2147483648,2147483647] + if(divisor == -1) return Integer.MAX_VALUE; + else if(divisor == 1) return Integer.MIN_VALUE; + } + // 需要进行转换,不然会在移位溢出的时候发生死循环 + long divd = (long)dividend; + long divs = (long)divisor; + int sign = 1; + if(divd < 0){ + divd = -divd; + sign = -sign; + } + if(divs < 0){ + divs = -divs; + sign = -sign; + } + int res = 0; + while(divd >= divs){ + int shift = 1; + while(divd >= (divs << shift)) + shift++; + res += (1 << (shift-1)); + divd -= (divs << (shift-1)); + } + return res * sign; + } +} +``` + diff --git "a/Algorithm/LeetCode/Bit/LeetCode-461.Hamming Distance(\344\275\215\350\277\220\347\256\227).md" "b/Algorithm/LeetCode/Bit/LeetCode-461.Hamming Distance(\344\275\215\350\277\220\347\256\227).md" new file mode 100644 index 00000000..7f1b500e --- /dev/null +++ "b/Algorithm/LeetCode/Bit/LeetCode-461.Hamming Distance(\344\275\215\350\277\220\347\256\227).md" @@ -0,0 +1,94 @@ +# LeetCode - 461. Hamming Distance(位运算) + + - 方法一 + - 方法二 + - 方法三 + +#### [题目链接](https://leetcode.com/problems/hamming-distance/description/) + +> https://leetcode.com/problems/hamming-distance/description/ + +#### 题目 + +![在这里插入图片描述](images/461_t.png) + +### 方法一 +思路是: + + - 由于题目给出的最大范围是0 ≤ x, y < 231所以对应的二进制最多有`31`位,所以我们判断两个数的每一个二进制位相不相等即可。 + - 求出数的二进制的每一位操作就是不停的`/2`。 + +图: + +

+ +代码: + +```java +class Solution { + public int hammingDistance(int x, int y) { + int res = 0; + for (int i = 0; i < 32; i++) { + if ((x & 1) != (y & 1)) res++;// x & 1 --> x % 2 == 1 + x >>>= 1; //x /= 2, // >>>1(不带符号右移)、>>1(带符号右移) + y >>>= 1; //y /= 2 + } + return res; + } +} +``` + + +### 方法二 +第二种方法解题思路: + + - 先求出两个数的异或值; + - 这个值的二进制位如果是`1`表明两个数对应的二进制位不相等,否则相等。 + +图: + +

+ +代码: + +```java +class Solution { + public int hammingDistance(int x, int y) { + int res = 0; + int xor = x ^ y; + while (xor > 0) { + res += xor & 1; // %2 == 1 + xor >>>= 1; // >>>1(不带符号右移)、>>1(带符号右移) + } + return res; + } +} +``` + +### 方法三 +方法三解题思路: **这个也是先出两个数的异或值**。 + +然后关键在`xor &= (xor - 1);`这行代码功能。 + +**将`xor`的二进制值中,最后一个`1`置`0`,其它不变。即达到从`xor`的尾部,删除一个`1`的效果**。 + +

+ +第二个例子: + +

+ +所以程序就变成了可以删除多少个`1`,`res`就加多少次,也就是我们要的结果。 +```java +class Solution { + public int hammingDistance(int x, int y) { + int res = 0; + int xor = x ^ y; + while (xor != 0) { + res++; + xor &= (xor - 1); + } + return res; + } +} +``` diff --git a/Algorithm/LeetCode/Bit/assets/1554638368456.png b/Algorithm/LeetCode/Bit/assets/1554638368456.png new file mode 100644 index 00000000..7203d05c Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554638368456.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554638375633.png b/Algorithm/LeetCode/Bit/assets/1554638375633.png new file mode 100644 index 00000000..7203d05c Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554638375633.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554786134064.png b/Algorithm/LeetCode/Bit/assets/1554786134064.png new file mode 100644 index 00000000..e268267a Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554786134064.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554786922547.png b/Algorithm/LeetCode/Bit/assets/1554786922547.png new file mode 100644 index 00000000..daad057a Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554786922547.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554787966000.png b/Algorithm/LeetCode/Bit/assets/1554787966000.png new file mode 100644 index 00000000..8fe601b0 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554787966000.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554789369712.png b/Algorithm/LeetCode/Bit/assets/1554789369712.png new file mode 100644 index 00000000..6d6c74a7 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554789369712.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554957246756.png b/Algorithm/LeetCode/Bit/assets/1554957246756.png new file mode 100644 index 00000000..d167f09f Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554957246756.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554957335329.png b/Algorithm/LeetCode/Bit/assets/1554957335329.png new file mode 100644 index 00000000..a5d7cc91 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554957335329.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554957642497.png b/Algorithm/LeetCode/Bit/assets/1554957642497.png new file mode 100644 index 00000000..fd7178d0 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554957642497.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1554957763762.png b/Algorithm/LeetCode/Bit/assets/1554957763762.png new file mode 100644 index 00000000..68565784 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1554957763762.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1558095704230.png b/Algorithm/LeetCode/Bit/assets/1558095704230.png new file mode 100644 index 00000000..67dfbb30 Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1558095704230.png differ diff --git a/Algorithm/LeetCode/Bit/assets/1558107070062.png b/Algorithm/LeetCode/Bit/assets/1558107070062.png new file mode 100644 index 00000000..e7beaf8a Binary files /dev/null and b/Algorithm/LeetCode/Bit/assets/1558107070062.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Bit/images/461_t.png" b/Algorithm/LeetCode/Bit/images/461_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Bit/images/461_t.png" rename to Algorithm/LeetCode/Bit/images/461_t.png diff --git a/Algorithm/LeetCode/DP/LeetCode-10.Regular Expression Matching.md b/Algorithm/LeetCode/DP/LeetCode-10.Regular Expression Matching.md new file mode 100644 index 00000000..f218c2f7 --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-10.Regular Expression Matching.md @@ -0,0 +1,115 @@ +# LeetCode - 10. Regular Expression Matching + +#### [题目链接](https://leetcode.com/problems/regular-expression-matching/) + +> https://leetcode.com/problems/regular-expression-matching/ + +#### 题目 + +![10_t.png](images/10_t.png) + +![10_t2.png](images/10_t2.png) + +## 解析 + +这题用动态规划来做。 + +我们先看递归的思路。然后通过记忆化来优化。 + +我们的递归`match(char[]s, char[] p, int ls, int lp)`函数代表的是求`s`中长度为`ls`的字符串和`p`中长度为`lp`的字符串是否可以匹配。我们要求的答案就是长度都为各自字符串的长度是否匹配。 + +先看两种情况,当前长度为`ls、lp`,那么对应当前字符为`s[ls - 1]`和`p[pl-1]`,如果此时`s[ls - 1] == p[pl - 1]`,那么我们直接去匹配`match(sl-1, pl-1)`即可。 + +如果当前`p[pl-1] == '.'`,也是可以和`s[sl-1]`匹配的,所以情况和上面一样。 + +

+ +然后看第三种情况,也就是`p[pl - 1] == '*'`,此时又可以分为三种情况: + +![10_s_2.png](images/10_s_2.png) + +最后还要注意边界,当`s == "" && p == ""`的时候返回`true`,当`p=="" & s!= ""`的时候返回`false`。 + +当`s == "" && p != ""`的时候就要注意,如果`p[i-1] == '*'`则`dp[0][i] = dp[0][i-2]`,因为可以用`*`可以消去前一个字符。 + +虽然第三种情况,可以合起来考虑,代码会更简洁一些,但是这里个人认为还是写的清楚一点较好。 + +代码: + +```java +class Solution { + + private int[][] dp; + + // s只有字符, p可以有.和*, .匹配单个任意字符,'*'匹配多个任意字符 + public boolean isMatch(String s, String p) { + dp = new int[s.length() + 1][p.length() + 1]; + return match(s.toCharArray(), p.toCharArray(), s.length(), p.length()); + } + + // ls代表当前s的长度, lp代表当前p的长度 + public boolean match(char[] s, char[] p, int ls, int lp) { + if (ls == 0 && lp == 0) return true; + if (dp[ls][lp] != 0) return dp[ls][lp] == 1; + if (lp == 0) return false; + boolean res = false; + if (ls == 0) // s为空 + res = lp >= 2 && p[lp - 1] == '*' && match(s, p, ls, lp - 2); + else { + if (p[lp - 1] == '.' || s[ls - 1] == p[lp - 1]) + return match(s, p, ls - 1, lp - 1); + else if (p[lp - 1] == '*') { + if (p[lp - 2] == '.') { // 可以匹配多个 + res = match(s, p, ls - 1, lp - 1) + || match(s, p, ls - 1, lp) + || match(s, p, ls, lp - 2); + } else if (s[ls - 1] == p[lp - 2]) { + res = match(s, p, ls - 1, lp - 2) //这里和上面不同,不是ls-1, lp-1, + || match(s, p, ls - 1, lp) + || match(s, p, ls, lp - 2); + } else { // 只能丢掉 p[pl-1]和p[pl-2] + res = match(s, p, ls, lp - 2); + } + } + } + dp[ls][lp] = res ? 1 : -1; + return res; + } +} +``` + +可以将上面的递归版本改成递推版本: 原理是一样的。 + +```java +class Solution { + public boolean isMatch(String s, String p) { + return match(s.toCharArray(), p.toCharArray()); + } + + private boolean match(char[] s, char[] p){ + int ls = s.length; + int lp = p.length; + boolean[][] dp = new boolean[ls + 1][lp + 1]; + dp[0][0] = true; + for(int i = 1; i <= lp; i++) if(p[i-1] == '*') + dp[0][i] = dp[0][i-2]; + for(int i = 1; i <= ls; i++){ + for(int j = 1; j <= lp; j++){ + if(s[i-1] == p[j-1] || p[j-1] == '.'){ + dp[i][j] = dp[i-1][j-1]; + }else if(p[j-1] == '*') { + if(p[j-2] == '.'){ + dp[i][j] = dp[i-1][j-1] || dp[i-1][j] || dp[i][j-2]; + }else if(s[i-1] == p[j-2]){ + dp[i][j] = dp[i-1][j-2] || dp[i-1][j] || dp[i][j-2]; + }else { + dp[i][j] = dp[i][j-2]; + } + } + } + } + return dp[ls][lp]; + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-120.Traingle & Hdu - 2084. \346\225\260\345\241\224\351\227\256\351\242\230(\347\256\200\345\215\225dp).md" "b/Algorithm/LeetCode/DP/LeetCode-120.Traingle & Hdu - 2084. \346\225\260\345\241\224\351\227\256\351\242\230(\347\256\200\345\215\225dp).md" new file mode 100644 index 00000000..8bf581a8 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-120.Traingle & Hdu - 2084. \346\225\260\345\241\224\351\227\256\351\242\230(\347\256\200\345\215\225dp).md" @@ -0,0 +1,216 @@ +# LeetCode - 120. Traingle & Hdu - 2084. 数塔问题(简单dp) +* 递归 +* 二维dp +* 一维dp +* Hdu2084数塔问题 + + +#### [题目链接](https://leetcode.com/problems/triangle/) + +> https://leetcode.com/problems/triangle/ + +#### 题目 + +![在这里插入图片描述](images/120_t.png) +## 递归 +递归的思路是从上到下: + +* 递归函数有两个主要的参数,记录行和列,表示当前行的位置是`matrix[r][c]`,要向下去求最小路径; +* 如果行 `r == n`,说明不需要向下求,则直接返回`matrix[r][c]`; +* 否则先给我递归求出从`matrix[r+1][c]`求出的最大路径,和从`matrix[r+1][c+1]`的最大路径,我取最大的,和自己相加即可; +* 然后用二维数组`map`记忆化即可; + +图: + +![1554768710141](assets/1554768710141.png) + +代码: + +```java +class Solution { + + private int[][] dp; + + public int minimumTotal(List> triangle) { + + if (triangle == null || triangle.size() == 0 || triangle.get(0).size() == 0) + return 0; + int n = triangle.size(); + int m = triangle.get(triangle.size() - 1).size(); + dp = new int[n][m]; + return rec(triangle, 0, 0, n - 1); + } + + // 递归 + private int rec(List> triangle, int r, int c, int level) { + if (r == level) + return triangle.get(r).get(c); + if (dp[r][c] != 0) + return dp[r][c]; + int ele = triangle.get(r).get(c); + dp[r][c] = Math.min( + ele + rec(triangle, r + 1, c, level), + ele + rec(triangle, r + 1, c + 1, level) + ); + return dp[r][c]; + } +} +``` +*** +## 二维dp +`dp`就是记忆化的反方向,从下到上求解: + +* 初始化最后一行的就是本来的值,代表的就是从这一行往下不需要求了; +* 然后就是往上递推即可; + +图: + +

+代码: + +```java +class Solution { + public int minimumTotal(List> triangle) { + if (triangle == null || triangle.size() == 0 || triangle.get(0).size() == 0) + return 0; + + int n = triangle.size(); + int m = triangle.get(triangle.size() - 1).size(); + + int[][] dp = new int[n][m]; + + for (int j = 0; j < m; j++) + dp[n - 1][j] = triangle.get(n - 1).get(j); + + for (int i = n - 2; i >= 0; i--) { + for (int j = 0; j <= i; j++) {// 或 for(int j = i;j >= 0; j--){ + dp[i][j] = triangle.get(i).get(j) + Math.min(dp[i + 1][j], dp[i + 1][j + 1]); + } + } + return dp[0][0]; + } +} +``` +## 一维dp +滚动优化也需要注意滚动的方向: + +* 因为`dp[i][j]`依赖 `dp[i+1][j]` 和`dp[i+1][j+1]`,所以不能先更新`dp[i+1][j+1]`; +* 所以滚动的方向是从`j = i `到`j = 0`; + +代码: +```java +class Solution { + public int minimumTotal(List> triangle) { + if (triangle == null || triangle.size() == 0 || triangle.get(0).size() == 0) + return 0; + + int n = triangle.size(); + int m = triangle.get(triangle.size() - 1).size(); + + int[] dp = new int[m]; + + for (int j = 0; j < m; j++) + dp[j] = triangle.get(n - 1).get(j); + + for (int i = n - 2; i >= 0; i--) { + for (int j = 0; j <= i; j++) { //不能写出 j = i ; j >= 0 ; j-- + dp[j] = triangle.get(i).get(j) + Math.min(dp[j], dp[j + 1]); + } + } + return dp[0]; + } +} +``` + +*** +## Hdu2084数塔问题 +#### [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=2084) + +> http://acm.hdu.edu.cn/showproblem.php?pid=2084 + +就是从求最小路径和变成求最大路径和,做法是一样的。 + +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +public class Main { + + // 方法一: 记忆化递归 + static int maxPath(int[][] matrix, int n) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int[][] map = new int[n][n]; //注意这里 + return rec(matrix, 0, 0, n - 1, map); + } + + static int rec(int[][] matrix, int r, int c, int n, int[][] map) { + if (r == n) + return matrix[r][c]; + + if (map[r][c] != 0) + return map[r][c]; + + map[r][c] = matrix[r][c] + Math.max(rec(matrix, r + 1, c, n, map), + rec(matrix, r + 1, c + 1, n, map) + ); + + return map[r][c]; + } + + + // 二维dp + static int maxPath2(int[][] matrix, int n) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int[][] dp = new int[n][n]; + + for (int j = 0; j < n; j++) + dp[n - 1][j] = matrix[n - 1][j]; + + for (int i = n - 2; i >= 0; i--) { + for (int j = 0; j <= i; j++) { + dp[i][j] = matrix[i][j] + Math.max(dp[i + 1][j], dp[i + 1][j + 1]); + } + } + return dp[0][0]; + } + + //一维dp + static int maxPath3(int[][] matrix, int n) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int[] dp = new int[n]; + + for (int j = 0; j < n; j++) + dp[j] = matrix[n - 1][j]; + + for (int i = n - 2; i >= 0; i--) { + for (int j = 0; j <= i; j++) {// 不能写出for(int j = i ; j >= 0; j--) + dp[j] = matrix[i][j] + Math.max(dp[j], dp[j + 1]); + } + } + return dp[0]; + } + + public static void main(String[] args) { + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + int C = cin.nextInt(); + while (C-- > 0) { + int n = cin.nextInt(); + int[][] matrix = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= i; j++) { + int x = cin.nextInt(); + matrix[i][j] = x; + } + } +// System.out.println(maxPath(matrix, n)); +// System.out.println(maxPath2(matrix, n)); + System.out.println(maxPath3(matrix, n)); + } + } +} + +``` + diff --git a/Algorithm/LeetCode/DP/LeetCode-139.Word Break.md b/Algorithm/LeetCode/DP/LeetCode-139.Word Break.md new file mode 100644 index 00000000..ddee276b --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-139.Word Break.md @@ -0,0 +1,181 @@ +# LeetCode - 139. Word Break + + +#### [题目链接](https://leetcode.com/problems/word-break/) + +> https://leetcode.com/problems/word-break/ + +#### 题目 +![在这里插入图片描述](images/139_t.png) +## 解析 +记忆化的思路: + +* 字符串在每个位置从左到右进行划分成左边和右边部分; +* 左边部分递归的去求看是否满足,右边看`wordDict`中是否有这个单词; +* 因为递归的时候有重复的子问题,所以使用`map`进行记忆化; +* 这里是从顶(**例如`"leetcode"`从最长求到最短**)到下,而`dp`是从短推到长; +* 这里每个位置只需要某个划分满足即可,所以下面递归函数中如果有一个划分满足条件,立即就`return `了; + +图: + + +![img.png](images/139_s.png) + +```java +class Solution { + + private HashMap dp; + + public boolean wordBreak(String s, List wordDict) { + if (s == null) + return true; + if (wordDict == null) + return false; + // memorize + dp = new HashMap<>(); + return rec(s, wordDict); + } + + private boolean rec(String s, List dict) { + if (dict.contains(s)) //这句话不能省略 因为下面只是判断到 < sb.length, + return true; + if (dp.containsKey(s)) + return dp.get(s); + for (int i = 0; i < s.length(); i++) { // for(int i = 1; i < s.length(); i++){ 也可以写成从1开始 因为L == ""可以不用划分 + String L = s.substring(0, i); // [0,i) + String R = s.substring(i); // [i,sb.length) + if (dict.contains(R) && rec(L, dict)) {//先判断右半部分 + dp.put(s, true); + return true; + } + } + dp.put(s, false); + return false; + } +} +``` + +**注意到,也可以反过来改成`L`去判断在不在`wordDic`t中,而右边部分`R`去递归(这样代码简介很多,而且速度也更快)** + +```java +class Solution { + + private HashMap dp; + + public boolean wordBreak(String s, List wordDict) { + if (s == null) + return true; + if (wordDict == null) + return false; + dp = new HashMap<>(); + return rec(s, wordDict); + } + + //相当于s的左边L去判断在不在wordDict中,而右边去递归 + private boolean rec(String s, List dict) { + if (s.isEmpty()) + return true; //""返回true + if (dp.containsKey(s)) + return dp.get(s); + for (String word : dict) { + if (s.startsWith(word)) {// 左边L在wordDict中有包含 + if (rec(s.substring(word.length()), dict)) { + dp.put(s, true); + return true; + } + } + } + dp.put(s, false); + return false; + } +} +``` + +递推dp, 也就是从下到上的求解: + +* 注意,一开始`dp.put("",true)`,表示的是相当于`""`是返回`true`的,这个是必须的。因为某个划分`L`是`""`,而`R` 在`wordDict`中。 +* 比如划分到`"leetcode"`的`"leet"`的时候,当`leet`被划分成`""`和`"leet"`左边就是`""`,右边是`"leet"`(在`wordDict`中),所以满足; + +代码: + + +```java +class Solution { + + public boolean wordBreak(String s, List wordDict) { + if (s == null) + return true; + if (wordDict == null) + return false; + HashMap dp = new HashMap<>(); + dp.put("", true);// must + for (int i = 1; i <= s.length(); i++) {// 从1开始就可以 因为dp.put("",true) + String sI = s.substring(0, i); + for (int j = 0; j < i; j++) { + String L = sI.substring(0, j); + String R = sI.substring(j); + if (dp.get(L) != null && dp.get(L) && wordDict.contains(R)) { + dp.put(sI.toString(), true); + break; + } + } + } + return dp.get(s) == null ? false : dp.get(s); + } +} +``` +稍微优化的思路: + +* 上面的`HashMap`中的`true`其实是表示的`dp`的值,因为`key`为`String`,所以不好用数组表示。 +* 但是其实我们可以将左边部分的字符串映射为只需要以某个位置结尾的字符串就可以了。 +* 也就是说`L`部分求解,只需要记录那个字符串的结束位置即可,也就是可以只用一个`boolean`数组求解即可。 + +代码: + +```java +class Solution { + public boolean wordBreak(String s, List wordDict) { + if (s == null) + return true; + if (wordDict == null) + return false; + StringBuilder sb = new StringBuilder(s); + boolean[] dp = new boolean[s.length() + 1]; + dp[0] = true; // 类似 dp.put("",true); + for (int i = 1; i <= sb.length(); i++) {//从1开始就可以 + String sbI = sb.substring(0, i); + for (int j = 0; j < i; j++) { + if (dp[j] && wordDict.contains(sbI.substring(j))) { + dp[i] = true; + break; + } + } + } + return dp[s.length()]; + } +} +``` +**再次优化,连`sbI`也可以省略,因为可以直接取`[j,i)`之间的字符作为`sbI`即可**。 + +```java +class Solution { + public boolean wordBreak(String s, List wordDict) { + if (s == null) + return true; + if (wordDict == null) + return false; + StringBuilder sb = new StringBuilder(s); + boolean[] dp = new boolean[s.length() + 1]; + dp[0] = true; // 类似 dp.put("",true); + for (int i = 0; i <= sb.length(); i++) { + for (int j = 0; j < i; j++) { + if (dp[j] && wordDict.contains(sb.substring(j, i))) {//这里简单的优化 + dp[i] = true; + break; + } + } + } + return dp[s.length()]; + } +} +``` diff --git a/Algorithm/LeetCode/DP/LeetCode-140.Word Break II.md b/Algorithm/LeetCode/DP/LeetCode-140.Word Break II.md new file mode 100644 index 00000000..5bcf0dee --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-140.Word Break II.md @@ -0,0 +1,117 @@ +# LeetCode - 140. Word BreakII + +#### [题目链接](https://leetcode.com/problems/word-break-ii/description/) + +> https://leetcode.com/problems/word-break-ii/description/ + +#### 题目 +![在这里插入图片描述](images/140_t.png) + +#### 解析 +和上题不同的是,这个题目要求出所有的组合: + +记忆化递归的方式:  + +* 在递归的每一层,如果R(右边部分) 包含在`wordDict`中,则左边求出的所有解(放在`List`中),都和右边部分的字符串组成一个新的解(新的`List`),并添加到结果中; +* 注意递归的每一层,一开始要判断没有划分的那个,也就是`dict.contains(s)`这句。 + +图: + +![在这里插入图片描述](images/140_s.png) + +代码: + +```java +class Solution { + + public List wordBreak(String s, List wordDict) { + if (s == null || wordDict == null) + return new ArrayList<>(); + return rec(s, new HashMap<>(), wordDict); + } + + public List rec(String s, HashMap> map, List dict) { + if (map.containsKey(s)) + return map.get(s); + List res = new ArrayList<>(); + if (dict.contains(s)) + res.add(s); + for (int i = 1; i < s.length(); i++) {//注意这里不需要<=因为是对每一个划分 + String R = s.substring(i); + if (!dict.contains(R)) + continue; + String L = s.substring(0, i); + List LRes = rec(L, map, dict); //先求出左边的结果 + for (String si : LRes) + res.add(si + " " + R); + } + map.put(s, res); + return res; + } +} +``` + +同理,也可以写成下面的样子(左边查看在不在`wordDict`中,右边递归) ; +```java +class Solution { + public List wordBreak(String s, List wordDict) { + if (s == null || wordDict == null) + return new ArrayList<>(); + return rec(s, new HashMap<>(), wordDict); + } + + public List rec(String s, HashMap> map, List dict) { + if (map.containsKey(s)) + return map.get(s); + List res = new ArrayList<>(); + if (dict.contains(s)) + res.add(s); + for (String word : dict) { + if (s.startsWith(word)) { + String R = s.substring(word.length()); + List RRes = rec(R, map, dict); + for (String si : RRes) + res.add(word + " " + si); + } + } + map.put(s, res); + return res; + } +} +``` +改成`dp`的方式超内存(有时候超时)了,不知道是写错了还是怎么的,代码也贴上来: + +```java +class Solution { + + public List wordBreak(String s, List wordDict) { + if (s == null || wordDict == null) + return new ArrayList<>(); + List res = new ArrayList<>(); + + HashMap> dp = new HashMap<>(); + + dp.put("", new ArrayList<>()); + + String sI, R; + List LRes; + for (int i = 1; i <= s.length(); i++) { + sI = s.substring(0, i); + res = new ArrayList<>(); + if (wordDict.contains(sI)) + res.add(sI); + + for (int j = 0; j < i; j++) { + R = sI.substring(j); + if (!wordDict.contains(R)) + continue; + LRes = dp.get(sI.substring(0, j)); + for (String si : LRes) + res.add(si + " " + R); + } + dp.put(sI, res); + } + return res; + } +} +``` diff --git "a/Algorithm/LeetCode/DP/LeetCode-152.Maximum Product Subarray(\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\347\264\257\344\271\230\347\247\257).md" "b/Algorithm/LeetCode/DP/LeetCode-152.Maximum Product Subarray(\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\347\264\257\344\271\230\347\247\257).md" new file mode 100644 index 00000000..2c54eff6 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-152.Maximum Product Subarray(\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\347\264\257\344\271\230\347\247\257).md" @@ -0,0 +1,224 @@ +## LeetCode - 152. Maximum Product Subarray(子数组最大累乘积) +* 一维dp +* 滚动优化 +* 递归版本 + +*** +#### [题目链接](https://leetcode.com/problems/maximum-product-subarray/description/) + +> https://leetcode.com/problems/maximum-product-subarray/description/ + +#### 题目 +![在这里插入图片描述](images/152_t.png) + +这题和[LeetCode - 53](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2053.%20Maximum%20Subarray(%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C)(%E4%B8%80%E7%BB%B4dp).md)类似。 + +### 一维dp +使用一个一维数组记录以每个位置结尾的最大累乘积,再使用一个`res`变量(记录结果),记录每一个位置结尾`ends[i]`的最大值。 + +如何快速求出所有以`i`位置结尾(`nums[i]`)的子数组的最大累乘积?  **假设以`nums[i-1]`结尾的最大累乘积为`maxEnds[i-1]`,以`nums[i-1]`记为的最小累乘积为`minEnds[i-1]`,那么以`nums[i]`结尾的最大累乘积只有三种可能** + +* 可能是` maxEnds[i-1] * nums[i]`,这个是显然的,因为记录前面的最大值,如`[3,4,5]`; +* 可能是 `minEnds[i-1] * nums[i]`,因为`minEnds[i-1]`和`nums[i]`都有可能是负数,如`[-2,-4]`; +* 也有可能是 `nums[i]`自己; + +

+ +**则以这个结尾的最大值`maxEnds[i]`就是这三者中的最大的一个。而`minEnds[i]`的更新就是这三者中的最小的一个**。 +```java +class Solution { + + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[] minEnds = new int[nums.length]; + int[] maxEnds = new int[nums.length]; + minEnds[0] = nums[0]; + maxEnds[0] = nums[0]; + int res = nums[0]; + for (int i = 1; i < nums.length; i++) { + int max = nums[i] * maxEnds[i - 1]; + int min = nums[i] * minEnds[i - 1]; + maxEnds[i] = Math.max(max, Math.max(min, nums[i])); + minEnds[i] = Math.min(min, Math.min(max, nums[i])); + res = Math.max(maxEnds[i], res); + } + return res; + } +} +``` +*** +### 滚动优化 +这里的滚动优化就是**当前位置只依赖前一个位置的最大和最小值,所以只需要两个变量即可**。 + +优化空间: + +```java +class Solution { + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int minEnd = nums[0]; + int maxEnd = nums[0]; + int res = nums[0]; + for (int i = 1; i < nums.length; i++) { + int max = nums[i] * maxEnd; + int min = nums[i] * minEnd; + maxEnd = Math.max(max, Math.max(min, nums[i])); + minEnd = Math.min(min, Math.min(max, nums[i])); + res = Math.max(maxEnd, res); + } + return res; + } +} +``` +*** +### 递归版本 +能用`dp`的基本都能写出递归,能写出递归的都可以改`dp`; + +但是这里要注意: + +* 当从最后一个计算完之后,因为在`return`前记录的`res`,所以最后一个没有记录; +* 所以在调用完函数之后,存储返回值,再比较一下`last`和`res`的值,然后返回; +代码: + +```java +class Solution { + + private int res; + + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + res = nums[0]; + int last = maxMul(nums, nums.length - 1); // 最后一个不要忘了比较 + res = Math.max(res, last); + return res; + } + + private int maxMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + int preMax = maxMul(arr, i - 1); + int preMin = minMul(arr, i - 1); + res = Math.max(res, preMax); + return Math.max(preMax * arr[i], + Math.max(preMin * arr[i], arr[i]) + ); + } + + private int minMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + int preMin = minMul(arr, i - 1); + int preMax = maxMul(arr, i - 1); + return Math.min(preMin * arr[i], + Math.min(preMax * arr[i], arr[i]) + ); + } +} +``` +递归改记忆化,记忆化代码可以通过,**不过在递归之后还要比较一次,注意细节:** + +```java +class Solution { + + private int res; + //记忆化 + private int[] maxEnds; + private int[] minEnds; + + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + res = nums[0]; + maxEnds = new int[nums.length]; + Arrays.fill(maxEnds, Integer.MIN_VALUE); + minEnds = new int[nums.length]; + Arrays.fill(minEnds, Integer.MAX_VALUE); + int last = maxMul(nums, nums.length - 1); // 最后一个不要忘了比较 + res = Math.max(res, last); + return res; + } + + private int maxMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + if (maxEnds[i] != Integer.MIN_VALUE) + return maxEnds[i]; + int preMax = maxMul(arr, i - 1); + int preMin = minMul(arr, i - 1); + res = Math.max(res, preMax); + maxEnds[i] = Math.max(preMax * arr[i], + Math.max(preMin * arr[i], arr[i]) + ); + return maxEnds[i]; + } + + private int minMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + if (minEnds[i] != Integer.MAX_VALUE) + return minEnds[i]; + int preMin = minMul(arr, i - 1); + int preMax = maxMul(arr, i - 1); + minEnds[i] = Math.min(preMin * arr[i], + Math.min(preMax * arr[i], arr[i]) + ); + return minEnds[i]; + } +} +``` +也可以稍微改动一下,就不需要单独处理最后一个`last`了,在记忆化返回之前记录`res`的最大值: + +```java +class Solution { + + private int res; + //记忆化 + private int[] maxEnds; + private int[] minEnds; + + public int maxProduct(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + res = nums[0]; + maxEnds = new int[nums.length]; + Arrays.fill(maxEnds, Integer.MIN_VALUE); + minEnds = new int[nums.length]; + Arrays.fill(minEnds, Integer.MAX_VALUE); + + maxMul(nums, nums.length - 1); + + return res; + } + + private int maxMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + if (maxEnds[i] != Integer.MIN_VALUE) + return maxEnds[i]; + int preMax = maxMul(arr, i - 1); + int preMin = minMul(arr, i - 1); + maxEnds[i] = Math.max(preMax * arr[i], + Math.max(preMin * arr[i], arr[i]) + ); + res = Math.max(res, maxEnds[i]); + return maxEnds[i]; + } + + private int minMul(int[] arr, int i) { + if (i == 0) + return arr[0]; + if (minEnds[i] != Integer.MAX_VALUE) + return minEnds[i]; + int preMin = minMul(arr, i - 1); + int preMax = maxMul(arr, i - 1); + minEnds[i] = Math.min(preMin * arr[i], + Math.min(preMax * arr[i], arr[i]) + ); + return minEnds[i]; + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-174.Dungeon Game(\345\234\260\344\270\213\345\237\216\346\270\270\346\210\217)(dp).md" "b/Algorithm/LeetCode/DP/LeetCode-174.Dungeon Game(\345\234\260\344\270\213\345\237\216\346\270\270\346\210\217)(dp).md" new file mode 100644 index 00000000..145eff1e --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-174.Dungeon Game(\345\234\260\344\270\213\345\237\216\346\270\270\346\210\217)(dp).md" @@ -0,0 +1,113 @@ +## LeetCode - 174. Dungeon Game(地下城游戏)(dp) + + - 记忆化 + - 二维dp + - 一维dp + +#### [题目链接](https://leetcode.com/problems/dungeon-game/description/) + +> https://leetcode.com/problems/dungeon-game/description/ + +#### 题目 + +![在这里插入图片描述](images/174_t.png) + +## 1、记忆化 + +总共分为四种情况: + + - 第一种就是最左下角; + - 第二种就是最后一行; + - 第三种就是最后一列; + - 第四种就是普通位置,依赖的位置是右边的和下面的; + +具体解释看下图: + +![1554799714709](assets/1554799714709.png) + +代码: + +```java +class Solution { + + private int[][] dp; + private int n, m; + + public int calculateMinimumHP(int[][] dungeon) { + if (dungeon.length <= 0 || dungeon[0].length <= 0) return 0; + n = dungeon.length; + m = dungeon[0].length; + dp = new int[n][m]; + return rec(dungeon, 0, 0); + } + + public int rec(int[][] matrix, int i, int j) { + if (i == n - 1 && j == m - 1) + return matrix[i][j] > 0 ? 1 : -matrix[i][j] + 1; + if (dp[i][j] != 0) return dp[i][j]; + if (i == n - 1) { + int R = rec(matrix, i, j + 1); + dp[i][j] = matrix[i][j] >= R ? 1 : R - matrix[i][j]; + } else if (j == m - 1) { + int D = rec(matrix, i + 1, j); + dp[i][j] = matrix[i][j] >= D ? 1 : D - matrix[i][j]; + } else { + int min = Math.min(rec(matrix, i, j + 1), rec(matrix, i + 1, j)); + dp[i][j] = matrix[i][j] >= min ? 1 : min - matrix[i][j]; + } + return dp[i][j]; + } +} +``` + +## 2、二维dp +改成`dp`。 + +递归方向: + +

+ +代码: + +```java +class Solution { + public int calculateMinimumHP(int[][] dungeon) { + if (dungeon.length <= 0 || dungeon[0].length <= 0) + return 0; + int n = dungeon.length, m = dungeon[0].length; + int[][] dp = new int[n][m]; + dp[n - 1][m - 1] = dungeon[n - 1][m - 1] > 0 ? 1 : -dungeon[n - 1][m - 1] + 1; + for (int j = m - 2; j >= 0; j--) + dp[n - 1][j] = dungeon[n - 1][j] >= dp[n - 1][j + 1] ? 1 : dp[n - 1][j + 1] - dungeon[n - 1][j]; + for (int i = n - 2; i >= 0; i--) + dp[i][m - 1] = dungeon[i][m - 1] >= dp[i + 1][m - 1] ? 1 : dp[i + 1][m - 1] - dungeon[i][m - 1]; + for (int i = n - 2; i >= 0; i--) { + for (int j = m - 2; j >= 0; j--) + dp[i][j] = (dungeon[i][j] >= Math.min(dp[i][j + 1], dp[i + 1][j])) ? 1 : Math.min(dp[i][j + 1], dp[i + 1][j]) - dungeon[i][j]; + } + return dp[0][0]; + } +} +``` + +## 3、一维dp +因为位置依赖当前`dp[j]`还是`dp[i+1][j]`,所以只需要一个滚动数组。 +```java +class Solution { + public int calculateMinimumHP(int[][] dungeon) { + if (dungeon.length <= 0 || dungeon[0].length <= 0) + return 0; + int n = dungeon.length, m = dungeon[0].length; + int[] dp = new int[dungeon[0].length]; + dp[m - 1] = dungeon[n - 1][m - 1] > 0 ? 1 : -dungeon[n - 1][m - 1] + 1; + for (int j = m - 2; j >= 0; j--) dp[j] = dungeon[n - 1][j] >= dp[j + 1] ? 1 : dp[j + 1] - dungeon[n - 1][j]; + for (int i = n - 2; i >= 0; i--) { + dp[m - 1] = dungeon[i][m - 1] >= dp[m - 1] ? 1 : dp[m - 1] - dungeon[i][m - 1]; + for (int j = m - 2; j >= 0; j--) + dp[j] = (dungeon[i][j] >= Math.min(dp[j + 1], dp[j])) ? 1 : Math.min(dp[j + 1], dp[j]) - dungeon[i][j]; + } + return dp[0]; + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-221.Maximal Square(\346\261\202\346\234\200\345\244\247\347\232\204\345\205\250\346\230\2571\347\232\204\346\255\243\346\226\271\345\275\242).md" "b/Algorithm/LeetCode/DP/LeetCode-221.Maximal Square(\346\261\202\346\234\200\345\244\247\347\232\204\345\205\250\346\230\2571\347\232\204\346\255\243\346\226\271\345\275\242).md" new file mode 100644 index 00000000..96b9ca77 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-221.Maximal Square(\346\261\202\346\234\200\345\244\247\347\232\204\345\205\250\346\230\2571\347\232\204\346\255\243\346\226\271\345\275\242).md" @@ -0,0 +1,229 @@ +## LeetCode - 221. Maximal Square(求最大的全是1的正方形) + +* 暴力 (`O(N^5)`) +* 改进动态规划(`O(N^3)`) +* [LeetCode - 304. Range Sum Query 2D - Immutable](#leetcode---304-range-sum-query-2d---immutable) +* 优化动态规划(`O(N^2)`) + +*** +#### [题目链接](https://leetcode.com/problems/maximal-square/) + +> https://leetcode.com/problems/maximal-square/ + +#### 题目 + +![在这里插入图片描述](images/221_t.png) + +### 暴力 (`O(N^5)`) + +暴力`O(N) * O(M) * O(min(N , M)) * O(N) * O(M) `,也就是`O(N^5)`,但是也能通过...... + +* 枚举`0 ~ n`和`0 ~ m`,然后枚举这个范围内的所有正方形。这里时间复杂度为` O(N) * O(M) * O(min(N, M))`; +* 然后枚举的每一个正方形还需要判断这个正方形内是不是全都是`1`,时间复杂度`O(N * M )`; + +![在这里插入图片描述](images/221_s.png) + +代码: + +```java +class Solution { + public int maximalSquare(char[][] matrix) { + if(matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int n = matrix.length; + int m = matrix[0].length; + int res = 0; + for(int x = 0; x < n; x++){ // 枚举左上角的 x + for(int y = 0; y < m; y++){ // 枚举左上角的 y + for(int size = Math.min(n-x, m-y); size >= 1; size--){ // 枚举 [1, min(n-x, m-y)]这么多的方阵(size表示方阵大小) + if(check(matrix, x, y, size)){ //检查这个方阵是否全为1 + res = Math.max(res, size*size); + break; //因为size是从大->小,所以可以break + } + } + } + } + return res; + } + + private boolean check(char[][] matrix, int x, int y, int size){ + for(int i = x; i < x+size; i++){ + for(int j = y; j < y+size; j++){ + if(matrix[i][j] - '0' == 0) + return false; + } + } + return true; + } +} +``` + +*** + +### 改进动态规划(`O(N^3)`) + +上面的`check()`函数在判断大正方形的时候其实包含了很多的子问题。 + +可以通过改进上面方式中的 `check()`过程来优化复杂度: + +* 将 `check()`提前预处理好,这个过程需要用到动态规划; +* 这个动态规划用到一个二维`sums`数组,`sum[x][y] (或者sums[i][j])` 代表的是 `(0, 0) ~ (x, y)`这个矩阵的和; + + +看下图对于`sum( 0~x, 0~y )`的求法: + +其中两个蓝色部分有一个重叠的绿色部分,所以要多减去一个。 + +![在这里插入图片描述](images/221_s2.png) + +如果求出了上面的`sums`数组,我们就可以在`O(1)`时间内检查这个枚举的正方形是不是全都是`1`了,我们只需要求枚举的正方形的和是不是等于`size * size`就可以判断。 + + +**怎么由当前已经求得的`sums`数组得到当前枚举的正方形的和呢**? + +可以由`sum`数组得到当前以`(x, y)`为左上角顶点的正方形的和,大体框架如下: + +![在这里插入图片描述](images/221_s3.png) + + +不过这里要注意代码的处理: + +我们`sums`数组起始从`1, 1`开始会比较方便处理,所以实际上下面的代码中`sums[x, y]`表示的是`[0 ~ i-1, 0 ~ j-1]`内的和。不然判断边界就会比较麻烦,所以检查的时候第三层循环`size = Math.min(n-x+1, m-y+1) `开始也需要注意。 +```java +class Solution { + public int maximalSquare(char[][] matrix) { + if(matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int n = matrix.length; + int m = matrix[0].length; + //预处理出 (0, 0) ~ (x,y) 矩阵的里面的数的和 + int[][] sums = new int[n+1][m+1]; + for(int x = 1; x <= n; x++){ // 从[0,0]到[x,y]递推 + for(int y = 1; y <= m; y++){ + sums[x][y] = sums[x][y-1] + sums[x-1][y] - sums[x-1][y-1] + matrix[x-1][y-1]-'0'; + } + } + int res = 0; + for(int x = 1; x <= n; x++){ // 枚举左上角的 x + for(int y = 1; y <= m; y++){ // 枚举左上角的 y + for(int size = Math.min(n-x+1, m-y+1); size >= 1; size--){ // 枚举 [1, min(n-x, m-y)]这么多的方阵(size表示方阵大小) + int sum = sums[x+size-1][y+size-1] - sums[x+size-1][y-1] - sums[x-1][y+size-1] + sums[x-1][y-1]; + if(sum == size*size){ + res = Math.max(res, size*size); + break; + } + } + } + } + return res; + } +} +``` + +#### LeetCode - 304. Range Sum Query 2D - Immutable + +这里[**LeetCode - 304. Range Sum Query 2D - Immutable**](https://leetcode.com/problems/range-sum-query-2d-immutable/) 就完全是这种方法解决: + +![在这里插入图片描述](images/304_t.png) + +代码: + +```java +class NumMatrix { + + private int[][] sums; + + public NumMatrix(int[][] matrix) { + if(matrix == null || matrix.length == 0 || matrix[0].length == 0) + return; + int n = matrix.length, m = matrix[0].length; + sums = new int[n+1][m+1]; + // sum[i, j]表示 矩形[0 ~ i-1, 0 ~ j-1]的数的和 + for(int i = 0; i < n; i++){ + for(int j = 0; j < m; j++){ + sums[i+1][j+1] = sums[i+1][j] + sums[i][j+1] - sums[i][j] + matrix[i][j]; + } + } + } + + // [row1, col1] -> [row2, col2] + public int sumRegion(int row1, int col1, int row2, int col2) { + return sums[row2+1][col2+1] - sums[row2+1][col1] - sums[row1][col2+1] + sums[row1][col1]; + } +} +``` +其实`sums[i, j]`也可以表示是`[0 ~ i, 0 ~j]`矩阵的和,但是这样下面的函数处理会不方便,所以我们用`sums[i, j]`表示的是 +`[0 ~ i-1, 0 ~ j-1]`内的和。 + + +下面是`sums[i][j]`表示`[0 ~ i, 0 ~j]`矩阵的和的代码: +```java +public NumMatrix(int[][] matrix) { + if(matrix == null || matrix.length == 0 || matrix[0].length == 0) + return; + int n = matrix.length, m = matrix[0].length; + sums = new int[n+1][m+1]; // sum[i, j]表示 矩形[0~i, 0~j]的数的和 + + sums[0][0] = matrix[0][0]; + for(int i = 1; i < n; i++) + sums[i][0] = sums[i-1][0] + matrix[i][0]; + for(int j = 1; j < m; j++) + sums[0][j] = sums[0][j-1] + matrix[0][j]; + for(int i = 1; i < n; i++){ + for(int j = 1; j < m; j++){ + sums[i][j] = sums[i-1][j] + sums[i][j-1] - sums[i-1][j-1] + matrix[i][j]; + } + } +} +``` + +### 优化动态规划(`O(N^2)`) + +这个方法是本题的最优解。 + +其中`dp[i][j]`表示的是从`(0, 0) `到当前 `(x, y)`能构成的最大的正方形的`size`(边的大小)。 + +则: + +* 如果当前`matrix[i][j] == 0`,则`dp[i][j] = 0`; +* 其他一般的情况,看下图,可以得到`dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1`; + +图: + + +![在这里插入图片描述](images/221_s4.png) + +代码: + +```java +class Solution { + public int maximalSquare(char[][] matrix) { + if(matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int n = matrix.length; + int m = matrix[0].length; + + int[][] dp = new int[n][m]; + + int res = 0; + for(int i = 0; i < n; i++){ + dp[i][0] = matrix[i][0] - '0'; + res = Math.max(res, dp[i][0]); + } + for(int j = 0; j < m; j++){ + dp[0][j] = matrix[0][j] - '0'; + res = Math.max(res, dp[0][j]); + } + for(int i = 1; i < n; i++){ + for(int j = 1; j < m; j++){ + if(matrix[i][j] - '0' == 0) + continue; + dp[i][j] = 1 + Math.min(dp[i][j-1], Math.min(dp[i-1][j], dp[i-1][j-1])); + res = Math.max(res, dp[i][j]); + } + } + return res * res; + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-241.Different Ways to Add Parentheses(\345\210\206\346\262\273\343\200\201dp).md" "b/Algorithm/LeetCode/DP/LeetCode-241.Different Ways to Add Parentheses(\345\210\206\346\262\273\343\200\201dp).md" new file mode 100644 index 00000000..164d3c8a --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-241.Different Ways to Add Parentheses(\345\210\206\346\262\273\343\200\201dp).md" @@ -0,0 +1,136 @@ +# LeetCode - 241. Different Ways to Add Parentheses(分治、dp) + +#### [题目链接](https://leetcode.com/problems/different-ways-to-add-parentheses/) + +> https://leetcode.com/problems/different-ways-to-add-parentheses/ + +#### 题目 +![在这里插入图片描述](images/241_t.png) +#### 解题思路 + +这个题目也有点分治的意思,但是主体还是记忆化递归,用记忆化递归写起来非常方便,思路也很清晰: + +* 递归函数,遍历当前字符串,只要有符号是`+`、`-`、`*`的就将整个字符串分开成两半; +* 然后左边一半的字符串`lstr`去递归求出那个解的集合,右边的`rstr`也求出解的集合; +* 最后关键的是当前的字符串的解是左和右的**笛卡尔积**; +* 然后记得记忆化; + +图: + +![在这里插入图片描述](images/241_s.png) + +代码: + +Java版本: + +```java +class Solution { + private HashMap> map; + + public List diffWaysToCompute(String input) { + map = new HashMap<>(); + return helper(input); + } + + private List helper(String input) { + if (map.containsKey(input)) + return map.get(input); + List res = new ArrayList<>(); + + for (int i = 0; i < input.length(); i++) { + + char op = input.charAt(i); + + if (op == '*' || op == '+' || op == '-') { + List L = helper(input.substring(0, i));//求出左边 + List R = helper(input.substring(i + 1));//求出右边 + + for (Integer l : L) {// 左右两边笛卡尔积 + for (Integer r : R) { + res.add(computer(l, r, op)); + } + } + } + } + if (res.isEmpty()) {// 没有操作符 直接加入数字 + res.add(Integer.valueOf(input)); + } + map.put(input, res);//记忆化 + return res; + } + + private int computer(int a, int b, char op) { + return op == '+' ? a + b : (op == '-' ? a - b : a * b); + } +} +``` +C++版本: + +```cpp +class Solution { +public: + vector diffWaysToCompute(string input) { + return helper(input); + } +private: + const vector& helper(const string& input){ //使用const+引用,即可防止随意修改,又不必建立拷贝 + if(mp.count(input)) + return mp[input]; + + vectorres; + for(int i = 0; i < input.length(); i++){ + char op = input[i]; + if(op == '+' || op == '-' || op == '*') { + const string lstr = input.substr(0,i); + const string rstr = input.substr(i+1); + + const vector& L = helper(lstr); + const vector& R = helper(rstr); + + //笛卡尔积 + for(int l : L){ + for(int r : R){ + res.push_back(computer(l, r, op)); + } + } + } + } + if(res.empty()) + res.push_back(stoi(input)); + return mp[input] = res; + } + int computer(int a, int b, char op){ + return op == '+' ? a + b : (op == '-' ? a - b : a * b); + } + unordered_map>mp; //使用无序map(类似HashMap,而不是TreeMap) +}; +``` + +Python版本: + +```python +class Solution: + def diffWaysToCompute(self, input): + if input.isdigit(): # 不同于Java版本,这里边界条件放在这里,之前那个是res.isEmpty()就加入数字 + return [int(input)] # 转换成数字 -> 列表并返回 + res = [] + for i in range(len(input)): + if input[i] in "+-*": + L = self.diffWaysToCompute(input[:i]) + R = self.diffWaysToCompute(input[i+1:]) + + for l in L: + for r in R: + res.append(self.computer(l, r, input[i])) + + return res + + def computer(self, a, b, op): + if op == '+': + return a + b + elif op == '-': + return a - b + else: + return a * b +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-268.Missing Number(\344\275\215\350\277\220\347\256\227) & 674. Longest Continuous Increasing Subsequence(DP).md" "b/Algorithm/LeetCode/DP/LeetCode-268.Missing Number(\344\275\215\350\277\220\347\256\227) & 674. Longest Continuous Increasing Subsequence(DP).md" new file mode 100644 index 00000000..38069392 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-268.Missing Number(\344\275\215\350\277\220\347\256\227) & 674. Longest Continuous Increasing Subsequence(DP).md" @@ -0,0 +1,153 @@ +## LeetCode - 268. Missing Number & 674. Longest Continuous Increasing Subsequence + +* [LeetCode - 268. Missing Number](#leetcode---268-missing-number) +* [LeetCode - 674. Longest Continuous Increasing Subsequence](#leetcode---674-longest-continuous-increasing-subsequence) + +*** +### LeetCode - 268. Missing Number +#### [题目链接](https://leetcode.com/problems/missing-number/) + +> https://leetcode.com/problems/missing-number/ + +#### 题目 +![在这里插入图片描述](images/268_t.png) +#### 解析 +题目要求在`O(n)`时间和`O(1)`空间内完成。 +也是一个很有意思的题目,简单的想法: + +* 用一个变量`sumAll`记录包含没有丢失的那个数的所有的和(也可以用等差数列求和公式求出); +* 然后求出数组的和`sum`,结果就是`sumAll - sum`; + +更好的解法,利用亦或的性质: + +![在这里插入图片描述](images/268_s.png) + +利用第四条性质,循环亦或`xor = xor ^ (i+1) ^ nums[i]`其中没有出现的数就会剩下来。 + +```java +class Solution { + public int missingNumber(int[] nums) { + int sumAll = 0, sum = 0; + for(int i = 0; i < nums.length; i++){ + sumAll += (i+1); + sum += nums[i]; + } + return sumAll-sum; + } +} +``` +用等差数列求和公式求出`sumAll`: +```java +class Solution { + + public int missingNumber(int[] nums) { + int sumAll = (0+nums.length)*(nums.length+1)/2; //等差数列 + int sum = 0; + for(int i = 0; i < nums.length; i++) + sum += nums[i]; + return sumAll-sum; + } + +} +``` +更加巧妙的方式,利用异或运算: + +```java +class Solution { + + public int missingNumber(int[] nums) { + int xor = 0; + for(int i = 0; i < nums.length; i++) + xor = xor ^ (i+1) ^ nums[i]; + return xor; + } +} +``` + +*** +### LeetCode - 674. Longest Continuous Increasing Subsequence +#### [题目链接](https://leetcode.com/problems/longest-continuous-increasing-subsequence/) + +> https://leetcode.com/problems/longest-continuous-increasing-subsequence/ + +#### 题目 +求最长连续递增子串(不是子序列)。 + +![在这里插入图片描述](images/674_t.png) + +#### 解析 +比[最长递增子序列](https://blog.csdn.net/zxzxzx0119/article/details/81224734)更简单,同样提供三种解法。 + +一维`dp` +```java +class Solution { + + public int findLengthOfLCIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int res = 1; + int[] dp = new int[nums.length]; + dp[0] = 1; + for(int i = 1; i < nums.length; i++){ + if(nums[i] > nums[i-1]) + dp[i] = dp[i-1] + 1; + else + dp[i] = 1; + res = Math.max(res, dp[i]); + } + return res; + } + +} +``` +记忆化递归: +```java +class Solution { + + public int res; + public int findLengthOfLCIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + Arrays.fill(dp, -1); + res = 1; + process(nums, nums.length-1, dp); + return res; + } + public int process(int[] nums, int i, int[] dp){ + if(i == 0) + return 1; + if(dp[i] != -1) + return dp[i]; + if(nums[i] > nums[i-1]){ + dp[i] = process(nums, i-1, dp) + 1; + }else { + dp[i] = 1; + process(nums, i-1, dp); // still should + } + res = Math.max(res, dp[i]); + return dp[i]; + } +} +``` +滚动优化 +```java +class Solution { + + public int findLengthOfLCIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int res = 1, cur = 1; + for(int i = 1; i < nums.length; i++){ + if(nums[i] > nums[i-1]) + cur += 1; + else + cur = 1; + res = Math.max(res, cur); + } + return res; + } +} +``` + +*** diff --git a/Algorithm/LeetCode/DP/LeetCode-312.Burst Balloons(DP).md b/Algorithm/LeetCode/DP/LeetCode-312.Burst Balloons(DP).md new file mode 100644 index 00000000..b51c96ae --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-312.Burst Balloons(DP).md @@ -0,0 +1,212 @@ +# LeetCode - 312. Burst Balloons(DP) + +* 递归写法 +* 二维dp + +*** +#### [题目链接](https://leetcode.com/problems/burst-balloons/description/) + +> https://leetcode.com/problems/burst-balloons/description/ + +#### 题目 + +#### 递归 +![在这里插入图片描述](images/312_t.png) + +这题递归本来用`ArrayList`写了一个,也是枚举删除的位置,递归后插入还原,但是那样不好记忆化,于是看了讨论区。。。答案有点分治的意思。。 + +思路: (注意这里`process`函数(递归函数)求的是在`[L,R]`闭区间可以取的最大值) + +* 如果`L > R`,则区间无数,这是递归边界,返回`0`; +* 否则,在`[L, R]`区间内,枚举"删除"每一个数,然后**这个数的两边(`[L, i-1]` 和`[i+1,R]`)都需要先递归的求出他们的最大值**; +* 然后,最重要的一点: **因为两边是先递归的**,所以其实这些数已经被删除了,所以我们枚举`[L,R]`的时候(用一个变量`i`枚举),不能在求的时候写出` arr[i] * arr[i-1] * arr[i+1]`,而是`arr[i] * arr[L-1] * arr[R+1]`; +* 然后我们要注意边界 ,也就是`i = 0` 和`i == arr.length - 1`的情况特殊处理一下; + +图: + +![1554881438745](assets/1554881438745.png) + +代码: + + +```java +class Solution { + + private int[][] dp; + + public int maxCoins(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + dp = new int[nums.length][nums.length]; + return rec(nums, 0, nums.length - 1); + } + + private int rec(int[] arr, int L, int R) { + if (L > R) //中间没有数了 + return 0; +// if(L == R) //注意这里不是习惯性的这样写,因为递归函数只是一个区间而已,并不是真的只剩下一个数了 +// return arr[L]; + if (dp[L][R] != 0) + return dp[L][R]; + int res = 0; + for (int i = L; i <= R; i++) { + int sum = 0; + int center = arr[i]; + if (L != 0) + center *= arr[L - 1]; + if (R != arr.length - 1) + center *= arr[R + 1]; + sum += center; + sum += rec(arr, L, i - 1); + sum += rec(arr, i + 1, R); + res = Math.max(res, sum); + } + dp[L][R] = res; + return res; + } +} +``` + +另一种写法,使用开区间的写法,有一些不同: + +* 先在数组的两边都加上`1`,这样就不需要处理边界,因为`center`是相乘,不影响结果; +* 边界就变成了`L + 1 == R`,因为是开区间`(L,R)`这种情况就是区间内没有数了,也就是边界; +* 其他的类似; + +代码: + +```java +class Solution { + + private int[][] dp; + + public int maxCoins(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[] newNums = new int[nums.length + 2]; + + int n = 1; + for (int num : nums) + newNums[n++] = num; + newNums[0] = newNums[n] = 1; + + dp = new int[nums.length + 2][nums.length + 2]; // num.length + 2 + return process(newNums, 0, newNums.length - 1); //注意都是newNum 实际求的是 [1,newNums.length-2] + } + + private int process(int[] arr, int L, int R) { + if (L + 1 == R) //中间没有数了 因为求的是开区间的 + return 0; + if (dp[L][R] != 0) + return dp[L][R]; + int res = 0; + for (int i = L + 1; i <= R - 1; i++) { + int sum = 0; + int center = arr[i]; + center *= arr[L]; + center *= arr[R]; + sum += center; + sum += process(arr, L, i); + sum += process(arr, i, R); + res = Math.max(res, sum); + } + dp[L][R] = res; + return res; + } +} +``` +*** +### 二维dp + +仿照第一种写法写出来的动态规划: + +这里有一个很重要的地方: + +* **就是更新的顺序,这就是为什么这个题目不好写出一维的动态规划的原因**。某个位置`dp[i][j] `依赖的地方很不是一排的(左边和下面); +* 图中,棕色的方块是`0`,因为`L > R`; +* 然后其他的就和递归差不多了; + +图: + +![在这里插入图片描述](images/312_s.png) + +代码: + +```java +class Solution { + + public int maxCoins(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[][] dp = new int[nums.length][nums.length]; + + for (int L = nums.length - 1; L >= 0; L--) {//注意这里的顺序 + for (int R = L; R < nums.length; R++) { + + int res = 0; + for (int i = L; i <= R; i++) { + int sum = 0; + int center = nums[i]; + if (L != 0) + center *= nums[L - 1]; + if (R != nums.length - 1) + center *= nums[R + 1]; + sum += center; + + if (L <= i - 1) + sum += dp[L][i - 1]; + if (i + 1 <= R) + sum += dp[i + 1][R]; + + res = Math.max(res, sum); + } + dp[L][R] = res; + + } + } + return dp[0][nums.length - 1]; + } +} +``` +同样第二种方法的`dp`写法,这种写法要自己拷贝一份数组,但是好处是,不要去判断一些繁琐的边界,因为我们的`newNum[0] = newNum[newNum.length -1] = 1`,这样不要判断越界: 同样也要注意`L`和`R`更新的顺序: + +```java +class Solution { + + public int maxCoins(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[] newNums = new int[nums.length + 2]; + + int n = 1; + for (int num : nums) + newNums[n++] = num; + newNums[0] = newNums[n] = 1; + + int[][] dp = new int[nums.length + 2][nums.length + 2]; + + for (int L = newNums.length - 1; L >= 0; L--) { //同样不能写成for(int L = 0; L < newNums.length; L++) + for (int R = L; R < newNums.length; R++) { + + int res = 0; + // (L,R) 开区间 + for (int i = L + 1; i <= R - 1; i++) { + int sum = 0; + int center = newNums[i]; + center *= newNums[L]; + center *= newNums[R]; + + sum += center; + sum += dp[L][i]; + sum += dp[i][R]; + res = Math.max(res, sum); + } + + dp[L][R] = res; + } + } + return dp[0][newNums.length - 1]; + } +} +``` + diff --git a/Algorithm/LeetCode/DP/LeetCode-32.Longest Valid Parentheses.md b/Algorithm/LeetCode/DP/LeetCode-32.Longest Valid Parentheses.md new file mode 100644 index 00000000..621ffaec --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-32.Longest Valid Parentheses.md @@ -0,0 +1,189 @@ +# LeetCode - 32. Longest Valid Parentheses + +#### [题目链接](https://leetcode.com/problems/longest-valid-parentheses/) + +> https://leetcode.com/problems/longest-valid-parentheses/ + +#### 题目 + +![1558315413365](assets/1558315413365.png) + +注意,不是问最多有多少可以匹配,而是问可以匹配的最大的长度。例如下面: + +![1558315395501](assets/1558315395501.png) + +### 解析 + +#### 1、暴力解法 + +枚举每一个串,判断这个串是不是合法匹配,然后记录最大的长度,**这里有一个优化就是:可以只判断偶数长度的串**,但是还是会超时,时间复杂度`O(N^3)`。 + +```java +import java.util.*; + +class Solution { + + // 判断s是不是合法匹配括号 + private boolean isValid(String s) { + Stack stack = new Stack<>(); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '(') + stack.push('('); + else if (!stack.empty() && stack.peek() == '(') + stack.pop(); + else + return false; + } + return stack.empty(); + } + + public int longestValidParentheses(String s) { + int res = 0; + for (int i = 0; i < s.length(); i++) { + // 只需要判断偶数长度的串 + for (int j = i + 2; j <= s.length(); j += 2) { //注意j<=s.length,因为s.substring() + if (isValid(s.substring(i, j))) + res = Math.max(res, j - i); + } + } + return res; + } +} +``` + +#### 2、暴力优化 + +记录以每一个位置`i`开头的最长结果。 + +我们判断从**每个位置开始的最长合法子串是多长即可**。而判断是否是合法子串,我们不用栈,**而是用一个变量记录当前的括号情况,遇到左括号加 1,遇到右括号减 1,如果变成 0 ,我们就更新下最长合法子串** + +`O(N^2)` + +```java +class Solution { + public int longestValidParentheses(String s) { + int res = 0; + for(int i = 0; i < s.length(); i++){ + int cnt = 0; + for(int j = i; j < s.length(); j++){//注意从i开始 + char c = s.charAt(j); + if(s.charAt(j) == '('){ + cnt++; + }else { + if(cnt <= 0) break; + else cnt--; + } + if(cnt == 0 && j-i+1 > res) + res = j-i+1; + } + } + return res; + } +} +``` + +#### 3、动态规划 + +动态规划的思路**是求以每一个位置结尾的最长符合条件的括号匹配的长度**。 + +`dp [ i ]` 代表以下标 `i `结尾的合法序列的最长长度,例如下图 + +![1558367533637](assets/1558367533637.png) + +下标 1 结尾的最长合法字符串长度是 2,下标 3 结尾的最长字符串是 str [ 0 , 3 ],长度是 4 。 + +(1)、以左括号结尾的字符串一定是非法序列,所以 dp 是零,不用更改。 + +(2)、以右括号结尾的字符串分两种情况。 + +- a)、右括号前边是` (` ,类似于 `……()`。 + + `dp [ i ] = dp [ i - 2] + 2 `(前一个合法序列的长度,加上当前新增的长度 2) + + 类似于上图中 `index = 3 `的时候的情况。 + + `dp [ 3 ] = dp [ 3 - 2 ] + 2 = dp [ 1 ] + 2 = 2 + 2 = 4` + +- b)、右括号前边是 `)`,类似于 `……))`。 + + 此时我们需要判断` i - dp[i - 1] - 1` (前一个合法序列的前边一个位置) 是不是左括号。 + + 例如上图的 index = 7 的时候,此时 index - 1 也是右括号,我们需要知道 `i - dp[i - 1] - 1 = 7 - dp [ 6 ] - 1 = 4 `位置的括号的情况。 + + 而刚好` index = 4 `的位置是左括号,此时 `dp [ i ] = dp [ i - 1 ] + dp [ i - dp [ i - 1] - 2 ] + 2 `(当前位置的前一个合法序列的长度,加上匹配的左括号前边的合法序列的长度,加上新增的长度 2),也就是 `dp [ 7 ] = dp [ 7 - 1 ] + dp [ 7 - dp [ 7 - 1] - 2 ] + 2 = dp [ 6 ] + dp [7 - 2 - 2] + 2 = 2 + 4 + 2 = 8`。 + + 如果 index = 4 不是左括号,那么此时位置 7 的右括号没有匹配的左括号,所以 dp [ 7 ] = 0 ,不需要更新。 + +代码: + +```java +class Solution { + public int longestValidParentheses(String s) { + char[] chs = s.toCharArray(); + int res = 0; + int dp[] = new int[s.length()]; + for (int i = 1; i < s.length(); i++) { + if (chs[i] != ')') continue; // dp[i] = 0 + if (chs[i - 1] == '(') //右括号前边是左括号 + dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; + else if (i - dp[i - 1] > 0 && chs[i - dp[i - 1] - 1] == '(') //右括号前边是右括号,并且除去前边的合法序列的前边是左括号 + dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2; + res = Math.max(res, dp[i]); + } + return res; + } +} +``` + +#### 4、栈 + +**从左到右扫描字符串,栈顶保存当前扫描的时候,合法序列前的一个位置位置下标是多少**。 + +我们扫描到左括号,就将当前位置入栈。 + +扫描到右括号,就将栈顶出栈(代表栈顶的左括号匹配到了右括号),然后分两种情况。 + +- 栈不空,那么就用当前的位置减去栈顶的存的位置,然后就得到当前合法序列的长度,然后更新一下最长长度。 +- 栈是空的,说明之前没有与之匹配的左括号,那么就将当前的位置入栈。 + +案例: + +![1558581280198](assets/1558581280198.png) + +![1558581993305](assets/1558581993305.png) + +![1558582399701](assets/1558582399701.png) + +![1558582172219](assets/1558582172219.png) + +![1558582223373](assets/1558582223373.png) + +![1558582291486](assets/1558582291486.png) + +![1558582341310](assets/1558582341310.png) + + + +```java +class Solution { + public int longestValidParentheses(String s) { + int res = 0; + Stack stack = new Stack<>(); + stack.push(-1); + for (int i = 0; i < s.length(); i++) { + if (s.charAt(i) == '(') { + stack.push(i); + } else { + stack.pop(); + if (stack.isEmpty()) { + stack.push(i); + } else { + res = Math.max(res, i - stack.peek()); + } + } + } + return res; + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-354.Russian Doll Envelopes\345\217\212\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/Algorithm/LeetCode/DP/LeetCode-354.Russian Doll Envelopes\345\217\212\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\200\273\347\273\223.md" new file mode 100644 index 00000000..e8eafc26 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-354.Russian Doll Envelopes\345\217\212\346\234\200\351\225\277\344\270\212\345\215\207\345\255\220\345\272\217\345\210\227\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -0,0 +1,284 @@ +## LeetCode - 354. Russian Doll Envelopes及最长上升子序列问题总结 + + - [最长上升子序列普通dp法](#最长上升子序列普通dp法) + - [最长上升子序列解的打印](#最长上升子序列解的打印) + - [最长上升子序列NlogN法](#最长上升子序列nlogn法) + - [LeetCode - 354. Russian Doll Envelopes](#leetcode---354-russian-doll-envelopes) + +*** +[**LeetCode300 测试最长上升子序列**](https://leetcode.com/problems/longest-increasing-subsequence/) + +### 最长上升子序列普通dp法 +> 生成长度为`N`的数组`dp`,`dp[i]`表示的是在以`arr[i]`这个数结尾的情况下,`arr[0...i]`中的最长递增子序列长度。 +> + - 对第一个数`arr[0]`来说,`dp[0] = 1`,最长递增子序列就是自己。 + - 当计算到`dp[i]`的时候,最长递增子序列要以`arr[i]`结尾,所以我们在`arr[0....i-1]`中所有比`arr[i]`小的数可以作为最长递增子序列的倒数第二个数,这些数中,哪个的最长递增子序列更大,就选择哪个。即`dp[i] = max(dp[j] + 1) ,0 <= j < i,arr[j] < arr[i]`; +代码: + +```java + /** + * dp[i]表示以arr[0]结尾的情况下,arr[0...i]中的最大递增子序列 + */ + class Solution { + public int lengthOfLIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + int res = 1; + for(int i = 0; i < nums.length; i++){ + dp[i] = 1; + for(int j = 0; j < i; j++){ + if(nums[j] < nums[i]) + dp[i] = Math.max(dp[i], dp[j]+1); + res = Math.max(res,dp[i]); + } + } + return res; + } +} +``` +也可以和普通动态规划一样写成递归(记忆化的): + +```java +class Solution { + public int res; + public int lengthOfLIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + Arrays.fill(dp, -1); + res = 1; + recur(nums, nums.length-1, dp); + return res; + } + + public int recur(int[] nums, int i, int[] dp){ + if(i == 0) + return 1; + if(dp[i] != -1) + return dp[i]; + int max = 1; + for(int k = 0; k < i; k++){ + int kCur = recur(nums, k, dp); // remember to recursive + if(nums[i] > nums[k]) + max = Math.max(kCur+1, max); + } + res = Math.max(res, max); + return dp[i] = max; + } +} +``` + +另一种递归的写法就是,直接在主程序中求每个位置结尾的最长递增子序列,然后记录一个最大值即可,这样递归函数就只有当`nums[i] > nums[k]`的时候才递归,而不是所有的时候都需要递归了。 + +```java +class Solution { + + public int lengthOfLIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + Arrays.fill(dp, -1); + recur(nums, nums.length-1, dp); + int res = 1; + for(int i = 0; i < nums.length; i++) // 求每个位置结尾的最长递增子序列,然后记录一个最大值即可 + res = Math.max(res, recur(nums, i, dp)); + return res; + } + + public int recur(int[] nums, int i, int[] dp){ + if(i == 0) + return 1; + if(dp[i] != -1) + return dp[i]; + int max = 1; + for(int k = 0; k < i; k++){ + if(nums[i] > nums[k]) + max = Math.max(recur(nums, k, dp) + 1, max); + } + return dp[i] = max; + } +} +``` + +*** +### 最长上升子序列解的打印 + +>根据上面的方法可以求得`dp`数组,我们根据`dp`数组就可以得到最长上升子序列的解。 +> + - 从`dp`数组中的最大值`dp[maxi]`表示的是以`arr[maxi]`结尾的,而且是最长的上升子序列; + - 我们从`maxi`往前面找,如果前面的某个`dp[i]`,**满足arr[i] < arr[maxi] 且dp[maxi] = dp[i] + 1**,就说明这个是我们找最长递增子序列时候取的值,可以作为最长递增子序列的倒数第二个数。 + - 然后依次往前找,可以得到解。 + +代码: + +```java + public static int[] getLis(int[] arr,int[] dp){ + int len = 0; + int index = 0; + for(int i = 0; i < dp.length; i++){ + if(dp[i] > len){ + len = dp[i]; + index = i; + } + } + int[] lis = new int[len]; + lis[--len] = arr[index]; + for(int i = index - 1; i >= 0; i--){ + if(dp[i] == dp[index] - 1 && arr[i] < arr[index]){ + lis[--len] = arr[i]; + index = i; + } + } + return lis; + } +``` +*** +### 最长上升子序列NlogN法 + 这个方法是使用一个额外的数组`ends[]`,`dp[i]`记录的还是以`arr[i]`结尾的最长递增子序列,`ends[i]`记录的是在所有长度为`i`的递增序列中,最小的结尾数是`ends[i]`。初始时整形变量`right = 1`,`ends[1...right]`为有效区,`ends[right+1...arr.length]`为无效区,然后使用二分的方式在`ends`数组中查找。 + + + * `dp[0] = 1`,`ends[1] = arr[0]`表示的是,在所有长度为`1`的递增序列中,最小结尾是`arr[0]`,此时只有一个数; + * `right`变量记录`ends`数组的有效范围,最后返回的就是`right`(`end`的有效范围); + * 遍历到某个数`arr[i]`的时候,在`ends[]`数组中二分查找最左边的`>=arr[i]`的数(二分查找看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/82670761#t4)) + + * 如果有某个位置`l`,即`arr[l] > arr[i]`,说明`l`长度的最小结尾可以更新为`arr[i]`,且`dp[i] = right`(目前的最长长度); + * 否则,如果没有找到(此时`l = right+1`),则说明有效区长度要扩大`1`,且`end[right+1] = arr[i]`,表示长度为`right + 1`的最小结尾暂时是`arr[i]`,此时`dp[i] = right + 1`; + - 一直遍历整个数组,最后的最长长度就是有效区的长度(`right`); + +```java + +/** + * dp[i]记录的还是以arr[i]结尾的最长递增子序列 + * ends数组中 ends[i]表示的是在所有长度为i的最长递增子序列中最小结尾是什么 + */ + +class Solution { + public int lengthOfLIS(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int[] dp = new int[nums.length]; + int[] ends = new int[nums.length + 1]; + dp[0] = 1; + ends[1] = nums[0]; + int right = 1; // [1~right]为有效区 ends数组是有序的(升序), right是右边界 + int L, mid, R; + for (int i = 1; i < nums.length; i++) { + L = 1; + R = right; + // 找到第一个>=arr[i]的,返回结果是 L + while (L <= R) { + mid = L + (R - L) / 2; + if (ends[mid] >= nums[i]) + R = mid - 1; + else + L = mid + 1; + } + // 说明以arr[i]以arr[i]结尾的最长递增子序列=ends区有效长度+1 + if (L > right) { //没有找到arr[i]是最长的 (因为下标从1开始,所以判断是>right), + dp[i] = right + 1; + ends[right + 1] = nums[i]; // 扩大ends数组 + right += 1; //扩大有效区 + } else { // 找到了arr[l] >= arr[i], 更新end[l] = arr[i] ,表示l长度的最长子序列结尾可以更新为arr[i] + dp[i] = right; // dp[i]还是没有加长 + ends[L] = nums[i]; + } + } + return right; + } +} +``` +一个测试用例: + +![在这里插入图片描述](images/354_s.png) + + + +*** +### LeetCode - 354. Russian Doll Envelopes +#### [题目链接](https://leetcode.com/problems/russian-doll-envelopes/description/) + +> https://leetcode.com/problems/russian-doll-envelopes/description/ + + #### 题目 + +![img.png](images/354_t.png) + +### 解析 + 求解过程:先按`a`从小到大进行排序,当`a`相同时,按`b`从大到小排序。然后求解`b` (第二维(宽))的最长递增子序列。(一维是长度,二维是宽度) + +* 排序后等于把在二维(长、宽)上的最长递增子序列问题转换成一维(宽)上的最长递增子序列的查找,因为对于 + 长度来说已经满足递增, 只需要在宽度上也递增即为递增序列; +* 同长时按宽度降序排列的原因是避免同长时宽度小的也被列入递增序列中, 例如`[3,3], [3,4]`,如果宽度也按升序来排列, `[3,3]`和`[3,4]`会形成递增序列,而实际上不行; + +图: + +![1554952364205](assets/1554952364205.png) + +代码: + +```java +import java.util.Arrays; + +class Solution { + + private class Node implements Comparable { + public int a; + public int b; + + public Node(int a, int b) { + this.a = a; + this.b = b; + } + + /** + * + 排序后等于把在二维(长、宽) + 上的最长递增子序列问题转换成一维(宽)上的最长递增子序列的查找, 因为对于 + 长度来说已经满足递增, 只需要在宽度上也递增即为递增序列。 + 同长时按宽度降序排列的原因是 + 避免同长时宽度小的也被列入递增序列中, 例如[3,3], [3,4] + 如果宽度也按升序来排列, [3,3]和[3,4]会形成递增序列, 而实际上不行 + */ + @Override + public int compareTo(Node o) { + if (o.a == a) + return o.b - b; + return a - o.a; + + } + } + + public int maxEnvelopes(int[][] envelopes) { + if (envelopes == null || envelopes.length == 0 || envelopes[0].length == 0) + return 0; + Node[] nodes = new Node[envelopes.length]; + for (int i = 0; i < envelopes.length; i++) + nodes[i] = new Node(envelopes[i][0], envelopes[i][1]); + Arrays.sort(nodes); + int[] ends = new int[envelopes.length + 1]; + ends[1] = nodes[0].b; + int right = 1; + int l, m, r; + for (int i = 1; i < nodes.length; i++) { + l = 1; + r = right; + while (l <= r) { + m = l + (r - l) / 2; + if (ends[m] >= nodes[i].b) { + r = m - 1; + } else { + l = m + 1; + } + } + if (l > right) { + ends[right + 1] = nodes[i].b; + right += 1; + } else { + ends[l] = nodes[i].b; + } + } + return right; + } +} +``` diff --git "a/Algorithm/LeetCode/DP/LeetCode-486.Predict the Winner(\346\216\222\346\210\220\344\270\200\346\235\241\347\272\277\347\232\204\347\272\270\347\211\214\345\215\232\345\274\210\351\227\256\351\242\230).md" "b/Algorithm/LeetCode/DP/LeetCode-486.Predict the Winner(\346\216\222\346\210\220\344\270\200\346\235\241\347\272\277\347\232\204\347\272\270\347\211\214\345\215\232\345\274\210\351\227\256\351\242\230).md" new file mode 100644 index 00000000..e273fcc7 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-486.Predict the Winner(\346\216\222\346\210\220\344\270\200\346\235\241\347\272\277\347\232\204\347\272\270\347\211\214\345\215\232\345\274\210\351\227\256\351\242\230).md" @@ -0,0 +1,94 @@ +# LeetCode - 486. Predict the Winner(排成一条线的纸牌博弈问题) + - 递归解法 + - 动态规划解法 + +*** +#### [题目链接](https://leetcode.com/problems/predict-the-winner/description/) + +> https://leetcode.com/problems/predict-the-winner/description/ + +#### 题目 +![pic](images/486_t.png) + +### 递归解法 + + - 定义递归函数`f[i,j]`,表示的是如果`arr[i...j]`这个排列上的纸牌被绝顶聪明的人先拿,最终可以获得什么分数。 + - 定义递归函数`s[i,j]`,表示的是如果`arr[i...j]`这个排列上的纸牌被绝顶聪明的人后拿,最终可以获得什么分数。 + +首先对于`f[i,j] `: + + * 如果`i == j`,即`arr[i...j]`上只有一张纸牌,当然会被先拿纸牌的人拿走,所以可以返回`arr[i]`; + * 如果`i != j`,此时先拿的人有两种选择。如果拿走`arr[i]`,那么会剩下`arr[i+1,j]`,这个时候,对于当前的玩家来说,他就成了后拿的人,所以他之后能获得的分数为`s(i+1,j)`; 如果他拿走的是`arr[j]`,那么会剩下`arr[i...j-1]`,对于当前的玩家来说,它成了后拿的人,之后能获得的分数为`s(i,j-1)`,因为当前的玩家会做出最好的选择,所以是`max(arr[i]+s[i+1,j], arr[j]+s[i,j-1]);` + +然后来考虑`s[i,j]` : + + * 如果`i == j`,即`arr[i...j]`上只有一张纸牌,作为后拿的人必然什么也得不到,所以返回`0`; + * 如果`i != j`,根据`s`函数的定义,当前的玩家的对手会先拿纸牌,如果对手拿走了`arr[i...j]`中的`arr[i]`,剩下`arr[i+1,j]`,然后轮到当前玩家拿。如果对手拿走了`arr[i...j]`中的`arr[j]`,剩下`arr[i,j-1]`,然后轮到当前玩家拿。因为对手会拿走最好的,所以当前玩家只能拿最差的,所以是`min(f[i+1,j],f[i,j-1])`; + +所以可以写出如下的代码 + +```java +class Solution { + + public boolean PredictTheWinner(int[] arr) { + if (arr == null || arr.length == 0) + return true; + return f(arr, 0, arr.length - 1) >= s(arr, 0, arr.length - 1); + } + + public int f(int[] arr, int i, int j) { //先拿的 + if (i == j) + return arr[i]; + return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1)); //拿了其中一个之后,当前玩家成了后拿的那个人 + } + + public int s(int[] arr, int i, int j) { //后拿的 + if (i == j) //后拿的这个 已经不能拿了 + return 0; + return Math.min(f(arr, i + 1, j), f(arr, i, j - 1)); + } +} +``` +*** +### 动态规划解法 + +上面的方法的时间复杂度达到`O(2^n)`次方,我们可以根据上面的方法来改出动态规划。 + + - 先生成两张二维`dp`表,`f`表和`s`表,我们发现,根据上面的方法,`f`表中的主对角线上的值就是`arr[i]`的值(即`f[i][i] = arr[i]`); + - 同理`s`表中的主对角线上的值为`0`; + - 然后我们可以得到`f`表中的某个位置`f[i,j]`依赖的是`s`表中的位置`s[i+1,j]`和`s[i,j-1] `(即下面的位置和左边的位置); + - `s`表中同理依赖`f`表中`f[i+1][j]`和`f[i,j-1]`的位置; + +具体过程看下图: + +![1554993044112](assets/1554993044112.png) + +最后我们要求的就是`f[0][len]` 和 `s[0][len] `这两个值,如果`f[0][len]`更大的话,第一个玩家就能赢; + +可以写出如下代码 + +```java +class Solution { + + public boolean PredictTheWinner(int[] nums) { + if (nums == null || nums.length == 0 || nums.length == 1) + return true; + int len = nums.length - 1; + int[][] f = new int[len + 1][len + 1]; + int[][] s = new int[len + 1][len + 1]; + for (int i = 0; i <= len; i++) {//对角线上填充(就是递归中的边界条件) + f[i][i] = nums[i]; +// s[i][i] = 0; // 数组自动初始化 + } + for (int i = len - 1; i >= 0; i--) { + for (int j = i + 1; j <= len; j++) { + f[i][j] = Math.max(nums[i] + s[i + 1][j], nums[j] + s[i][j - 1]); //注意这里是arr[j]+是s[i][j-1] + s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]); + } + } + return f[0][len] >= s[0][len]; + } +} +``` + + diff --git "a/Algorithm/LeetCode/DP/LeetCode-518.Coin Change 2(\351\233\266\351\222\261\345\205\221\346\215\242 II)(\346\215\242\351\222\261\347\232\204\346\226\271\346\263\225\346\225\260\351\227\256\351\242\230).md" "b/Algorithm/LeetCode/DP/LeetCode-518.Coin Change 2(\351\233\266\351\222\261\345\205\221\346\215\242 II)(\346\215\242\351\222\261\347\232\204\346\226\271\346\263\225\346\225\260\351\227\256\351\242\230).md" new file mode 100644 index 00000000..4254991c --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-518.Coin Change 2(\351\233\266\351\222\261\345\205\221\346\215\242 II)(\346\215\242\351\222\261\347\232\204\346\226\271\346\263\225\346\225\260\351\227\256\351\242\230).md" @@ -0,0 +1,238 @@ +# LeetCode - 518. Coin Change 2(零钱兑换 II)(换钱的方法数问题) + - 暴力递归解法 + - 记忆化搜索解法 + - 二维dp解法 + - 二维dp优化 + - 滚动数组优化空间O(n) +*** +#### [题目链接](https://leetcode.com/problems/coin-change-2/description/) + +> https://leetcode.com/problems/coin-change-2/description/ + +#### 题意 +![在这里插入图片描述](images/518_t.png) + +### 暴力递归解法 + 暴力递归的解法,比如上面的例子: + + - 用`0`张`1`元的货币,让`[2,5]`组成剩下的`5`元,最终方法数为`sum1`; + - 用`1`张`1`元的货币,让`[2,5]`组成剩下的`4`元,最终方法数为`sum2`; + - 用`2`张`1`元的货币,让`[2,5]`组成剩下的`3`元,最终方法数为`sum3`; + - 用`3`张`1`元的货币,让`[2,5]`组成剩下的`2`元,最终方法数为`sum4`; + - 用`4`张`1`元的货币,让`[2,5]`组成剩下的`1`元,最终方法数为`sum5`; + - 用`5`张`1`元的货币,让`[2,5]`组成剩下的`0`元,最终方法数为`sum6`; + +那么我们要求的结果就是`sum1 + sum2 + sum3 + sum4 + sum5 + sum6 `; + +根据上面的分析过程可以写出一个递归函数`process(coins,index,amount)`表示的是用`coins[index,N-1]`这 + +些硬币来组成`amount`,返回的总的方法数,所以代码如下(超时): + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + return process(coins, 0, amount); + } + + public int process(int[] coins, int index, int amount) { + int res = 0; + if (index == coins.length) //到了最后一个说明找到了一种方法 + return res = amount == 0 ? 1 : 0; + for (int i = 0; i * coins[index] <= amount; i++) + res += process(coins, index + 1, amount - coins[index] * i); + return res; + } +} +``` +*** +### 记忆化搜索解法 + +上面的递归过程计算了许多的重复子问题,我们可以在递归的过程中,记录子问题的解,当再次用到的时候,如果 + +之前已经计算过,可以取出来用。减少递归过程 + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int[][] map = new int[coins.length + 1][amount + 1]; + return process(coins, 0, amount, map); + } + + public int process(int[] coins, int index, int amount, int[][] map) { + int res = 0; //方法数 + if (index == coins.length) + res = amount == 0 ? 1 : 0; + else { + int mapValue = 0; + for (int i = 0; i * coins[index] <= amount; i++) { + mapValue = map[index + 1][amount - i * coins[index]]; + if (mapValue != 0) { + res += mapValue == -1 ? 0 : mapValue; + } else { + res += process(coins, index + 1, amount - i * coins[index], map); + } + } + } + map[index][amount] = res == 0 ? -1 : res; + return res; + } +} +``` +**注意这里`map`的几个特殊值: `0`表示没有计算过。`-1`表示计算过但是返回值是`0`;其他值就是计算过且不为`0`**; + +或者不用上面的特殊值,初始化`map`为`-1`,程序就会变得简单一点: + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int[][] map = new int[coins.length + 1][amount + 1]; + + for(int i = 0; i < map.length; i++) + Arrays.fill(map[i], -1); + + return process(coins, 0, amount, map); + } + + public int process(int[] coins, int index, int amount, int[][] map) { + int res = 0; //方法数 + if (index == coins.length) + res = amount == 0 ? 1 : 0; + else { + int mapValue = 0; + for (int i = 0; i * coins[index] <= amount; i++) { + mapValue = map[index + 1][amount - i * coins[index]]; + if (mapValue != -1) + res += mapValue; + else + res += process(coins, index + 1, amount - i * coins[index], map); + } + } + return map[index][amount] = res; + } +} +``` +再简写: + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int[][] map = new int[coins.length + 1][amount + 1]; + + for(int i = 0; i < map.length; i++) + Arrays.fill(map[i], -1); + + return process(coins, 0, amount, map); + } + + public int process(int[] coins, int index, int amount, int[][] map) { + if (index == coins.length) + return amount == 0 ? 1 : 0; + if(map[index][amount] != -1) + return map[index][amount]; + int res = 0; + for (int i = 0; i * coins[index] <= amount; i++) + res += process(coins, index + 1, amount - i * coins[index], map); + return map[index][amount] = res; + } +} +``` + +*** +### 二维dp解法 + +这个方法也是通过递归的方法改过来的,递归中的边界条件就是一开始要填的位置,如下图。 + +![1554995470391](assets/1554995470391.png) + + - 我们先填好最后一行的`dp`表(根据递归函数) + - 然后看一个普通的位置依赖的是左边的一些位置(逐渐减,不越界) + - 然后我们就通过递推的方向推出整张`dp`表,右上角是答案; + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int len = coins.length; + int[][] dp = new int[len + 1][amount + 1]; + for (int j = 0; j <= amount; j++) dp[len][j] = 0; + dp[len][0] = 1; + int sum = 0; + for (int i = len - 1; i >= 0; i--) { + for (int j = 0; j <= amount; j++) { + sum = 0; + for (int k = 0; j - coins[i] * k >= 0; k++) sum += dp[i + 1][j - coins[i] * k]; + dp[i][j] = sum; + } + } + return dp[0][amount]; + } +} +``` +*** +### 二维dp优化 +在计算左边的一些依赖的值的时候,我们迭代的求和,其实没有必要,因为`dp[i][j-coins[j]]`已经将前面的和求了一遍,我们可以直接拿过来用就可以了;于是里面的第三层循环就可以不写; +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int len = coins.length; + int[][] dp = new int[len + 1][amount + 1]; + for (int j = 0; j <= amount; j++) dp[len][j] = 0; + dp[len][0] = 1; + for (int i = len - 1; i >= 0; i--) { + for (int j = 0; j <= amount; j++) { + dp[i][j] = j - coins[i] >= 0 ? dp[i][j - coins[i]] + dp[i + 1][j] : dp[i + 1][j]; + } + } + return dp[0][amount]; + } +} +``` +*** +### 滚动数组优化空间O(n) +由于递推的方向是从下到上,从右到左,所以我们可以通过滚动数组进行优化,`dp[i-coins[j]]`的值还是之前 + +的`dp[i][i-coins[j]]`,因为此时的dp[i]还没有更新,所以`dp[i]`也是`dp[i+1][j]`,所以只需要一个一维数组即可; + +```java +class Solution { + public int change(int amount, int[] coins) { + if (amount == 0) + return 1; + if (coins == null || coins.length == 0 || amount < 0) + return 0; + int[] dp = new int[amount + 1]; + dp[0] = 1; + for (int i = coins.length - 1; i >= 0; i--) { + for (int j = 0; j <= amount; j++) { + dp[j] = j - coins[i] >= 0 ? dp[j - coins[i]] + dp[j] : dp[j]; + } + } + return dp[amount]; + } +} + +``` + diff --git a/Algorithm/LeetCode/DP/LeetCode-53.Maximum Subarray.md b/Algorithm/LeetCode/DP/LeetCode-53.Maximum Subarray.md new file mode 100644 index 00000000..349036c6 --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-53.Maximum Subarray.md @@ -0,0 +1,254 @@ +## LeetCode - 53. Maximum Subarray(最大子序和)(一维dp) + + - 递归写法 + - 一维dp数组 + - 滚动优化 + - 分治解法 + - Hdu - 1003. Max Sum +*** +#### [题目链接](https://leetcode.com/problems/maximum-subarray/) + +> https://leetcode.com/problems/maximum-subarray/ + +#### 题目 +![在这里插入图片描述](images/53_t.png) + +### 1、递归写法 +我们可以从数组的最后开始往前看: + +* 对于当前数`nums[i]`,以这个`nums[i]`结尾的最大值一定是**你前面的所有数求出一个最大的子序和**(但是由于是子数组,所以必须是判断前一个数)`+`我自己(`nums[i]`)。 +* 所以这是一个递归的过程,边界条件就是`i = 0`时,最大子序和就是自己。 + +图: + +

+代码: + +```java +class Solution { + + public int res; + + public int maxSubArray(int[] nums) { + res = nums[0]; + rec(nums, nums.length - 1); + return res; + } + + public int rec(int[] nums, int index) { + if (index == 0) { + return nums[0]; + } else { + int pre = rec(nums, index - 1); //先给我求出前面的最大子序和 + int cur = pre > 0 ? pre + nums[index] : nums[index]; + res = Math.max(cur, res); + return cur; + } + } +} + +``` +*** +### 2、一维dp数组 +动态规划就是从小问题到大问题,递归相反的方向,我们可以正向的保存一个**以每一个数结尾的最大子序和的数组**,然后递推到最后一个,其中使用个`max`保存最大值; + +

+代码: + +```java +class Solution { + + public int maxSubArray(int[] nums) { + int[] ends = new int[nums.length]; + ends[0] = nums[0]; + int max = ends[0]; + for (int i = 1; i < nums.length; i++) { + ends[i] = ends[i - 1] > 0 ? ends[i - 1] + nums[i] : nums[i]; + max = Math.max(max, ends[i]); + } + return max; + } +} + +``` +### 3、滚动优化 +类似二维dp的滚动数组优化,因为每个位置只需要它前面的一个位置,所以我们只需要用一个变量保存即可。 + +```java +class Solution { + + public int maxSubArray(int[] nums) { + int preMax = nums[0]; + int max = nums[0]; + for (int i = 1; i < nums.length; i++) { + preMax = preMax > 0 ? preMax + nums[i] : nums[i]; + max = Math.max(max, preMax); + } + return max; + } +} + +``` +### 4、分治解法 + +思路: +* 找到中间位置,所求子串不是在中间位置的左边,就是右边,还有中间位置两边; +* 中间位置左边右边的和最大的子串可以递归地求得; +* 再求中间位置往左挨个加的最大和以及中间位置往右挨个数的最大和,这两个和就是子串跨越中间位置时的最大和; +* 这三个最大和中的最大值就是所求最大值; +* **这里要注意`LMax = process(arr,L,mid)`,这里的右边界不是`mid-1`,而是`mid`,因为边界是`L==R`的时候返回`arr[L]`,而且我们计算`crossMax`的时候包括了`arr[mid]`,就不需要再加**上`arr[mid]`。 + +图: + +![53_ss3.png](images/53_ss3.png) + +代码: + +```java +class Solution { + + public int maxSubArray(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + return rec(nums, 0, nums.length - 1); + } + + //返回这个之间的最大子序和 + private int rec(int[] arr, int L, int R) { + if (L == R) + return arr[L]; + int mid = L + (R - L) / 2; + int LMax = rec(arr, L, mid); + int RMax = rec(arr, mid + 1, R); + + int sum = 0, LSumMax = Integer.MIN_VALUE, RSumMax = Integer.MIN_VALUE; + + for (int i = mid; i >= L; i--) { + sum += arr[i]; + if (sum > LSumMax) { + LSumMax = sum; + } + } + sum = 0; + for (int i = mid + 1; i <= R; i++) { + sum += arr[i]; + if (sum > RSumMax) { + RSumMax = sum; + } + } + int crossMax = LSumMax + RSumMax; + + //compare crossMax、LMax,RMax + if (LMax >= RMax && LMax >= crossMax) + return LMax; + if (RMax >= LMax && RMax >= crossMax) + return RMax; + return crossMax; + } +} +``` + +*** +### 5、Hdu - 1003. Max Sum +#### [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=1003) + +> http://acm.hdu.edu.cn/showproblem.php?pid=1003 + +#### 题目 +![在这里插入图片描述](images/hdu1003_t.png) + + + +#### 解析 +这个和上面有点不同的是要我们求出求得最大字段和的同时,要**求出左右边界,其实很简单,先记录一下右边界,然后从后往前递推左边界即可**; + +一维dp: + +```java +import java.io.*; +import java.util.Scanner; + +public class Main { + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + int T = in.nextInt(); + int kase = 0; + while (T-- > 0) { + int n = in.nextInt(); + int[] arr = new int[n]; + for (int i = 0; i < n; i++) + arr[i] = in.nextInt(); + + int[] ends = new int[arr.length]; + ends[0] = arr[0]; + int max = ends[0], maxi = 0; + for (int i = 1; i < arr.length; i++) { + ends[i] = ends[i - 1] > 0 ? ends[i - 1] + arr[i] : arr[i]; + if (ends[i] > max) { + max = ends[i]; + maxi = i; + } + } + int curSum = max, L = maxi; // L 是左边界 + for (int i = maxi; i >= 0; i--) { + curSum -= arr[i]; + if (curSum == 0) { + L = Math.min(L, i); + } + } + out.println("Case " + (++kase) + ":"); + out.println(max + " " + (L + 1) + " " + (maxi + 1)); + if (T != 0) out.println(); + } + out.close(); + } +} +``` +滚动优化: + +```java +import java.io.*; +import java.util.Scanner; + + +public class Main { + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + int T = in.nextInt(); + int kase = 0; + while (T-- > 0) { + int n = in.nextInt(); + int[] arr = new int[n]; + for (int i = 0; i < n; i++) + arr[i] = in.nextInt(); + + int preMax = arr[0]; + int max = preMax, maxi = 0; + for (int i = 1; i < arr.length; i++) { + preMax = preMax > 0 ? preMax + arr[i] : arr[i]; + if (preMax > max) { + max = preMax; + maxi = i; + } + } + + int curSum = max, L = maxi; + for (int i = maxi; i >= 0; i--) { + curSum -= arr[i]; + if (curSum == 0) { + L = Math.min(L, i); + } + } + out.println("Case " + (++kase) + ":"); + out.println(max + " " + (L + 1) + " " + (maxi + 1)); + if (T != 0) out.println(); + } + out.close(); + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-62.Unique Paths(\344\270\215\345\220\214\347\232\204\350\267\257\345\276\204\346\225\260\351\207\217)(\347\256\200\345\215\225dp).md" "b/Algorithm/LeetCode/DP/LeetCode-62.Unique Paths(\344\270\215\345\220\214\347\232\204\350\267\257\345\276\204\346\225\260\351\207\217)(\347\256\200\345\215\225dp).md" new file mode 100644 index 00000000..46092419 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-62.Unique Paths(\344\270\215\345\220\214\347\232\204\350\267\257\345\276\204\346\225\260\351\207\217)(\347\256\200\345\215\225dp).md" @@ -0,0 +1,142 @@ +# LeetCode - 62. Unique Paths(不同的路径数量)(简单dp) + - 记忆化 + - 二维dp + - 空间优化 + +*** +#### [题目链接](https://leetcode.com/problems/unique-paths/description/) + +> https://leetcode.com/problems/unique-paths/description/ + +#### 题目 + +![png](images/62_t.png) + +这个题目和[最小路径和问题](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2064.%20Minimum%20Path%20Sum(%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C).md)很类似。 + +## 1、记忆化 + + +* **来到某个结点,因为只能往右边和下面走,所以一个位置`[i,j]`依赖的位置是`[i-1,j]`和`[i,j-1]`位置两个点之前的数量之和**; + * 所以我们把`[i,j]`看做是**终点**的话就得出下面的递归关系。 + * 但是由于有重复的子问题,所以我们需要使用记忆化递归来优化。 + +图: + +

+ +如果不记录重复子问题的话就会是`O(2^n)`; + + +```java +class Solution { + + private int[][] dp; + + public int uniquePaths(int m, int n) { + dp = new int[m + 1][n + 1]; + return rec(m, n, m, n); + } + + public int rec(int m, int n, int i, int j) { + if (i == 1 && j == 1) + return 1; + if (dp[i][j] != 0) //已经计算过 + return dp[i][j]; + if (i == 1) + dp[i][j] = rec(m, n, i, j - 1); + else if (j == 1) + dp[i][j] = rec(m, n, i - 1, j); + else + dp[i][j] = rec(m, n, i - 1, j) + rec(m, n, i, j - 1); + return dp[i][j]; + } +} +``` +## 2、二维dp +递归改动态规划就是和递归相反的方向: +

+ +代码: + + +```java +class Solution { + public int uniquePaths(int m, int n) { + int[][] dp = new int[m + 1][n + 1]; + dp[m][n] = 1; + for (int j = n - 1; j >= 1; j--) dp[m][j] = dp[m][j + 1]; + for (int i = m - 1; i >= 1; i--) dp[i][n] = dp[i + 1][n]; + for (int i = m - 1; i >= 1; i--) { + for (int j = n - 1; j >= 1; j--) { + dp[i][j] = dp[i][j + 1] + dp[i + 1][j]; + } + } + return dp[1][1]; + } +} +``` + +可以有两种写法,在于你看问题的角度: + + + - 第一种看问题的角度,把`[i,j]`看做是终点,那就是上面的递归关系; + - 第二种看问题的角度,把`[i,j]`看做是起点,此时`[i,j]`总共的数量就是从`[i+1,j]`出发和从`[i,j+1]`出发的数量,那就是下面的递归关系; + +就是说递归和动态规划也可以写成这样: + +

+```java +class Solution { + public int uniquePaths(int m, int n) { + int[][] dp = new int[m + 1][n + 1]; + dp[1][1] = 1; + for (int j = 2; j <= n; j++) dp[1][j] = dp[1][j - 1]; //第一行 + for (int i = 2; i <= m; i++) dp[i][1] = dp[i - 1][1]; + for (int i = 2; i <= m; i++) { + for (int j = 2; j <= n; j++) { + dp[i][j] = dp[i][j - 1] + dp[i - 1][j]; + } + } + return dp[m][n]; + } +} + +``` + +## 3、滚动优化 +**滚动数组的优化就是其实你在算`dp[i][j]`的时候,你左边的`dp[i][j-1]`还是`dp[j-1]`,而你上面的`dp[i-1][j]`还是`dp[j] `(没有更新),所以可以只需要一个数组,所以滚动优化决定的是你更新的顺序**; + +```java +class Solution { + public int uniquePaths(int m, int n) { + int[] dp = new int[n + 1]; + for (int i = 1; i <= n; i++) dp[i] = 1; + for (int i = 2; i <= m; i++) { + for (int j = 2; j <= n; j++) { + dp[j] = dp[j] + dp[j - 1]; + } + } + return dp[n]; + } +} + +``` +或者这样 (第二种): + +```java +class Solution { + public int uniquePaths(int m, int n) { + int[] dp = new int[n + 1]; + for (int i = 1; i <= n; i++) dp[i] = 1; + for (int i = m - 1; i >= 1; i--) { + for (int j = n - 1; j >= 1; j--) { + dp[j] = dp[j] + dp[j + 1]; + } + } + return dp[1]; + } +} + +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-63.Unique Paths II(\346\234\211\351\232\234\347\242\215\347\211\251\347\232\204\344\270\215\345\220\214\350\267\257\345\276\204).md" "b/Algorithm/LeetCode/DP/LeetCode-63.Unique Paths II(\346\234\211\351\232\234\347\242\215\347\211\251\347\232\204\344\270\215\345\220\214\350\267\257\345\276\204).md" new file mode 100644 index 00000000..79573682 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-63.Unique Paths II(\346\234\211\351\232\234\347\242\215\347\211\251\347\232\204\344\270\215\345\220\214\350\267\257\345\276\204).md" @@ -0,0 +1,136 @@ +# LeetCode - 63. Unique Paths II(有障碍物的不同路径) + + - 记忆化 + - 二维dp + - 一维dp + +#### [题目链接](https://leetcode.com/problems/unique-paths-ii/description/) + +> https://leetcode.com/problems/unique-paths-ii/description/ + +#### 题目 +![在这里插入图片描述](images/63_t.png) + +## 1、记忆化 +和`LeetCode - 62`唯一不同的就是这里当格子是`1`的时候直接不能走。返回`0`即可。 + +递归思路如下。 + +

+ +代码: + +```java +class Solution { + + private int[][] dp; + private int n; + private int m; + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + n = obstacleGrid.length; + m = obstacleGrid[0].length; + dp = new int[n][m]; + for (int i = 0; i < dp.length; i++) + Arrays.fill(dp[i], -1); + return rec(obstacleGrid, 0, 0); + } + + public int rec(int[][] G, int i, int j) { + if (i == n - 1 && j == m - 1) return G[i][j] == 0 ? 1 : 0; + if (dp[i][j] != -1) return dp[i][j]; + if (i == n - 1) + dp[i][j] = G[i][j] == 1 ? 0 : (G[i][j + 1] == 0 ? rec(G, i, j + 1) : 0); + else if (j == m - 1) { + dp[i][j] = G[i][j] == 1 ? 0 : (G[i + 1][j] == 0 ? rec(G, i + 1, j) : 0); + } else { + int right = G[i][j + 1] == 0 ? rec(G, i, j + 1) : 0; + int down = G[i + 1][j] == 0 ? rec(G, i + 1, j) : 0; + dp[i][j] = (G[i][j] == 1) ? 0 : (right + down); + } + return dp[i][j]; + } +} +``` +简单优化: + + - 由于右边和下面的不管是`0`还是真的`>0`的值,都是一个值,不影响; + - 或者说每一个`(i,j)`没有必要重复判断`(i,j+1)`或者`(i+1,j)`位置的值,所以只需要判断自己`(i,j)`; + +```java +class Solution { + + private int[][] dp; + private int n; + private int m; + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + n = obstacleGrid.length; + m = obstacleGrid[0].length; + dp = new int[n][m]; + for (int i = 0; i < dp.length; i++) + Arrays.fill(dp[i], -1); + return rec(obstacleGrid, 0, 0); + } + + public int rec(int[][] G, int i, int j) { + if (i == n - 1 && j == m - 1) return G[i][j] == 0 ? 1 : 0; + if (dp[i][j] != -1) return dp[i][j]; + if (i == n - 1) + dp[i][j] = G[i][j] == 1 ? 0 : rec(G, i, j + 1); + else if (j == m - 1) + dp[i][j] = G[i][j] == 1 ? 0 : rec(G, i + 1, j); + else + dp[i][j] = (G[i][j] == 1) ? 0 : (rec(G, i, j + 1) + rec(G, i + 1, j)); + return dp[i][j]; + } +} +``` + + +## 2、二维dp + +根据递归可以改成dp。(下面从从右下角到左上角,也可以反过来) + +```java +class Solution { + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + int n = obstacleGrid.length; + int m = obstacleGrid[0].length; + int[][] dp = new int[n][m]; + dp[n - 1][m - 1] = obstacleGrid[n - 1][m - 1] == 1 ? 0 : 1; + for (int i = n - 2; i >= 0; i--) dp[i][m - 1] = obstacleGrid[i][m - 1] == 1 ? 0 : dp[i + 1][m - 1]; + for (int j = m - 2; j >= 0; j--) dp[n - 1][j] = obstacleGrid[n - 1][j] == 1 ? 0 : dp[n - 1][j + 1]; + for (int i = n - 2; i >= 0; i--) { + for (int j = m - 2; j >= 0; j--) + dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : (dp[i][j + 1] + dp[i + 1][j]); + } + return dp[0][0]; + } +} +``` + +## 3、一维dp +滚动优化。 + +```java +class Solution { + + public int uniquePathsWithObstacles(int[][] obstacleGrid) { + int n = obstacleGrid.length; + int m = obstacleGrid[0].length; + int[] dp = new int[m]; + dp[m - 1] = obstacleGrid[n - 1][m - 1] == 1 ? 0 : 1; + for (int j = m - 2; j >= 0; j--) dp[j] = obstacleGrid[n - 1][j] == 1 ? 0 : dp[j + 1]; + for (int i = n - 2; i >= 0; i--) { + dp[m - 1] = obstacleGrid[i][m - 1] == 1 ? 0 : dp[m - 1]; + for (int j = m - 2; j >= 0; j--) { + dp[j] = obstacleGrid[i][j] == 1 ? 0 : (dp[j + 1] + dp[j]); + } + } + return dp[0]; + } +} +``` + diff --git a/Algorithm/LeetCode/DP/LeetCode-639.Decode Ways II.md b/Algorithm/LeetCode/DP/LeetCode-639.Decode Ways II.md new file mode 100644 index 00000000..a4840975 --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-639.Decode Ways II.md @@ -0,0 +1,121 @@ +# LeetCode - 639. Decode Ways II + +#### [题目链接](https://leetcode.com/problems/decode-ways-ii/) + +> https://leetcode.com/problems/decode-ways-ii/ + +#### 题目 +![在这里插入图片描述](images/639_t.png) +#### 解析 + +大体思路和[LeetCode - 91. Decode Ways](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2091.%20Decode%20Ways.md)相同: + +* 不同点: 上面的情况当一个字符的时候直接返回`1`,但是这里需要考虑当只有一个字符的时候: ① `c == 0`返回`0`;②`c == {0~9}`返回`1`;③`c == *`返回`9`; +* 两个字符的时候更多的情况,我写在了下图的右边,可以自己推一下,不难; +* 然后就是注意结果的返回,上一题相当于是`1 * r1 + 1 * r2`,这一题就是上面的一些不同的结果导致,如果前缀不是`1`,就要和系数相乘,即`res = p1 * r1 + p2 * r2`,其中`r1`还是隔离`str[i]`之后的返回结果,`r2`是隔离`str[i]str[i+1]`之后的返回结果,不过要带上系数即可; + +图: + +![在这里插入图片描述](images/639_s.png) + +由于这一题数据规模比较大,所以递归会产生`StackOverflowError`,即递归深度太大,所以最好使用递推代码: + +```java +class Solution { + + final int mod = 1000000000 + 7; + + public int numDecodings(String s) { + int n = s.length(); + char[] chs = s.toCharArray(); + long[] dp = new long[n+1]; + dp[n] = 1; // 空串 + dp[n-1] = ways1(chs[n-1]); + for(int i = n - 2; i >= 0; i--){ + if(chs[i] == '0') + dp[i] = 0; + else { + dp[i] = ways1(chs[i]) * dp[i+1] + ways2(chs[i], chs[i+1]) * dp[i+2]; + dp[i] %= mod; + } + } + return (int)dp[0]; + } + + // only one character + private int ways1(char c){ + if(c == '0') + return 0; + if(c == '*') + return 9; + return 1; + } + + // two characters + private int ways2(char c1, char c2){ + if(c1 == '*' && c2 == '*') + return 15; + if(c1 == '*') // c1 == '*' && c2 != '*' + return (c2 >= '0' && c2 <= '6') ? 2 : 1; // *0 ~ *6 --> 2 + if(c2 == '*') // c1 != '*' && c2 == '*' + return c1 == '1' ? 9 : (c1 == '2' ? 6 : 0); + int se = 10 * (c1 - '0') + (c2 - '0'); + return (se >= 10 && se <= 26) ? 1 : 0; // contains 01, 02... + } +} +``` + +下面是递归的代码(`StackOverflowError`) + +```java +class Solution { + + final int mod = 1000000000 + 7; + + private long[] dp; + + public int numDecodings(String s) { + if(s == null || s.length() == 0) + return 0; + dp = new long[s.length()]; + Arrays.fill(dp, -1); + return (int)recur(s.toCharArray(), 0); + } + + private long recur(char[] chs, int pos){ + if(pos == chs.length) //空串 + return 1; + if(chs[pos] == '0') + return 0; + if(pos == chs.length-1) + return ways1(chs[pos]); + if(dp[pos] != -1) + return dp[pos]; + long res = ways1(chs[pos]) * recur(chs, pos+1) % mod; + if(pos < chs.length-1) + res += (ways2(chs[pos], chs[pos+1]) * recur(chs, pos+2)%mod) % mod; + return dp[pos] = res%mod; + } + + // only one character + private int ways1(char c){ + if(c == '0') + return 0; + if(c == '*') + return 9; + return 1; + } + + // two characters + private int ways2(char c1, char c2){ + if(c1 == '*' && c2 == '*') + return 15; + if(c1 == '*') // c1 == '*' && c2 != '*' + return (c2 >= '0' && c2 <= '6') ? 2 : 1; // *0 ~ *6 --> 2 + if(c2 == '*') // c1 != '*' && c2 == '*' + return c1 == '1' ? 9 : (c1 == '2' ? 6 : 0); + int se = 10 * (c1 - '0') + (c2 - '0'); + return (se >= 10 && se <= 26) ? 1 : 0; // contains 01, 02... + } +} +``` diff --git "a/Algorithm/LeetCode/DP/LeetCode-64.Minimum Path Sum(\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214).md" "b/Algorithm/LeetCode/DP/LeetCode-64.Minimum Path Sum(\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214).md" new file mode 100644 index 00000000..705004f1 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-64.Minimum Path Sum(\346\234\200\345\260\217\350\267\257\345\276\204\345\222\214).md" @@ -0,0 +1,152 @@ +# LeetCode - 64. Minimum Path Sum(最小路径和) + - 记忆化搜索 + - 二维空间dp表 + - 滚动数组优化空间O(min{N,M}) + - 打印解 +*** +#### [题目链接](https://leetcode.com/problems/minimum-path-sum/description/) + +> https://leetcode.com/problems/minimum-path-sum/description/ + +#### 题目 +![在这里插入图片描述](images/64_t.png) + +## 1、记忆化搜索 + +递归思路: + +- 假如我们就在最右下角的格子(也可以想象成网格只有一个格子),那么最短路径和就是格子中的值; + - 然后假如我们在最后一行的格子中,假如是`grid[grid.length][j]`,那么从这个点出发到最右下角的最小路径和就是本身加上它左边的格子到最右下角的最小路径和。 + - 最后一列和最后一行是同理的。 + - 一个普通的位置,它到最右下角的最小路径和是多少呢,**是它左边一个位置和它下面一个位置的最小路径和中最小的那个加上它自己格子的值**。 + +上面的过程有很多重复计算的子问题,使用一个二维数组记录一下,那么我们下次再需要这个子问题的解的时候不需要递归,直接拿出来用即可。 + +图: + +

+ +代码: + +```java +class Solution { + + private int[][] dp; + private int n, m; + + public int minPathSum(int[][] grid) { + n = grid.length; + m = grid[0].length; + dp = new int[n][m]; + for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1); + return rec(grid, 0, 0); + } + + public int rec(int[][] G, int i, int j) { + if (i == n - 1 && j == m - 1) return G[i][j]; + if (dp[i][j] != -1) return dp[i][j]; + if (i == n - 1) + return dp[i][j] = G[i][j] + rec(G, i, j + 1); + else if (j == m - 1) + return dp[i][j] = G[i][j] + rec(G, i + 1, j); + else + return dp[i][j] = G[i][j] + Math.min(rec(G, i + 1, j), rec(G, i, j + 1)); + } +} +``` + +## 2、二维dp表 + 动态规划的过程**可以看做是递归的逆过程,既然递归是从上往下求解,每个大的问题都要先去求解子问题,所以,动态规划是先求解小的子问题,依次往上,所以大问题需要的子问题已经提前求好了**。 + +对于这个题目 : + + - 先生成一张二维`dp`表,然后按照递归相反的方向求解; + - 先把`dp`表的最右下角,最后一行,和最后一列的位置填好; + - 然后一个普通的位置依赖它下面的位置和左边的位置,所以我们依次从下到上,做右往左,填整张`dp`表,最后`dp[0][0]`就是答案。 + +图: +

+ +代码:  + +```java +class Solution { + + public int minPathSum(int[][] grid) { + if (grid == null || grid.length == 0 || grid[0] == null || grid[0].length == 0) + return 0; + int r = grid.length - 1; + int c = grid[0].length - 1; + int[][] dp = new int[r + 1][c + 1]; + dp[r][c] = grid[r][c]; + for (int i = r - 1; i >= 0; i--) dp[i][c] = dp[i + 1][c] + grid[i][c]; //填充最后一列的 + for (int j = c - 1; j >= 0; j--) dp[r][j] = dp[r][j + 1] + grid[r][j]; //填充最后一行的 + for (int i = r - 1; i >= 0; i--) { //填充其他的 + for (int j = c - 1; j >= 0; j--) { + dp[i][j] = Math.min(dp[i + 1][j], dp[i][j + 1]) + grid[i][j]; + } + } + return dp[0][0]; + } +} +``` +*** +## 3、滚动数组优化空间`O(min{N,M})` + + 在我们计算整张`dp`表的时候,某个位置依赖的位置只是它右边的位置和下面的位置,所以如果只是要得到最小路径和的话,没有必要生成整张`dp`表,只需要一个一位数组即可。 + + 为什么可以这样呢,看下图,**因为某个位置`dp[i][j] `依赖` dp[i+1][j] `和`dp[i][j+1]`,如果只使用一维数组的话,其实此时`dp[i]`代表的就是`dp[i+1][j]`,而`dp[i+1]`代表的是`dp[i][j+1]`,此时它已经被更新,但是`dp[i]`没有被更新,所以可以直接比较`dp[i]`和`dp[i+1]`的大小赋给dp[i]即可**。 + +

+ +代码: + +```java +class Solution { + + public int minPathSum(int[][] grid) { + int n = grid.length; + int m = grid[0].length; + int big = Math.max(n - 1, m - 1); + int small = Math.min(n - 1, m - 1); + boolean rowMore = n - 1 == big; + int[] dp = new int[small + 1]; + dp[small] = grid[n - 1][m - 1]; + for (int i = small - 1; i >= 0; i--) + dp[i] = dp[i + 1] + (rowMore ? grid[big][i] : grid[i][big]); + for (int i = big - 1; i >= 0; i--) { + dp[small] = dp[small] + (rowMore ? grid[i][small] : grid[small][i]); + for (int j = small - 1; j >= 0; j--) { + dp[j] = Math.min(dp[j], dp[j + 1]) + (rowMore ? grid[i][j] : grid[j][i]); + } + } + return dp[0]; + } +} +``` +上面的代码处理了如果行数少于列数时,更新的方向就是一列一列的更新。这样就可以达到空间复杂度控制在`O(min{N,M})`内。 +*** +## 4、打印解 +注意要打印解的话,不能使用空间优化,必须保存原来的二维`dp`数组,这样才能回溯回去。 +```java +public void printAnswer(int[][] grid,int[][] dp){ + int i = 0,j = 0; + System.out.println("[0,0]"); + while(i < grid.length && j < grid[0].length){ + if(i == grid.length - 1 && j == grid[0].length - 1){ + break; + } + if(i == grid.length - 1) { + System.out.println("[" + i + "," + (++j) + "]"); + }else if( j == grid[0].length - 1){ + System.out.println("[" + (++i) + "," + j + "]"); + } else if(dp[i+1][j] < dp[i][j+1]){ + System.out.println("[" + (++i) + "," + j + "]"); + }else { + System.out.println("[" + i + "," + (++j) + "]"); + } + } +} +``` + + diff --git a/Algorithm/LeetCode/DP/LeetCode-664.Strange Printer(DP).md b/Algorithm/LeetCode/DP/LeetCode-664.Strange Printer(DP).md new file mode 100644 index 00000000..5da7e462 --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-664.Strange Printer(DP).md @@ -0,0 +1,85 @@ +# LeetCode - 664. Strange Printer(DP) +#### [题目链接](https://leetcode.com/problems/strange-printer/) + +> https://leetcode.com/problems/strange-printer/ + +#### 题目 + +![在这里插入图片描述](images/664_t.png) + +#### 解析 + +递归思路: + +* 当前递归函数求解`[i ~ j]`范围的最小打印次数; +* 则我们需要在`[i, j )`中寻找一个`k`,其中`s[k] == s[j]`,此时`[k ~ j]`范围内的字符可以一次打印,所以这样可以优化,所以我们要去在所以的`k && s[k] == s[j]`中取一个最小的,然后去递归`[i, k]`和`[k+1, j]`即可; + +举例: + +![在这里插入图片描述](images/664_s.png) + +代码: + +```java +class Solution { + + private int[][] dp; + + public int recur(StringBuilder sb, int i, int j){ + if(i > j) + return 0; + if(i == j) + return 1; + if(dp[i][j] > 0) + return dp[i][j]; + int res = recur(sb, i, j-1) + 1; // 最大的 + for(int k = i; k < j; k++){ + if(sb.charAt(k) == sb.charAt(j)) + res = Math.min(res, recur(sb, i, k) + recur(sb, k+1, j-1)); + } + return dp[i][j] = res; + } + + public int strangePrinter(String s) { + if(s == null || s.length() == 0) + return 0; + dp = new int[s.length()][s.length()]; + return recur(new StringBuilder(s), 0, s.length()-1); + } +} +``` + + +然后就是改成动态规划(递推),按照边界条件改即可,看一个例子`dp[2][7]`的可能依赖位置: + +![在这里插入图片描述](images/664_s2.png) + +代码: + +```java +class Solution { + public int strangePrinter(String s) { + if(s == null || s.length() == 0) + return 0; + int n = s.length(); + int[][] dp = new int[n][n]; + for(int i = 0; i < n; i++) + dp[i][i] = 1; + for(int i = n-2; i >= 0; i--){ //注意顺序 + for(int j = i + 1; j < n; j++){ + int tmp = dp[i][j-1] + 1; + for(int k = i; k < j; k++) + if(s.charAt(j) == s.charAt(k)) + tmp = Math.min(tmp, dp[i][k]+dp[k+1][j-1]); + dp[i][j] = tmp; + } + } + return dp[0][n-1]; + } +} +``` + + + + + diff --git "a/Algorithm/LeetCode/DP/LeetCode-673.Number of Longest Increasing Subsequence(\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260).md" "b/Algorithm/LeetCode/DP/LeetCode-673.Number of Longest Increasing Subsequence(\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260).md" new file mode 100644 index 00000000..f79fd0eb --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-673.Number of Longest Increasing Subsequence(\346\234\200\351\225\277\351\200\222\345\242\236\345\255\220\345\272\217\345\210\227\347\232\204\344\270\252\346\225\260).md" @@ -0,0 +1,118 @@ +# LeetCode - 673. Number of Longest Increasing Subsequence(最长递增子序列的个数) + +#### [题目链接](https://leetcode.com/problems/number-of-longest-increasing-subsequence/) + +> https://leetcode.com/problems/number-of-longest-increasing-subsequence/ + +#### 题目 + +![1555036709602](assets/1555036709602.png) + +![在这里插入图片描述](images/673_t.png) + + +#### 解析 +做这题之前先要知道[**求一个数组的最长递增子序列**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20354.%20Russian%20Doll%20Envelopes%E5%8F%8A%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93.md)。 + +做法: + +* 求出最长递增子序列的长度(`max`),可以用记忆化也可以递推; +* 然后遍历数组,看以哪些数结尾的序列是最长序列,然后对每一个这样的序列进行递归处理,从后往前求以这个结尾的最长序列的个数,这个递归函数记为`NLIS`; +* `NLIS`函数递归的转移方程: `NLIS(i) = sum {NLIS(k)} 其中 lis[k] + 1 == lis[i] && nums[i] > nums[k] `,其中`k`的范围即`[0, i)`; + +一个例子: `[1, 2, 4, 2, 3]` + +![在这里插入图片描述](images/673_s.png) + +代码: + +```java +class Solution { + + public int findNumberOfLIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int max = 1; + int[] lis = new int[nums.length]; + Arrays.fill(lis, -1); // 求以每个位置结尾的最长递增子序列的记忆化数组 + for(int i = 0; i < nums.length; i++) + max = Math.max(max, LIS(nums, i, lis)); + int[] nlis = new int[nums.length]; + Arrays.fill(nlis, -1); + int res = 0; + for(int i = 0; i < nums.length; i++){ + if(lis[i] == max) + res += NLIS(nums, i, nlis, lis); + } + return res; + } + + // 求以每个位置结尾的最长递增子序列的个数 + private int NLIS(int[] nums, int i, int[] nlis, int[] lis){ + if(i == 0) + return 1; + if(nlis[i] != -1) + return nlis[i]; + int res = 0; + for(int k = 0; k < i; k++) + if(nums[i] > nums[k] && lis[k] + 1 == lis[i]) + res += NLIS(nums, k, nlis, lis); + if(res == 0) // 至少有自己一个 + res = 1; + return nlis[i] = res; + } + + // 求以每一个位置结尾的最长递增子序列 + private int LIS(int[] nums, int i, int[] lis){ + if(i == 0) + return lis[i] = 1; // 这里将 lis[0]也要正确的赋值, 因为上面的 NLIS要用到这个 lis数组 + if(lis[i] != -1) + return lis[i]; + int res = 1; + for(int k = 0; k < i; k++){ + if(nums[i] > nums[k]) + res = Math.max(res, LIS(nums, k, lis) + 1); + } + return lis[i] = res; + } +} +``` + +按照上面的方式也可以写成递推形式。 + +```java +class Solution { + + public int findNumberOfLIS(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + int max = 1; + int[] lis = new int[nums.length]; + for(int i = 0; i < nums.length; i++){ + lis[i] = 1; + for(int j = 0; j < i; j++){ + if(nums[j] < nums[i]){ + lis[i] = Math.max(lis[i], lis[j] + 1); + } + } + max = Math.max(lis[i], max); + } + int[] nlis = new int[nums.length]; + int res = 0; + for(int i = 0; i < nums.length; i++){ + nlis[i] = 0; + for(int j = 0; j < i; j++){ + if(nums[j] < nums[i] && lis[j] + 1 == lis[i]){ + nlis[i] += nlis[j]; + } + } + if(nlis[i] == 0) + nlis[i] = 1; + if(lis[i] == max) + res += nlis[i]; + } + return res; + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/DP/LeetCode - 678. Valid Parenthesis String (DP _ \346\200\235\347\273\264).md" "b/Algorithm/LeetCode/DP/LeetCode-678.Valid Parenthesis String (DP _ \346\200\235\347\273\264).md" similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/LeetCode - 678. Valid Parenthesis String (DP _ \346\200\235\347\273\264).md" rename to "Algorithm/LeetCode/DP/LeetCode-678.Valid Parenthesis String (DP _ \346\200\235\347\273\264).md" diff --git a/Algorithm/LeetCode/DP/LeetCode-688.Knight Probability in Chessboard (DP).md b/Algorithm/LeetCode/DP/LeetCode-688.Knight Probability in Chessboard (DP).md new file mode 100644 index 00000000..62e75a40 --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-688.Knight Probability in Chessboard (DP).md @@ -0,0 +1,154 @@ +# LeetCode - 688. Knight Probability in Chessboard (DP) + +#### [题目链接](https://leetcode.com/problems/knight-probability-in-chessboard/) + +> https://leetcode.com/problems/knight-probability-in-chessboard/ + +#### 题目 +![在这里插入图片描述](images/688_t.png) + +#### 解析 + +思路: + +当前的步数以及当前的位置,可以由上一个可以走到当前位置的位置走到当前位置,如果没有越界,总共有`8`个这样的**上一个位置**。 + +看题目中的例子: + +![在这里插入图片描述](images/688_s.png) + +于是如果是递归求解: + +* 则当前递归层依赖的是上一层(`k-1`)的`8`种位置的方法数量的和; +* 概率就是到第`k`层的时候的方法数 / `8 ^ K`; +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + final int[][] dir = { {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, + {1, -2}, {2, -1}, {-1, -2}, {-2, -1} }; + + private double dp[][][]; + + private double recur(int k, int x, int y, int N){ + if(x < 0 || x >= N || y < 0 || y >= N) // 这个必须写在下面k == 0的条件之前 + return 0.0; + if(k == 0) + return 1.0; + if(dp[k][x][y] != 0) + return dp[k][x][y]; + double res = 0; + for(int d = 0; d < 8; d++) + res += recur(k-1, x - dir[d][0], y - dir[d][1], N); + return dp[k][x][y] = res; + } + + public double knightProbability(int N, int K, int r, int c) { + dp = new double[K+1][N][N]; + return recur(K, r, c, N) / Math.pow(8, K); + } + + public static void main(String[] args){ + Scanner cin = new Scanner(System.in); + PrintStream out = System.out; + + out.println(new Solution().knightProbability(3, 2, 0, 0)); + } +} +``` + +也可以用递推来求解: +```java +import java.io.*; +import java.util.*; + +class Solution { + + final int[][] dir = { {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, + {1, -2}, {2, -1}, {-1, -2}, {-2, -1} }; + + public double knightProbability(int N, int K, int r, int c) { + double[][][] dp = new double[K+1][N][N]; + dp[0][r][c] = 1.0; + for(int k = 1; k <= K; k++){ + for(int i = 0; i < N; i++){ + for(int j = 0; j < N; j++){ + for(int d = 0; d < 8; d++){ + int x = i + dir[d][0]; + int y = j + dir[d][1]; + if(x < 0 || x >= N || y < 0 || y >= N) + continue; + dp[k][x][y] += dp[k-1][i][j]; + } + } + } + } + double sum = 0; + for(int i = 0; i < N; i++){ + for(int j = 0; j < N; j++) + sum += dp[K][i][j]; + } + return sum / Math.pow(8, K); + } + + public static void main(String[] args){ + Scanner cin = new Scanner(System.in); + PrintStream out = System.out; + + out.println(new Solution().knightProbability(3, 2, 0, 0)); + } +} + +``` +由于只需要前后两个状态,所以可以优化空间。 + +优化空间: `O(N ^ 2)`: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + final int[][] dir = { {-2, 1}, {-1, 2}, {1, 2}, {2, 1}, + {1, -2}, {2, -1}, {-1, -2}, {-2, -1} }; + + public double knightProbability(int N, int K, int r, int c) { + double[][] dp0 = new double[N][N]; + dp0[r][c] = 1.0; + for(int k = 1; k <= K; k++){ + double[][] dp1 = new double[N][N]; + for(int i = 0; i < N; i++){ + for(int j = 0; j < N; j++){ + for(int d = 0; d < 8; d++){ + int x = i + dir[d][0]; + int y = j + dir[d][1]; + if(x < 0 || x >= N || y < 0 || y >= N) + continue; + dp1[x][y] += dp0[i][j]; + } + } + } + dp0 = dp1; + } + double sum = 0; + for(int i = 0; i < N; i++){ + for(int j = 0; j < N; j++) + sum += dp0[i][j]; + } + return sum / Math.pow(8, K); + } + + public static void main(String[] args){ + Scanner cin = new Scanner(System.in); + PrintStream out = System.out; + + out.println(new Solution().knightProbability(3, 2, 0, 0)); + } +} +``` + diff --git "a/Algorithm/LeetCode/DP/LeetCode-72.Edit Distance(\347\274\226\350\276\221\350\267\235\347\246\273\351\227\256\351\242\230)(\344\270\211\344\270\252\344\276\235\350\265\226\347\232\204\346\273\232\345\212\250\344\274\230\345\214\226).md" "b/Algorithm/LeetCode/DP/LeetCode-72.Edit Distance(\347\274\226\350\276\221\350\267\235\347\246\273\351\227\256\351\242\230)(\344\270\211\344\270\252\344\276\235\350\265\226\347\232\204\346\273\232\345\212\250\344\274\230\345\214\226).md" new file mode 100644 index 00000000..54ce72ee --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-72.Edit Distance(\347\274\226\350\276\221\350\267\235\347\246\273\351\227\256\351\242\230)(\344\270\211\344\270\252\344\276\235\350\265\226\347\232\204\346\273\232\345\212\250\344\274\230\345\214\226).md" @@ -0,0 +1,296 @@ +# LeetCode - 72. Edit Distance(编辑距离问题)(三个依赖的滚动优化) + + - 记忆化 + - 二维dp + - 一维dpO(M) + - 一维dpO(min(N,M)) + - 加强问题(ic,dc,rc) + +#### [题目链接](https://leetcode.com/problems/edit-distance/description/) + +> https://leetcode.com/problems/edit-distance/description/ + +#### 题目 +![在这里插入图片描述](images/72_t.png) + +## 1、记忆化 +使用递归的解法: + +从两个字符串的最后的位置开始考虑: + + * 如果最后两个字符`(i,j)`相等,最后两个字符就不要配对,所以等于`minDistance(s1[0..i-1],s2[0...j-1])`; + * 如果最后两个字符不相等: 说明要编辑,具体可以分为三种情况: + * 如果 `s1[i-1]`和`s2[j]`可以配对,那我就删除`s1[i]`即可(删除); + * 如果 `s1[i]和s2[j-1]`可以配对,那我就在`s1`的后面加上`s2[j]`即可(插入); + * 如果 `s1[i-1]和s2[j-1]`可以配对,那我就把`s1[i]`修改(`replace`)成`s2[j]`即可; + +图: +

+ +然后就是使用一个`dp`数组记录递归中已经求解的子问题。 + +```java +class Solution { + + private int[][] dp; + private char[] s1; + private char[] s2; + + public int minDistance(String word1, String word2) { + s1 = word1.toCharArray(); + s2 = word2.toCharArray(); + dp = new int[s1.length][s2.length]; + for (int[] arr : dp) Arrays.fill(arr, -1); + return rec(s1.length - 1, s2.length - 1); + } + + public int rec(int i, int j) { + if (j == -1) return i+1; //将s1编辑成空串(注意空串是-1,不是0), 注意下标是i 但是有i+1个(下标从0开始) + if (i == -1) return j+1; //将s2编辑成空串 + if (dp[i][j] != -1) return dp[i][j]; + if (s1[i] == s2[j]) + dp[i][j] = rec(i - 1, j - 1); + else { + dp[i][j] = 1 + Math.min(rec(i - 1, j), // delete i + Math.min(rec(i, j - 1), // add i+1 index + rec(i - 1, j - 1))); // replace i + } + return dp[i][j]; + } +} +``` + +当然你也可以是`i==0`的时候返回`j`,不过这样你判断的时候就是`s1[i-1]`和`s2[j-1]`,而且你的dp数组就要多开一个空间。(下面的dp也是基于这个版本) + +```java +class Solution { + + private int[][] dp; + private char[] s1; + private char[] s2; + + public int minDistance(String word1, String word2) { + s1 = word1.toCharArray(); + s2 = word2.toCharArray(); + dp = new int[s1.length+1][s2.length+1];//多一个空间 + for (int[] arr : dp) Arrays.fill(arr, -1); + return rec(s1.length , s2.length); + } + + public int rec(int i, int j) { + if (j == 0) return i; + if (i == 0) return j; + if (dp[i][j] != -1) return dp[i][j]; + if (s1[i-1] == s2[j-1]) //不是 s1[i] == s2[j] + dp[i][j] = rec(i - 1, j - 1); + else { + dp[i][j] = 1 + Math.min(rec(i - 1, j), // delete i + Math.min(rec(i, j - 1), // add i+1 index + rec(i - 1, j - 1))); // replace i + } + return dp[i][j]; + } +} +``` + + + + +## 2、二维dp + +![这里写图片描述](images/72_ss2.png) + +二维`dp`就是使用一个二维数组从左到右,从上倒下来递归出最后的答案,注意几点: + + - `dp`数组的大小 `dp[chs1.length + 1] [chs2.length + 1]`,因为一开始是空串`" "`,所以要多开一个; + - 一开始初始化第一行和第一列,第一行表示的是`chs1`为`""`,变成`chs2`要添加`chs2`长度的次数。第一列表示的是要删除的。 + +```java +class Solution { + + public int minDistance(String word1, String word2) { + int[][] dp = new int[word1.length() + 1][word2.length() + 1]; //注意要 + 1因为一开始是空串 + for (int i = 0; i < word1.length() + 1; i++) dp[i][0] = i; // 0 是空串,所以我们要多考虑一个位置 + for (int j = 0; j < word2.length() + 1; j++) dp[0][j] = j; + char c1, c2; + for (int i = 1; i < word1.length() + 1; i++) { + for (int j = 1; j < word2.length() + 1; j++) { + c1 = word1.charAt(i - 1); // 注意是 i-1, 0是空串,后面的都要累加 + c2 = word2.charAt(j - 1); + dp[i][j] = c1 == c2 ? dp[i - 1][j - 1] : + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1; + } + } + return dp[word1.length()][word2.length()]; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } +} +``` +## 3、一维dpO(M) +这个题目的滚动优化和之前的动态规划有一点点不同,不同的地方在于: + + - 一个普通位置依赖的位置有三个地方,而如果只使用一个数组的话,左上角的位置的在更新的时候产生了变动; + +解决的办法是使用两个变量`temp`和`pre`保存,在更新之前,使用`temp`保存`dp[j]`,然后`dp[j]`要被更新,然后将`dp[j]`赋值给`pre`,下次递推的时候,左上角的值就是`pre`; + +![这里写图片描述](images/72_ss3.png) + +代码 + +```java +class Solution { + + public int minDistance(String word1, String word2) { + char[] chs1 = word1.toCharArray(); + char[] chs2 = word2.toCharArray(); + int[] dp = new int[word2.length() + 1]; //注意要 + 1因为一开始是空串 + for (int j = 0; j < word2.length() + 1; j++) dp[j] = j; + char c1, c2; + int temp, pre; + for (int i = 1; i < word1.length() + 1; i++) { + pre = dp[0]; //上一排的 + dp[0] = i; //这一排新dp[0] + for (int j = 1; j < word2.length() + 1; j++) { + c1 = word1.charAt(i - 1); + c2 = word2.charAt(j - 1); //注意下标对应的关系 + temp = dp[j]; // 先要保存,因为等下就更新了 + dp[j] = c1 == c2 ? pre : min(dp[j], dp[j - 1], pre) + 1; + pre = temp; //记得先保存一下左上角的 pre的值(在二维的dp中就是dp[i-1][j-1]) + } + } + return dp[word2.length()]; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } +} +``` +*** +## 4、一维dpO(min(N,M)) +**这个就是上面的一点改进,选取行和列中最小的作为`dp`数组的大小**。 +```java +class Solution { + + public int minDistance(String word1, String word2) { + char[] chs1 = word1.toCharArray(); + char[] chs2 = word2.toCharArray(); + + char[] more = chs1.length >= chs2.length ? chs1 : chs2; + char[] less = chs1.length < chs2.length ? chs1 : chs2; + + int[] dp = new int[less.length + 1]; + for (int j = 0; j < less.length + 1; j++) dp[j] = j; + + int temp, pre; + for (int i = 1; i < more.length + 1; i++) { + pre = dp[0]; + dp[0] = i; + for (int j = 1; j < less.length + 1; j++) { + temp = dp[j]; + dp[j] = more[i - 1] == less[j - 1] ? pre : min(dp[j], dp[j - 1], pre) + 1; + pre = temp; + } + } + return dp[less.length]; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } +} +``` + +*** +## 5、加强问题(ic,dc,rc) + +题目: +给定两个字符串`str1`和`str2`,再给定三个整数`ic、dc`和`rc`,分别代表插入、删除和替换一个字符的代价,返回将`str1`编辑成`str2`的最小代价。 + +这个问题是上面问题的加强版,不同的地方在于这里三个编辑的代价不同,所以我们要更加的清楚是哪个编辑的更新: + +![在这里插入图片描述](images/72_s4.png) + +**也就是说:** + + - `dp[i-1][j]`代表的是删除了 `chs1[i-1]`,然后配对`chs1[i-2]`和`chs2[j-1]`, 所以加上`dc`(删除`chs1[i-1]`的); + - `dp[i][j-1]`代表的是配对`chs1[i-1]`和`chs2[j-2]`,所以我们在`chs1`后面添加一个来和`chs2[j-1]`字符配对,所以加上`ic`; + +```java +class Solution { + + /** + * 给定的 ic, dc ,rc 分别代表的是 插入,删除 取代的代价 + *  普通二维dp + */ + public int minDistance(String word1, String word2, int ic, int dc, int rc) { + char[] chs1 = word1.toCharArray(); + char[] chs2 = word2.toCharArray(); + + int[][] dp = new int[chs1.length + 1][chs2.length + 1]; + + for (int i = 0; i < chs1.length + 1; i++) dp[i][0] = i * dc; //chs1是 , chs2是"" ->要删除 + for (int j = 0; j < chs2.length + 1; j++) dp[0][j] = j * ic; //chs1 是"" 转换成chs2 -> 要添加 + + for (int i = 1; i < chs1.length + 1; i++) { + for (int j = 1; j < chs2.length + 1; j++) { + dp[i][j] = chs1[i - 1] == chs2[j - 1] ? dp[i - 1][j - 1] + : min(dp[i][j - 1] + ic, dp[i - 1][j] + dc, dp[i - 1][j - 1] + rc); //dp[i-1][j]代表的是删除了 chs1[i-1] 所以加上dc + } + } + return dp[chs1.length][chs2.length]; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } + +} +``` + +空间优化: + +**这里要注意如果`chs1`作为更短的话,要进行字符串的调换**。 + +```java +class Solution { + + //空间 O(min(N,M)) + public int minDistance(String word1, String word2, int ic, int dc, int rc) { + char[] chs1 = word1.toCharArray(); + char[] chs2 = word2.toCharArray(); + + char[] more = chs1.length >= chs2.length ? chs1 : chs2; + char[] less = chs1.length < chs2.length ? chs1 : chs2; + + int temp, pre; + if (chs1 == less) { //把chs1作为了列对应的字符串 就交换一下 + temp = ic; + ic = dc; + dc = temp; + } + + int[] dp = new int[less.length + 1]; + for (int j = 0; j < less.length + 1; j++) dp[j] = j * ic; + + for (int i = 1; i < more.length + 1; i++) { + pre = dp[0]; + dp[0] = i * dc; + for (int j = 1; j < less.length + 1; j++) { + temp = dp[j]; + dp[j] = more[i - 1] == less[j - 1] ? pre : min(pre + rc, dp[j] + dc, dp[j - 1] + ic); + pre = temp; + } + } + return dp[less.length]; + } + + public int min(int a, int b, int c) { + return Math.min(Math.min(a, b), c); + } + +} +``` diff --git "a/Algorithm/LeetCode/DP/LeetCode-730.Count Different Palindromic Subsequences(\347\273\237\350\256\241\344\270\215\345\220\214\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\351\207\217).md" "b/Algorithm/LeetCode/DP/LeetCode-730.Count Different Palindromic Subsequences(\347\273\237\350\256\241\344\270\215\345\220\214\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\351\207\217).md" new file mode 100644 index 00000000..a20357c9 --- /dev/null +++ "b/Algorithm/LeetCode/DP/LeetCode-730.Count Different Palindromic Subsequences(\347\273\237\350\256\241\344\270\215\345\220\214\345\233\236\346\226\207\345\255\220\345\255\227\347\254\246\344\270\262\347\232\204\346\225\260\351\207\217).md" @@ -0,0 +1,124 @@ +# LeetCode - 730. Count Different Palindromic Subsequences(统计不同回文子字符串的数量) + +#### [题目链接](https://leetcode.com/problems/count-different-palindromic-subsequences/) + +> https://leetcode-cn.com/problems/count-different-palindromic-subsequences/ + +#### 题目 + +![730_t.png](images/730_t.png) + +#### 解析 + +首先不考虑重复的情况: + +我们的递归函数设计的是求`[L, R]`区间的种类数。 + +当`s[L] == s[R]`的时候: + +* 就是`2 * count(L+1, R-1) + 2`;因为下一层返回的结果,我们可以在两边分别都加上`s[L]`和`s[R]`于是就是两倍了; +* 此外,还要加上我们自己本身两个字符`s[L]、s[R]`,这是两种情况; +* 所以就是`count[L, R] = 2 * count[L+1, R-1] + 2`; + +当`s[L] != s[R]`的时候: + +* 我们的种类数就是`count(L, R) = count[L, R-1] + count(L+1, R) - count(L+1, R-1)`; + +图: + +![730_s.png](images/730_s.png) + +再来看重复的情况,注意只有在`s[L] == s[R]`的时候才考虑重复的情况: + +* 当里面只有一个字符和`s[L]`相等的时候,就是`count(L, R) = count(L+1, R-1) + 1`,即`+2`改成了`+1`,因为有一种情况重复了; +* 此外就是有多个和`s[L]`相同的字符,我们找到最左边的和最右边的相同的字符,我们的结果就是`2 * count(L + 1, R - 1) - count(i + 1, j - 1) `,也就是减去中间会产生重复的部分; + +图: + +![730_s2.png](images/730_s2.png) + +记忆化递归: + +```java +public class Solution { + + private char[] s; + private int dp[][]; + private final int mod = 1000000000 + 7; + + public int rec(int L, int R) { + if (L > R) + return 0; + if (L == R) + return 1; + if (dp[L][R] != 0) return dp[L][R]; + long res = 0; + if (s[L] == s[R]) { + int i = L + 1, j = R - 1; + while (i <= j && s[i] != s[L]) i++; //找到最左边的和s[L]相同的 + while (j >= i && s[j] != s[R]) j--; //找到最右边的和s[R]相同的 + if (i == j) // 只有一个和s[L](s[R])相同的 + res = 2 * rec(L + 1, R - 1) + 1; + else if (i > j) // 没有 和s[L](s[R])相同的 + res = 2 * rec(L + 1, R - 1) + 2; + else // 有多个和s[L](s[R])相同的,取最左的和最右的 + res = 2 * rec(L + 1, R - 1) - rec(i + 1, j - 1); + } else + res = rec(L, R - 1)+ rec(L + 1, R) - rec(L + 1, R - 1); +// return dp[L][R] = (int) ( res % mod ); //错误 + return dp[L][R] = (int) ( (res+mod) % mod); + } + + public int countPalindromicSubsequences(String S) { + if (S == null || S.length() == 0) + return 0; + s = S.toCharArray(); + dp = new int[S.length()][S.length()]; + return rec(0, S.length() - 1) % mod; + } +} +``` +改成dp: + +![730_s3.png](images/730_s3.png) + + +递推的形式: + +```java +public class Solution { + + private final int mod = 1000000000 + 7; + + public int countPalindromicSubsequences(String S) { + if (S == null || S.length() == 0) + return 0; + int n = S.length(); + char[] s = new char[n + 1]; + for (int i = 0; i < n; i++) s[i + 1] = S.charAt(i); //为了下面考虑方便 + int[][] dp = new int[n + 2][n + 2]; //要考虑下标,所以多开了2的长度 + + for (int i = 1; i <= n; i++) dp[i][i] = 1; + + for (int i = n; i >= 1; i--) {//注意从下到上 + for (int j = i + 1; j <= n; j++) {//从左到右 + if (s[i] == s[j]) { + int l = i + 1, r = j - 1; + while (l <= r && s[l] != s[i]) l++; + while (r >= l && s[r] != s[j]) r--; + if (l == r) + dp[i][j] = 2 * dp[i + 1][j - 1] + 1; + else if (l > r) + dp[i][j] = 2 * dp[i + 1][j - 1] + 2; + else + dp[i][j] = 2 * dp[i + 1][j - 1] - dp[l + 1][r - 1]; + } else + dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1]; + dp[i][j] = dp[i][j] < 0 ? dp[i][j] + mod : dp[i][j] % mod; // 注意dp[i][j] < 0(溢出)的情况 + } + } + return (dp[1][n] + mod) % mod; + } +} +``` + diff --git a/Algorithm/LeetCode/DP/LeetCode-91.Decode Ways.md b/Algorithm/LeetCode/DP/LeetCode-91.Decode Ways.md new file mode 100644 index 00000000..0d682c0f --- /dev/null +++ b/Algorithm/LeetCode/DP/LeetCode-91.Decode Ways.md @@ -0,0 +1,109 @@ +# LeetCode - 91. Decode Ways + +#### [题目链接](https://leetcode.com/problems/decode-ways/) + +> https://leetcode.com/problems/decode-ways/ + +#### 题目 +![在这里插入图片描述](images/91_t.png) + +## 解析 + +也是很明显的DP题目,递归思路 + +* 当前的字符串,当前的位置,总共的可能就是两种情况; +* 第一种情况,拆分出来当前的`str[i]`,然后求出`[i+1, s.length()]`的方法数`r1`,只要当前`str[i] != 0`,答案`res`就累加`r1`,然后我们还可以考虑将`str[i], str[i+1]`这两个字符拆分出来,这两个字符组成的数字只要满足`10 <= X <= 26 `,则递归`[i+2, s.length()]`得到的结果`r2`,则`res = r1 + r2`; +* 然后用记忆化记录结果即可; + +图: + +![在这里插入图片描述](images/91_s.png) + +直观的递归代码: + +```java +class Solution { + HashMap dp; + + public int numDecodings(String s) { + if(s == null || s.length() == 0) + return 0; + dp = new HashMap<>(); + return ways(s); + } + + private int ways(String s){ + if(s.length() == 0)//找到一个了 + return 1; + if(s.charAt(0) == '0') + return 0; + if(s.length() == 1)//len = 1, 防止下面substr(0, 2)出错 + return 1; + if(dp.containsKey(s)) + return dp.get(s); + int res = ways(s.substring(1)); + int se = Integer.parseInt(s.substring(0, 2)); + if(se <= 26) + res += ways(s.substring(2)); + dp.put(s, res); + return res; + } +} +``` +也可以将字符串改成下标的方式,这样内存更少开销。 +```java +class Solution { + private int[] dp; + + public int numDecodings(String s) { + if(s == null || s.length() == 0) + return 0; + dp = new int[s.length()]; + Arrays.fill(dp, -1); + return ways(s.toCharArray(), 0); + } + + private int ways(char[] chs, int pos){ + if(pos == chs.length)//越界/空串 + return 1; + if(chs[pos] == '0') + return 0; + if(pos == chs.length-1)//一个字符返回1 + return 1; + if(dp[pos] != -1) + return dp[pos]; + int res = ways(chs, pos+1); + int se = 10 * (chs[pos] - '0') + (chs[pos+1] - '0'); + if(se <= 26) + res += ways(chs, pos+2); + dp[pos] = res; + return res; + } +} +``` +也可以改成递推的代码: (注意这里的特殊处理,空串是在`n == s.length()`的位置)。 +```java +class Solution { + + public int numDecodings(String s) { + if (s == null || s.length() == 0) + return 0; + int n = s.length(); + char[] chs = s.toCharArray(); + int[] dp = new int[n + 1]; + dp[n] = 1; // 空串要特殊处理 + dp[n-1] = chs[n-1] == '0' ? 0 : 1; + for(int i = n-2; i >= 0; i--){ + if(chs[i] == '0') + dp[i] = 0; + else { + dp[i] = dp[i+1]; + int se = 10 * (chs[i] - '0') + (chs[i+1] - '0'); + if(se <= 26) + dp[i] += dp[i+2]; + } + } + return dp[0]; + } +} +``` diff --git a/Algorithm/LeetCode/DP/assets/1554768710141.png b/Algorithm/LeetCode/DP/assets/1554768710141.png new file mode 100644 index 00000000..acdaa043 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554768710141.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554769081043.png b/Algorithm/LeetCode/DP/assets/1554769081043.png new file mode 100644 index 00000000..d4390a43 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554769081043.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554796481482.png b/Algorithm/LeetCode/DP/assets/1554796481482.png new file mode 100644 index 00000000..258d4995 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554796481482.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554799714709.png b/Algorithm/LeetCode/DP/assets/1554799714709.png new file mode 100644 index 00000000..b6d05167 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554799714709.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554800168091.png b/Algorithm/LeetCode/DP/assets/1554800168091.png new file mode 100644 index 00000000..92bff9d7 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554800168091.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554881438745.png b/Algorithm/LeetCode/DP/assets/1554881438745.png new file mode 100644 index 00000000..8a26e087 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554881438745.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554952364205.png b/Algorithm/LeetCode/DP/assets/1554952364205.png new file mode 100644 index 00000000..8b7cf073 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554952364205.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554993044112.png b/Algorithm/LeetCode/DP/assets/1554993044112.png new file mode 100644 index 00000000..51b13c01 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554993044112.png differ diff --git a/Algorithm/LeetCode/DP/assets/1554995470391.png b/Algorithm/LeetCode/DP/assets/1554995470391.png new file mode 100644 index 00000000..50418215 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1554995470391.png differ diff --git a/Algorithm/LeetCode/DP/assets/1555036709602.png b/Algorithm/LeetCode/DP/assets/1555036709602.png new file mode 100644 index 00000000..60a95eb4 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1555036709602.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558315395501.png b/Algorithm/LeetCode/DP/assets/1558315395501.png new file mode 100644 index 00000000..56659c0d Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558315395501.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558315413365.png b/Algorithm/LeetCode/DP/assets/1558315413365.png new file mode 100644 index 00000000..e2446cfb Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558315413365.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558367533637.png b/Algorithm/LeetCode/DP/assets/1558367533637.png new file mode 100644 index 00000000..7b914c28 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558367533637.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558581280198.png b/Algorithm/LeetCode/DP/assets/1558581280198.png new file mode 100644 index 00000000..b0ae4899 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558581280198.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558581993305.png b/Algorithm/LeetCode/DP/assets/1558581993305.png new file mode 100644 index 00000000..43505fcb Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558581993305.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582072320.png b/Algorithm/LeetCode/DP/assets/1558582072320.png new file mode 100644 index 00000000..08a8ee7e Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582072320.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582172219.png b/Algorithm/LeetCode/DP/assets/1558582172219.png new file mode 100644 index 00000000..ccbe2103 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582172219.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582223373.png b/Algorithm/LeetCode/DP/assets/1558582223373.png new file mode 100644 index 00000000..32cdd3b5 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582223373.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582291486.png b/Algorithm/LeetCode/DP/assets/1558582291486.png new file mode 100644 index 00000000..32f56c7c Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582291486.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582341310.png b/Algorithm/LeetCode/DP/assets/1558582341310.png new file mode 100644 index 00000000..ae3b9b1d Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582341310.png differ diff --git a/Algorithm/LeetCode/DP/assets/1558582399701.png b/Algorithm/LeetCode/DP/assets/1558582399701.png new file mode 100644 index 00000000..93cbb1b5 Binary files /dev/null and b/Algorithm/LeetCode/DP/assets/1558582399701.png differ diff --git a/Algorithm/LeetCode/DP/images/10_s.png b/Algorithm/LeetCode/DP/images/10_s.png new file mode 100644 index 00000000..6da7f45f Binary files /dev/null and b/Algorithm/LeetCode/DP/images/10_s.png differ diff --git a/Algorithm/LeetCode/DP/images/10_s_2.png b/Algorithm/LeetCode/DP/images/10_s_2.png new file mode 100644 index 00000000..c4a4e7fe Binary files /dev/null and b/Algorithm/LeetCode/DP/images/10_s_2.png differ diff --git a/Algorithm/LeetCode/DP/images/10_t.png b/Algorithm/LeetCode/DP/images/10_t.png new file mode 100644 index 00000000..db4b7e5f Binary files /dev/null and b/Algorithm/LeetCode/DP/images/10_t.png differ diff --git a/Algorithm/LeetCode/DP/images/10_t2.png b/Algorithm/LeetCode/DP/images/10_t2.png new file mode 100644 index 00000000..31f0e39a Binary files /dev/null and b/Algorithm/LeetCode/DP/images/10_t2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/120_s.png" b/Algorithm/LeetCode/DP/images/120_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/120_s.png" rename to Algorithm/LeetCode/DP/images/120_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/120_t.png" b/Algorithm/LeetCode/DP/images/120_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/120_t.png" rename to Algorithm/LeetCode/DP/images/120_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/139_s.png" b/Algorithm/LeetCode/DP/images/139_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/139_s.png" rename to Algorithm/LeetCode/DP/images/139_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/139_t.png" b/Algorithm/LeetCode/DP/images/139_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/139_t.png" rename to Algorithm/LeetCode/DP/images/139_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/140_s.png" b/Algorithm/LeetCode/DP/images/140_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/140_s.png" rename to Algorithm/LeetCode/DP/images/140_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/140_t.png" b/Algorithm/LeetCode/DP/images/140_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/140_t.png" rename to Algorithm/LeetCode/DP/images/140_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/152_t.png" b/Algorithm/LeetCode/DP/images/152_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/152_t.png" rename to Algorithm/LeetCode/DP/images/152_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/174_s.png" b/Algorithm/LeetCode/DP/images/174_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/174_s.png" rename to Algorithm/LeetCode/DP/images/174_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/174_t.png" b/Algorithm/LeetCode/DP/images/174_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/174_t.png" rename to Algorithm/LeetCode/DP/images/174_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/221_s.png" b/Algorithm/LeetCode/DP/images/221_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/221_s.png" rename to Algorithm/LeetCode/DP/images/221_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/221_s2.png" b/Algorithm/LeetCode/DP/images/221_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/221_s2.png" rename to Algorithm/LeetCode/DP/images/221_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/221_s3.png" b/Algorithm/LeetCode/DP/images/221_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/221_s3.png" rename to Algorithm/LeetCode/DP/images/221_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/221_s4.png" b/Algorithm/LeetCode/DP/images/221_s4.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/221_s4.png" rename to Algorithm/LeetCode/DP/images/221_s4.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/221_t.png" b/Algorithm/LeetCode/DP/images/221_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/221_t.png" rename to Algorithm/LeetCode/DP/images/221_t.png diff --git a/Algorithm/LeetCode/DP/images/241_s.png b/Algorithm/LeetCode/DP/images/241_s.png new file mode 100644 index 00000000..aeab3485 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/241_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/241_t.png" b/Algorithm/LeetCode/DP/images/241_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/241_t.png" rename to Algorithm/LeetCode/DP/images/241_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/268_s.png" b/Algorithm/LeetCode/DP/images/268_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/268_s.png" rename to Algorithm/LeetCode/DP/images/268_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/268_t.png" b/Algorithm/LeetCode/DP/images/268_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/268_t.png" rename to Algorithm/LeetCode/DP/images/268_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/304_t.png" b/Algorithm/LeetCode/DP/images/304_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/304_t.png" rename to Algorithm/LeetCode/DP/images/304_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/312_s.png" b/Algorithm/LeetCode/DP/images/312_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/312_s.png" rename to Algorithm/LeetCode/DP/images/312_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/312_t.png" b/Algorithm/LeetCode/DP/images/312_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/312_t.png" rename to Algorithm/LeetCode/DP/images/312_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/354_s.png" b/Algorithm/LeetCode/DP/images/354_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/354_s.png" rename to Algorithm/LeetCode/DP/images/354_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/354_t.png" b/Algorithm/LeetCode/DP/images/354_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/354_t.png" rename to Algorithm/LeetCode/DP/images/354_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/486_s.png" b/Algorithm/LeetCode/DP/images/486_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/486_s.png" rename to Algorithm/LeetCode/DP/images/486_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/486_t.png" b/Algorithm/LeetCode/DP/images/486_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/486_t.png" rename to Algorithm/LeetCode/DP/images/486_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/518_s.png" b/Algorithm/LeetCode/DP/images/518_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/518_s.png" rename to Algorithm/LeetCode/DP/images/518_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/518_t.png" b/Algorithm/LeetCode/DP/images/518_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/518_t.png" rename to Algorithm/LeetCode/DP/images/518_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/53_s.png" b/Algorithm/LeetCode/DP/images/53_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/53_s.png" rename to Algorithm/LeetCode/DP/images/53_s.png diff --git a/Algorithm/LeetCode/DP/images/53_ss.png b/Algorithm/LeetCode/DP/images/53_ss.png new file mode 100644 index 00000000..4d94ae03 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/53_ss.png differ diff --git a/Algorithm/LeetCode/DP/images/53_ss2.png b/Algorithm/LeetCode/DP/images/53_ss2.png new file mode 100644 index 00000000..4834a15a Binary files /dev/null and b/Algorithm/LeetCode/DP/images/53_ss2.png differ diff --git a/Algorithm/LeetCode/DP/images/53_ss3.png b/Algorithm/LeetCode/DP/images/53_ss3.png new file mode 100644 index 00000000..451a53c1 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/53_ss3.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/53_t.png" b/Algorithm/LeetCode/DP/images/53_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/53_t.png" rename to Algorithm/LeetCode/DP/images/53_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/62_s.png" b/Algorithm/LeetCode/DP/images/62_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/62_s.png" rename to Algorithm/LeetCode/DP/images/62_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/62_s2.png" b/Algorithm/LeetCode/DP/images/62_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/62_s2.png" rename to Algorithm/LeetCode/DP/images/62_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/62_s3.png" b/Algorithm/LeetCode/DP/images/62_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/62_s3.png" rename to Algorithm/LeetCode/DP/images/62_s3.png diff --git a/Algorithm/LeetCode/DP/images/62_ss.png b/Algorithm/LeetCode/DP/images/62_ss.png new file mode 100644 index 00000000..27437844 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/62_ss.png differ diff --git a/Algorithm/LeetCode/DP/images/62_ss2.png b/Algorithm/LeetCode/DP/images/62_ss2.png new file mode 100644 index 00000000..c37c1b1e Binary files /dev/null and b/Algorithm/LeetCode/DP/images/62_ss2.png differ diff --git a/Algorithm/LeetCode/DP/images/62_ss3.png b/Algorithm/LeetCode/DP/images/62_ss3.png new file mode 100644 index 00000000..cb1ce6f4 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/62_ss3.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/62_t.png" b/Algorithm/LeetCode/DP/images/62_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/62_t.png" rename to Algorithm/LeetCode/DP/images/62_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/639_s.png" b/Algorithm/LeetCode/DP/images/639_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/639_s.png" rename to Algorithm/LeetCode/DP/images/639_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/639_t.png" b/Algorithm/LeetCode/DP/images/639_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/639_t.png" rename to Algorithm/LeetCode/DP/images/639_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/63_s.png" b/Algorithm/LeetCode/DP/images/63_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/63_s.png" rename to Algorithm/LeetCode/DP/images/63_s.png diff --git a/Algorithm/LeetCode/DP/images/63_ss.png b/Algorithm/LeetCode/DP/images/63_ss.png new file mode 100644 index 00000000..80025366 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/63_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/63_t.png" b/Algorithm/LeetCode/DP/images/63_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/63_t.png" rename to Algorithm/LeetCode/DP/images/63_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/64_s.png" b/Algorithm/LeetCode/DP/images/64_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/64_s.png" rename to Algorithm/LeetCode/DP/images/64_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/64_s2.png" b/Algorithm/LeetCode/DP/images/64_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/64_s2.png" rename to Algorithm/LeetCode/DP/images/64_s2.png diff --git a/Algorithm/LeetCode/DP/images/64_ss.png b/Algorithm/LeetCode/DP/images/64_ss.png new file mode 100644 index 00000000..31fc06c7 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/64_ss.png differ diff --git a/Algorithm/LeetCode/DP/images/64_ss2.png b/Algorithm/LeetCode/DP/images/64_ss2.png new file mode 100644 index 00000000..73cab2d4 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/64_ss2.png differ diff --git a/Algorithm/LeetCode/DP/images/64_ss3.png b/Algorithm/LeetCode/DP/images/64_ss3.png new file mode 100644 index 00000000..e0a5f79b Binary files /dev/null and b/Algorithm/LeetCode/DP/images/64_ss3.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/64_t.png" b/Algorithm/LeetCode/DP/images/64_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/64_t.png" rename to Algorithm/LeetCode/DP/images/64_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/664_s.png" b/Algorithm/LeetCode/DP/images/664_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/664_s.png" rename to Algorithm/LeetCode/DP/images/664_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/664_s2.png" b/Algorithm/LeetCode/DP/images/664_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/664_s2.png" rename to Algorithm/LeetCode/DP/images/664_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/664_t.png" b/Algorithm/LeetCode/DP/images/664_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/664_t.png" rename to Algorithm/LeetCode/DP/images/664_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/673_s.png" b/Algorithm/LeetCode/DP/images/673_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/673_s.png" rename to Algorithm/LeetCode/DP/images/673_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/673_t.png" b/Algorithm/LeetCode/DP/images/673_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/673_t.png" rename to Algorithm/LeetCode/DP/images/673_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/674_t.png" b/Algorithm/LeetCode/DP/images/674_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/674_t.png" rename to Algorithm/LeetCode/DP/images/674_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/678_s.png" b/Algorithm/LeetCode/DP/images/678_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/678_s.png" rename to Algorithm/LeetCode/DP/images/678_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/678_t.png" b/Algorithm/LeetCode/DP/images/678_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/678_t.png" rename to Algorithm/LeetCode/DP/images/678_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/688_s.png" b/Algorithm/LeetCode/DP/images/688_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/688_s.png" rename to Algorithm/LeetCode/DP/images/688_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/688_t.png" b/Algorithm/LeetCode/DP/images/688_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/688_t.png" rename to Algorithm/LeetCode/DP/images/688_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/72_s.png" b/Algorithm/LeetCode/DP/images/72_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/72_s.png" rename to Algorithm/LeetCode/DP/images/72_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/72_s2.png" b/Algorithm/LeetCode/DP/images/72_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/72_s2.png" rename to Algorithm/LeetCode/DP/images/72_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/72_s3.png" b/Algorithm/LeetCode/DP/images/72_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/72_s3.png" rename to Algorithm/LeetCode/DP/images/72_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/72_s4.png" b/Algorithm/LeetCode/DP/images/72_s4.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/72_s4.png" rename to Algorithm/LeetCode/DP/images/72_s4.png diff --git a/Algorithm/LeetCode/DP/images/72_ss.png b/Algorithm/LeetCode/DP/images/72_ss.png new file mode 100644 index 00000000..5e453e01 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/72_ss.png differ diff --git a/Algorithm/LeetCode/DP/images/72_ss2.png b/Algorithm/LeetCode/DP/images/72_ss2.png new file mode 100644 index 00000000..54098683 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/72_ss2.png differ diff --git a/Algorithm/LeetCode/DP/images/72_ss3.png b/Algorithm/LeetCode/DP/images/72_ss3.png new file mode 100644 index 00000000..8b9a5194 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/72_ss3.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/72_t.png" b/Algorithm/LeetCode/DP/images/72_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/72_t.png" rename to Algorithm/LeetCode/DP/images/72_t.png diff --git a/Algorithm/LeetCode/DP/images/730_s.png b/Algorithm/LeetCode/DP/images/730_s.png new file mode 100644 index 00000000..2943eb50 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/730_s.png differ diff --git a/Algorithm/LeetCode/DP/images/730_s2.png b/Algorithm/LeetCode/DP/images/730_s2.png new file mode 100644 index 00000000..df666e38 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/730_s2.png differ diff --git a/Algorithm/LeetCode/DP/images/730_s3.png b/Algorithm/LeetCode/DP/images/730_s3.png new file mode 100644 index 00000000..eef69d2f Binary files /dev/null and b/Algorithm/LeetCode/DP/images/730_s3.png differ diff --git a/Algorithm/LeetCode/DP/images/730_t.png b/Algorithm/LeetCode/DP/images/730_t.png new file mode 100644 index 00000000..48647b07 Binary files /dev/null and b/Algorithm/LeetCode/DP/images/730_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/91_s.png" b/Algorithm/LeetCode/DP/images/91_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/91_s.png" rename to Algorithm/LeetCode/DP/images/91_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/91_t.png" b/Algorithm/LeetCode/DP/images/91_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/91_t.png" rename to Algorithm/LeetCode/DP/images/91_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/DP/images/hdu1003_t.png" b/Algorithm/LeetCode/DP/images/hdu1003_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DP/images/hdu1003_t.png" rename to Algorithm/LeetCode/DP/images/hdu1003_t.png diff --git "a/Algorithm/LeetCode/DataStructure/Bucket/LeetCode-164.Maximum Gap(\346\234\200\345\244\247\351\227\264\350\267\235)(\346\241\266).md" "b/Algorithm/LeetCode/DataStructure/Bucket/LeetCode-164.Maximum Gap(\346\234\200\345\244\247\351\227\264\350\267\235)(\346\241\266).md" new file mode 100644 index 00000000..be87b8f0 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Bucket/LeetCode-164.Maximum Gap(\346\234\200\345\244\247\351\227\264\350\267\235)(\346\241\266).md" @@ -0,0 +1,70 @@ +## LeetCode - 164. Maximum Gap(最大间距)(桶) +#### [题目链接](https://leetcode-cn.com/problems/maximum-gap/description/) + +> https://leetcode-cn.com/problems/maximum-gap/description/ + +#### 题目 +![在这里插入图片描述](assets/164_t.png) + +#### 解析 +桶排序思想和相关实现可以看下[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Algorithm/Sort/%E5%90%84%E7%A7%8D%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93(%E5%85%A8%E9%9D%A2).md#%E6%A1%B6%E6%8E%92%E5%BA%8F)。 +思路: + +* 先找到数组的最大值和最小值,记为`max`和`min`; +* 假设数组长度为`N`,准备`N+1`个桶,把`max`放在`N+1`号桶,`nums`中在`[min,max)`范围上的数放在`1~N`号桶中,对于`1 ~ N`号桶的每一个桶来说,负责的区间大小为`(max - min )/N`; +* 注意每个桶中存的不是很多的数,只存三个值,是否有数进入过这个桶,以及所有进入这个桶的数的最大值、最小值; +* 最后计算相邻非空桶的间距(当前桶的`min` 减去前一个桶的`max`) ,然后记录更新最大值; + +比如下面的例子: + +![在这里插入图片描述](images/164_s.png) + +注意不一定就是空桶两侧的非空桶的答案: + +![在这里插入图片描述](images/164_s2.png) + +代码: + +```java +class Solution { + //将 num 映射到对应的桶子 + public int mapToBucket(long num, long len, long min, long max) { + return (int) ((num - min) * len / (max - min)); + } + + public int maximumGap(int[] nums) { + if (nums == null || nums.length < 2) + return 0; + int len = nums.length; + int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE; + for (int i = 0; i < nums.length; i++) { + min = nums[i] < min ? nums[i] : min; + max = nums[i] > max ? nums[i] : max; + } + if (max == min) + return 0; + //准备 n + 1个桶 + boolean[] hasNum = new boolean[len + 1]; + int[] mins = new int[len + 1]; + int[] maxs = new int[len + 1]; + + for (int i = 0; i < nums.length; i++) { + int bid = mapToBucket(nums[i], len, min, max); + mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i]; + maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i]; + hasNum[bid] = true; + } + int res = 0, preMax = maxs[0]; //第一个桶一定不空 因为一定有一个 最小值 + // 每一个非空桶 都找到 左边离它最近的非空桶 然后计算答案 + for (int i = 1; i <= len; i++) { + if (hasNum[i]) { // 是非空的 + res = Math.max(res, mins[i] - preMax); + preMax = maxs[i]; + } + } + return res; + } +} +``` + + diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_t.png" b/Algorithm/LeetCode/DataStructure/Bucket/assets/164_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_t.png" rename to Algorithm/LeetCode/DataStructure/Bucket/assets/164_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_s.png" b/Algorithm/LeetCode/DataStructure/Bucket/images/164_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_s.png" rename to Algorithm/LeetCode/DataStructure/Bucket/images/164_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_s2.png" b/Algorithm/LeetCode/DataStructure/Bucket/images/164_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Bucket/images/164_s2.png" rename to Algorithm/LeetCode/DataStructure/Bucket/images/164_s2.png diff --git a/Algorithm/LeetCode/DataStructure/Bucket/images/164_t.png b/Algorithm/LeetCode/DataStructure/Bucket/images/164_t.png new file mode 100644 index 00000000..a06332b5 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Bucket/images/164_t.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/LeetCode-215.Kth Largest Element in an Array.md b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-215.Kth Largest Element in an Array.md new file mode 100644 index 00000000..ed67631f --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-215.Kth Largest Element in an Array.md @@ -0,0 +1,366 @@ +## LeetCode - 215. Kth Largest Element in an Array(6种写法(包括BFPRT算法)) + + - 最小堆 + - 最大堆 + - 分治解法(利用快排的partition过程) + - BFPRT算法 + - Hash + +*** +#### [题目链接](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) + +> https://leetcode.com/problems/kth-largest-element-in-an-array/description/ + +#### 题意 + +![在这里插入图片描述](images/215_t.png) + + 在未排序的数组中找到第` k `个最大的元素。注意这个和找第`K`小的数,以及求出最小(大)的`k`个数是一样的。 + +### 最小堆 + +**最小堆的方法就是先用`K`个数建成一个堆,然后后面的数和堆顶(最小的数)比较,如果大于堆顶,就替换这个堆顶,然后调整堆,最后,堆顶就是答案。这个方法可以达到`O(N*logK)`的时间复杂度** + +

+ +堆的话可以用`api`的`PriorityQueue`,也可以自己写一下。 + +```java +class Solution { + public int findKthLargest(int[] nums, int k) { + if (nums == null || nums.length == 0) return Integer.MAX_VALUE; + int[] KHeap = new int[k]; //建成一个有K个数的堆 + for (int i = 0; i < k; i++) { + heapInsert(KHeap, nums[i], i); + } + //后面的数如果 + for (int i = k; i < nums.length; i++) { + if (nums[i] > KHeap[0]) { + KHeap[0] = nums[i]; + siftDown(KHeap, 0, k); //调整从0到k + } + } + return KHeap[0]; + } + + //插入数一直往上面调整的过程 + private void heapInsert(int[] KHeap, int num, int i) { + KHeap[i] = num; + while (KHeap[i] < KHeap[(i - 1) / 2]) { + swap(KHeap, i, (i - 1) / 2); + i = (i - 1) / 2; + } + } + + //这个函数就是一个数变大了,往下沉的函数,改变的数为index 目前的自己指定的堆的大小为heapSize + private void siftDown(int[] kHeap, int i, int heapSize) { + int L = 2 * i + 1; + while (L < heapSize) { + int maxIndex = L + 1 < heapSize && kHeap[L + 1] < kHeap[L] ? L + 1 : L; + maxIndex = kHeap[i] < kHeap[maxIndex] ? i : maxIndex; + if (maxIndex == i) break; //自己就是最大的不用往下面沉 + swap(kHeap, i, maxIndex); + i = maxIndex; + L = 2 * i + 1; + } + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` + +这里只是把上面调整堆的过程改成递归写一下。 + +```java +class Solution { + public int findKthLargest(int[] nums, int k) { + if (nums == null || nums.length == 0) return Integer.MAX_VALUE; + int[] KHeap = new int[k]; //建成一个有K个数的堆 + for (int i = 0; i < k; i++) { + heapInsert(KHeap, nums[i], i); + } + //后面的数如果 + for (int i = k; i < nums.length; i++) { + if (nums[i] > KHeap[0]) { + KHeap[0] = nums[i]; + siftDown(KHeap, 0, k); //调整从0到k + } + } + return KHeap[0]; + } + + private void heapInsert(int[] KHeap, int num, int i) { + KHeap[i] = num; + while (KHeap[i] < KHeap[(i - 1) / 2]) { + swap(KHeap, i, (i - 1) / 2); + i = (i - 1) / 2; + } + } + + public void siftDown(int[] data, int i, int size) { //从A[i] 开始往下调整 + int L = 2 * i + 1; //左孩子的下标 + int R = 2 * i + 2; //右孩子的下标 + int maxi = i; + if (L < size && data[L] < data[maxi]) maxi = L; + if (R < size && data[R] < data[maxi]) maxi = R; + if (maxi != i) { + swap(data, i, maxi); //把当前结点和它的最大(直接)子节点进行交换 + siftDown(data, maxi, size); //继续调整它的孩子 + } + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` +*** +### 最大堆 +最大堆的做法和最小堆有点不同,一开始将**整个数组**中的元素构成一个最大堆,这时堆顶元素是最大的,连续将堆顶元素弹出`k-1`次(**每次弹出后都要调整**)后堆顶元素就是第k大的数了。 +```java +class Solution { + public int findKthLargest(int[] nums, int k) { + if (nums == null || nums.length == 0) return Integer.MAX_VALUE; + int[] KHeap = new int[nums.length]; //建成一个有K个数的堆 + for (int i = 0; i < nums.length; i++) { + heapInsert(KHeap, nums[i], i); + } + int size = nums.length; + for (int i = 0; i < k - 1; i++) { //弹出k个 + swap(KHeap, 0, size - 1); + size--; + siftDown(KHeap, 0, size); + } + return KHeap[0]; + } + + + //插入数一直往上面调整的过程 + private void heapInsert(int[] KHeap, int num, int i) { + KHeap[i] = num; + while (KHeap[i] > KHeap[(i - 1) / 2]) { + swap(KHeap, i, (i - 1) / 2); + i = (i - 1) / 2; + } + } + + //这个函数就是一个数变大了,往下沉的函数,改变的数为index 目前的自己指定的堆的大小为heapSize + private void siftDown(int[] kHeap, int i, int heapSize) { + int L = 2 * i + 1; + while (L < heapSize) { + int maxIndex = L + 1 < heapSize && kHeap[L + 1] > kHeap[L] ? L + 1 : L; + maxIndex = kHeap[i] > kHeap[maxIndex] ? i : maxIndex; + if (maxIndex == i) break; //自己就是最大的不用往下面沉 + swap(kHeap, i, maxIndex); + i = maxIndex; + L = 2 * i + 1; + } + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` + +*** +### 分治解法(利用快排的partition过程) +这个方法就是利用快排的`partition`将数组分成左边部分大于某个数,中间部分等于某个数,右边部分小于某个数(如果是求第`k`小的就是左边小于某个数,中间等于某个数,右边大于某个数),然后我们每次划分之后,递归的从左边或者从右边去找第`K`大的数,直到找到在等于部分的。 +```java +class Solution { + /** 分治 */ + public int findKthLargest(int[] nums, int k) { + if (nums == null || nums.length == 0) return Integer.MAX_VALUE; + return process(nums, 0, nums.length - 1, k - 1); //一定要注意这里是k-1 + } + + public int process(int[] arr, int L, int R, int k) { + if (L == R) return arr[L]; + int[] p = partition(arr, L, R, arr[L + (int) (Math.random() * (R - L + 1))]); //随机选一个数划分 + if (k >= p[0] && k <= p[1]) + return arr[k]; + else if (k < p[0]) + return process(arr, L, p[0] - 1, k); + else + return process(arr, p[1] + 1, R, k); + } + + //划分函数 左边的都比num大,右边的都比num小 用一个数组来记录和num相等的下标的下限和上限 + public int[] partition(int[] arr, int L, int R, int num) { + int less = L - 1; //小于部分的最后一个数 + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] > num) { + swap(arr, ++less, cur++); //把这个比num大的数放到大于区域的下一个,并且把小于区域扩大一个单位 + } else if (arr[cur] < num) { + swap(arr, --more, cur); //把这个比num小的数放到小于去余的下一个,并且把小于区域扩大一个单位 + //同时,因为从小于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur] + } else { + cur++; + } + } + return new int[]{less + 1, more - 1}; //返回的是等于区域的两个下标 + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` +*** +### BFPRT算法 +这个方法其实是在上面的分治方法上面的改进,唯一的不同就是寻找那个划分的数的不同,上面的数是随机生成的数,而`BFPRT`算法能找到那样一个数,使得划分的时候左右两边相对均匀,看下面的具体的求解过程: + +![1554862854102](assets/1554862854102.png) + +看下面的一个例子: + +

+ +代码: + +```java +class Solution { + + public int findKthLargest(int[] arr, int K) { + int[] copyArr = copyArray(arr); //不改变原来的数组 + return select(copyArr, 0, copyArr.length - 1, K - 1); + } + + public int[] copyArray(int[] arr) { + int[] res = new int[arr.length]; + for (int i = 0; i != res.length; i++) { + res[i] = arr[i]; + } + return res; + } + + public int select(int[] arr, int L, int R, int k) { + if (L == R) { + return arr[L]; + } + int pivot = medianOfMedians(arr, L, R); //获得这个划分的标准 + int[] p = partition(arr, L, R, pivot); + if (k >= p[0] && k <= p[1]) + return arr[k]; + else if (k < p[0]) + return select(arr, L, p[0] - 1, k); + else + return select(arr, p[1] + 1, R, k); + } + + //划分函数 左边的都比num大,右边的都比num小 用一个数组来记录和num相等的下标的下限和上限 + public int[] partition(int[] arr, int L, int R, int num) { + int less = L - 1; //小于部分的最后一个数 + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] > num) + swap(arr, ++less, cur++); //把这个比num大的数放到大于区域的下一个,并且把小于区域扩大一个单位 + else if (arr[cur] < num) + swap(arr, --more, cur); //把这个比num小的数放到小于去余的下一个,并且把小于区域扩大一个单位 + //同时,因为从小于区域拿过来的数是未知的,所以不能cur++ 还要再次判断一下arr[cur] + else + cur++; + + } + return new int[]{less + 1, more - 1}; //返回的是等于区域的两个下标 + } + + //划分成5组,取出每一组中的中位数,组成一个中位数组 + public int medianOfMedians(int[] arr, int L, int R) { + int num = R - L + 1; + int offset = num % 5 == 0 ? 0 : 1; + int[] mArr = new int[num / 5 + offset]; + for (int i = 0; i < mArr.length; i++) { + int beginI = L + i * 5; + int endI = beginI + 4; + mArr[i] = getMedian(arr, beginI, Math.min(R, endI)); + } + return select(mArr, 0, mArr.length - 1, mArr.length / 2); + } + + + //得到中位数 + public int getMedian(int[] arr, int L, int R) { + insertionSort(arr, L, R); + int sum = L + R; + int mid = (sum / 2) + (sum % 2); + return arr[mid]; + } + + //插入排序 + public void insertionSort(int[] arr, int L, int R) { + for (int i = L + 1; i <= R; i++) { + for (int j = i; j > L && arr[j] < arr[j - 1]; j--) + swap(arr, j, j - 1); + } + } + + private void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` + +### Hash思想 +这个是提交的时候,点击了最快的那个解答看了一下,觉得有点厉害。 +```java +class Solution { + /** Hash思想 */ + public int findKthLargest(int[] nums, int k) { + if (nums == null || nums.length == 0) return Integer.MAX_VALUE; + int max = nums[0], min = nums[0]; + for (int i = 0; i < nums.length; i++) { + if (nums[i] > max) max = nums[i]; + if (nums[i] < min) min = nums[i]; + } + int[] arr = new int[max - min + 1]; + for (int n : nums) arr[max - n]++; + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + if (sum >= k) { + return max - i; + } + } + return 0; + } +} +``` + + +**只要得到了第`K`(小/大)的数,要得到最小(大)的`K`个数,就很简单了,再遍历一遍就可以了,如下面的函数,得到最小的`K`个数**: + +```java + public int[] getMinKNums(int[] arr, int k) { + if (k < 1 || k > arr.length) { + return arr; + } + int minKth = findKthLargest(arr, k); + int[] res = new int[k]; + int index = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] < minKth) { + res[index++] = arr[i]; + } + } + for (; index < res.length; index++) { + res[index] = minKth; + } + return res; + } +``` diff --git a/Algorithm/LeetCode/DataStructure/Heap/LeetCode-295.Find Median from Data Stream.md b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-295.Find Median from Data Stream.md new file mode 100644 index 00000000..96558851 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-295.Find Median from Data Stream.md @@ -0,0 +1,63 @@ +# LeetCode - 295. Find Median from Data Stream + +#### [题目链接](https://leetcode.com/problems/find-median-from-data-stream/) + +> https://leetcode.com/problems/find-median-from-data-stream/ + +#### 题目 +![在这里插入图片描述](images/295_t.png) +#### 解析 +准备两个堆: 一个最大堆(`maxHeap`),一个最小堆`minHeap`。 + +* 最大堆存储较小元素的一半,最大堆存储较大元素的一半; +* 添加元素后,始终要维持要么两个堆的元素相等,要么左边的堆(`maxHeap`)元素比右边多一个; +* 如果不是上面两种情况,就要在添加元素之后维护; +* `findMedian`函数: 查询时,如果两个堆的元素个数相等就返回两个堆顶的元素的和除以一半,否则返回`maxHeap.peek()`; + +看一个例子: +| num | smaller(maxHeap) | bigger(minHeap) | median | +| ---- | ----------------------------------------- | ---------------------------------------- | ------------------------- | +| 5 | 5 | | 5.0 | +| 8 | 5 | 8 | 6.5 | +| 2 | [2、5] | 8 | 5 | +| 11 | [2、5] | [8、11] | 6.5 | +| 3 | [2、3、5] | [8、11] | 5 | +| 4 | [2、3、4、5] | [8、11] | 先调整 | +| | [2、3、4] | [5、8、11] | 4.5 | +| 14 | [2、3、4] | [5、8、11、14] | 先调整 | +| | [2、3、4、5] | [8、11、14] | 5 | + +代码: + + +```java +class MedianFinder { + + private PriorityQueuemaxHeap; // 第一个(更小的)是最大堆 (左边的) + private PriorityQueueminHeap; // 第二个(更大的)是最小堆 (右边的) + + public MedianFinder() { + maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); + minHeap = new PriorityQueue<>(); // java默认是最小堆 (堆顶最小) + } + + public void addNum(int num) { + if(maxHeap.isEmpty() || (!maxHeap.isEmpty() && num <= maxHeap.peek())) + maxHeap.add(num); + else + minHeap.add(num); + + if(maxHeap.size() < minHeap.size()) + maxHeap.add(minHeap.poll()); + else if(maxHeap.size() - minHeap.size() == 2) + minHeap.add(maxHeap.poll()); + } + + public double findMedian() { + if(maxHeap.size() == minHeap.size()) + return (maxHeap.peek() + minHeap.peek())/2.0; + else // minHeap.size() = maxHeap.size() + 1; + return maxHeap.peek(); + } +} +``` diff --git a/Algorithm/LeetCode/DataStructure/Heap/LeetCode-347.Top K Frequent Elements.md b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-347.Top K Frequent Elements.md new file mode 100644 index 00000000..c61c168a --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-347.Top K Frequent Elements.md @@ -0,0 +1,135 @@ +# LeetCode - 347. Top K Frequent Elements (堆 | 桶) +#### [题目链接](https://leetcode.com/problems/top-k-frequent-elements/) + +> https://leetcode.com/problems/top-k-frequent-elements/ + +#### 题目 + +![在这里插入图片描述](images/347_t.png) +## 解析 + +两种解法,一种`O(N * logK)`,一种`O(N)`。 + +`O(N*logK)`,维护一个K个数的堆即可。这里用`PriorityQueue`。 +```java +class Solution { + + private class Freq { + int e; + int freq; //元素和频次 + + public Freq(int e, int freq) { + this.e = e; + this.freq = freq; + } + } + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap counts = new HashMap<>(); + for (int num : nums) + counts.put(num, 1 + counts.getOrDefault(num, 0)); + PriorityQueue heap = new PriorityQueue<>((o1, o2) -> o1.freq - o2.freq); + counts.forEach((key, val) -> { + heap.add(new Freq(key, val)); + if(heap.size() > k) + heap.poll(); + }); + List res = new ArrayList<>(); + while (!heap.isEmpty()) + res.add(heap.poll().e); + return res; + } +} +``` + +代码: + +```java +class Solution { + + //维护一个K个数的优先队列 + public List topKFrequent(int[] nums, int k) { + HashMap counts = new HashMap<>(); + for (int num : nums) + counts.put(num, 1 + counts.getOrDefault(num, 0)); + PriorityQueue< Map.Entry > heap = + new PriorityQueue<>((o1, o2) -> o1.getValue() -o2.getValue()); + for (Map.Entry entry: counts.entrySet()) { + heap.add(entry); + if(heap.size() > k) + heap.poll(); + } + List res = new ArrayList<>(); + while (!heap.isEmpty()) + res.add(heap.poll().getKey()); + return res; + } +} +``` + +`O(N)`解法: + +* 先用一个`HashMap`来统计每个值出现的频率; +* 然后记最大频率为`maxFreq`,然后生成`maxFreq`个桶,每个桶中放对应的频率的集合; +* 然后从后向前取从高频率到低频率的桶中的元素即可(取到`k`个就退出); + +图: + +![在这里插入图片描述](images/347_s.png) + +代码: + +```java +class Solution { + public List topKFrequent(int[] nums, int k) { + HashMap counts = new HashMap<>(); + int maxFreq = 1; + for(int num : nums){ + counts.put(num, 1 + counts.getOrDefault(num, 0)); + maxFreq = Math.max(maxFreq, counts.get(num)); + } + HashMap> buckets = new HashMap<>(); + counts.forEach((key, val) -> { + ArrayList tmp = buckets.getOrDefault(val, new ArrayList<>()); + tmp.add(key); + buckets.put(val, tmp); + }); + ArrayListres = new ArrayList<>(); + for(int freq = maxFreq; freq >= 1; freq--){ + if(buckets.containsKey(freq)) + res.addAll(buckets.get(freq)); + if(res.size() == k) + break; + } + return res; + } +} +``` + +还有一种和`Java8`更密切的写法(利用`streamAPI`): +```java +class Solution { + public List topKFrequent(int[] nums, int k) { + Map counts = new HashMap<>(); + Map> buckets = new HashMap<>(); + + IntStream.of(nums).forEach(n -> counts.put(n, counts.getOrDefault(n, 0) + 1)); + + // Sort by occurrences, so we can later get the most frequent. + counts.forEach((key, val) -> { + Set set = buckets.getOrDefault(val, new HashSet<>()); + set.add(key); + buckets.put(val, set); + }); + + //buckets.forEach((key, value) -> System.out.format("[%d->%s]", key, Arrays.asList(value.toArray()))); + List res = new ArrayList<>(); + + // Sort in reverse order and get the first K items, but since this is a set we need to save into a list. + buckets.keySet().stream().sorted(Comparator.reverseOrder()).limit(k).forEach(freq -> res.addAll(buckets.get(freq))); + + return res.stream().limit(k).collect(Collectors.toList()); + } +} +``` diff --git "a/Algorithm/LeetCode/DataStructure/Heap/LeetCode-502.IPO(\350\264\252\345\277\203 + \345\240\206).md" "b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-502.IPO(\350\264\252\345\277\203 + \345\240\206).md" new file mode 100644 index 00000000..f331310c --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-502.IPO(\350\264\252\345\277\203 + \345\240\206).md" @@ -0,0 +1,59 @@ +# LeetCode - 502. IPO(贪心 + 堆) +#### [题目链接](https://leetcode.com/problems/ipo/description/) + +> https://leetcode.com/problems/ipo/description/ + +#### 题目 + +![在这里插入图片描述](images/502_t.png) +*** +## 解析 + 这个题目要用到贪心的思想,首先准备两个堆,一个按照**花费成本升序的小根堆**,另一个按照**纯利润降序的大根堆**。 + + - 首先我们将所有的项目加入到**小根堆**中,则这个堆中的项目是按照花费成本升序的。 + - 然后,我们从**小根堆**中拿出所有能做的项目(目前的`W`(目前有的钱))放到按照利润降序的**大根堆**中。 + - 然后我们从大根堆中取出堆顶元素,也就是说我们这一次做这个项目。 + - 循环`K`次,做`K`个项目。 + - 注意,当大根堆中没有元素,但是循环没有结束的时候,就可以返回了,因为现有的钱已经做不起任何的项目。 + +看题目中例子的过程: + +![这里写图片描述](images/502_s.png) + +代码如下 : + +```java +import java.util.PriorityQueue; + +class Solution { + + //花费和利润的结构体 + private class Node { + public int p; + public int c; + + public Node(int p, int c) { + this.p = p; + this.c = c; + } + } + + public int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) { + Node[] node = new Node[Profits.length]; + for (int i = 0; i < Profits.length; i++) + node[i] = new Node(Profits[i], Capital[i]); + + PriorityQueue minCostQ = new PriorityQueue<>((o1, o2) -> o1.c - o2.c);// minHeap by c + PriorityQueue maxProfitQ = new PriorityQueue<>((o1, o2) -> -(o1.p - o2.p));// maxHeap by p + for (int i = 0; i < node.length; i++) minCostQ.add(node[i]); + //最多做k个项目 + for (int i = 0; i < k; i++) { + while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) + maxProfitQ.add(minCostQ.poll()); + if (maxProfitQ.isEmpty()) return W; //不能做了 做不起了 + W += maxProfitQ.poll().p; + } + return W; + } +} +``` diff --git a/Algorithm/LeetCode/DataStructure/Heap/LeetCode-692.Top K Frequent Words.md b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-692.Top K Frequent Words.md new file mode 100644 index 00000000..c0fa3373 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Heap/LeetCode-692.Top K Frequent Words.md @@ -0,0 +1,65 @@ +# LeetCode - 692. Top K Frequent Words + +#### [题目链接](https://leetcode.com/problems/top-k-frequent-words/) + +> https://leetcode.com/problems/top-k-frequent-words/ + +#### 题目 +![在这里插入图片描述](images/629_t.png) + + +## 解析 + +题目明确是`O(n log k)`,很明显是用堆了。 + +解法一: 直接排序取前`K`个。(主要是见识一下`Java8`的一些写法) + +`O(N*logN)`: +```java +class Solution { + public List topKFrequent(String[] words, int k) { + Map freqs = new HashMap(); + for (String word: words) + freqs.put(word, 1 + freqs.getOrDefault(word, 0)); + List res = new ArrayList(freqs.keySet()); + Collections.sort(res, (w1, w2) -> freqs.get(w1) == freqs.get(w2) ? w1.compareTo(w2) : + freqs.get(w2) - freqs.get(w1)); + return res.subList(0, k); + } +} +``` +解法二: 维护一个`K`个数的堆。 + +`O(N*logK)` +```java +class Solution { + + private class Freq{ + String word; + int freq; + + public Freq(String word, int freq) { + this.word = word; + this.freq = freq; + } + } + + public List topKFrequent(String[] words, int k) { + List res = new ArrayList<>(); + if(words == null) + return res; + Queueheap = new PriorityQueue<>((o1, o2) -> { // lambda will be slow + if(o1.freq == o2.freq) + return o1.word.compareTo(o2.word); + return -(o1.freq - o2.freq); // big heap + }); + HashMap counts = new HashMap<>(); + for(String word : words) + counts.put(word,1 + counts.getOrDefault(word, 0)); + counts.forEach((key, val) -> heap.add(new Freq(key, val))); // java 8 + for(int i = 0; i < k; i++) + res.add(heap.poll().word); + return res; + } +} +``` diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554862687366.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862687366.png new file mode 100644 index 00000000..98476758 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862687366.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554862715280.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862715280.png new file mode 100644 index 00000000..a62424dd Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862715280.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554862777485.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862777485.png new file mode 100644 index 00000000..081f2c81 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862777485.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554862793735.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862793735.png new file mode 100644 index 00000000..081f2c81 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862793735.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554862854102.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862854102.png new file mode 100644 index 00000000..962f061c Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554862854102.png differ diff --git a/Algorithm/LeetCode/DataStructure/Heap/assets/1554863091470.png b/Algorithm/LeetCode/DataStructure/Heap/assets/1554863091470.png new file mode 100644 index 00000000..a7675bdf Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/assets/1554863091470.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/215_t.png" b/Algorithm/LeetCode/DataStructure/Heap/images/215_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/215_t.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/215_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/295_t.png" b/Algorithm/LeetCode/DataStructure/Heap/images/295_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/295_t.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/295_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/347_s.png" b/Algorithm/LeetCode/DataStructure/Heap/images/347_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/347_s.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/347_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/347_t.png" b/Algorithm/LeetCode/DataStructure/Heap/images/347_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/347_t.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/347_t.png diff --git a/Algorithm/LeetCode/DataStructure/Heap/images/502_s.png b/Algorithm/LeetCode/DataStructure/Heap/images/502_s.png new file mode 100644 index 00000000..8db8b37a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Heap/images/502_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/502_t.png" b/Algorithm/LeetCode/DataStructure/Heap/images/502_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/502_t.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/502_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/629_t.png" b/Algorithm/LeetCode/DataStructure/Heap/images/629_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Heap/images/629_t.png" rename to Algorithm/LeetCode/DataStructure/Heap/images/629_t.png diff --git "a/Algorithm/LeetCode/DataStructure/List/LeetCode-138.Copy List with Random Pointer(\345\220\253\346\234\211\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250\347\232\204\346\213\267\350\264\235).md" "b/Algorithm/LeetCode/DataStructure/List/LeetCode-138.Copy List with Random Pointer(\345\220\253\346\234\211\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250\347\232\204\346\213\267\350\264\235).md" new file mode 100644 index 00000000..abef4feb --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/List/LeetCode-138.Copy List with Random Pointer(\345\220\253\346\234\211\351\232\217\346\234\272\346\214\207\351\222\210\347\232\204\351\223\276\350\241\250\347\232\204\346\213\267\350\264\235).md" @@ -0,0 +1,195 @@ +## LeetCode - 138. Copy List with Random Pointer(含有随机指针的链表的拷贝) + - 方法一 : 使用HashMap保存 + - 方法二 : O(1)的空间复杂度 +*** +#### [题目链接](https://leetcode.com/problems/copy-list-with-random-pointer/description/) + +> https://leetcode.com/problems/copy-list-with-random-pointer/description/ + +#### 题意 +![1554790494580](assets/1554790494580.png) + +链表结构: + +```java +class Node { + public int val; + public Node next; + public Node random; + + public Node() {} + + public Node(int _val,Node _next,Node _random) { + val = _val; + next = _next; + random = _random; + } +}; +``` + +## 方法一、使用HashMap保存 + +方法: + +* 从左到右遍历链表,对每个结点都复制生成相应的**副本结点**,然后将对应的关系(之前的结点和新的副本结点)放入哈希表中; +* 然后从左到右设置每一个副本结点的`next`和`random`指针,即找到原先`cur`的`next`和`random`的拷贝(从`Map`中获取); +* 最后返回副本结点的头结点(`map.get(head)`)即可; + +看一个例子: + +例如: 原链表 `1->2->3->null`,假设 `1 `的 `rand `指针指向 `3`,`2` 的 `rand` 指针指向 `null`,`3`的` rand `指针指向` 1`。 + +

+ +遍历到节点` 1` 时,可以从 `map `中得到节点` 1` 的副本节点`1’`,节点 `1` 的`next `指向节点 `2`,所以从 `map `中得到节点 `2` 的副本节点` 2`,然后令 `1’.next=2'`,副本节点的 `next` 指针就设置好了。同时节点 `1`的 `rand `指向节点` 3`,所以从` map` 中得到节点` 3` 的副本节点 `3`,然后令 `1‘.rand=3'`,副本节点`1`的 `rand` 指针也设置好了。 + +以这种方式可以设置每一个副本节点的 `next` 与` rand` 指针。 + +```java +class Solution { + + //普通的使用一个HashMap的额外空间为O(n)的方法 + public Node copyRandomList(Node head) { + if (head == null) return null; + HashMap map = new HashMap<>(); + + Node cur = head; + while (cur != null) { + map.put(cur, new Node(cur.val, null, null)); + cur = cur.next; + } + cur = head; + while (cur != null) { + map.get(cur).next = map.get(cur.next); + map.get(cur).random = map.get(cur.random); + cur = cur.next; + } + return map.get(head); + } +} +``` + +方法一的另一种写法 + +思路: + + * 方法一的写法是第一次存储每个结点的时候没有直接找到拷贝结点的`next`域结点; + * 这个方法是在拷贝原结点的时候,顺便拷贝了结点的`next`域,拷贝完`next`域之后,最后就只要拷贝`random`域了; + * 注意这里使用`cur`指向原链表的`head`,使用`copyCur`指向复制链表的`head`,然后这两个指针同时完成的是两个工作: 先设置好副本拷贝结点的`next`域,然后将对应的原来链表的结点和拷贝的结点`put`进`map`,然后`cur`和`copyCur`都同时向后继续移动一个位置; + +代码: + +```java +class Solution { + + //使用Map的另一种写法,速度一般 + public Node copyRandomList(Node head) { + if (head == null) + return null; + HashMap map = new HashMap<>(); + Node copyHead = new Node(head.val, null, null); + map.put(head, copyHead); + + Node cur = head, copyCur = copyHead; + + while (cur != null) { + if (cur.next != null) + copyCur.next = new Node(cur.next.val, null, null); + map.put(cur.next, copyCur.next); + cur = cur.next; + copyCur = copyCur.next; + } + + cur = head; + while (cur != null) { + map.get(cur).random = map.get(cur.random); + cur = cur.next; + } + return copyHead; + } +} +``` +由于链表的天然的递归结构,也可以使用递归的写法: + +```java +class Solution { + + //上一种方法的递归的写法 + public Node copyRandomList(Node head) { + if (head == null) + return null; + HashMap map = new HashMap<>(); + Node copyHead = rec(head, map); + + Node cur = head; + while (cur != null) { + map.get(cur).random = map.get(cur.random); + cur = cur.next; + } + return copyHead; + } + + // 宏观来看: 就是返回拷贝以node为头结点的链表的拷贝 以及next的拷贝 + private Node rec(Node node, HashMap map) { + if (node == null) + return null; + Node copyNode = new Node(node.val, null, null); + map.put(node, copyNode); + copyNode.next = rec(node.next, map); + return copyNode; + } +} +``` + + +## 方法二、O(1)的空间复杂度 +这个方法是最好的解决办法,分为三个步骤: +* 第一个步骤,先从左到右遍历一遍链表,对每个结点`cur`都复制生成相应的副本结点`copy`,然后把副本结点`copy`放在`cur`和下一个要遍历结点的中间; +* 再从左到右遍历一遍链表,在遍历时设置每一个结点的副本结点的`random`指针; +* 设置完`random`指针之后,将链表拆成两个链表,返回第二个链表的头部; + +图: + +![在这里插入图片描述](images/138_s2.png) + +代码: + +```java +class Solution { + //O(1)的空间复杂度 + public Node copyRandomList(Node head) { + if (head == null) return null; + Node cur = head, next = null; + //先拷贝一份原来的链表 + while (cur != null) { + next = cur.next; //先存着之前的next + cur.next = new Node(cur.val, null, null); + cur.next.next = next; + cur = next; + } + + //复制结点的random指针 + cur = head; + Node copyCur = null; + while (cur != null) { + next = cur.next.next; //保存原来链表中的下一个 + copyCur = cur.next; //复制链表的cur + copyCur.random = cur.random != null ? cur.random.next : null; + cur = next; + } + + //拆开两个链表 + Node copyHead = head.next; + cur = head; + while (cur != null) { + next = cur.next.next; + copyCur = cur.next; + cur.next = next; + copyCur.next = next != null ? next.next : null; + cur = next; + } + return copyHead; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-141.Linked List Cycle.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-141.Linked List Cycle.md new file mode 100644 index 00000000..75aee96b --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-141.Linked List Cycle.md @@ -0,0 +1,61 @@ +# LeetCode - 141. Linked List Cycle + +#### [题目链接](https://leetcode.com/problems/linked-list-cycle/) + +> https://leetcode.com/problems/linked-list-cycle/ + +#### 题目 +![在这里插入图片描述](images/141_t.png) +#### 解析 +第一种做法: 使用`HashSet`记录已经走过的节点,如果再次碰到,则有环: + +```java +public class Solution { + public boolean hasCycle(ListNode head) { + if (head == null) + return false; + HashSet set = new HashSet<>(); + ListNode cur = head; + while (cur != null) { + if (set.contains(cur)) + return true; + set.add(cur); + cur = cur.next; + } + return false; + } +} +``` +题目要求不能使用额外的空间,所以第二种解法: + +* 使用两个指针,一个快指针`fast`一次走两步,慢指针一次走一步; +* 如果快指针`fast`某个时刻走到空了,说明没有环, **因为如果有环,快指针一定不会走到空(慢指针也不会(因为是单链表))**; +* 所以如果`fast`没有走到空,那快指针`fast`和慢指针`slow`就会在环里面打圈,因为`fast`更快,所以一定会追上`slow`,当`fast == slow`的时候就返回`true`就可以了; + +图: + +

+ +代码: + +```java +public class Solution { + public boolean hasCycle(ListNode head) { + if (head == null) + return false; + ListNode slow = head; + ListNode fast = head; + while (fast.next != null && fast.next.next != null) { + fast = fast.next.next; + slow = slow.next; + if (fast == slow) + return true; + } + return false; + } +} +``` + + + + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-142.Linked List Cycle II.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-142.Linked List Cycle II.md new file mode 100644 index 00000000..550e19e6 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-142.Linked List Cycle II.md @@ -0,0 +1,50 @@ +# LeetCode - 142. Linked List Cycle II-找到第一个入环节点 + +#### [题目链接](https://leetcode.com/problems/linked-list-cycle-ii/) + +> https://leetcode.com/problems/linked-list-cycle-ii/ + +#### 题目 +![在这里插入图片描述](images/142_t.png) +## 解析 +同样可以使用`HashSet`,但是也不符合题目使用`O(1)`空间的要求。 + +`O(1)`空间的解法还是使用快慢指针,唯一的不同就是: + +**他们相遇之后,让`fast`回到`head`(头结点),然后两个指针每次都只走一步,他们一定会相遇,而且相遇的节点就是第一个入环节点**; + +按照上面的思路写出代码很简单,关键是怎么证明就是那样走? + +看下图的解释,需要证明的是 边`a == 边c`,即最后可以`fast`和`slow`一起一步一步的走。。。 + +图: + +![在这里插入图片描述](images/142_s.png) + +代码: + + +```java +public class Solution { + public ListNode detectCycle(ListNode head) { + if (head == null) + return null; + ListNode fast = head; + ListNode slow = head; + + while (fast.next != null && fast.next.next != null) { + fast = fast.next.next; + slow = slow.next; + if (fast == slow) {//当fast和slow相交的时,让fast回到起点,fast和slow都只走一步,然后fast和slow第一次相遇的地方就是交点 + fast = head; + while (fast != slow) { + fast = fast.next; + slow = slow.next; + } + return fast; + } + } + return null; + } +} +``` \ No newline at end of file diff --git "a/Algorithm/LeetCode/DataStructure/List/LeetCode-146.LRU Cache(LRU\347\274\223\345\255\230\345\217\230\346\233\264\347\256\227\346\263\225)(LinkedHashMap\345\272\225\345\261\202).md" "b/Algorithm/LeetCode/DataStructure/List/LeetCode-146.LRU Cache(LRU\347\274\223\345\255\230\345\217\230\346\233\264\347\256\227\346\263\225)(LinkedHashMap\345\272\225\345\261\202).md" new file mode 100644 index 00000000..4c0c3151 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/List/LeetCode-146.LRU Cache(LRU\347\274\223\345\255\230\345\217\230\346\233\264\347\256\227\346\263\225)(LinkedHashMap\345\272\225\345\261\202).md" @@ -0,0 +1,184 @@ +# LeetCode - 146. LRU Cache(LRU缓存变更算法)(LinkedHashMap底层) + +#### [题目链接](https://leetcode.com/problems/lru-cache/description/) + +> https://leetcode.com/problems/lru-cache/description/ + +#### 题目 +![在这里插入图片描述](images/146_t.png) + + + +### 解析 +这种缓存结构使用**双向链表**和**哈希表**相结合的方式实现。 + + +首先看双向链表: + + - **在双向链表中有两个指针`head`(头)和`tail`(尾),其中头部是优先级最低的,也就是最早进行`get`或者`put`的,而尾部是最晚(优先级最高)进行`get`和`put`的;** + - 双向链表的`add()`操作,将新加入的结点放到链表的尾部,并将这个结点设置成新的尾部; + - 双向链表的`removeHead()`操作,移除最不常用的头部,并设置新的头部; + - 双向链表的`moveToTail()`操作,在链表中分离**不是尾部的中间结点**放到尾部; + + +然后看`HashMap:` + + - 一旦加入(`put`)新的记录,就把该记录加入到双向链表`doubleLinkedList`的尾部;(最近的更新) + - **一旦`get`或者更新一个记录的`key`,就将这个`key`对应的`node`在`doubleLinkedList`中调整到尾部(`moveToTail`);** + - 一旦缓存结构满了,就"删除就不经常使用的"的记录; + +图: + +![在这里插入图片描述](images/146_s.png) + +代码: + +```java +class LRUCache { + + private class Node { //双向链表的结点结构 + public int value; + public Node next; + public Node pre; + + public Node(int value) { + this.value = value; + } + } + + /** + * 双向链表,实现三个功能: + * (1) 添加结点 + * (2) 删除头部的结点 + * (3) 把一个结点从中间移到尾部 + */ + private class DoubleLinkedList { + public Node head; //头指针 + public Node tail; //尾指针 + + public DoubleLinkedList() { + head = null; + tail = null; + } + + public void add(Node node) { + if (node == null) return; + if (head == null) { + head = node; + tail = node; + } else { + tail.next = node; + node.pre = tail; + tail = node; + } + } + + //移除头部 并返回头部 + public Node removeHead() { + if (head == null) return null; + Node res = head; + if (head == tail) { + head = null; + tail = null; + } else { + head = res.next; + res.next = null; + head.pre = null; + } + return res; + } + + //把一个结点从链表的中间放到尾部(变成最经常使用的) + public void moveToTail(Node node) { + if (node == null) return; + if (node == tail) return; + if (head == node) { //删除头部 + head = head.next; //更换头部 + head.pre = null; + } else { //删除的中间的 + node.pre.next = node.next; + node.next.pre = node.pre; + } + tail.next = node; + node.next = null; + node.pre = tail; + tail = node; + } + } + + private HashMap kNMap; // key -> value + private HashMap nKMap; // value -> key node ->value + + private DoubleLinkedList nodeList; //双向链表 + + private int capacity; //容量 + + public LRUCache(int capacity) { + kNMap = new HashMap<>(); + nKMap = new HashMap<>(); + nodeList = new DoubleLinkedList(); + this.capacity = capacity; + } + + public int get(int key) { + if (!kNMap.containsKey(key)) { + return -1; + } + Node res = kNMap.get(key); + nodeList.moveToTail(res); + return res.value; + } + + public void put(int key, int value) { + if (kNMap.containsKey(key)) { //已经有这个key 更新其value + Node node = kNMap.get(key); + node.value = value; + nodeList.moveToTail(node); //放到最后 + } else { //新增 + Node newNode = new Node(value); + kNMap.put(key, newNode); + nKMap.put(newNode, key); + nodeList.add(newNode); + if (kNMap.size() == capacity + 1) { //缓存结构满了,超过了必须移除最不经常使用的 + Node removeNode = nodeList.removeHead(); //在链表中移除头部 + Integer removeKey = nKMap.get(removeNode); //要移除的结点的key + //在两个Map中删除 + kNMap.remove(removeKey); + nKMap.remove(removeNode); + } + } + } +} +``` +*** +### Java的LinkedHashMap +Java的容器`LinkedHashMap`底层也使用了`LRU`算法,下面的程序也可以通过LeetCode: + +```java +class LRUCache extends LinkedHashMap { + + private int capacity; + + public LRUCache(int capacity) { + super(capacity, 1, true); + this.capacity = capacity; + } + + public int get(int key) { + Integer res = super.get(key); + if (res == null) return -1; + return res; + + } + + public void put(int key, int value) { + super.put(key, value); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > capacity; + } +} +``` +更多`LinkedHashMap`底层实现可以看下[这篇博客](https://blog.csdn.net/justloveyou_/article/details/71713781)和[这篇博客](http://www.cnblogs.com/lzrabbit/p/3734850.html)。 diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-160.Intersectin of Two Linked Lists.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-160.Intersectin of Two Linked Lists.md new file mode 100644 index 00000000..84b1ec94 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-160.Intersectin of Two Linked Lists.md @@ -0,0 +1,216 @@ +# LeetCode - 160. Intersection Of Two Linked Lists以及扩展问题 + +#### [题目链接](https://leetcode.com/problems/intersection-of-two-linked-lists/) + +> https://leetcode.com/problems/intersection-of-two-linked-lists/ + +#### 题目 +![在这里插入图片描述](images/160_t.png) +![在这里插入图片描述](images/160_t2.png) +![在这里插入图片描述](images/160_t3.png) +#### 解析 + +一个比较好的解法是: + +* 先统计两个链表的长度`lenA`和`lenB`(都先遍历一次); +* 然后判断两个链表各自走到最后的指针`end1`和`end2`是否相等,如果不相等,直接返回`false`; +* 然后如果`lenA > lenB`,则`B`链表的指针先走`lenA - lenB`步,然后两个指针一起走,第一个相等的节点就是相交的节点; +* 同理`lenB > lenA`,则`A`链表的指针先走`lenB - lenA`步,然后一起走; + +图: + +![1554794205224](assets/1554794205224.png) + +代码: + +```java +public class Solution { + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + if (headA == null || headB == null) + return null; + + //先求出两个链表的长度 + int lenA = 1; + ListNode cur1 = headA; + while (cur1.next != null) { + lenA++; + cur1 = cur1.next; + } + int lenB = 1; + ListNode cur2 = headB; + while (cur2.next != null) { + lenB++; + cur2 = cur2.next; + } + //最后都不相等,肯定不相交 + if (cur1 != cur2) + return null; + + // cur1是长的,cur2是短的 + cur1 = lenA > lenB ? headA : headB; + cur2 = cur1 == headA ? headB : headA; + int n = Math.abs(lenA - lenB); + + //cur1先走 abs(lenA - lenB步) + while (n > 0) { + n--; + cur1 = cur1.next; + } + + //一起走,第一次相遇就是交点 + while (cur1 != cur2) { + cur1 = cur1.next; + cur2 = cur2.next; + } + return cur1; + } +} +``` +## 2、扩展问题 + +两个链表可以有环的情况下找到两个链表的第一个相交的节点。 + +两个链表,可以有环或者无环,可以相交或者不相交,如果相交,返回第一个入环节点,否则返回`null`。 + +#### 解析 + +有三种情况: + +* 一个链表有环,另一个链表无环,这种情况两个链表不可能相交(不论怎么画),直接返回`null`; +* 两个链表都无环,就是`LeetCode160`的解法; +* 第三种情况: 两个链表有环,这种情况又可以分为三种情况。 + +第三种情况的三种情况,假设链表`1`的第一个入环节点记为`loop1`,链表`2`的第一个入环节点记为`loop2`: + +* 如果`loop1 == loop2`,则两个链表结构如图(一)所示,这时我们只要考虑`head1`到`loop1`和`head2`到`loop2`的部分也就是图中红色阴影的部分,这个又是`LeetCode160`的处理方式,只不过改了结束的位置; +* 如果`loop1 != loop2`,则又分两种情况,第一种如图(二),第二种如图三,区别这两种方式的方法看下图解释; + +图: + +![在这里插入图片描述](images/160_s.png) + +代码如下: + +```java +public class Solution { + //主过程 + public ListNode getIntersectionNode(ListNode head1, ListNode head2) { + if (head1 == null || head2 == null) { + return null; + } + ListNode loop1 = detectCycle(head1); + ListNode loop2 = detectCycle(head2); + if (loop1 == null && loop2 == null) { + return getIntersectionNodeNoLoop(head1, head2); //两个都无环的处理方式 + } + if (loop1 != null && loop2 != null) { + return bothLoop(head1, loop1, head2, loop2); //两个都有环的处理 + } + return null; // 一个有环一个无环 + } + + //找到某个链表第一个入环节点 + public ListNode detectCycle(ListNode head) { + if(head == null) + return null; + ListNode fast = head; + ListNode slow = head; + + while(fast.next != null && fast.next.next != null){ + fast = fast.next.next; + slow = slow.next; + if(fast == slow){//当fast和slow相交的时,让fast回到起点,fast和slow都只走一步,然后fast和slow第一次相遇的地方就是交点 + fast = head; + while(fast != slow){ + fast = fast.next; + slow = slow.next; + } + return fast; + } + } + return null; + } + + public ListNode getIntersectionNodeNoLoop(ListNode headA, ListNode headB) { + if(headA == null || headB == null) + return null; + + //先求出两个链表的长度 + int lenA = 1; + ListNode cur1 = headA; + while(cur1.next != null){ + lenA++; + cur1 = cur1.next; + } + int lenB = 1; + ListNode cur2 = headB; + while(cur2.next != null){ + lenB++; + cur2 = cur2.next; + } + //最后都不相等,肯定不相交 + if(cur1 != cur2) + return null; + + // cur1是长的,cur2是短的 + cur1 = lenA > lenB ? headA : headB; + cur2 = cur1 == headA ? headB : headA; + int n = Math.abs(lenA - lenB); + + //cur1先走 abs(lenA - lenB步) + while(n > 0){ + n--; + cur1 = cur1.next; + } + + //一起走,第一次相遇就是交点 + while(cur1 != cur2){ + cur1 = cur1.next; + cur2 = cur2.next; + } + return cur1; + } + + //两个都有环的处理 + public ListNode bothLoop(ListNode head1, ListNode loop1, ListNode head2, ListNode loop2) { + ListNode cur1 = null; + ListNode cur2 = null; + + if (loop1 == loop2) { // 图(一)情况 类似无环的处理 LeetCode 160 这里和上面处理稍有点不同 + cur1 = head1; + cur2 = head2; + int n = 0; + while (cur1 != loop1) { + n++; + cur1 = cur1.next; + } + while (cur2 != loop2) { + n--; + cur2 = cur2.next; + } + cur1 = n > 0 ? head1 : head2; + cur2 = cur1 == head1 ? head2 : head1; + n = Math.abs(n); + while (n != 0) { + n--; + cur1 = cur1.next; + } + while (cur1 != cur2) { + cur1 = cur1.next; + cur2 = cur2.next; + } + return cur1; + } else { //图(三) 的情况 + cur1 = loop1.next; + while (cur1 != loop1) { + if (cur1 == loop2) { + return loop1; + } + cur1 = cur1.next; + } + return null; + } + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-19.Remove Nth Node From End of List.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-19.Remove Nth Node From End of List.md new file mode 100644 index 00000000..bd4019dc --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-19.Remove Nth Node From End of List.md @@ -0,0 +1,70 @@ +# LeetCode - 19. Remove Nth Node From End of List + +#### [题目链接](https://leetcode.com/problems/remove-nth-node-from-end-of-list/) + +> https://leetcode.com/problems/remove-nth-node-from-end-of-list/ + +#### 题目 + +![1555064547654](assets/1555064547654.png) + +## 解析 + +两种方法,一种遍历二次,一种遍历一次。 + +比较直接的思路,先求出长度`len`,然后第从前往后遍历`len - n`次就可以了。 + +```java +public class Solution { + + // 两次遍历的方法 + public ListNode removeNthFromEnd(ListNode head, int n) { + int len = 0; + ListNode cur = head; + while (cur != null) { + len++; + cur = cur.next; + } + if (len < n) return null; + ListNode dummyHead = new ListNode(-1); + dummyHead.next = head; + cur = dummyHead; + for (int i = 0; i < len - n; i++) cur = cur.next; + cur.next = cur.next.next;//删除倒数第N个节点 + return dummyHead.next; + } +} +``` + +第二种思路是使用快慢指针。 + +第一个指针`fast`从列表的开头向前移动 `n+1`步,而第二个指针`slow`将从列表的开头出发。 + +现在,这两个指针被 `n` 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 `n` 个结点。 + +然后我们重新链接第二个指针所引用的结点的 `next` 指针指向该结点的下下个结点。 + +

+ +代码: + +```java +public class Solution { + + public ListNode removeNthFromEnd(ListNode head, int n) { + ListNode dummyHead = new ListNode(-1); + dummyHead.next = head; + ListNode fast = dummyHead; + ListNode slow = dummyHead; + for (int i = 0; i < n + 1; i++) + fast = fast.next; + while (fast != null) { + fast = fast.next; + slow = slow.next; + } + slow.next = slow.next.next; + return dummyHead.next; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-2.Add Two Numbers.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-2.Add Two Numbers.md new file mode 100644 index 00000000..239e0d8f --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-2.Add Two Numbers.md @@ -0,0 +1,55 @@ +## LeetCode - 2. Add Two Numbers + +#### [题目链接](https://leetcode.com/problems/add-two-numbers/) + +> + +#### 题目 + +![2_t.png](images/2_t.png) + +## 解析 + +直接模拟两数加法操作,然后使用一个`carry`记录进位,下一位要加上**上一位的进位**。 + +注意三种特殊情况: + +* 当一个列表比另一个列表长时; +* 当一个列表为空时,即出现空列表; +* **求和运算最后可能出现额外的进位**; + +图: + +

+代码: + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +class Solution { + public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + ListNode dummyHead = new ListNode(-1); + ListNode p1 = l1, p2 = l2; + ListNode p3 = dummyHead; + int carry = 0; + while (p1 != null || p2 != null) { + int v1 = p1 == null ? 0 : p1.val; + int v2 = p2 == null ? 0 : p2.val; + p3.next = new ListNode((carry + v1 + v2) % 10);//当前位的值 + carry = (v1 + v2 + carry) / 10; + p3 = p3.next; + if (p1 != null) p1 = p1.next; + if (p2 != null) p2 = p2.next; + } + if (carry != 0) p3.next = new ListNode(carry);//最后别忘了进位 + return dummyHead.next; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-203.Remove Linked List Elements.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-203.Remove Linked List Elements.md new file mode 100644 index 00000000..003952d0 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-203.Remove Linked List Elements.md @@ -0,0 +1,117 @@ +## LeetCode - 203. Remove Linked List Elements(删除链表中的元素(多个)) + + - 不设置dummyHead + - 设置dummyHead + - 使用递归 + +*** +#### [题目链接](https://leetcode.com/problems/remove-linked-list-elements/) + +> https://leetcode.com/problems/remove-linked-list-elements/ + +#### 题目 +![在这里插入图片描述](images/203_t.png) + +## 非递归 + +链表非递归的一般都有设置`dummyHead`和不设置`dummyHead`(虚拟头结点)。 + +删除一个结点的操作: + + - 先找到对应结点`node`的`pre`结点,然后令`pre.next = pre.next.next`就让`node`结点脱离整个链即可; + - 注意要先判断一下,链表一开始相等的部分,要把前面所有相等的部分都先删除; + +图: + +

+ +代码: + +```java +class Solution { + + public ListNode removeElements(ListNode head, int val) { + if (head == null) + return null; + while (head != null && head.val == val) + head = head.next; //删除一开始的 + if (head == null) + return null; + ListNode pre = head; + while (pre.next != null) { + if (pre.next.val == val) { + pre.next = pre.next.next; + } else { + pre = pre.next;//注意这里必须写在else中,因为pre.next删除之后,还是要检查pre.next + } + } + return head; + } +} +``` +**这个就是设置一个虚拟的头结点,让我们不再需要特判链表的第一个元素**。 + +

+ +代码: + +```java +class Solution { + + public ListNode removeElements(ListNode head, int val) { + if (head == null) return null; + ListNode dummyHead = new ListNode(-1); + dummyHead.next = head; + ListNode pre = dummyHead; + while (pre.next != null) { + if (pre.next.val == val) + pre.next = pre.next.next; + else + pre = pre.next; + } + return dummyHead.next; + } +} + +``` +## 递归 + +宏观的来看: + + - **就是先去处理我的`next`,我的`next`链开始的链先给我删除那些`val`的,然后我再连接上**; + - **我连接的时候,要看看自己要不要被删除,如果要,就直接返回我的`next`就可以了,如果不要就先连接上,然后返回**; + +微观的来看: + +![在这里插入图片描述](images/203_s.png) + +代码: + +```java +class Solution { + + public ListNode removeElements(ListNode head, int val) { + if (head == null) return null; + + ListNode res = removeElements(head.next, val); + if (head.val == val) { + return res; + } else { + head.next = res; + return head; + } + } +} +``` + +```java +class Solution { + + public ListNode removeElements(ListNode head, int val) { + if (head == null) return null; + head.next = removeElements(head.next, val); //我的next 等于 你后面的先处理完val我再连上你 + return head.val == val ? head.next : head; //我自己的话 就要判断一下 + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-206.Reverse Linked List.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-206.Reverse Linked List.md new file mode 100644 index 00000000..8b001cea --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-206.Reverse Linked List.md @@ -0,0 +1,247 @@ +# LeetCode - 206. Reverse Linked List单链表反转(递归和非递归)(以及双向链表的反转) + - 单链表的反转 + - 双向链表的反转 + - 完整测试代码 + +#### [题目链接](https://leetcode.com/problems/reverse-linked-list/description/) + +> https://leetcode.com/problems/reverse-linked-list/description/ + +#### 题目 +![在这里插入图片描述](images/206_t.png) + +### 单链表的反转 + +单链表的反转主要是利用两个辅助的结点: +* `pre` 和 `next` 分别代表当前要处理的节点的前一个结点和后一个结点; +* 然后通过改变结点的`next`域的指向来反转整个链表,通过循环处理,每次反转一个之后,`pre`指向下一个结点,也就是`head`,`head`更改为`head`的下一个结点,也就是`next`,这样直到`head`为空,返回`pre`就反转了整个单链表; + +可以看一下下面的两个步骤: + +![1554854942777](assets/1554854942777.png) + +可以用递归版本和非递归版本的写法,代码如下 : + +非递归版本 + +```java +class Solution { + public ListNode reverseList(ListNode head) { + if(head == null || head.next == null) + return head; + ListNode pre = null, next = null, cur = head; + while(cur != null){ + next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return pre; + } +} +``` + +递归版本 + +```java +class Solution { + public ListNode reverseList(ListNode head) { + if(head == null || head.next == null) + return head; + return reverse(head,null); + } + + private ListNode reverse(ListNode cur,ListNode pre){ + if(cur == null) + return pre; + ListNode next = cur.next; + cur.next = pre; + return reverse(next,cur); + } +} +``` + +### 双向链表反转 + +和单链表区别不大,不过要注意反转的时候,**`head`的`pre`域要指向`next`**,因为双向链表的任意一个结点要同时有前驱和后继,所以这里要同时给出`head`的`pre`和`next`域,可以参考下图。 + +![1554855106932](assets/1554855106932.png) + + +同样可以两种方式实现 + +```java +//双向链表反转(非递归) +static DoubleNode reverseList(DoubleNode head){ + DoubleNode pre = null; + DoubleNode next = null; + while(head != null) { + next = head.next; + head.next = pre; + head.pre = next; + pre = head; + head = next; + } + return pre; +} + +//递归 +static DoubleNode reverseList2(DoubleNode head){ + return process(head,null); +} +static DoubleNode process(DoubleNode cur,DoubleNode pre){ + if(cur == null){ + return pre; + } + DoubleNode next = cur.next; + cur.next = pre; + cur.pre = next; + return process(next,cur); +} +``` + +### 完整测试代码 + +```java +/** + * 反转单向链表和双向链表 + * 这里写的是不带头结点的 (改成带头结点的不难) (带头结点的就是第一个节点一般没有数据域(插入删除的时候比不带头结点的方便)) + */ +public class ReverseList { + + static class ListNode{ + public int val; + public ListNode next; + + public ListNode(int value) { + this.val = value; + } + } + + //翻转单向链表 (不带头结点) + static ListNode reverseList(ListNode head){ + ListNode pre = null; + ListNode next = null; + while(head != null){ + next = head.next; //记录原先的下一个结点 + head.next = pre; //指向前一个结点 ,如果是第一个,那么前一个结点就是null + //进行下一次的循环 + pre = head; + head = next; + } + return pre; //这个时候head = null head的前一个就是头结点了 + } + + + + static ListNode reverseList2(ListNode head){ + return process(head,null); + } + + static ListNode process(ListNode cur,ListNode pre){ + if(cur == null){ + return pre; + } + ListNode next = cur.next; + cur.next = pre; + return process(next,cur); + } + + + //打印单向链表 + static void printList(ListNode head){ + ListNode node = head; + while(node != null){ + System.out.print(node.val + " "); + node = node.next; + } + System.out.println(); + } + + static class DoubleNode{ + public int value; + public DoubleNode pre; + public DoubleNode next; + + public DoubleNode(int value) { + this.value = value; + } + } + + //双向链表反转 + static DoubleNode reverseList(DoubleNode head){ + DoubleNode pre = null; + DoubleNode next = null; + while(head != null) { + next = head.next; + head.next = pre; + head.pre = next; + pre = head; + head = next; + } + return pre; + } + + + static DoubleNode reverseList2(DoubleNode head){ + return process(head,null); + } + static DoubleNode process(DoubleNode cur,DoubleNode pre){ + if(cur == null){ + return pre; + } + DoubleNode next = cur.next; + cur.next = pre; + cur.pre = next; + return process(next,cur); + } + + //打印双向链表 + static void printDoubleList(DoubleNode head){ + DoubleNode node = head; + while(node != null){ + System.out.print(node.value + " "); + node = node.next; + } + System.out.println(); + } + + public static void main(String[] args) { + //测试单链表 1 2 3 + ListNode head1 = new ListNode(1); + head1.next = new ListNode(2); + head1.next.next = new ListNode(3); + printList(head1); + head1 = reverseList(head1); + printList(head1); + head1 = reverseList2(head1); + printList(head1); + + + System.out.println("============================="); + + //测试双向链表 1 2 3 4 + DoubleNode head2 = new DoubleNode(1); + head2.next = new DoubleNode(2); + head2.next.pre = head2; + + head2.next.next = new DoubleNode(3); + head2.next.next.pre= head2.next; + + head2.next.next.next = new DoubleNode(4); + head2.next.next.next.pre = head2.next.next; + + printDoubleList(head2); + head2 = reverseList(head2); + printDoubleList(head2); + head2 = reverseList2(head2); + printDoubleList(head2); + + } +} + +``` +测试结果 + +![这里写图片描述](images/206_s3.png) + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-21.Merge Two Sorted Lists.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-21.Merge Two Sorted Lists.md new file mode 100644 index 00000000..f44c3148 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-21.Merge Two Sorted Lists.md @@ -0,0 +1,71 @@ +# LeetCode - 21. Merge Two Sorted Lists(合并两个有序链表)(非递归和递归) + + - 非递归 + - 递归 +*** +#### [题目链接](https://leetcode.com/problems/merge-two-sorted-lists/description/) + +> https://leetcode.com/problems/merge-two-sorted-lists/description/ + +#### 题目 +![在这里插入图片描述](images/21_t.png) + +**这个题目[剑指Offer](https://github.com/ZXZxin/ZXNotes/blob/master/%E5%88%B7%E9%A2%98/Other/%E5%89%91%E6%8C%87Offer/%E5%89%91%E6%8C%87Offer%20-%2016%20-%20%E5%90%88%E5%B9%B6%E4%B8%A4%E4%B8%AA%E6%8E%92%E5%BA%8F%E7%9A%84%E9%93%BE%E8%A1%A8.md)中也出现过。** + +### 1、非递归解法 +非递归解法很简单,就是每次比较两个结点,把较小的加到结果链表中,并且那个指针向后移动,代码如下: + +```java +class Solution { + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + if (l1 == null) + return l2; + if (l2 == null) + return l1; + ListNode dummyHead = new ListNode(-1); + ListNode cur = dummyHead; + while (l1 != null && l2 != null) { + if (l1.val <= l2.val) { + cur.next = l1; + l1 = l1.next; + } else { + cur.next = l2; + l2 = l2.next; + } + cur = cur.next; + } + if (l1 != null) + cur.next = l1; + if (l2 != null) + cur.next = l2; + return dummyHead.next; + } +} +``` + + +### 2、递归解法 +非递归的解法就是每次比较两个链表的头部,将较小的头部单独取出来,然后剩下的两个部分继续递归,具体流程 + +看下图 : + +![在这里插入图片描述](images/21_s.png) + +代码: + +```java +class Solution { + public ListNode mergeTwoLists(ListNode l1, ListNode l2) { + if (l1 == null || l2 == null) + return l1 == null ? l2 : l1; + if (l1.val < l2.val) { + l1.next = mergeTwoLists(l1.next, l2); + return l1; + } else { + l2.next = mergeTwoLists(l1, l2.next); + return l2; + } + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-23.Merge k Sorted Lists.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-23.Merge k Sorted Lists.md new file mode 100644 index 00000000..ee49a3b3 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-23.Merge k Sorted Lists.md @@ -0,0 +1,79 @@ +# LeetCode - 23. Merge K Sorted Lists + +#### [题目链接](https://leetcode-cn.com/problems/merge-k-sorted-lists/) + +> https://leetcode-cn.com/problems/merge-k-sorted-lists/ + +#### 题目 + +![1557647859411](assets/1557647859411.png) + +### 解析 + +做这题之前可以先做一下[LeetCode - 21 Merge Two Sorted List](https://leetcode-cn.com/problems/merge-two-sorted-lists/)。 + +这题是合并K个有序链表。 + +分治解法: 每次取数组链表的中点,然后划分,递归到底的时候使用合并两个链表的算法解决即可。 + +![1557649788003](assets/1557649788003.png) + +代码: + +```java +class Solution { + + public ListNode mergeKLists(ListNode[] lists) { + if(lists == null || lists.length == 0) return null; + return mergeK(lists, 0, lists.length - 1); + } + + private ListNode mergeK(ListNode[] lists, int L, int R){ + if(L >= R) return lists[L]; + int mid = L + (R - L) / 2; + ListNode LN = mergeK(lists, L, mid); + ListNode RN = mergeK(lists, mid + 1, R); + return merge(LN, RN); + } + + private ListNode merge(ListNode l1, ListNode l2) { + if (l1 == null || l2 == null) + return l1 == null ? l2 : l1; + if (l1.val < l2.val) { + l1.next = merge(l1.next, l2); + return l1; + } else { + l2.next = merge(l1, l2.next); + return l2; + } + } +} +``` + +堆的解法: + +* 首先将所有链表头节点加入到优先队列中; +* 然后每次从队列中取出最小的节点,并构造结果链表,且如果这个最小的节点的`next`不为空,就加入优先队列中; + +代码: + +```java +class Solution { + + public ListNode mergeKLists(ListNode[] lists) { + PriorityQueue pq = new PriorityQueue<>((a, b) -> a.val - b.val); + for(ListNode node : lists) if(node != null) pq.add(node); + ListNode dummyHead = new ListNode(-1); + ListNode cur = dummyHead; + while(!pq.isEmpty()){ + ListNode top = pq.poll(); + cur.next = top; + cur = cur.next; + if(top.next != null) + pq.add(top.next); + } + return dummyHead.next; + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/List/LeetCode-234.Palindrome Linked List(\345\233\236\346\226\207\351\223\276\350\241\250).md" "b/Algorithm/LeetCode/DataStructure/List/LeetCode-234.Palindrome Linked List(\345\233\236\346\226\207\351\223\276\350\241\250).md" new file mode 100644 index 00000000..9fbd46d8 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/List/LeetCode-234.Palindrome Linked List(\345\233\236\346\226\207\351\223\276\350\241\250).md" @@ -0,0 +1,155 @@ +## LeetCode - 234. Palindrome Linked List(回文链表) + - 方法一 : 使用一个栈(O(n)的空间复杂度) + - 方法二 : 使用快慢指针,以及O(n/2)的空间复杂度 + - 方法三 : 使用快慢指针并反转链表(不需要额外的空间复杂度) +*** +#### [题目链接](https://leetcode.com/problems/palindrome-linked-list/description/) + +> https://leetcode.com/problems/palindrome-linked-list/description/ + +#### 题目 +![在这里插入图片描述](images/234_t.png) + +### 1、使用一个栈(O(n)的空间复杂度) + + 这个方法很简单,先遍历一遍链表,把整个链表都压入栈中,然后再遍历一遍,每次从栈顶拿出一个元素进行比较,如果所有元素都相同,则返回`true`,否则只要有一个不同就返回`false`。 + +```java +class Solution { + public boolean isPalindrome(ListNode head) { + if(head == null || head.next == null) + return true; + + Stackstack = new Stack<>(); + ListNode cur = head; + while(cur != null){ + stack.push(cur); + cur = cur.next; + } + + cur = head; + while(!stack.isEmpty()){ + if(stack.pop().val != cur.val){ + return false; + } + cur = cur.next; + } + return true; + } +} +``` + +*** +### 2、使用快慢指针,以及O(n/2)的空间复杂度 + +这个就是在第一种方法的基础上进行简单的改进: +* 首先定义两个指针`fast`和`slow`,每次让`fast`指针走两步,`slow`指针走一步; +* 然后当`fast`指针走完的时候,`slow`指针刚好来到中点,此时把链表的后半部分压入栈中,然后拿栈中的元素依次和链表的前半部分比较,就可以得到结果。 +* 在代码实现 的过程中,要注意链表长度奇偶的不同,奇数的时候,`slow`指针指到中间位置的下一个位置,偶数的时候也要`slow`指针知道中间位置的下一个位置,一开始的时候`slow = head.next`,还有就是要注意`fast`移动的过程中,要做两个判断,防止空指针异常,具体奇偶的过程看下图。 + +代码: + +![在这里插入图片描述](images/234_s.png)![在这里插入图片描述](images/234_s2.png) + +代码实现: + +```java +class Solution { + public boolean isPalindrome(ListNode head) { + if(head == null || head.next == null) + return true; + ListNode fast = head; + ListNode slow = head.next; + while(fast.next != null && fast.next.next != null){ + fast = fast.next.next; + slow = slow.next; + } + + Stackstack = new Stack<>(); + //把链表的后半部分推入到栈中 + while(slow != null){ + stack.push(slow); + slow = slow.next; + } + + //check + slow = head; + while(!stack.isEmpty()){ + if(stack.pop().val != slow.val) + return false; + slow = slow.next; + } + return true; + } +} +``` + +*** +### 3、使用快慢指针并反转链表(不需要额外的空间复杂度) + +方法三的思想也要使用快指针和慢指针: +* 快指针一次走两步,慢指针一次走一步,当快指针走完的时候,慢指针来到中间的位置(或者中间的前一个位置(偶数的情况(和上面的方法是不同的(上面的是慢指针在中间的后一个位置)))) (因为一开始`fast`和`slow`的起点和上面的不同); +* 然后,要用到链表的反转,此时,我们将后半部分链表反转,然后使用两个指针分别从头部和尾部位置开始比较,直到其中一个为空。 当然,不管返回`true`还是`false`,我们最后都将反转的链表的结构反转回来; + * 在代码实现的过程中,也要注意奇数和偶数的情况,奇数的时候,`slow`指针正好来到中间结点,偶数的时候来到中间结点的前一个节点。 + + 具体奇偶的过程可以看下图 + +![这里写图片描述](images/234_s3.png) + +![这里写图片描述](images/234_s4.png) + +代码实现 + +```java +class Solution { + public boolean isPalindrome(ListNode head) { + if(head == null || head.next == null) + return true; + ListNode fast = head; + ListNode slow = head; //注意这里起点和之前的不同 + while(fast.next != null && fast.next.next != null){ + fast = fast.next.next; + slow = slow.next; + } + + //开始反转 + ListNode next = null; + ListNode pre = null; + ListNode cur = slow; + while(cur != null){ + next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + + //记录最后一个结点 , 反转之后pre是最后一个结点, + ListNode end = pre; + + //开始判断 + ListNode R = end; //R从后一个结点开始比较 + ListNode L = head; + boolean res = true; + while( L != null && R != null){ + if(L.val != R.val){ + res = false; + break; + } + L = L.next; + R = R.next; + } + + //最后还原链表 + cur = end; + pre = null; + while(cur != null){ + next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-24.Swap Nodes in Pairs.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-24.Swap Nodes in Pairs.md new file mode 100644 index 00000000..f72558a2 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-24.Swap Nodes in Pairs.md @@ -0,0 +1,63 @@ +# LeetCode - 24. Swap Nodes in Pairs + +#### [题目链接](https://leetcode.com/problems/swap-nodes-in-pairs/) + +https://leetcode.com/problems/swap-nodes-in-pairs/ + +#### 题目 + +![1557660730345](assets/1557660730345.png) + +### 解析 + +可以有两种写法,递归和非递归。 + +先看非递归。 + +看下面的链表中交换`1、4`两个节点需要做出的指针的变化,红色是新的指针的指向,虚线是丢弃的指向。 + +![1557661325143](assets/1557661325143.png) + +代码: + +```java +class Solution { + + public ListNode swapPairs(ListNode head) { + ListNode dummyHead = new ListNode(-1); + dummyHead.next = head; + ListNode pre = dummyHead, cur = head; + while (cur != null && cur.next != null) { + swap(pre, cur); + pre = cur; // 下一轮 + cur = cur.next; + } + return dummyHead.next; + } + + private void swap(ListNode pre, ListNode cur) { + ListNode next = cur.next; + cur.next = next.next; + next.next = cur; + pre.next = next; + } +} +``` + +非递归的写法也比较简单。代码可以更加的简单: + +主要是从宏观的思想去想,即使很简单。 + +```java +class Solution { + + public ListNode swapPairs(ListNode head) { + if (head == null || head.next == null) return head; + ListNode next = head.next; //先保存next + head.next = swapPairs(next.next); // 先交换后面的并且让head.next链接上交换后的头结点 + next.next = head; // 然后next的next指向head + return next; // 最后返回的是next(作为新的头结点) + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-25.Reverse Nodes in K-Group.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-25.Reverse Nodes in K-Group.md new file mode 100644 index 00000000..75f6ac40 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-25.Reverse Nodes in K-Group.md @@ -0,0 +1,100 @@ +# LeetCode - 25. Reverse Nodes in K-Group + +#### [题目链接](https://leetcode.com/problems/reverse-nodes-in-k-group/) + +> https://leetcode.com/problems/reverse-nodes-in-k-group/ + +#### 题目 + +![1557715027129](assets/1557715027129.png) + +### 解析 + +两种做法。一种用栈(`O(K)`空间),另一种不需要额外空间。 + +方法一: 使用`O(K)`的空间: + +* 每次推入`k`个节点到栈中; +* 然后利用栈来翻转这`k`个节点; +* 注意如果不足`k`个就直接return; +* 然后还要注意段与段之间的衔接; + +图: + +![1557742702506](assets/1557742702506.png) + +代码: + +```java +class Solution { + + // method 1 : use stack + public ListNode reverseKGroup(ListNode head, int k) { + if(head == null) return null; + Stack stack = new Stack<>(); + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode cur = dummy, next = dummy.next; + while(next != null){ + for(int i = 0; i < k && next != null; i++) { + stack.push(next); + next = next.next; + } + // 最后不足k个了 + if(stack.size() != k) return dummy.next; + while (!stack.isEmpty()) { //翻转 + cur.next = stack.pop(); + cur = cur.next; + } + cur.next = next; //衔接上下一段 + } + return dummy.next; + } +} +``` + +方法二: 不用额外空间: + +* 也是每次翻转`k`个,我们规定每次翻转`(pre ~ last)`之间的元素(不包括`pre、last`); +* 每次都是将从第二个开始的部分放到前面去,例如`1 ~ 2 ~ 3`,先将`2`放到前面,变成`2 ~ 1 ~ 3`,然后将`3`放到前面,变成`3 ~ 2 ~ 1`。 +* 每次返回每一段一开始的那个元素作为`tail`,并作为下一段的`pre`。 + +步骤(从上往下(演示翻转`1 ~ 3`部分)): + +![1557742255148](assets/1557742255148.png) + +代码: + +```java +class Solution { + + // method 2 : no extra space + public ListNode reverseKGroup(ListNode head, int k) { + if(head == null) return null; + ListNode dummy = new ListNode(-1); + dummy.next = head; + ListNode pre = dummy; + while(pre != null) pre = reverse(pre, k); + return dummy.next; + } + + private ListNode reverse(ListNode pre, int k){ + ListNode last = pre; + for(int i = 0; i <= k; i++){ + last = last.next; + if(last == null && i != k) return null; // 不足k个 + } + ListNode tail = pre.next; //最后要返回的值,作为下一次k个翻转的pre + ListNode cur = pre.next.next; + while(cur != last){ // 当 cur == last 的时候说明k个已经完全翻转了 + ListNode next = cur.next; + cur.next = pre.next; + pre.next = cur; + tail.next = next; + cur = next; + } + return tail; + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/List/LeetCode-460.LFU Cache(LFU\347\274\223\345\255\230\347\256\227\346\263\225\357\274\214\344\272\214\347\273\264\351\223\276\350\241\250\350\247\243\345\206\263).md" "b/Algorithm/LeetCode/DataStructure/List/LeetCode-460.LFU Cache(LFU\347\274\223\345\255\230\347\256\227\346\263\225\357\274\214\344\272\214\347\273\264\351\223\276\350\241\250\350\247\243\345\206\263).md" new file mode 100644 index 00000000..7346c0df --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/List/LeetCode-460.LFU Cache(LFU\347\274\223\345\255\230\347\256\227\346\263\225\357\274\214\344\272\214\347\273\264\351\223\276\350\241\250\350\247\243\345\206\263).md" @@ -0,0 +1,257 @@ +## LeetCode - 460. LFU Cache(LFU缓存算法,二维链表解决) +#### [题目链接](https://leetcode.com/problems/lfu-cache/description/) + +> https://leetcode.com/problems/lfu-cache/description/ + +#### 题目 +![在这里插入图片描述](images/460_t.png) + +### 解析 +设计使用两个链表: + + - **一个大链表表示键值对出现次数的链表**; + - **小链表表示这条链表的次数都一样,但是由于我们的添加可以决定添加的顺序,可以达到在次数相同的情况下,先移除最近最少使用的键值对**; + + +下面的代码中的方法: + + - **`addNodeFromHead()`**,大链表的结点(本身也是一个小链表)在头部添加一个键值对); + - **`deleteNode()`**,大链表的结点(本身也是一个小链表) 中删除一个结点; + - `LFU`结构中的`move()`方法,表示要当我操作了一个键值对,对应的次数增加,就需要调整结点的位置,`move()`方法就是从原来的小链表(大链表的结点)移动到一个新的小链表(也就是次数比原来次数`+1`)的新小链表; + - `modifyHeadList()`表示的当我删除一个结点(也就是在旧的小链表中删除一个之后)有可能这个小链表本来只有这一个元素,所以要销毁这个小链表,为什么要写成`boolean`型的,因为等下在下面的代码中,在`move`的时候,要重新连接一个大链表的结点要知道`preList`,所以需要判断; + +![1554955859170](assets/1554955859170.png) + +两个链表示意图: + +![这里写图片描述](images/460_s.png) + +代码有点长,都写了注释:  +```java +class LFUCache { + + //小链表(挂在下面的) + private class Node { + public Integer key; //map中push的key + public Integer value; //map中对应的value + public Integer times; // 操作的次数 + + public Node up; //小链表的上一个 + public Node down; //小链表的下一个 + + public Node(Integer key, Integer value, Integer times) { + this.key = key; + this.value = value; + this.times = times; + } + } + + //大的链表的结点结构 (每一个结点都是一个小链表) + private class NodeList { + public Node head; //大链表的头部指针 + public Node tail; //大链表的尾部指针 + + public NodeList pre; //大链表的前一个结点 + public NodeList next; //大链表的下一个结点 + + public NodeList(Node node) { + head = node; + tail = node; + } + + public boolean isEmpty() {//返回这个小链表(小链表本身又是大链表的结点)是不是空的 + return head == null; + } + + //小链表的头部添加结点 + public void addNodeFromHead(Node newHead) { + newHead.down = head; + head.up = newHead; + head = newHead; + } + + //删除小链表中的任意一个结点 + public void deleteNode(Node node) { + if (head == tail) { //只有一个结点 + head = null; + tail = null; + } else { + if (head == node) { //删除的是小链表的头部 + head = head.down; //头结点变成下一个 + head.up = null; //头结点的上一个 置空 + } else if (tail == node) { //删除的是小链表的尾部 + tail = tail.up; + tail.down = null; + } else { //删除的是链表的中间 + node.up.down = node.down; + node.down.up = node.up; + } + } + //完全断链 + node.up = null; + node.down = null; + } + } + + + private int capacity; //最大容量 + private int size; //当前容量 + // key 对应的node node是在小链表上面的 + private HashMap kNMap; + //Node对应的NodeList的头是哪个 就是任何一个小结点都能查到在大链表的哪个小链表上 + private HashMap heads; //一个链表对应的大链表的结点是哪个 + public NodeList headList; //整个大链表的头部 动态的头部 不一定就是1作为头 + + + public LFUCache(int capacity) { + this.capacity = capacity; + size = 0; + kNMap = new HashMap<>(); + heads = new HashMap<>(); + headList = null; + } + + + public void put(int key, int value) { + if (capacity == 0) return; //注意特判 + if (kNMap.containsKey(key)) { //如果已经存在 就要更新值 + Node node = kNMap.get(key); + node.value = value; + node.times++; + NodeList curNodeList = heads.get(node); //找到属于哪一个大链表 + /** + * move方法 + * 就是在一个大链表中,和自己的上下级解耦,然后放到下一个词频链表中 + * 比如说现在是5 times链上的,则从5times链中拿出来,如果6存在,放到6times链的头部(头插法) + * 如果6不存在,建出6times的链表 + */ + move(node, curNodeList); + } else { //kNMap中不存在,是新插入的 没包含 + //要先判断容量够不够 + if (size == capacity) { //已经满了 要删掉一个结点 + //要删掉的就是作为大链表的 头部的尾结点 (次数最少的 用了最久的) + Node deNode = headList.tail; + headList.deleteNode(deNode); + /** + * 如果我删掉了 这个deNode 有可能我整个大链表的headList都没有东西了,整个大Node要删掉,要更新大headList + * 又因为加入了新节点,所以又要更新headList + * 先删再加 + */ + modifyHeadList(headList); + kNMap.remove(deNode.key); //不要忘记在kNMap中删掉 + heads.remove(deNode); + size--; + + } + + Node node = new Node(key, value, 1); //新建 次数为1 + + if (headList == null) { //整个大链表都不存在 + headList = new NodeList(node); //建出大链表的头部 + } else { // 已经有了大链表的头部 + /** + * 如果有 次数为1的头 就直接添加到大头的头部 + * 如果没有次数为1大头 就建一个大头 然后添加到大头的尾部 + */ + if (headList.head.times.equals(1)) { //大链表的头的头 的次数是 1 也就是说 有为times的小链表 + headList.addNodeFromHead(node); //加到这里 + } else { //没有times为 1 的小链表 要自己建一个 + NodeList newList = new NodeList(node); //建出一个times为1的小链表 + newList.next = headList; + headList.pre = newList; + headList = newList; //更新大头 + } + } + + //最后再添加这条记录 + kNMap.put(key, node); + heads.put(node, headList); //这个结点所在的头 肯定是在大头的头部 也就是times一定是1 因为已经判断了不是1(已经存在的情况) + size++; + } + } + + /** + * 解耦原来的链表 并放入到一个新的链表中 + * @param node 这个node + * @param oldNodeList node 的原来属于的list + */ + private void move(Node node, NodeList oldNodeList) { + oldNodeList.deleteNode(node); //老链表你自己先删掉 + /** + * 因为你的老链表删掉了一个结点 是不是有可能 连老链表都没了 + *要判断老链表 是否还存在 + * modifyHeadList(oldNodeList) 返回true就是说老链表都被删掉了 所以要去找老链表的前一个链表 + * 返回false就是说老链表还存在 preList就是老链表 + */ + NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.pre : oldNodeList; + NodeList nextList = oldNodeList.next; //要去的地方  新家 + + if (nextList == null) { //你的oldNodeList是大链表的最后一个 + NodeList newList = new NodeList(node); //建一个  放在最后 + if (preList != null) { + preList.next = newList; + } + newList.pre = preList; + + if (headList == null) { //本来就是空的就要给headList一个交代 + headList = newList; + } + heads.put(node, newList); //换新家了 + } else { //不是最后一个不是times最高的 + + if (nextList.head.times.equals(node.times)) { //下一个存在  就直接挂在下一个的头部 + nextList.addNodeFromHead(node); + heads.put(node, nextList); + } else { //下一个不是 times + 1的 要自己新建一个node  然后左右两边重新连接好 + NodeList newList = new NodeList(node); + + if (preList != null) preList.next = newList; + newList.pre = preList; + + newList.next = nextList; + nextList.pre = newList; + + if (headList == nextList) { //这个是也要更换头 + headList = newList; + } + heads.put(node, newList); + } + } + } + + /** + * 这个方法的调用时机是 把一个node从一个nodelist中删掉 ,然后判断是不是要不这个nodelist给删掉 + * 就是在delete之后, 要不要把整个小链表删掉 + * @param nodeList + */ + private boolean modifyHeadList(NodeList nodeList) { + if (nodeList.isEmpty()) { //为空了才要删掉整个大链表中的这个结点 + if (headList == nodeList) { //要删的这个 是整个大链表的头部 + headList = headList.next; //新的头部是老头部的下一个 + if (headList != null) { //新链表不为空 + headList.pre = null; + } + } else { //要删的不是头 + nodeList.pre.next = nodeList.next; + if (nodeList.next != null) { + nodeList.next.pre = nodeList.pre; + } + } + return true; //也就是 这个是要整个都要删掉的 + } + return false; //不空的话(也就是不只一个)就不要删 留着 + } + + public Integer get(int key) { + if (capacity == 0) return -1; //特判一下 + if (!kNMap.containsKey(key)) { + return -1; + } + Node node = kNMap.get(key); //获取结点所在的原来的链表 + node.times++; + NodeList curNodeList = heads.get(node); //找到这个结点属于的小链表 + move(node, curNodeList); + return node.value; //返回对应的node的值 + } +} +``` diff --git a/Algorithm/LeetCode/DataStructure/List/LeetCode-725.Split Linked List in Parts.md b/Algorithm/LeetCode/DataStructure/List/LeetCode-725.Split Linked List in Parts.md new file mode 100644 index 00000000..e17680c7 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/List/LeetCode-725.Split Linked List in Parts.md @@ -0,0 +1,58 @@ +# LeetCode - 725. Split Linked List in Parts + +#### [题目链接](https://leetcode.com/problems/split-linked-list-in-parts/) + +> https://leetcode.com/problems/split-linked-list-in-parts/ + +#### 题目 + +![1555044781926](assets/1555044781926.png) + +## 解析 + +这种题目就是考察你的代码实现能力,强力建议不要看别人的代码,一定要自己手撸实现。 + +代码: + +```java + +class Solution { + public ListNode[] splitListToParts(ListNode root, int k) { + ListNode[] res = new ListNode[k]; + ListNode cur = root; + int cnt = 0; + while (cur != null) { + cur = cur.next; + cnt++; + } + int split = cnt / k; + int remain = cnt % k; + int r = 0; + cur = root; + for (int c = 0; c < k; c++) { + ListNode head = cur; + ListNode tmp = head; + if (tmp == null) + break; + cur = cur.next; + for (int i = 0; i < split - 1; i++) { + tmp.next = cur; + tmp = tmp.next; + if (cur != null) cur = cur.next; + } + if (split > 0 && remain > 0) { + remain--; + tmp.next = cur; + tmp = tmp.next; + if (cur != null) cur = cur.next; + } + tmp.next = null; + res[r] = new ListNode(-1); + res[r++] = head; + } + return res; + } + +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554790494580.png b/Algorithm/LeetCode/DataStructure/List/assets/1554790494580.png new file mode 100644 index 00000000..e935b015 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554790494580.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554791775847.png b/Algorithm/LeetCode/DataStructure/List/assets/1554791775847.png new file mode 100644 index 00000000..df2617b9 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554791775847.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554794205224.png b/Algorithm/LeetCode/DataStructure/List/assets/1554794205224.png new file mode 100644 index 00000000..e65918ef Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554794205224.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554805688445.png b/Algorithm/LeetCode/DataStructure/List/assets/1554805688445.png new file mode 100644 index 00000000..f4dec38b Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554805688445.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554806021146.png b/Algorithm/LeetCode/DataStructure/List/assets/1554806021146.png new file mode 100644 index 00000000..8328054a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554806021146.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554854942777.png b/Algorithm/LeetCode/DataStructure/List/assets/1554854942777.png new file mode 100644 index 00000000..943d3f20 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554854942777.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554855106932.png b/Algorithm/LeetCode/DataStructure/List/assets/1554855106932.png new file mode 100644 index 00000000..ee0c5b27 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554855106932.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1554955859170.png b/Algorithm/LeetCode/DataStructure/List/assets/1554955859170.png new file mode 100644 index 00000000..8d502968 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1554955859170.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1555044781926.png b/Algorithm/LeetCode/DataStructure/List/assets/1555044781926.png new file mode 100644 index 00000000..8d3a77ec Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1555044781926.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1555064547654.png b/Algorithm/LeetCode/DataStructure/List/assets/1555064547654.png new file mode 100644 index 00000000..aeeffd0a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1555064547654.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1555065374461.png b/Algorithm/LeetCode/DataStructure/List/assets/1555065374461.png new file mode 100644 index 00000000..8b48756f Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1555065374461.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557647859411.png b/Algorithm/LeetCode/DataStructure/List/assets/1557647859411.png new file mode 100644 index 00000000..040d5ade Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557647859411.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557649788003.png b/Algorithm/LeetCode/DataStructure/List/assets/1557649788003.png new file mode 100644 index 00000000..5f8f8e46 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557649788003.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557660730345.png b/Algorithm/LeetCode/DataStructure/List/assets/1557660730345.png new file mode 100644 index 00000000..5a00d4c6 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557660730345.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557661325143.png b/Algorithm/LeetCode/DataStructure/List/assets/1557661325143.png new file mode 100644 index 00000000..6e2a23c3 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557661325143.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557715027129.png b/Algorithm/LeetCode/DataStructure/List/assets/1557715027129.png new file mode 100644 index 00000000..9f7671a8 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557715027129.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557742255148.png b/Algorithm/LeetCode/DataStructure/List/assets/1557742255148.png new file mode 100644 index 00000000..d0ef626b Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557742255148.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/assets/1557742702506.png b/Algorithm/LeetCode/DataStructure/List/assets/1557742702506.png new file mode 100644 index 00000000..b65b053e Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/assets/1557742702506.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/138_s.png" b/Algorithm/LeetCode/DataStructure/List/images/138_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/138_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/138_s.png diff --git a/Algorithm/LeetCode/DataStructure/List/images/138_s2.png b/Algorithm/LeetCode/DataStructure/List/images/138_s2.png new file mode 100644 index 00000000..6b4187a6 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/138_s2.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/images/141_s.png b/Algorithm/LeetCode/DataStructure/List/images/141_s.png new file mode 100644 index 00000000..b88d6e12 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/141_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/141_t.png" b/Algorithm/LeetCode/DataStructure/List/images/141_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/141_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/141_t.png diff --git a/Algorithm/LeetCode/DataStructure/List/images/142_s.png b/Algorithm/LeetCode/DataStructure/List/images/142_s.png new file mode 100644 index 00000000..a448c034 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/142_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/142_t.png" b/Algorithm/LeetCode/DataStructure/List/images/142_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/142_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/142_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/146_s.png" b/Algorithm/LeetCode/DataStructure/List/images/146_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/146_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/146_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/146_t.png" b/Algorithm/LeetCode/DataStructure/List/images/146_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/146_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/146_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_s.png" b/Algorithm/LeetCode/DataStructure/List/images/160_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/160_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t.png" b/Algorithm/LeetCode/DataStructure/List/images/160_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/160_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t2.png" b/Algorithm/LeetCode/DataStructure/List/images/160_t2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t2.png" rename to Algorithm/LeetCode/DataStructure/List/images/160_t2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t3.png" b/Algorithm/LeetCode/DataStructure/List/images/160_t3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/160_t3.png" rename to Algorithm/LeetCode/DataStructure/List/images/160_t3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/203_s.png" b/Algorithm/LeetCode/DataStructure/List/images/203_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/203_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/203_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/203_t.png" b/Algorithm/LeetCode/DataStructure/List/images/203_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/203_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/203_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s.png" b/Algorithm/LeetCode/DataStructure/List/images/206_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/206_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s2.png" b/Algorithm/LeetCode/DataStructure/List/images/206_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s2.png" rename to Algorithm/LeetCode/DataStructure/List/images/206_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s3.png" b/Algorithm/LeetCode/DataStructure/List/images/206_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_s3.png" rename to Algorithm/LeetCode/DataStructure/List/images/206_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_t.png" b/Algorithm/LeetCode/DataStructure/List/images/206_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/206_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/206_t.png diff --git a/Algorithm/LeetCode/DataStructure/List/images/21_s.png b/Algorithm/LeetCode/DataStructure/List/images/21_s.png new file mode 100644 index 00000000..6cebe79e Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/21_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/21_t.png" b/Algorithm/LeetCode/DataStructure/List/images/21_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/21_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/21_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_s.png" b/Algorithm/LeetCode/DataStructure/List/images/234_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_s.png" rename to Algorithm/LeetCode/DataStructure/List/images/234_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_s2.png" b/Algorithm/LeetCode/DataStructure/List/images/234_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_s2.png" rename to Algorithm/LeetCode/DataStructure/List/images/234_s2.png diff --git a/Algorithm/LeetCode/DataStructure/List/images/234_s3.png b/Algorithm/LeetCode/DataStructure/List/images/234_s3.png new file mode 100644 index 00000000..6702818c Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/234_s3.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/images/234_s4.png b/Algorithm/LeetCode/DataStructure/List/images/234_s4.png new file mode 100644 index 00000000..eb02037a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/234_s4.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_t.png" b/Algorithm/LeetCode/DataStructure/List/images/234_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/234_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/234_t.png diff --git a/Algorithm/LeetCode/DataStructure/List/images/2_s.png b/Algorithm/LeetCode/DataStructure/List/images/2_s.png new file mode 100644 index 00000000..5d040eb9 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/2_s.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/images/2_t.png b/Algorithm/LeetCode/DataStructure/List/images/2_t.png new file mode 100644 index 00000000..06f86514 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/2_t.png differ diff --git a/Algorithm/LeetCode/DataStructure/List/images/460_s.png b/Algorithm/LeetCode/DataStructure/List/images/460_s.png new file mode 100644 index 00000000..4e095c93 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/List/images/460_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/List/images/460_t.png" b/Algorithm/LeetCode/DataStructure/List/images/460_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/List/images/460_t.png" rename to Algorithm/LeetCode/DataStructure/List/images/460_t.png diff --git a/Algorithm/LeetCode/DataStructure/Map/LeetCode-1.Two Sum.md b/Algorithm/LeetCode/DataStructure/Map/LeetCode-1.Two Sum.md new file mode 100644 index 00000000..2295ea9b --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Map/LeetCode-1.Two Sum.md @@ -0,0 +1,107 @@ +# LeetCode - 1. Two Sum(Hash) + +#### [题目链接](https://leetcode.com/problems/two-sum/) + +> https://leetcode.com/problems/two-sum/ + +#### 题目 +![在这里插入图片描述](images/1_t.png) + +暴力方法就不说了。这题用Hash(`O(N)`)或者双指针`O(N*logN)`。 + +## 一、方法一 + +时间复杂度`2 * O(N)`。 + +* 先遍历一边数组,使用`HashMap`存储每个数的**下标**; +* 然后再遍历一遍数组,寻找`target - nums[i]`在`map`中存不存在,如果存在且**对应的值不等于当前的下标`i`**(即目标元素不能是`nums[i]`本身),就说明存在解,返回两个下标即可; + +图: + +

+ +```java +class Solution { + public int[] twoSum(int[] nums, int target) { + HashMap map = new HashMap<>(); + for(int i = 0; i < nums.length; i++) map.put(nums[i], i); + for(int i = 0; i < nums.length; i++){ + int val = target - nums[i]; + if(map.containsKey(val) && map.get(val) != i) + return new int[]{i, map.get(val)}; + } + throw new RuntimeException("No such solution!"); + } +} +``` +*** +## 二、方法二 + +时间复杂度`O(N)`。 + +在检查完当前元素`num[i]`之后(检查`target - nums[i]`)。 + +再顺便将`{nums[i], i}`放入哈希表,因为当前放入的元素**后面还会回过头来检查这个元素是否是目标元素**。 + +图: + +

+ +```java +class Solution { + public int[] twoSum(int[] nums, int target) { + HashMap map = new HashMap<>(); + for(int i = 0; i < nums.length; i++){ + int val = target - nums[i]; + if(map.containsKey(val)) // 肯定不会map.get(val) == i + return new int[]{i, map.get(val)}; + map.put(nums[i], i); + } + throw new RuntimeException("No such solution!"); + } +} +``` + + +> 类似的[进阶问题](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/%E6%9D%82%E9%A2%98/%E5%AD%90%E6%95%B0%E7%BB%84%E7%B4%AF%E5%8A%A0%E5%92%8C%E4%B8%BAaim(%E5%B0%8F%E4%BA%8E%E7%AD%89%E4%BA%8Eaim)%E7%9A%84%E4%B8%89%E4%B8%AA%E9%97%AE%E9%A2%98.md)。 + +### 三、排序双指针 + +这个方法时间复杂度是排序的时间,即`N*logN`。 + +思想就是,使用两个指针,从两边往中间靠拢,这个原理是基于**数组已经排序**了。因为如果当前`nums[L] + nums[R] < target`,那我只能增加`nums[L]`才有可能去达到`target`,所以`L++`,类似,如果`nums[L] + nums[R] > target`,我们就`R--`。 + +由于我们需要返回的是两个元素的下标,所以我们还需要存储原来数据的下标的位置。我们可以构造一个结构体,然后对值`val`排序,最后找到的时候,返回对应的原来的下标即可。 + +```java +class Solution { + + class Pair { + int id; + int val; + + public Pair(int id, int val) { + this.id = id; + this.val = val; + } + } + + public int[] twoSum(int[] nums, int target) { + Pair[] pairs = new Pair[nums.length]; + for (int i = 0; i < nums.length; i++) + pairs[i] = new Pair(i, nums[i]); + Arrays.sort(pairs, (o1, o2) -> o1.val - o2.val);//按照值排序 + int L = 0, R = nums.length - 1; + while (L < R) { + if (pairs[L].val + pairs[R].val == target) + return new int[]{pairs[L].id, pairs[R].id}; + else if (pairs[L].val + pairs[R].val < target) + L++; + else + R--; + } + throw new RuntimeException("No such solution!"); + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Map/LeetCode-128.Longest Consecutive Sequence (\345\223\210\345\270\214\350\241\250).md" "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-128.Longest Consecutive Sequence (\345\223\210\345\270\214\350\241\250).md" new file mode 100644 index 00000000..4941563a --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-128.Longest Consecutive Sequence (\345\223\210\345\270\214\350\241\250).md" @@ -0,0 +1,235 @@ +# LeetCode - 128. Longest Consecutive Sequence (哈希表) + +#### [题目链接](https://leetcode.com/problems/longest-consecutive-sequence/) + +> https://leetcode.com/problems/longest-consecutive-sequence/ + +#### 题目 +![在这里插入图片描述](images/128_t.png) + + +## 解析 +第一种方法: + + +* 使用一个`HashSet`来存储对应的值,**一开始先将所有的值都加入`set`**; + +* 遍历数组的每一个元素,每次去检查当前元素`num`的前一个元素`num - 1`是不是在`set`中,如果是,说明`num`不是最长长度的起点,跳过; + +* 如果不是,则在`set`集合中不断的每次`+1`,(也就是不断检查`num + 1、num + 2、num + 3、num + 4.....`在不在`set`中),并记录查到的元素个数(也就是长度),然后更新结果(记录最大长度)`res`即可; + +* 时间复杂度: 虽然有两重循环,但是每个元素最多访问两次,所以时间复杂度为`O(N)`; + +看两个例子: + +![在这里插入图片描述](images/128_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + public int longestConsecutive(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + HashSetset = new HashSet<>(); + for(int num : nums) + set.add(num); + int res = 0; + for(int num : nums){ + if(!set.contains(num - 1)){ + int len = 1, temp = num + 1; + while(set.contains(temp++)) + len++; + res = Math.max(res, len); + } + } + return res; + } + + public static void main(String[] args){ + PrintStream out = System.out; + int[] nums = {1, 2, 3, 6, 4, 5, 7}; + out.println(new Solution(). + longestConsecutive(nums) + ); + } +} +``` + +第二种解法: + +* 用一个`HashMap`记录` = ` <数组的值,这个值如果作为边界(左/右)时的最大长度>。整个过程和动态规划有点类似; + +* 遍历数组的每一个元素,先得到`num - 1`和`num + 1`在`map`中对应的`value`,如果两个`value`都为空,说明此时`num`两边都没有相邻的元素,所以`put(num, 1)`,表示`num`作为**左/右**边界的最大长度为`1`; +* 如果`map.get(num - 1) == null && map.get(num + 1) != null`,说明此时`num`可以作为右边界,而此时不但要更新`num`的`value`,也要更新`nums[num - map.get(num - 1)]`的`value`,这个`value`就是`map.get(num - 1) + 1`,所以说这个过程有点类似动态规划; +* 同理,如果`map.get(num - 1) != null && map.get(num + 1) == null`,说明此时`num`可以作为**左边界**,而此时不但要更新`num`的`value`,也要更新`nums[num + map.get(num + 1)]`的`value`,这个`value`就是`map.get(num + 1) + 1`。 + +* 如果`map.get(num - 1) != null && map.get(num + 1) != null`,则此时同时可以作为**左右边界**,说明它是连接两边的桥梁,所以要同时更新`num、nums[num - map.get(num - 1)]、nums[num + map.get(num + 1)] `的值为`map.get(num - 1) + map.get(num + 1) + 1`; + +看一个例子: + +![在这里插入图片描述](images/128_s2.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + public int longestConsecutive(int[] nums) { + if(nums == null || nums.length == 0) + return 0; + HashMap map = new HashMap<>(); + for(int num : nums){ + if(map.containsKey(num)) // reduplicative numbers + continue; + Integer L = map.get(num - 1); + Integer R = map.get(num + 1); + if(L == null && R == null) + map.put(num, 1); + else if(L == null && R != null){ + map.put(num, R + 1); + map.put(num + R, R + 1); + }else if(L != null && R == null){ + map.put(num, L + 1); + map.put(num - L, L + 1); + }else { + map.put(num, L + R + 1); + map.put(num - L, L + R + 1); + map.put(num + R, L + R + 1); + } + } + int res = 0; + for(Map.Entryentry : map.entrySet()) + res = Math.max(res, entry.getValue()); + return res; + } + + public static void main(String[] args){ + PrintStream out = System.out; + + int[] nums = {1, 2, 3, 6, 4, 5, 7}; + + out.println(new Solution(). + longestConsecutive(nums) + ); + } +} +``` + +另外还有一种并查集的代码,和上面有点类似,就是将有`num- 1`和`num + 1`的下标都加入到同一个集合中,可以基于`size`的并查集,最后取一个最大的集合即可。 + +```java +import java.util.*; + +class Solution { + + public int longestConsecutive(int[] nums) { + if (nums == null || nums.length == 0) return 0; + HashMap map = new HashMap<>(); + int count = 0; + for (int num : nums) if (!map.containsKey(num)) map.put(num, count++); + UnionFind uf = new UnionFind(count); + int res = 1; + for (int num : nums) { + if (map.containsKey(num - 1)) uf.union(map.get(num), map.get(num - 1)); + if (map.containsKey(num + 1)) uf.union(map.get(num), map.get(num + 1)); + res = Math.max(res, uf.sz[uf.findRoot(map.get(num))]); + } + return res; + } + + class UnionFind { + int[] parent; + int[] sz; + + public UnionFind(int n) { + parent = new int[n]; + sz = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + sz[i] = 1; + } + } + + public int findRoot(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public void union(int a, int b) { + int aR = findRoot(a); + int bR = findRoot(b); + if (aR == bR) return; + if (sz[aR] > sz[bR]) { + parent[bR] = aR; + sz[aR] += sz[bR]; + } else { + parent[aR] = bR; + sz[bR] += sz[aR]; + } + } + } +} +``` + +相关`C++`代码: + + +```java +class Solution { +public: + int longestConsecutive(vector& nums) { + unordered_set set(nums.begin(), nums.end()); + int res = 0; + for(int num : nums){ + if(!set.count(num - 1)){ + int len = 1, temp = num + 1; + while(set.count(temp++)) + len++; + res = max(res, len); + } + } + return res; + } +}; +``` + +```cpp +class Solution { +public: + int longestConsecutive(vector& nums) { + unordered_mapmp; + for(int num : nums){ + if(mp.count(num)) + continue; + auto it_l = mp.find(num - 1); + auto it_r = mp.find(num + 1); + int l = it_l != mp.end() ? it_l->second : 0; + int r = it_r != mp.end() ? it_r->second : 0; + if(l > 0 && r > 0) + mp[num] = mp[num - l] = mp[num + r] = l + r + 1; + else if(l > 0) + mp[num] = mp[num - l] = l + 1; + else if(r > 0) + mp[num] = mp[num + r] = r + 1; + else + mp[num] = 1; + } + int res = 0; + for(const auto & kv : mp) + res = max(res, kv.second); + return res; + } +}; +``` + diff --git a/Algorithm/LeetCode/DataStructure/Map/LeetCode-242.Valid Anagram.md b/Algorithm/LeetCode/DataStructure/Map/LeetCode-242.Valid Anagram.md new file mode 100644 index 00000000..400c3845 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Map/LeetCode-242.Valid Anagram.md @@ -0,0 +1,52 @@ +# LeetCode - 242. Valid Angram + +#### [题目链接](https://leetcode.com/problems/valid-anagram/) + +> https://leetcode.com/problems/valid-anagram/ + +#### 题目 + +![1554870279097](assets/1554870279097.png) + +## 解析 + +很简单的用哈希表统计个数的问题。过于简单就不画图了。。 + +值得一提的是`Arrays`里面有一个可以比较两个数组内容是否都相同的静态方法`equals`。可以省掉代码。 + +```java +import java.util.Arrays; + +class Solution { + public boolean isAnagram(String s, String t) { + if (s.length() != t.length()) return false; + int c1[] = new int[26]; + int c2[] = new int[26]; + for (int i = 0; i < s.length(); i++) { + c1[s.charAt(i) - 'a']++; + c2[t.charAt(i) - 'a']++; + } + return Arrays.equals(c1, c2); // Arrays里面的工具类 + } +} +``` + +也可以省略一个`count`数组,第一个累加,第二个减,最后看是不是全是`0`就可以了。 + +```java +import java.util.Arrays; + +class Solution { + public boolean isAnagram(String s, String t) { + if (s.length() != t.length()) return false; + int c[] = new int[26]; + for (int i = 0; i < s.length(); i++) { + c[s.charAt(i) - 'a']++; + c[t.charAt(i) - 'a']--; + } + for(int n : c)if(n != 0) return false; + return true; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/Map/LeetCode-30.Substring with Concatenation of All Words.md b/Algorithm/LeetCode/DataStructure/Map/LeetCode-30.Substring with Concatenation of All Words.md new file mode 100644 index 00000000..2fbaf1a9 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Map/LeetCode-30.Substring with Concatenation of All Words.md @@ -0,0 +1,59 @@ +# LeetCode - 30. Substring with Concatenation of All Words + +#### [题目链接](https://leetcode.com/problems/substring-with-concatenation-of-all-words/) + +> https://leetcode.com/problems/substring-with-concatenation-of-all-words/ + +#### 题目 + +![1558181642627](assets/1558181642627.png) + +### 解析 + +第一种暴力的解法。 + +遍历每个子串看是不是满足条件。 + +![1558182438645](assets/1558182438645.png) + +怎么判断子串是否符合?如果是两个单词 A,B,我们只需要判断子串是否是 AB 或者 BA 即可。如果是三个单词 A,B,C 也还好,只需要判断子串是否是 ABC,或者 ACB,BAC,BCA,CAB,CBA 就可以了,但如果更多单词呢?**可以求出排列,但是更好的方法是使用两个Map**。 + +首先,我们把所有的单词存到 HashMap 里,key 直接存单词,value 存单词出现的个数(因为给出的单词可能会有重复的,所以可能是 1 或 2 或者其他)。然后扫描子串的单词,如果当前扫描的单词在之前的 HashMap 中,就把该单词存到新的 HashMap 中,并判断新的 HashMap 中该单词的 value 是不是大于之前的 HashMap 该单词的 value ,如果大了,就代表该子串不是我们要找的,接着判断下一个子串就可以了。如果不大于,那么我们接着判断下一个单词的情况。**子串扫描结束,如果子串的全部单词都符合,那么该子串就是我们找的其中一个**。 + +![1558184902477](assets/1558184902477.png) + +代码: + +```java +class Solution { + // 给定一个字符串 s 和一些 长度相同 的 词 words。 + // 找出 s 中 所有 恰好可以由 words 中所有单词(可以打乱顺序)串联形成的子串的起始位置。 + // 输入:s = "barfoothefoobarman", words = ["foo","bar"] + // 输出:[0,9] + public List findSubstring(String s, String[] words) { + List res = new ArrayList<>(); + if(words.length == 0) return res; + HashMap dict = new HashMap<>(); + for(String word : words) dict.put(word, dict.getOrDefault(word, 0) + 1); + int wordLen = words[0].length(); // words中所有单词长度一样 + int sLen = s.length(); + for(int i = 0; i < sLen - words.length * wordLen + 1; i++){ // 遍历所有的子串 + HashMap seen = new HashMap<>(); + int j = 0; // 遍历 words的数目 + while(j < words.length){ + String word = s.substring(i + j * wordLen, i + (j+1) * wordLen); + if(!dict.containsKey(word)) break; // 必须要在dict中 + seen.put(word, seen.getOrDefault(word, 0) + 1); + if(seen.get(word) > dict.get(word)) break; // 当超过了也不行 + j++; + } + if(j == words.length) res.add(i); // 所有的单词都是符合的 + } + return res; + } +} +``` + +这题还可以优化。具体可以参考下面链接。 + + \ No newline at end of file diff --git a/Algorithm/LeetCode/DataStructure/Map/LeetCode-350.Intersection of Two Arrays II.md b/Algorithm/LeetCode/DataStructure/Map/LeetCode-350.Intersection of Two Arrays II.md new file mode 100644 index 00000000..af13af01 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Map/LeetCode-350.Intersection of Two Arrays II.md @@ -0,0 +1,59 @@ +# LeetCode - 350. Intersection of Two Arrays II + +#### [题目链接](https://leetcode.com/problems/intersection-of-two-arrays-ii/) + +> https://leetcode.com/problems/intersection-of-two-arrays-ii/ + +#### 题目 + +![1554950981200](assets/1554950981200.png) + +## 解析 + +和上个题目类似,不过这个题目需要统计重复的元素,所以我们要从`set`变成`map`,来统计`nums1`中的元素出现的次数,然后每次在`nums2`中查询的时候,每找到一个就要`--`。 + +图: + +![1554950934160](assets/1554950934160.png) + +代码: + +```java +class Solution { + public int[] intersect(int[] nums1, int[] nums2) { + HashMap map = new HashMap<>(); + for (int n : nums1) map.put(n, map.getOrDefault(n, 0) + 1); + int k = 0; + for (int n : nums2) { + if (map.containsKey(n) && map.get(n) > 0) + nums1[k++] = n; + map.put(n, map.getOrDefault(n, 0) - 1); + } + return Arrays.copyOfRange(nums1, 0, k);//取数组的[0, k) + } +} +``` + +同样也可以用排序的方法,然后双指针来做: + +```java +public class Solution { + public int[] intersect(int[] nums1, int[] nums2) { + Arrays.sort(nums1); + Arrays.sort(nums2); + int k = 0; + for (int i = 0, j = 0; i < nums1.length && j < nums2.length; ) { + if (nums1[i] == nums2[j]) { + nums1[k++] = nums1[i]; + i++; + j++; + } else if (nums1[i] < nums2[j]) + i++; + else + j++; + } + return Arrays.copyOfRange(nums1, 0, k); + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Map/LeetCode-380.Insert Delete GetRandom O(1) (\345\270\270\346\225\260\346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240).md" "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-380.Insert Delete GetRandom O(1) (\345\270\270\346\225\260\346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240).md" new file mode 100644 index 00000000..a1615abd --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-380.Insert Delete GetRandom O(1) (\345\270\270\346\225\260\346\227\266\351\227\264\346\217\222\345\205\245\343\200\201\345\210\240\351\231\244\345\222\214\350\216\267\345\217\226\351\232\217\346\234\272\345\205\203\347\264\240).md" @@ -0,0 +1,131 @@ +# LeetCode - 380. Insert Delete GetRandom O(1) (常数时间插入、删除和获取随机元素) + +* [HashMap + List 实现](#hashmap---list-实现) +* [两个HashMap实现](#两个hashmap实现) + +*** + +#### [题目链接](https://leetcode.com/problems/insert-delete-getrandom-o1/) + +> https://leetcode.com/problems/insert-delete-getrandom-o1/ + +#### 题目 +![在这里插入图片描述](images/380_t.png) + +*** +### HashMap + List 实现 + +解析: + +* 准备一个`HashMap`和一个`List`容器,`HashMap`存放`val`到`index`的映射,`List`容器中存放`val`集合; +* `insert()`: 将`val`插入`List`,并加`` 放入`HashMap`; +* `remove()`: 先获取要移除的值`val`的 `index`,记为`removeIndex`,然后取`List`中的最后一个元素,记为`lastVal`,然后执行` valIndexMap.put(lastVal, removeIndex);`和` vals.set(removeIndex, lastVal); `也就是将最后一个元素放到要删除的位置。 最后在两个容器中移除要删除的元素即可; +* `getRandom()`: 在`List`中随机取一个元素即可; + +图: + +![在这里插入图片描述](images/380_s.png) + +代码: + +```java +class RandomizedSet { + + private HashMapvalIndexMap; // val -> index + private ArrayList vals; + + public RandomizedSet() { + valIndexMap = new HashMap<>(); + vals = new ArrayList<>(); + } + + public boolean insert(int val) { + if(valIndexMap.containsKey(val)) + return false; + valIndexMap.put(val, vals.size()); + vals.add(val); + return true; + } + + public boolean remove(int val) { + if(!valIndexMap.containsKey(val)) // doesn't exit, return false; + return false; + + int removeIndex = valIndexMap.get(val); + Integer lastVal = vals.get(vals.size()-1); + + valIndexMap.put(lastVal, removeIndex); // put the lastVal to the removeIndex position + vals.set(removeIndex, lastVal); // update vals + + valIndexMap.remove(val); + vals.remove(vals.size() - 1); + + return true; + } + + public int getRandom() { + if(vals.size() == 0) + throw new RuntimeException(); + int randomIndex = (int)(Math.random() * vals.size()); + return vals.get(randomIndex); + } +} +``` +*** + +### 两个HashMap实现 + +这个和上面那个也是类似的: + +* 通过一个`size`变量来维护这个**容器**的容量; +* 然后要增加一个`index -> val`的容器,这个就是替代了那个`List`,可以通过`index`来获取`val`; +* 主要的思想还是通过将最后一个元素(`size - 1`)放到删除元素的位置(`removeIndex`); + + +```java +class RandomizedSet { + + private HashMapvalIndexMap; // val -> index + private HashMapindexValMap; // index -> val + private int size; + + public RandomizedSet() { + valIndexMap = new HashMap<>(); + indexValMap = new HashMap<>(); + size = 0; + } + + public boolean insert(int val) { + if(valIndexMap.containsKey(val)) + return false; + valIndexMap.put(val, size); + indexValMap.put(size++, val); + return true; + } + + public boolean remove(int val) { + if(!valIndexMap.containsKey(val)) // doesn't exit, return false; + return false; + int removeIndex = valIndexMap.get(val); // get the index of deleteVal + int lastIndex = --size; // the last element's index + + Integer lastVal = indexValMap.get(lastIndex); + + valIndexMap.put(lastVal, removeIndex); + indexValMap.put(removeIndex, lastVal); + + // remove val and the lastIndex + valIndexMap.remove(val); + indexValMap.remove(lastIndex); + return true; + } + + public int getRandom() { + if(size == 0) + throw new RuntimeException(); + int randomIndex = (int)(Math.random() * size); + return indexValMap.get(randomIndex); + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Map/LeetCode-381.Insert Delete GetRandom O(1) - Duplicates allowed(\345\205\201\350\256\270\351\207\215\345\244\215).md" "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-381.Insert Delete GetRandom O(1) - Duplicates allowed(\345\205\201\350\256\270\351\207\215\345\244\215).md" new file mode 100644 index 00000000..3ec57775 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-381.Insert Delete GetRandom O(1) - Duplicates allowed(\345\205\201\350\256\270\351\207\215\345\244\215).md" @@ -0,0 +1,130 @@ +# LeetCode - 381. Insert Delete GetRandom O(1) - Duplicates allowed(允许重复) + +#### [题目链接](https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/) + +> https://leetcode.com/problems/insert-delete-getrandom-o1-duplicates-allowed/ + +#### 题目 +![在这里插入图片描述](images/381_t.png) + +#### 解析 +做这题之前先做[**LeetCode - 380**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20380.%20Insert%20Delete%20GetRandom%20O(1)%20(%E5%B8%B8%E6%95%B0%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0).md)。 + + +这一题加上了可以加入重复的元素,题目就变得复杂了一些。 + +* 大体思路还是使用`HashMap + ArrayList`,只不过`Map`是`HashMap>`的形式,而`List`则是`ArrayList`形式; +* 其中`Map`的`key`存的是值`val`,而对应的`value`存的是一个下标的集合,即`List`中值是`val`的下标的集合; +* `List`中每个位置也要存两个信息,一个是值`val`(数组`[0]`位置),另一个存这个值`val`在`map`中的下标集合中的位置(数组`[1]`位置); + +下图展示了存储 + +容器中有`2`个`6`、`1`个`8`、`2`个`9`。 + + +![在这里插入图片描述](images/381_s.png) + +添加元素很简单,维护`map`和`list`即可。 + +例如添加元素`6`: + +![在这里插入图片描述](images/381_s2.png) + +删除元素思想也是和最后一个元素交换位置,不过也要维护相应的信息即可。 + +删除元素`9`: + +![在这里插入图片描述](images/381_s3.png) + +代码: + +```java +class RandomizedCollection { + + private HashMap>valIndexsMap; + private ArrayList vals; // store the position in the map's value + + public RandomizedCollection() { + valIndexsMap = new HashMap<>(); + vals = new ArrayList<>(); + } + + public boolean insert(int val) { + boolean contain = !valIndexsMap.containsKey(val); +// ArrayListindexs = valIndexsMap.get(val); +// if(indexs == null){ +// indexs = new ArrayList<>(); +// } +// indexs.add(vals.size()); + ArrayListindexs = valIndexsMap.computeIfAbsent(val, k -> new ArrayList<>()); + indexs.add(vals.size()); + vals.add(new int[]{val, indexs.size()-1}); + return contain; + } + + public boolean remove(int val) { + if(!valIndexsMap.containsKey(val)) + return false; + ArrayListindexs = valIndexsMap.get(val); + int removeIndex = indexs.get(indexs.size() - 1); + indexs.remove(indexs.size()-1); + if(indexs.isEmpty()) + valIndexsMap.remove(val); + + if(removeIndex < vals.size()-1){ // exchange + // 将最后一个元素放在 removeIndex位置 + vals.get(removeIndex)[0] = vals.get(vals.size()-1)[0]; + vals.get(removeIndex)[1] = vals.get(vals.size()-1)[1]; + valIndexsMap.get(vals.get(removeIndex)[0]).set(vals.get(removeIndex)[1], removeIndex); + } + vals.remove(vals.size()-1); + return true; + } + + public int getRandom() { + return vals.get((int)(Math.random() * vals.size()))[0]; + } +} + +``` + +这里注意`Java8`中`computeIfAbsent`用法: + +类似下面的用法 +```java +Object key = map.get("key"); +if (key == null) { + key = new Object(); + map.put("key", key); +} +//上面的操作可以简化为一行,若key对应的value为空,会将第二个参数的返回值存入并返回 +Object key2 = map.computeIfAbsent("key", k -> new Object()); +``` + +具体用法举例: + +```java +import java.util.*; + +public class Test { + + public static void main(String[] args){ + Map> map = new HashMap<>(); + List list; + + list = map.get("list-1"); + if (list == null) { + list = new ArrayList<>(); + map.put("list-1", list); + } + list.add("one"); + + //使用 computeIfAbsent 可以这样写 + map.computeIfAbsent("list-1", k -> new ArrayList<>()).add("one"); + + System.out.println(map); // {list-1=[one, one]} + } +} + +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Map/LeetCode-561.Array Partition I(\346\225\260\347\273\204\346\213\206\345\210\206 I)(\350\264\252\345\277\203\345\222\214Hash\346\200\235\346\203\263).md" "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-561.Array Partition I(\346\225\260\347\273\204\346\213\206\345\210\206 I)(\350\264\252\345\277\203\345\222\214Hash\346\200\235\346\203\263).md" new file mode 100644 index 00000000..e9569f77 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Map/LeetCode-561.Array Partition I(\346\225\260\347\273\204\346\213\206\345\210\206 I)(\350\264\252\345\277\203\345\222\214Hash\346\200\235\346\203\263).md" @@ -0,0 +1,86 @@ +# LeetCode - 561. Array Partition I(数组拆分 I)(贪心和Hash思想) + + - 贪心解法 + - hash思想解法 + +*** +#### [题目链接](https://leetcode.com/problems/array-partition-i/description/) + +> https://leetcode.com/problems/array-partition-i/description/ + +#### 题目 +![在这里插入图片描述](images/561_t.png) +## 1、贪心解法 +贪心的解法就是对数组进行排序,因为我们要对数组进行划分,每次选取两个,并且选出最小的那个,所以我们不能浪费那些大的数,所以每次不能浪费更大的数,所以选取相邻的数作为一对。 + +

+ +代码: + +```java +class Solution { + + public int arrayPairSum(int[] nums) { + Arrays.sort(nums); + int res = 0; + for (int i = 0; i < nums.length; i += 2) + res += nums[i]; + return res; + } +} +``` +## 2、hash思想解法 +**思想也是对数组进行排序,主要是题目中说数的范围在`[-10000,10000]`之间,所以我们可以开一个`20000`大小的数组,足以保存下这些数,然后统计每个元素出现的次数,遍历一遍`hash`数组即可,最多循环`20000`次**。 + +图: + +

+ +代码: + +```java +class Solution { + + public int arrayPairSum(int[] nums) { + int[] hash = new int[20001]; + for (int i = 0; i < nums.length; i++) hash[nums[i] + 10000]++; + int res = 0; + boolean odd = true; + for (int i = 0; i < hash.length; ) { + if (hash[i] != 0) { //原数组中存在 + if (odd) { + res += (i - 10000); + odd = false; + } else { + odd = true; + } + if (--hash[i] == 0) //注意重复元素 + i++; + } else + i++; + } + return res; + } +} +``` +**更加优化的解法**: +```java +class Solution { + + public int arrayPairSum(int[] nums) { + int[] hash = new int[20001]; + for (int i = 0; i < nums.length; i++) + hash[nums[i] + 10000]++; + int res = 0; + boolean odd = true; + for (int i = 0; i < hash.length; i++) { + while (hash[i] != 0) { + if (odd) res += (i - 10000); + odd = !odd; + --hash[i]; + } + } + return res; + } +} +``` diff --git a/Algorithm/LeetCode/DataStructure/Map/LeetCode-729.My Calendar.md b/Algorithm/LeetCode/DataStructure/Map/LeetCode-729.My Calendar.md new file mode 100644 index 00000000..1d680973 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Map/LeetCode-729.My Calendar.md @@ -0,0 +1,75 @@ +## LeetCode - 729. My Calendar(区间) + +#### [题目链接](https://leetcode.com/problems/my-calendar-i/) +> https://leetcode.com/problems/my-calendar-i/ + +#### 题目 + +![729_t_.png](images/729_t_.png) + +#### 解析 + +第一种暴力法,去判断前面已经添加的和当前的有没有冲突即可。 + +```java +class MyCalendar { + + private int[] L; + private int[] R; + private int n; + + public MyCalendar() { + L = new int[1001]; + R = new int[1001]; + n = 0; + } + + public boolean book(int start, int end) { + for (int i = 0; i < n; i++) + if (Math.max(L[i], start) < Math.min(end, R[i]))// 相交的四种情况 + // if(!ok(L[i], R[i], start, end)) + return false; + L[n] = start; + R[n++] = end; + return true; + } + + private boolean ok(int l1, int r1, int l2, int r2){ + return l2 >= r1 || l1 >= r2;//不相交的情况 + } +} +``` + +第二种利用`TreeMap`(即二叉搜索树)的性质,可以在`logN`内完成查找。 + +因为`TreeMap`会按照`key`在内部排序,然后我们就可以快速的找到最后一个`<= e` (floorKey)和第一个`>=e`(ceilingKey)的元素。 + +因为区间是有序的,所以我们只要找到这两个区间,然后判断和当前加入区间是否有重叠即可。 + +图: + +![729_s.png](images/729_s.png) + +代码: + +```java +class MyCalendar { + + private TreeMap treeMap; + + public MyCalendar() { + treeMap = new TreeMap<>(); + } + + public boolean book(int start, int end) { + Integer lastSmall = treeMap.floorKey(start);// 最后一个 <= start的 + if(lastSmall != null && treeMap.get(lastSmall) > start) return false; + Integer firstBig = treeMap.ceilingKey(start);//第一个 >= start 的 + if(firstBig != null && end > firstBig) return false; + treeMap.put(start, end); + return true; + } +} +``` + + diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1554870279097.png b/Algorithm/LeetCode/DataStructure/Map/assets/1554870279097.png new file mode 100644 index 00000000..a6e2bb5b Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1554870279097.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1554950934160.png b/Algorithm/LeetCode/DataStructure/Map/assets/1554950934160.png new file mode 100644 index 00000000..6c6778fb Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1554950934160.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1554950981200.png b/Algorithm/LeetCode/DataStructure/Map/assets/1554950981200.png new file mode 100644 index 00000000..569fd82e Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1554950981200.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1555029510497.png b/Algorithm/LeetCode/DataStructure/Map/assets/1555029510497.png new file mode 100644 index 00000000..11fb217b Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1555029510497.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1555029732434.png b/Algorithm/LeetCode/DataStructure/Map/assets/1555029732434.png new file mode 100644 index 00000000..50be6472 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1555029732434.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1558181642627.png b/Algorithm/LeetCode/DataStructure/Map/assets/1558181642627.png new file mode 100644 index 00000000..993ec1da Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1558181642627.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1558182438645.png b/Algorithm/LeetCode/DataStructure/Map/assets/1558182438645.png new file mode 100644 index 00000000..e143bfe0 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1558182438645.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/assets/1558184902477.png b/Algorithm/LeetCode/DataStructure/Map/assets/1558184902477.png new file mode 100644 index 00000000..eb959a2e Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/assets/1558184902477.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_s.png" b/Algorithm/LeetCode/DataStructure/Map/images/128_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_s.png" rename to Algorithm/LeetCode/DataStructure/Map/images/128_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_s2.png" b/Algorithm/LeetCode/DataStructure/Map/images/128_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_s2.png" rename to Algorithm/LeetCode/DataStructure/Map/images/128_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_t.png" b/Algorithm/LeetCode/DataStructure/Map/images/128_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/128_t.png" rename to Algorithm/LeetCode/DataStructure/Map/images/128_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/1_s.png" b/Algorithm/LeetCode/DataStructure/Map/images/1_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/1_s.png" rename to Algorithm/LeetCode/DataStructure/Map/images/1_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/1_t.png" b/Algorithm/LeetCode/DataStructure/Map/images/1_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/1_t.png" rename to Algorithm/LeetCode/DataStructure/Map/images/1_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/380_s.png" b/Algorithm/LeetCode/DataStructure/Map/images/380_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/380_s.png" rename to Algorithm/LeetCode/DataStructure/Map/images/380_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/380_t.png" b/Algorithm/LeetCode/DataStructure/Map/images/380_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/380_t.png" rename to Algorithm/LeetCode/DataStructure/Map/images/380_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s.png" b/Algorithm/LeetCode/DataStructure/Map/images/381_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s.png" rename to Algorithm/LeetCode/DataStructure/Map/images/381_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s2.png" b/Algorithm/LeetCode/DataStructure/Map/images/381_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s2.png" rename to Algorithm/LeetCode/DataStructure/Map/images/381_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s3.png" b/Algorithm/LeetCode/DataStructure/Map/images/381_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_s3.png" rename to Algorithm/LeetCode/DataStructure/Map/images/381_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_t.png" b/Algorithm/LeetCode/DataStructure/Map/images/381_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/381_t.png" rename to Algorithm/LeetCode/DataStructure/Map/images/381_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/561_t.png" b/Algorithm/LeetCode/DataStructure/Map/images/561_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Map/images/561_t.png" rename to Algorithm/LeetCode/DataStructure/Map/images/561_t.png diff --git a/Algorithm/LeetCode/DataStructure/Map/images/729_s.png b/Algorithm/LeetCode/DataStructure/Map/images/729_s.png new file mode 100644 index 00000000..536ca187 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/729_s.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/images/729_t_.png b/Algorithm/LeetCode/DataStructure/Map/images/729_t_.png new file mode 100644 index 00000000..471c9a3a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/729_t_.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/images/731_t.png b/Algorithm/LeetCode/DataStructure/Map/images/731_t.png new file mode 100644 index 00000000..69e57cc5 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/731_t.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/images/732_t.png b/Algorithm/LeetCode/DataStructure/Map/images/732_t.png new file mode 100644 index 00000000..b35dd662 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/732_t.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/images/xin_1.png b/Algorithm/LeetCode/DataStructure/Map/images/xin_1.png new file mode 100644 index 00000000..3b1ec034 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/xin_1.png differ diff --git a/Algorithm/LeetCode/DataStructure/Map/images/xin_1_2.png b/Algorithm/LeetCode/DataStructure/Map/images/xin_1_2.png new file mode 100644 index 00000000..0e1f0b76 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Map/images/xin_1_2.png differ diff --git "a/Algorithm/LeetCode/DataStructure/SegmentTree/LeetCode-303.Range Sum Query - Immutable(\347\256\200\345\215\225dp\346\210\226\350\200\205\347\272\277\346\256\265\346\240\221).md" "b/Algorithm/LeetCode/DataStructure/SegmentTree/LeetCode-303.Range Sum Query - Immutable(\347\256\200\345\215\225dp\346\210\226\350\200\205\347\272\277\346\256\265\346\240\221).md" new file mode 100644 index 00000000..1ad3e2e5 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/SegmentTree/LeetCode-303.Range Sum Query - Immutable(\347\256\200\345\215\225dp\346\210\226\350\200\205\347\272\277\346\256\265\346\240\221).md" @@ -0,0 +1,128 @@ +## LeetCode - 303. Range Sum Query - Immutable(简单dp或者线段树) + + - dp + - 线段树 + +#### [题目链接](https://leetcode.com/problems/range-sum-query-immutable/description/) + +> https://leetcode.com/problems/range-sum-query-immutable/description/ + +#### 题目 +![在这里插入图片描述](images/303_t.png) +### 解析 +#### dp +一维的动态规划,直接从左到右记录`0~每个位置`的和,然后递归`sums[i] = sums[i-1] + nums[i]`,求出所有的 +`sums`,然后要求某个区间的和,就直接返回`sums[j] - sums[i-1]`即可,如果`i = 0`,就直接返回`sums[j]`。 + +![1554879059264](assets/1554879059264.png) + +代码: + +```java +public class NumArray { + + public int[] sums; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) + return; + sums = new int[nums.length]; + sums[0] = nums[0]; + for (int i = 1; i < nums.length; i++) sums[i] = sums[i - 1] + nums[i]; // dp + } + + public int sumRange(int i, int j) { + if (i == 0) return sums[j]; + return sums[j] - sums[i - 1]; + } +} + +``` +*** +### 线段树 +线段树解析看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/SegmentTree/%E7%BA%BF%E6%AE%B5%E6%A0%91%E6%80%BB%E7%BB%93%E4%BB%A5%E5%8F%8ALeetCode%20-%20307.%20Range%20Sum%20Query%20-%20Mutable.md#%E7%BA%BF%E6%AE%B5%E6%A0%91%E6%9F%A5%E8%AF%A2)。 + +```java +public class NumArray { + + private interface Merger { + E merge(E a, E b); + } + + private class SegmentTree { + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger) { + this.merger = merger; + + data = (E[]) new Object[arr.length]; + for (int i = 0; i < arr.length; i++) data[i] = arr[i]; + tree = (E[]) new Object[4 * arr.length]; //最多需要4 * n + buildSegmentTree(0, 0, arr.length - 1); + } + + public void buildSegmentTree(int treeIndex, int L, int R) { + if (L == R) { + tree[treeIndex] = data[L]; + return; + } + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + int m = L + (R - L) / 2; + + buildSegmentTree(treeL, L, m); + buildSegmentTree(treeR, m + 1, R); + + tree[treeIndex] = merger.merge(tree[treeL], tree[treeR]); + } + + + public E query(int qL, int qR) { + if (qL < 0 || qL >= data.length || qR < 0 || qR >= data.length || qL > qR) return null; + return query(0, 0, data.length - 1, qL, qR); + } + + private E query(int treeIndex, int L, int R, int qL, int qR) { + if (L == qL && R == qR) { + return tree[treeIndex]; + } + int m = L + (R - L) / 2; + + int treeL = treeIndex * 2 + 1; + int treeR = treeIndex * 2 + 2; + + if (qR <= m) { //和右区间没关系 ,直接去左边查找 [0,4] qR <= 2 [0,2]之间查找 + return query(treeL, L, m, qL, qR); + } else if (qL > m) {//和左区间没有关系,直接去右边查找 [0,4] qL > 2 --> [3,4] + return query(treeR, m + 1, R, qL, qR); + } else { //在两边都有,查询的结果 合并 + return merger.merge(query(treeL, L, m, qL, m), //注意是查询 [qL,m] + query(treeR, m + 1, R, m + 1, qR)); //查询[m+1,qR] + } + } + + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + if (nums == null || nums.length == 0) return; + Integer[] arr = new Integer[nums.length]; + for (int i = 0; i < nums.length; i++) arr[i] = nums[i]; + segTree = new SegmentTree(arr, new Merger() { + @Override + public Integer merge(Integer a, Integer b) { + return a + b; + } + }); + } + + public int sumRange(int i, int j) { + return segTree.query(i, j); + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/SegmentTree/assets/1554879059264.png b/Algorithm/LeetCode/DataStructure/SegmentTree/assets/1554879059264.png new file mode 100644 index 00000000..9053cee5 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/SegmentTree/assets/1554879059264.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/SegmentTree/images/303_t.png" b/Algorithm/LeetCode/DataStructure/SegmentTree/images/303_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/SegmentTree/images/303_t.png" rename to Algorithm/LeetCode/DataStructure/SegmentTree/images/303_t.png diff --git a/Algorithm/LeetCode/DataStructure/Set/LeetCode-349.Intersection of Two Arrays.md b/Algorithm/LeetCode/DataStructure/Set/LeetCode-349.Intersection of Two Arrays.md new file mode 100644 index 00000000..0673abfe --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Set/LeetCode-349.Intersection of Two Arrays.md @@ -0,0 +1,84 @@ +# LeetCode - 349. Intersection of Two Arrays + +#### [题目链接](https://leetcode.com/problems/intersection-of-two-arrays/) + +> https://leetcode.com/problems/intersection-of-two-arrays/ + +#### 题目 + +![1554949616358](assets/1554949616358.png) + +## 解析 + +比较直观的使用`HashSet`的题目。 + +思路: + +![1554949837600](assets/1554949837600.png) + +代码: + +```java +class Solution { + public int[] intersection(int[] nums1, int[] nums2) { + HashSetset = new HashSet<>(); + for(int n : nums1) set.add(n); + ArrayList list = new ArrayList<>(); + for(int n : nums2) { + if(set.contains(n)){ + list.add(n); + set.remove(n); // 下次不需要重复统计 + } + } + int[] res = new int[list.size()]; + int k = 0; + for(int n : list) res[k++] = n; + return res; + } +} +``` + +也可以用两个`set`,后面那个`set`用来存结果,这样我们第一个`set`就不需要`remove()`之前的了。 + +```java +public class Solution { + public int[] intersection(int[] nums1, int[] nums2) { + HashSet set = new HashSet<>(); + HashSet intersect = new HashSet<>(); + for (int n : nums1) set.add(n); + for(int n : nums2) if(set.contains(n)) intersect.add(n); + int[] res = new int[intersect.size()]; + int k = 0; + for (Integer num : intersect) res[k++] = num; + return res; + } +} +``` + +还有一种`N *logN`的解法,对两个数组都排序,结果还需要一个`set`去重。 + +```java +public class Solution { + public int[] intersection(int[] nums1, int[] nums2) { + HashSet set = new HashSet<>(); + Arrays.sort(nums1); + Arrays.sort(nums2); + for (int i = 0, j = 0; i < nums1.length && j < nums2.length; ) { + if (nums1[i] < nums2[j]) { + i++; + } else if (nums1[i] > nums2[j]) { + j++; + } else { + set.add(nums1[i]); + i++; + j++; + } + } + int[] res = new int[set.size()]; + int k = 0; + for (Integer num : set) res[k++] = num; + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/Set/assets/1554949616358.png b/Algorithm/LeetCode/DataStructure/Set/assets/1554949616358.png new file mode 100644 index 00000000..9dae0853 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Set/assets/1554949616358.png differ diff --git a/Algorithm/LeetCode/DataStructure/Set/assets/1554949837600.png b/Algorithm/LeetCode/DataStructure/Set/assets/1554949837600.png new file mode 100644 index 00000000..a23d9548 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Set/assets/1554949837600.png differ diff --git "a/Algorithm/LeetCode/DataStructure/Sort/LeetCode-75.Sort Colors(\350\256\241\346\225\260\346\216\222\345\272\217\345\222\214\345\277\253\351\200\237\346\216\222\345\272\217\345\217\230\345\275\242).md" "b/Algorithm/LeetCode/DataStructure/Sort/LeetCode-75.Sort Colors(\350\256\241\346\225\260\346\216\222\345\272\217\345\222\214\345\277\253\351\200\237\346\216\222\345\272\217\345\217\230\345\275\242).md" new file mode 100644 index 00000000..ab2168f7 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Sort/LeetCode-75.Sort Colors(\350\256\241\346\225\260\346\216\222\345\272\217\345\222\214\345\277\253\351\200\237\346\216\222\345\272\217\345\217\230\345\275\242).md" @@ -0,0 +1,72 @@ +# LeetCode - 75. Sort Colors(计数排序和快速排序变形) + + - 使用类似计数排序解决 + - 使用快速排序的partition过程解决 + +#### [题目链接](https://leetcode.com/problems/sort-colors/description/) + +> https://leetcode.com/problems/sort-colors/description/ + +#### 题目 +![在这里插入图片描述](images/75_t.png) +*** +## 1、使用类似计数排序解决 +这个思路很简单,直接统计这三个数字出现的次数,然后填充一遍即可。 + +

+ +代码: + +```java +class Solution { + public void sortColors(int[] nums) { + int[] count = new int[3]; + for(int i = 0; i < nums.length; i++) + count[nums[i]]++; + int k = 0; + for(int i = 0; i < 3; i++){ + for(int j = 0; j < count[i]; j++){ + nums[k++] = i; + } + } + } +} +``` +## 2、使用快速排序的partition过程解决 +还可以使用快速排序中的三路快排的思想来解决这个问题,快速排序和三路快排看我的[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79826380#t8),快速排序的 + +`partition`过程也是经典的荷兰国旗问题。 + +

+ +代码: + +```java +class Solution { + public void sortColors(int[] nums) { + if (nums == null || nums.length < 2) return; + partition(nums, 0, nums.length - 1); + } + + public void partition(int[] arr, int L, int R) { + int less = -1;//左边界 + int more = arr.length; //右边界 + int cur = 0; + while (cur < more) { + if (arr[cur] == 0) + swap(arr, ++less, cur++); + else if (arr[cur] == 2) + swap(arr, --more, cur); + else + cur++; + } + } + + private void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/Sort/images/75_s.png b/Algorithm/LeetCode/DataStructure/Sort/images/75_s.png new file mode 100644 index 00000000..0032191f Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Sort/images/75_s.png differ diff --git a/Algorithm/LeetCode/DataStructure/Sort/images/75_s2.png b/Algorithm/LeetCode/DataStructure/Sort/images/75_s2.png new file mode 100644 index 00000000..27c5f18a Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Sort/images/75_s2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Sort/images/75_t.png" b/Algorithm/LeetCode/DataStructure/Sort/images/75_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Sort/images/75_t.png" rename to Algorithm/LeetCode/DataStructure/Sort/images/75_t.png diff --git a/Algorithm/LeetCode/DataStructure/Stack/LeetCode-20.Valid Parentheses.md b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-20.Valid Parentheses.md new file mode 100644 index 00000000..583997a4 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-20.Valid Parentheses.md @@ -0,0 +1,53 @@ +# LeetCode - 20. Valid Parentheses + +#### [题目链接](https://leetcode.com/problems/valid-parentheses/) + +> https://leetcode.com/problems/valid-parentheses/ + +#### 题目 + +![20_t.png](images/20_t.png) + +## 解析 + +很简单的用栈处理的括号匹配问题。栈的入门题目吧。 + +这里看到`LeetCode`官方题解中有两个很好的图,这里就借用一下吧`^_^`。 + +* 每次碰到`(、[、{`就入栈; +* 否则就要看当前字符和对应的栈顶是不是匹配的。如果不匹配就返回`false`; +* 最后如果栈空就是`true`; + +图: + +

+ +操作过程: + +

+ + + +代码: + +```java +class Solution { + public boolean isValid(String s) { + Stack stack = new Stack<>(); + for(int i = 0; i < s.length(); i++){ + char c = s.charAt(i); + if(c == '(' || c == '[' || c == '{') + stack.push(c); + else { + if(stack.isEmpty()) return false; + if(stack.peek() == '(' && c != ')') return false; + if(stack.peek() == '[' && c != ']') return false; + if(stack.peek() == '{' && c != '}') return false; + stack.pop(); + } + } + return stack.isEmpty(); + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Stack/LeetCode-84.Largest Rectangle in Histogram(\345\215\225\350\260\203\346\240\210).md" "b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-84.Largest Rectangle in Histogram(\345\215\225\350\260\203\346\240\210).md" new file mode 100644 index 00000000..0fc8f91d --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-84.Largest Rectangle in Histogram(\345\215\225\350\260\203\346\240\210).md" @@ -0,0 +1,107 @@ +# LeetCode - 84. Largest Rectangle in Histogram(单调栈) + +#### [题目链接](https://leetcode.com/problems/largest-rectangle-in-histogram/) + +> https://leetcode.com/problems/largest-rectangle-in-histogram/ + +#### 题目 + +![1554685911888](assets/1554685911888.png) + +## 解析 + +给出两种解法,一种`O(N^2)`,一种单调栈的`O(N)`。 + +第一种方法: + +* 先从做往右扫描,每次找到第一个当前位置不比后一个位置小的位置; +* 然后从这个位置开始往前面扫描,计算出以这个位置结尾的最大矩形; +* 然后我们更新全局最大值即可; + +图: + +![1554687058708](assets/1554687058708.png) + +代码: + +```java +class Solution { + public int largestRectangleArea(int[] heights) { + if(heights == null || heights.length == 0) return 0; + int maxArea = 0; + for(int cur = 0; cur < heights.length; cur++){ + if(cur != heights.length - 1 && heights[cur] <= heights[cur+1]) continue; + int minHeight = Integer.MAX_VALUE; + for(int i = cur; i >= 0; i--){ + if(heights[i] < minHeight) minHeight = heights[i]; + maxArea = Math.max(maxArea, minHeight * (cur - i + 1)); + } + } + return maxArea; + } +} +``` + +单调栈的解法。 + +单调栈的知识看[**这篇博客**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/InterviewAlgorithm.md#%E5%8D%81%E5%85%AB%E5%8D%95%E8%B0%83%E6%A0%88),注意这个栈是 **从栈底到栈顶依次是从小到大的**: + +- 如果栈中的数比当前的数大(或者等于)就要处理栈顶的(记录左右两边的比它小的第一个数); +- 然后如果遍历完之后,单独处理栈,此时所有元素右边都不存在比它小的; +- 这样我们就可以求得以每个位置的最大矩形,然后我们取一个最大值即可; + +看下图(上面的例子)栈的变化过程: (**栈的左侧是索引,右侧是值,但是实际中栈只存索引**) + +![1554689247272](assets/1554689247272.png) + +代码: + +```java +class Solution { + public int largestRectangleArea(int[] heights) { + if(heights == null || heights.length == 0) return 0; + Stack s = new Stack<>(); + int maxArea = 0; + for(int i = 0; i < heights.length; i++) { + while (!s.isEmpty() && heights[i] <= heights[s.peek()]){ + int top = s.pop(); + int L = s.isEmpty() ? -1 : s.peek(); //如果左边没有比height[top]小的就是-1 + maxArea = Math.max(maxArea, heights[top] * (i - L - 1)); + } + s.push(i); //注意是下标入栈 + } + while(!s.isEmpty()){ + int top = s.pop(); + int L = s.isEmpty() ? -1 : s.peek(); + maxArea = Math.max(maxArea, heights[top] * (heights.length - L - 1)); // 右边没有比height[top]大的,就是右边界height.length + } + return maxArea; + } +} +``` + +也可以写成这样,是一样的: + +```java +class Solution { + public int largestRectangleArea(int[] heights) { + if(heights == null || heights.length == 0) return 0; + Stack s = new Stack<>(); + int maxArea = 0; + for(int i = 0; i < heights.length; i++) { + while (!s.isEmpty() && heights[i] <= heights[s.peek()]){ + int top = s.pop(); + maxArea = Math.max(maxArea, heights[top] * (i - (s.isEmpty() ? 0 : s.peek() + 1))); + } + s.push(i); //注意是下标入栈 + } + while(!s.isEmpty()){ + int top = s.pop(); + // 右边没有比height[top]大的,就是右边界height.length + maxArea = Math.max(maxArea, heights[top] * (heights.length - (s.isEmpty() ? 0 : s.peek() + 1))); + } + return maxArea; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/Stack/LeetCode-85.Maximal Rectangle.md b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-85.Maximal Rectangle.md new file mode 100644 index 00000000..53f0e8c9 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Stack/LeetCode-85.Maximal Rectangle.md @@ -0,0 +1,74 @@ +# LeetCode - 85. Maximal Rectangle + +#### [题目链接]() + +#### 题目 + +![1554690247272](assets/1554690247272.png) + +## 解析 + +这个题目就是[**LeetCode - 84. 直方图最大矩形覆盖**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Stack/LeetCode%20-%2084.%20Largest%20Rectangle%20in%20Histogram(%E5%8D%95%E8%B0%83%E6%A0%88).md)的一个变式: +我们只需要求出以**每一行作为底最大的矩形是多少**,每一行都有一个`height`数组,把每个数组都调用上个题目的方法就可以求出,以每一行作为底(直方图最下面)时最大矩形面积,然后记录最大值即可。 +**关键就是每次更新`height`数组,`height`数组代表的是这一列上面有多少个连续的**`1` + + +例如: +```c +map = 1 0 1 1 + 1 1 1 1 + 1 1 1 0 +``` + +以第一行做切割后,`height = {1, 0, 1, 1}`,`height[j]`表示目前的底上(第`1`行),`j`位置往上(包括`j`位置)有多少连续的`1`; + +以第`2`行做切割后,`height = {2, 1, 2, 2}`,注意到从第一行到第二行,`height`数组的更新是十分方便的,即`height[j] = matrix[i][j] == 0 ? 0 : height[j] + 1`;(这里有点dp的意思) + +第`3`行做切割后,`height = {3, 2, 3, 0}`; + +最后的结果: 对于每一次切割,都利用更新后的`height`数组来求出以每一行为底的情况下,最大的矩形是多大(然后记录最大值即可)。 + +拿样例的例子来看: + +![1554691509451](assets/1554691509451.png) + +由于只需要计算`N`行,而单调栈过程是`O(N)`,所以时间复杂度为`O(N^2)`。 + +```java +class Solution { + public int maximalRectangle(char[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) + return 0; + int maxArea = 0; + int[] height = new int[matrix[0].length]; //依次的求每一行的直方图最大面积 + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) + height[j] = matrix[i][j] == '0' ? 0 : height[j] + 1; //有点dp的意思, 如果是1的话就是上面的height[j] + 1 + maxArea = Math.max(largestRectangleArea(height), maxArea); // 计算以当前行作为底的最大矩形 + } + return maxArea; + } + // 这个就是 LeetCode - 84的代码 + public int largestRectangleArea(int[] h) { + if (h == null || h.length == 0) return 0; + Stack stack = new Stack<>(); + int maxArea = 0; + for (int i = 0; i < h.length; i++) { + while (!stack.isEmpty() && h[i] <= h[stack.peek()]) { + int top = stack.pop(); + int L = stack.isEmpty() ? -1 : stack.peek(); + maxArea = Math.max(maxArea, (i - L - 1) * h[top]);//注意i自己就是右边界 左边界到右边界中间的格子(i-L-1) + } + stack.push(i); //注意是下标入栈 + } + //处理完整个数组之后,再处理栈中的 + while (!stack.isEmpty()) { + int top = stack.pop(); + int L = stack.isEmpty() ? -1 : stack.peek(); + maxArea = Math.max(maxArea, (h.length - 1 - L) * h[top]);//注意所有还在栈中的右边界都是 数组的长度右边没有比它小的 + } + return maxArea; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/Stack/assets/1554685911888.png b/Algorithm/LeetCode/DataStructure/Stack/assets/1554685911888.png new file mode 100644 index 00000000..841c1fc1 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/assets/1554685911888.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/assets/1554687058708.png b/Algorithm/LeetCode/DataStructure/Stack/assets/1554687058708.png new file mode 100644 index 00000000..c0f35d9d Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/assets/1554687058708.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/assets/1554689247272.png b/Algorithm/LeetCode/DataStructure/Stack/assets/1554689247272.png new file mode 100644 index 00000000..080a19f7 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/assets/1554689247272.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/assets/1554690247272.png b/Algorithm/LeetCode/DataStructure/Stack/assets/1554690247272.png new file mode 100644 index 00000000..4a2d86d0 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/assets/1554690247272.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/assets/1554691509451.png b/Algorithm/LeetCode/DataStructure/Stack/assets/1554691509451.png new file mode 100644 index 00000000..eada95b4 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/assets/1554691509451.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/images/20_s.gif b/Algorithm/LeetCode/DataStructure/Stack/images/20_s.gif new file mode 100644 index 00000000..56e67abd Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/images/20_s.gif differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/images/20_s1.png b/Algorithm/LeetCode/DataStructure/Stack/images/20_s1.png new file mode 100644 index 00000000..2ff93a8f Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/images/20_s1.png differ diff --git a/Algorithm/LeetCode/DataStructure/Stack/images/20_t.png b/Algorithm/LeetCode/DataStructure/Stack/images/20_t.png new file mode 100644 index 00000000..bbf33a65 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Stack/images/20_t.png differ diff --git a/Algorithm/LeetCode/DataStructure/Trie/LeetCode-14.Longest Common Prefix.md b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-14.Longest Common Prefix.md new file mode 100644 index 00000000..8cfac782 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-14.Longest Common Prefix.md @@ -0,0 +1,216 @@ +# LeetCode - 14. Longest Common Prefix + +#### [题目链接](https://leetcode.com/problems/longest-common-prefix/) + +> https://leetcode.com/problems/longest-common-prefix/ + +#### 题目 + +![14_-t.png](images/14_-t.png) + +## 解析 + +这题解法有挺多种,LeetCode官方题解也给的挺多。我这里也给出几种。 + + + +### 1、按照列比较 + +第一种比较直观的做法。按照**列**来比较,看图就懂。 + +

+ +代码: + +```java +class Solution { + + public String longestCommonPrefix(String[] strs) { + if(strs == null || strs.length == 0) return ""; + int minLen = Integer.MAX_VALUE; + for (int i = 0; i < strs.length; i++) minLen = Math.min(minLen, strs[i].length()); + StringBuilder sb = new StringBuilder(); + for (int len = 0; len < minLen; len++) { + char c = strs[0].charAt(len); + boolean ok = true; + for (int i = 1; i < strs.length; i++) if(c != strs[i].charAt(len)){ + ok = false; + break; + } + if(ok) + sb.append(c); + else + break; + } + return sb.toString(); + } +} +``` + +### 2、水平的两两之间做比较 + +这个方法就是每两个之间进行比较,得到每两个的最长公共前缀。 + +

+ +但是这个过程我们要用到**查找和剪裁**的做法,例如我们比较两个字符串之间的公共前缀的做法是: + +* 我们不断的在`B`中查找`A`(使用`indexOf()`方法); +* 如果目前不存在,就将`A`减掉一位,继续在`B`中查找`A`; + +代码: + +```java +class Solution { + + public String longestCommonPrefix(String[] strs) { + if (strs.length == 0) return ""; + String prefix = strs[0]; // 前缀不断的更新,就是图中绿色部分 + for (int i = 1; i < strs.length; i++) + while (strs[i].indexOf(prefix) != 0) { // 一定要是0,因为是前缀,如果达到了0,说明找到了一个前缀 + prefix = prefix.substring(0, prefix.length() - 1); //如果不满足,就在prefix的后面去掉一位 + if (prefix.isEmpty()) return ""; + } + return prefix; + } +} +``` + + + +### 3、分治 + +这个方法很有创意: + +* 整体思路是将这`n`个字符串不断的划分,按照中间位置分开后,左边和右边先求出`LCP`; +* 此时只有两个字符串,我们可以利用列或者行比较这两个字符串的`LCP`返回即可; + +图(这里只有四个字符串,可以画更多): + +

+ +代码: + +```java +class Solution { + + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) return ""; + return dc(strs, 0 , strs.length - 1); + } + + private String dc(String[] strs, int L, int R){ + if(L == R) return strs[L]; + int mid = L + (R - L) / 2; + String lcpL = dc(strs, L, mid); + String lcpR = dc(strs, mid + 1, R); + return findPrefixInTwo(lcpL, lcpR); + } + + private String findPrefixInTwo(String a, String b){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < Math.min(a.length(), b.length()); i++){ + char c1 = a.charAt(i), c2 = b.charAt(i); + if(c1 == c2) + sb.append(c1); + else + break; + } + return sb.toString(); + } +} +``` + +### 4、Trie + +这题也可以用字典树来做。 + +用字典树其实很明显: + +* 我们只需要找到第一个分叉的点就可以了(前面的都可以答案); +* 但是分叉之前的点要确保没有已经结束了的(某个字符串还没到分叉点就结束了); + +看图: + +

+ +代码: + +```java +class Solution { + + class Trie { + class Node { + int size; + boolean end; + Node[] next; + + public Node() { + size = 0; + end = false; + next = new Node[256]; + + } + } + + Node root; + + public Trie() { + root = new Node(); + } + + void insert(String s) { + if (s == null) return; + Node cur = root; + for (int i = 0; i < s.length(); i++) { + int index = s.charAt(i) - 'a'; + if (cur.next[index] == null) { + cur.size++; + cur.next[index] = new Node(); + } + cur = cur.next[index]; + } + cur.end = true; + } + + String lcp() { + StringBuilder sb = new StringBuilder(); + Node cur = root; + while (cur.size == 1 && !cur.end) { // 必须只有一个孩子,而且不能是结束点 + boolean ok = false; + for (int i = 0; i < cur.next.length; i++) { + if (cur.next[i] != null) { + sb.append((char) ('a' + i)); + cur = cur.next[i]; + ok = true; + break; + } + } + if (!ok) break; + } + return sb.toString(); + } + } + + + public String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) return ""; + Trie trie = new Trie(); + for (String s : strs) { + if (s.equals("")) return ""; + trie.insert(s); + } + return trie.lcp(); + } + + public static void main(String[] args) { + + System.out.println(new Solution().longestCommonPrefix( +// new String[]{"flower","flow","flight"} +// new String[]{"","b"} + new String[]{"aaa", "aa"} + )); + } +} +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Trie/LeetCode-211.Add and Search Word - Data structure design(\345\255\227\345\205\270\346\240\221\345\222\214\351\200\222\345\275\222).md" "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-211.Add and Search Word - Data structure design(\345\255\227\345\205\270\346\240\221\345\222\214\351\200\222\345\275\222).md" new file mode 100644 index 00000000..18ee6f9e --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-211.Add and Search Word - Data structure design(\345\255\227\345\205\270\346\240\221\345\222\214\351\200\222\345\275\222).md" @@ -0,0 +1,84 @@ +## LeetCode - 211. Add and Search Word - Data structure design(字典树和递归) +#### [题目链接](https://leetcode.com/problems/add-and-search-word-data-structure-design/description/) + +> https://leetcode.com/problems/add-and-search-word-data-structure-design/description/ + +#### 题目 +![在这里插入图片描述](assets/211_t.png) +#### 解析 +首先知道[字典树的基本操作](shttps://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/Trie/LeetCode%20-%20208.%20Implement%20Trie%20(Prefix%20Tree)%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%AD%97%E5%85%B8%E6%A0%91(%E5%89%8D%E7%BC%80%E6%A0%91).md)。 + +对于这个题目:  + + - `addWord()`操作没什么好说的,就是向字典树添加元素,记得维护结点的`end`值; + - **匹配的过程是一个递归的过程,从根结点开始,递归终止条件是`node.end > 0`(返回(也就是是否有))**; + - **对于匹配操作要分两种情况,如果不是`'.'`,就定位到该匹配的子树,继续递归匹配**; + - **如果是`'.'`,就要遍历该结点的所有`next[i]`,去搜索匹配,这些`next[i]`中只要有一个匹配成功就返回true,否则返回`false`**; + +图: + +![在这里插入图片描述](images/211_s.png) + +代码: + +```java +class WordDictionary { + + private class Node { + public int end; + public Node[] next;//使用整数表示字符 c - 'a' + + public Node() { + end = 0; + next = new Node[26]; + } + } + + private Node root; + + public WordDictionary() { + root = new Node(); + } + + public void addWord(String word) { + if (word == null) + return; + Node cur = root; + int index; + for (int i = 0; i < word.length(); i++) { + index = word.charAt(i) - 'a'; + if (cur.next[index] == null) + cur.next[index] = new Node(); + cur = cur.next[index]; + } + cur.end++; + } + + public boolean search(String word) { + return process(root, word, 0); + } + + public boolean process(Node node, String word, int index) { + if (node == null) + return false; + if (index == word.length()) + return node.end > 0; + char c = word.charAt(index); + if (c != '.') { + int idx = c - 'a'; + if (node.next[idx] == null) + return false; + else { + return process(node.next[idx], word, index + 1); + } + } else { // c == '.' search all the subTree + for (Node cur : node.next) { + if (process(cur, word, index + 1)) + return true; + } + return false; + } + } +} + +``` diff --git "a/Algorithm/LeetCode/DataStructure/Trie/LeetCode-676.Implement Magic Dictionary(\345\255\227\345\205\270\346\240\221).md" "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-676.Implement Magic Dictionary(\345\255\227\345\205\270\346\240\221).md" new file mode 100644 index 00000000..a1fcb91e --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-676.Implement Magic Dictionary(\345\255\227\345\205\270\346\240\221).md" @@ -0,0 +1,151 @@ +# LeetCode - 676. Implement Magic Dictionary +#### [题目链接](https://leetcode.com/problems/implement-magic-dictionary/) + +> https://leetcode.com/problems/implement-magic-dictionary/ + +#### 题目 +![在这里插入图片描述](images/676_t.png) +#### 解析 + +两种解法: **模糊搜索和利用字典树**。 + + +**模糊搜索**: + + +`buildDict`过程: + +* 准备一个`HashMap>`,`key`存字符串,`val`存的是字符的集合; +* 其中`key`是在枚举每一个字符串的每一个位置,去掉原来的那个字符,然后用一个特殊字符加入进去,并把替换的字符加入`val`中的`set`集合。 + +`search`过程: + +* 查看在替换任意一个字符,之后的集合,且这个集合中不能包含替换的字符,或者可以包含但是`set.size() > 1`也行。 + +图: + +![在这里插入图片描述](images/676_s.png) + +代码: + +```java +class MagicDictionary { + + private HashMap>magicMap; + + /** Initialize your data structure here. */ + public MagicDictionary() { + magicMap = new HashMap<>(); + } + + /** Build a dictionary through a list of words */ + public void buildDict(String[] dict) { + for(int i = 0; i < dict.length; i++){ + for(int j = 0; j < dict[i].length(); j++){ + String key = dict[i].substring(0, j) + "*" + dict[i].substring(j+1, dict[i].length()); + HashSetvalSet = magicMap.get(key); + if(valSet == null) + valSet = new HashSet<>(); + valSet.add(dict[i].charAt(j)); + magicMap.put(key, valSet); + } + } + } + + /** Returns if there is any word in the trie that equals to the given word after modifying exactly one character */ + public boolean search(String word) { + for(int i = 0; i < word.length(); i++){ + String key = word.substring(0, i) + "*" + word.substring(i+1, word.length()); + HashSetvalSet = magicMap.get(key); + if(valSet == null) + continue; + // 只要有一个满足这种情况就可以了 注意第二种情况,例如查询 hello,之前map里如果有hello, hallo (valSet.size() > 1)也是可以的 + if(!valSet.contains(word.charAt(i)) || valSet.size() > 1) + return true; + } + return false; + } +} +``` + +**字典树写法** + +字典树基础可以看[这里](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/Trie/LeetCode%20-%20208.%20Implement%20Trie%20(Prefix%20Tree)%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%AD%97%E5%85%B8%E6%A0%91(%E5%89%8D%E7%BC%80%E6%A0%91).md)。 + +插入没什么好说的,建立字典树即可,`search`的过程要维护一个`isOneDiff`变量。表示的是当前是否已经有一个不同了。然后`dfs`即可。 + + +```java +class MagicDictionary { + + private class Node{ + public boolean end; + public Node[] nexts; + public Node() { + end = false; + this.nexts = new Node[26]; + } + } + + private class Trie{ + + private Node root; + + public Trie() { + this.root = new Node(); + } + + public void insert(String word){ + Node cur = root; + for(int i = 0; i < word.length(); i++){ + int index = word.charAt(i) - 'a'; + if(cur.nexts[index] == null) + cur.nexts[index] = new Node(); + cur = cur.nexts[index]; + } + cur.end = true; + } + } + + private Trie trie; + + public MagicDictionary() { + trie = new Trie(); + } + + public void buildDict(String[] dict) { + for(int i = 0; i < dict.length; i++) + trie.insert(dict[i]); + } + + public boolean search(String word) { + return rec(trie.root, word, 0, false); + } + + public boolean rec(Node node, String word, int i, boolean isOneDiff){ + if(i == word.length() && node.end && isOneDiff) + return true; + else if(i == word.length()) + return false; + int index = word.charAt(i) - 'a'; + for(int k = 0; k < 26; k++){ + if(node.nexts[k] == null) + continue; + if(k == index && !isOneDiff){ + if(rec(node.nexts[k], word, i+1, false)) + return true; + } else if(k == index && isOneDiff){ + if(rec(node.nexts[k], word, i+1, true)) + return true; + }else if(k != index && !isOneDiff){ + if(rec(node.nexts[k], word, i+1, true)) + return true; + } + // k!=index && isOneDiff shouldn't be consider + } + return false; + } +} +``` + +*** \ No newline at end of file diff --git "a/Algorithm/LeetCode/DataStructure/Trie/LeetCode-677.Map Sum Pairs(\351\224\256\345\200\274\346\230\240\345\260\204)(\345\255\227\345\205\270\346\240\221\345\217\230\345\275\242).md" "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-677.Map Sum Pairs(\351\224\256\345\200\274\346\230\240\345\260\204)(\345\255\227\345\205\270\346\240\221\345\217\230\345\275\242).md" new file mode 100644 index 00000000..8d20e45f --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-677.Map Sum Pairs(\351\224\256\345\200\274\346\230\240\345\260\204)(\345\255\227\345\205\270\346\240\221\345\217\230\345\275\242).md" @@ -0,0 +1,147 @@ +# LeetCode - 677. Map Sum Pairs(键值映射)(字典树变形) +#### [题目链接](https://leetcode.com/problems/map-sum-pairs/description/) + +> https://leetcode.com/problems/map-sum-pairs/description/ + +#### 题目 +![在这里插入图片描述](images/677_t.png) +#### 解析 +做这题之前先学[字典树基础](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/Trie/LeetCode%20-%20208.%20Implement%20Trie%20(Prefix%20Tree)%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%AD%97%E5%85%B8%E6%A0%91(%E5%89%8D%E7%BC%80%E6%A0%91).md)。这个题目和普通字典树不同的是结点内部存放的是`val`,一个具体的值,不是`path`和`end`。 + + - `insert()`也没什么好说的,注意找到末尾结点之后,维护`val`; + - **注意求这里的`sum`,分为两部,第一步先找到对应字符串的结尾结点,然后使用递归来求解它所有孩子的结点的值的和**; + +图: + +![这里写图片描述](images/677_s.png) + +代码: + +```java +class MapSum { + + private class Node{ + public int val; + public Node[] next;//使用整数表示字符 c - 'a' + + public Node() { + val = 0; + next = new Node[26]; + } + } + + private Node root; + + public MapSum() { + root = new Node(); + } + + public void insert(String key, int val) { + if(key == null) + return; + Node cur = root; + int index = 0; + for(int i = 0; i < key.length(); i++){ + index = key.charAt(i) - 'a'; + if(cur.next[index] == null) + cur.next[index] = new Node(); + cur = cur.next[index]; + } + cur.val = val; + } + + public int sum(String prefix) { + if(prefix == null) + return 0; + Node cur = root; + int index = 0; + for(int i = 0; i < prefix.length(); i++){ + index = prefix.charAt(i) - 'a'; + if(cur.next[index] == null) + return 0; + cur = cur.next[index]; + } + + //开始递归求解 所有孩子的值的和 + return process(cur); + } + + public int process(Node node){ + if(node == null) + return 0; + int sum = node.val; + for(Node cur : node.next){ + sum += process(cur); + } + return sum; + } +} +``` + + +使用`HashMap`来替代数组`next`的写法: + +```java +class MapSum { + + private class Node{ + public int val; + public HashMap nexts; + + public Node() { + val = 0; + nexts = new HashMap<>(); + } + } + + private Node root; + + public MapSum() { + root = new Node(); + } + + public void insert(String key, int val) { + if(key == null) + return; + Node cur = root; + for(int i = 0; i < key.length(); i++){ + char c = key.charAt(i); +// Node nxt = cur.nexts.get(c); +// if(nxt == null) { +// nxt = new Node(); +// cur.nexts.put(c, nxt); +// } + cur.nexts.computeIfAbsent(c, k -> new Node()); + cur = cur.nexts.get(c); + } + cur.val = val; + } + + public int sum(String prefix) { + if(prefix == null) + return 0; + Node cur = root; + int index = 0; + for(int i = 0; i < prefix.length(); i++){ + char c = prefix.charAt(i); + if(cur.nexts.get(c) == null) + return 0; + cur = cur.nexts.get(c); + } + //开始递归求解 所有孩子的值的和 + return process(cur); + } + + public int process(Node node){ + if(node == null) + return 0; + int sum = node.val; + for(Map.Entryentry : node.nexts.entrySet()){ + sum += process(entry.getValue()); + } + return sum; + } +} + +``` + diff --git "a/Algorithm/LeetCode/DataStructure/Trie/LeetCode-720.Longest Word in Dictionary(\345\255\227\345\205\270\346\240\221).md" "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-720.Longest Word in Dictionary(\345\255\227\345\205\270\346\240\221).md" new file mode 100644 index 00000000..ef55c5c5 --- /dev/null +++ "b/Algorithm/LeetCode/DataStructure/Trie/LeetCode-720.Longest Word in Dictionary(\345\255\227\345\205\270\346\240\221).md" @@ -0,0 +1,136 @@ +# LeetCode - 720. Longest Word in Dictionary(字典树) + +#### [题目链接](https://leetcode.com/problems/longest-word-in-dictionary/) + +> https://leetcode.com/problems/longest-word-in-dictionary/ + +#### 题目 + +![img.png](images/720_t.png) + +## 解析 + +第一种方法: 暴力。这方法没什么好说的。。 + +* 先将`words`构建成一个`HashSet`表,这样查询快; +* 然后对于每一个`word`,去到`HashSet`中查是不是都在,如果都在,就和最优答案比较就行了,注意长度和字典序的更新; + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + // return the longest word(have all prefix in the words) with the smallest lexicographical order. + public String longestWord(String[] words) { + HashSetdict = new HashSet<>(Arrays.asList(words)); + String res = ""; + for(int i = 0; i < words.length; i++){ + // 剪枝 + if(words[i].length() < res.length() || (words[i].length() == res.length() && words[i].compareTo(res) > 0)) + continue; + StringBuilder prefix = new StringBuilder(); + boolean ok = true; + for(int j = 0; j < words[i].length(); j++){ + prefix.append(words[i].charAt(j)); + if(!dict.contains(prefix.toString())){ + ok = false; + break; + } + } + if(ok) + res = words[i]; + } + return res; + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + String[] words = {"a", "banana", "app", "appl", "ap", "apply", "apple" }; + out.println(new Solution().longestWord(words)); + } +} + +``` + +第二种方法: 利用字典树 + +也很简单: + +* 先构造好字典树,对`words`中的每一个`word`都`insert`进字典树; +* 然后对于每一个`word`在字典树中遍历,看是否每个前缀都在字典树中,如果是,就更新最好答案即可; + +图: + +![](images/720_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private class Trie{ + public int end; + public Trie[] next; + + public Trie(){ + end = 0; + next = new Trie[26]; + } + } + + private Trie root; + + // return the longest word(have all prefix in the words) with the smallest lexicographical order. + public String longestWord(String[] words) { + root = new Trie(); + Trie cur; + for(int i = 0; i < words.length; i++){ + cur = root; + for(int j = 0; j < words[i].length(); j++){ + int idx = words[i].charAt(j) - 'a'; + if(cur.next[idx] == null) + cur.next[idx] = new Trie(); + cur = cur.next[idx]; + } + cur.end = 1; + } + String res = ""; + for(int i = 0; i < words.length; i++){ + // 剪枝,这样就不要在下面判断了 + if(words[i].length() < res.length() || (words[i].length() == res.length() && words[i].compareTo(res) > 0)) + continue; + boolean ok = true; + cur = root; + for(int j = 0; j < words[i].length(); j++){ + int idx = words[i].charAt(j) - 'a'; + if(cur.next[idx].end == 0){ + ok = false; + break; + } + cur = cur.next[idx]; + } + if(!ok) + continue; +// if(words[i].length() > res.length() || (words[i].length() == res.length() && words[i].compareTo(res) < 0)) + res = words[i]; // 不判断了,在开始已经剪枝了,这样更快 + + } + return res; + } + + public static void main(String[] args){ + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + String[] words = {"a", "banana", "app", "appl", "ap", "apply", "apple" }; + out.println(new Solution().longestWord(words)); + } +} + +``` \ No newline at end of file diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/211_t.png" b/Algorithm/LeetCode/DataStructure/Trie/assets/211_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/211_t.png" rename to Algorithm/LeetCode/DataStructure/Trie/assets/211_t.png diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/14_-t.png b/Algorithm/LeetCode/DataStructure/Trie/images/14_-t.png new file mode 100644 index 00000000..6792ec04 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/14_-t.png differ diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/14_s.png b/Algorithm/LeetCode/DataStructure/Trie/images/14_s.png new file mode 100644 index 00000000..650d29bb Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/14_s.png differ diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/14_s2.png b/Algorithm/LeetCode/DataStructure/Trie/images/14_s2.png new file mode 100644 index 00000000..1a056c63 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/14_s2.png differ diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/14_s3.png b/Algorithm/LeetCode/DataStructure/Trie/images/14_s3.png new file mode 100644 index 00000000..895ccc7e Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/14_s3.png differ diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/14_s4.png b/Algorithm/LeetCode/DataStructure/Trie/images/14_s4.png new file mode 100644 index 00000000..8540d159 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/14_s4.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/211_s.png" b/Algorithm/LeetCode/DataStructure/Trie/images/211_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/211_s.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/211_s.png diff --git a/Algorithm/LeetCode/DataStructure/Trie/images/211_t.png b/Algorithm/LeetCode/DataStructure/Trie/images/211_t.png new file mode 100644 index 00000000..2a154b5c Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/Trie/images/211_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/676_s.png" b/Algorithm/LeetCode/DataStructure/Trie/images/676_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/676_s.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/676_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/676_t.png" b/Algorithm/LeetCode/DataStructure/Trie/images/676_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/676_t.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/676_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/677_s.png" b/Algorithm/LeetCode/DataStructure/Trie/images/677_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/677_s.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/677_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/677_t.png" b/Algorithm/LeetCode/DataStructure/Trie/images/677_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/677_t.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/677_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/720_s.png" b/Algorithm/LeetCode/DataStructure/Trie/images/720_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/720_s.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/720_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/720_t.png" b/Algorithm/LeetCode/DataStructure/Trie/images/720_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/Trie/images/720_t.png" rename to Algorithm/LeetCode/DataStructure/Trie/images/720_t.png diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-200.Number of Islands.md b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-200.Number of Islands.md new file mode 100644 index 00000000..3d95d489 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-200.Number of Islands.md @@ -0,0 +1,116 @@ +# LeetCode - 200. Number of Islands + +#### [题目链接](https://leetcode-cn.com/problems/number-of-islands/) + +> https://leetcode-cn.com/problems/number-of-islands/ + +#### 题目 + +![1554804160739](assets/1554804160739.png) + +## 解析 + +dfs写法,很简单的寻找连通块的问题。 + +

代码: + +```java +class Solution { + public int numIslands(char[][] grid) { + int res = 0; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == '1') { + dfs(grid, i, j); + res++; + } + } + } + return res; + } + + private void dfs(char[][] grid, int i, int j) { + if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return; + if (grid[i][j] == '0') return; + grid[i][j] = '0'; + dfs(grid, i, j - 1); + dfs(grid, i - 1, j); + dfs(grid, i + 1, j); + dfs(grid, i, j + 1); + } +} +``` + +并查集写法。 + +也是按照上面的思路,只不过将四周可以合并的点合并到同一个集合中,一开始并查集数目为 `count = n * m`,以后每次合并两个元素,`count--`,最后就是连通块的数目。 + +```java +class Solution { + + class UF { + int count = 0; // 统计集合的数目 + int[] parent, rank; + + UF(int n, int count) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + rank[i] = 1; + } + this.count = count; + + } + + int find(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + void union(int a, int b) { + int aR = find(a); + int bR = find(b); + if (aR == bR) + return; + count--; // 每次合并一个就减掉一个 + if(aR < bR){ + parent[aR] = bR; + }else if(aR > bR){ + parent[bR] = aR; + }else { + parent[aR] = bR; + rank[bR]++; + } + } + } + + public int numIslands(char[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) return 0; + int n = grid.length; + int m = grid[0].length; + int count = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == '1') + count++; + } + } + UF uf = new UF(n * m, count); + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == '0') continue; + if (i > 0 && grid[i - 1][j] == '1') uf.union(i * m + j, (i - 1) * m + j); + if (i < n - 1 && grid[i + 1][j] == '1') uf.union(i * m + j, (i + 1) * m + j); + if (j > 0 && grid[i][j - 1] == '1') uf.union(i * m + j, i * m + j - 1); + if (j < m - 1 && grid[i][j + 1] == '1') uf.union(i * m + j, i * m + j + 1); + } + } + return uf.count; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-547.Friend Circles.md b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-547.Friend Circles.md new file mode 100644 index 00000000..777a5437 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-547.Friend Circles.md @@ -0,0 +1,100 @@ +# LeetCode - 547. Friend Circles + +#### [题目链接](https://leetcode.com/problems/friend-circles/) + +> https://leetcode.com/problems/friend-circles/ + +#### 题目 + +![1554997814220](assets/1554997814220.png) + +## 解析 + +就是求连通块的数量,非常简单的图论入门题目。 + +DFS: + +```java +class Solution { + + private boolean[] vis; + + public int findCircleNum(int[][] M) { + int n = M.length; + int res = 0; + vis = new boolean[n + 1]; + for (int i = 0; i < n; i++) { + if (!vis[i]) { + dfs(i, M); + res++; + } + } + return res; + } + + private void dfs(int v, int[][] M) { + vis[v] = true; + for (int i = 0; i < M.length; i++)if (M[v][i] == 1 && !vis[i]) dfs(i, M); + } +} +``` + +并查集: + +```java +class Solution { + + class UF { + + private int[] parent; + private int[] rank; + + public UF(int n) { + parent = new int[n]; + rank = new int[n]; + for (int i = 0; i < n; i++) { + parent[i] = i; + rank[i] = 0; + } + } + + public int find(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public void union(int a, int b) { + int aR = find(a); + int bR = find(b); + if (aR == bR) + return; + if (rank[aR] < rank[bR]) { + parent[aR] = bR; + } else if (rank[aR] < rank[bR]) { + parent[bR] = aR; + } else { + parent[aR] = bR; + rank[bR]++; + } + } + } + + + public int findCircleNum(int[][] M) { + int n = M.length; + UF uf = new UF(n); + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (M[i][j] == 1) uf.union(i, j); + } + } + int res = 0; + for (int i = 0; i < n; i++) if (uf.find(i) == i) res++; + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-684.Redundant Connection.md b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-684.Redundant Connection.md new file mode 100644 index 00000000..bf4a60a3 --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-684.Redundant Connection.md @@ -0,0 +1,117 @@ +# LeetCode - 684. Redundant Connection (DFS | 并查集) + +#### [题目链接](https://leetcode.com/problems/redundant-connection/) +> https://leetcode.com/problems/redundant-connection/ + +#### 题目 +![在这里插入图片描述](images/684_t.png) + +#### DFS + +比较简单的图论题。 + +思路: + +* 每次添加一条边,然后判断加上这条边之后会不会构成环; +* 判断一个图有没有环用`dfs`,这里需要维护一个`pre`变量,表示上次访问的节点,然后使用`vis`数组标记以及访问的节点,如果再次访问到,就表明有环了; + +代码: + + +```java +class Solution { + public int[] findRedundantConnection(int[][] edges) { + if (edges == null || edges.length == 0) + return new int[2]; + int n = edges.length; + ArrayList G[] = new ArrayList[n+1]; //二维数组中的整数在1到N之间,其中N是输入数组的大小。 + for(int i = 1; i <= n; i++) + G[i] = new ArrayList<>(); + for (int i = 0; i < edges.length; i++) { + int from = edges[i][0]; + int to = edges[i][1]; + G[from].add(to); + G[to].add(from); + boolean[] vis = new boolean[n+1]; + if(!dfs(from, -1, vis, G))// 从当前节点出发查找 + return edges[i]; + } + return new int[2]; + } + + // 判断一个图有没有环(维护一个pre变量) + private boolean dfs(int v, int pre, boolean[] vis, ArrayListG[]){ + if(vis[v]) + return false; + vis[v] = true; + for(int next : G[v]){ + if(next != pre) + if(!dfs(next, v, vis, G)) + return false; + } + return true; + } +} +``` + +*** +### 并查集 + +并查集模板题。 + +* 直接判断当前的两个顶点有没有在同一个集合中,如果是,则一定会构成环; +* 否则合并这两个顶点即可; +代码: + +```java +class Solution { + + private int[] parent; + private int[] rank; + + public int findRoot(int p) { + while (parent[p] != p) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public void union(int a, int b) { + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + if (rank[aRoot] < rank[bRoot]) { + parent[aRoot] = bRoot; + } else if (rank[aRoot] > rank[bRoot]) { + parent[bRoot] = aRoot; + } else { + parent[aRoot] = bRoot; + rank[bRoot]++; + } + } + + public int[] findRedundantConnection(int[][] edges) { + if (edges == null || edges.length == 0) + return new int[2]; + int n = edges.length; + parent = new int[n + 1]; + rank = new int[n + 1]; + for (int i = 1; i <= n; i++) { + parent[i] = i; + rank[i] = 1; + } + + for (int i = 0; i < edges.length; i++) { + int from = edges[i][0]; + int to = edges[i][1]; + if (findRoot(from) == findRoot(to)) + return edges[i]; + else + union(from, to); + } + return new int[2]; + } +} +``` \ No newline at end of file diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-685.Redundant Connection II.md b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-685.Redundant Connection II.md new file mode 100644 index 00000000..97681b8a --- /dev/null +++ b/Algorithm/LeetCode/DataStructure/UnionFind/LeetCode-685.Redundant Connection II.md @@ -0,0 +1,138 @@ +# LeetCode - 685. Redundant Connection II + +#### [题目链接](https://leetcode.com/problems/redundant-connection-ii/) + +> https://leetcode.com/problems/redundant-connection-ii/ + +#### 题目 +![在这里插入图片描述](images/685_t.png) + +#### 解析 + +这个和上面那个不同的是这里是有向图。 + +由于必须要求父节点个数为`1`(除根节点),那么添加额外边后不合法只有两种情况 : + +* `case 1` : **没有节点存在两个父节点**,但是存在环; (和`LeetCode - 684. Redundant Connection`一样) + +![在这里插入图片描述](images/684_s.png) +* `case 2` : 又可以分为两种情况: + * `case 2.1` : 一个节点存在两个父节点但是不存在环; + + ![在这里插入图片描述](images/685_s2.png) + * `case 2.2` : 一个节点存在两个父节点且存在环; + + ![在这里插入图片描述](images/685_s3.png) + + +代码实现: + +* 一开始先处理有两个父亲的节点,分别将有两个父亲的节点相连的边记为 `candiA(更早的)、candiB(更晚的)`; +* 然后有一个很重要的处理,将更晚的的父亲对应的那条边(`candiB`)标记为`-1`,这条边在后续并查集中不考虑;(如果后面并查集没有找到环,就会返回`candiB`); +* 并查集过程: ①若存在回路且没有节点有两个父节点,那么返回最后遇到的一条边即可(`case1`);②若存在回路且有节点是有`2`个`parent`,则返回`candiA`(更早标记的那条在环中的边); +* 注意最后返回的`candiB`有两种情况: ①`case 2.1`;②`case 2.2`的变式,看下面,如果给定数组的顺序改变如下: + ![在这里插入图片描述](images/685_s4.png) + + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private int[] parent; + private int[] rank; + + public int findRoot(int p){ + while(parent[p] != p){ + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + public void union(int a, int b){ + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if(aRoot == bRoot) + return; + if(rank[aRoot] < rank[bRoot]){ + parent[aRoot] = bRoot; + }else if(rank[aRoot] > rank[bRoot]){ + parent[bRoot] = aRoot; + }else { + parent[aRoot] = bRoot; + rank[bRoot]++; + } + } + + public int[] findRedundantDirectedConnection(int[][] edges) { + if(edges == null || edges.length == 0 || edges[0].length == 0) + return new int[2]; + int n = edges.length; + parent = new int[n + 1]; + rank = new int[n + 1]; + + int[] candiA = new int[]{-1, -1}; + int[] candiB = new int[]{-1, -1}; + + // 第一遍循环,若节点存在有两个父节点,那么将这两条边分别作为候选A, B + for(int[] edge : edges){ + int from = edge[0]; + int to = edge[1]; + + if(parent[to] == 0){ // now, 'to' don't have parent + parent[to] = from; // now, 'to' only have one parent, is 'from' + }else { // 'to' already have a parent + candiA[0] = parent[to]; // pre parent + candiA[1] = to; + + candiB[0] = from; + candiB[1] = to; + + edge[0] = edge[1] = -1; // mark this, unionSet will don't consider this + } + } + + // init unionSet + for(int i = 1; i <= n; i++){ + parent[i] = i; + rank[i] = 1; // height + } + + for(int[] edge : edges){ + int from = edge[0]; + int to = edge[1]; + if(from == -1 || to == -1) // the later(second) edge, don't consider + continue; + if(findRoot(from) == findRoot(to)){ // 添加这条边之后存在回路 + if(candiA[0] == -1 && candiA[1] == -1) // no 2 parent + return edge; + else // have 2 parent, can't return edge, return previous candinate + return candiA; + }else + union(from, to); + } + + return candiB; // case2.1 && case2.2 egde[0] = edge[1] = -1 + } + + public static void main(String[] args){ + PrintStream out = System.out; + + int[][] edges = { + {1, 2}, + {2, 3}, + {3, 4}, + {4, 1}, + {1, 5} + }; + out.println(Arrays.toString(new Solution(). + findRedundantDirectedConnection(edges)) + ); + } +} +``` + diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804160739.png b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804160739.png new file mode 100644 index 00000000..7e0a4fc9 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804160739.png differ diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804402132.png b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804402132.png new file mode 100644 index 00000000..290f6993 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554804402132.png differ diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997811727.png b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997811727.png new file mode 100644 index 00000000..81c88416 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997811727.png differ diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997813169.png b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997813169.png new file mode 100644 index 00000000..81c88416 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997813169.png differ diff --git a/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997814220.png b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997814220.png new file mode 100644 index 00000000..81c88416 Binary files /dev/null and b/Algorithm/LeetCode/DataStructure/UnionFind/assets/1554997814220.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/684_s.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/684_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/684_s.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/684_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/684_t.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/684_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/684_t.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/684_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s2.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/685_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s2.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/685_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s3.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/685_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s3.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/685_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s4.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/685_s4.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_s4.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/685_s4.png diff --git "a/\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_t.png" b/Algorithm/LeetCode/DataStructure/UnionFind/images/685_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Data Structure/UnionFind/images/685_t.png" rename to Algorithm/LeetCode/DataStructure/UnionFind/images/685_t.png diff --git "a/Algorithm/LeetCode/DivideConquer/LeetCode-169.Majority Element (\350\256\241\346\225\260 + \344\275\215\350\277\220\347\256\227 + Partition + \345\210\206\346\262\273).md" "b/Algorithm/LeetCode/DivideConquer/LeetCode-169.Majority Element (\350\256\241\346\225\260 + \344\275\215\350\277\220\347\256\227 + Partition + \345\210\206\346\262\273).md" new file mode 100644 index 00000000..55d62d50 --- /dev/null +++ "b/Algorithm/LeetCode/DivideConquer/LeetCode-169.Majority Element (\350\256\241\346\225\260 + \344\275\215\350\277\220\347\256\227 + Partition + \345\210\206\346\262\273).md" @@ -0,0 +1,174 @@ +## LeetCode - 169. Majority Element (计数 + 位运算 + Partition + 分治) + +* [直接排序](#直接排序) +* [hashmap计数](#hashmap计数) +* [利用二进制位运算](#利用二进制位运算) +* [维护更新方法](#维护更新方法) +* [利用快排的Partition](#利用快排的partition) +* [分治方法](#分治方法) +*** +#### [题目链接](https://leetcode.com/problems/majority-element/) + +> https://leetcode.com/problems/majority-element/ + +#### 题目 +![在这里插入图片描述](images/169_t.png) + + +## 1、直接排序 +方法一: 直接排序,取中间的数,最简单的方法。 + +```java +class Solution{ + public int majorityElement(int[] nums) { + Arrays.sort(nums); + return nums[nums.length/2]; + } +} +``` +## 2、`HashMap`计数 +方法二: `HashMap`计数,然后检查`value(次数) > n/2`即可。 +```java +class Solution { + // HashMap count + public int majorityElement(int[] nums) { + HashMap counts = new HashMap<>(); + for(int num : nums){ + counts.put(num, counts.getOrDefault(num, 0) + 1); + if(counts.get(num) > nums.length/2) + return num; + } + return -1; + } +} +``` +## 3、利用二进制位运算 +方法三: 利用二进制位运算,检查每一个二进制位是否为`1`,如果是就累加`count`, 如果`>n/2`就置为`1`。 + +![在这里插入图片描述](images/169_s.png) + +代码: + +```java +class Solution { + // bit + public int majorityElement(int[] nums) { + int major = 0; + for(int i = 0; i < 32; i++){ + int count = 0; + for(int num : nums) if( (num & (1 << i)) != 0) + count++; + if(count > nums.length/2) major |= (1 << i); + } + return major; + } +} +``` +## 4、维护更新方法 +方法四: 很巧妙的方法: + +* 维护一个`count`值和当前最多`major`,然后如果当前值 `==major`,则累加`count`; +* 如果不是,就`--count`,最后的答案就是`major`,这是最好的方法,时间复杂度`O(N)`,空间`O(1)`; + +代码: + +```java +class Solution{ + // O(1) count --> best Solution + public int majorityElement(int[] nums) { + int count = 0; + int major = nums[0]; + for(int num : nums){ + if(num == major) count += 1; + else if(--count == 0){ + count = 1; + major = num; + } + } + return major; + } +} +``` +## 5、利用快排的Partition +方法五: 利用快排的[**双路快速排序**](https://blog.csdn.net/zxzxzx0119/article/details/79826380#t8),也就是按照`key`将数组划分成,左边都`<=`key,右边都`>=`key的数组,然后看返回的这个划分索引是不是等于`n/2`即可,如果不是,就按照类似二分的思想继续查找。 + +代码: + +```java +class Solution { + // use partition + public int majorityElement(int[] nums) { + int mid = nums.length >> 1; + int L = 0; + int R = nums.length - 1; + int p = partition(nums, L, R); + while(p != mid){ + if(p > mid) + R = p - 1; + else + L = p + 1; + p = partition(nums, L, R); + } + return nums[mid]; + } + + // 双路快排 : 左边部分 <= key, 右边部分 >= key + private int partition(int[] arr, int L, int R){ + int key = arr[L]; + int less = L + 1; + int more = R; + while(true){ + while(less < R && arr[less] < key) less++; // 找到第一个>=key的 + while(more > L && arr[more] > key) more--; // 找打第一个<=key的 + if(less >= more) + break; + swap(arr, less++, more--); + } + swap(arr, L, more); // 最后将key放在中间 + return more; + } + + private void swap(int[] arr, int a, int b){ + int tmp = arr[a]; + arr[a] = arr[b]; + arr[b] = tmp; + } +} +``` + +## 6、分治方法 + +方法六: 分治解法 + +* 递归求出左边的值`LS`和右边的值`RS`,如果左右相等,则直接返回; +* 否则,统计当前`[L, R]`区间中`LS`和`RS`哪一个更多,哪个多就返回哪个; + +![1554798022404](assets/1554798022404.png) + +代码: + +```java +class Solution { + // divide and conquer + public int majorityElement(int[] nums) { + return divc(nums, 0, nums.length - 1); + } + private int divc(int[] nums, int L, int R){ + if(L == R) // 只有一个元素, 直接返回 + return nums[L]; + int mid = L + (R - L)/2; + int LS = divc(nums, L, mid); + int RS = divc(nums, mid+1, R); + if(LS == RS) // 两边的众数相同,直接返回即可 + return LS; + int c1 = 0, c2 = 0; + for(int i = L; i <= R; i++){ + if(nums[i] == LS) c1++; + if(nums[i] == RS) c2++; + } + return c1 > c2 ? LS : RS; + } +} + +``` + diff --git a/Algorithm/LeetCode/DivideConquer/assets/1554798022404.png b/Algorithm/LeetCode/DivideConquer/assets/1554798022404.png new file mode 100644 index 00000000..0ec2d85b Binary files /dev/null and b/Algorithm/LeetCode/DivideConquer/assets/1554798022404.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/DivideConquer/images/169_s.png" b/Algorithm/LeetCode/DivideConquer/images/169_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DivideConquer/images/169_s.png" rename to Algorithm/LeetCode/DivideConquer/images/169_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/DivideConquer/images/169_t.png" b/Algorithm/LeetCode/DivideConquer/images/169_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/DivideConquer/images/169_t.png" rename to Algorithm/LeetCode/DivideConquer/images/169_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Graph/LeetCode - 332. Reconstruct Itinerary(\351\207\215\346\226\260\345\256\211\346\216\222\350\241\214\347\250\213 \345\233\276\350\275\254\346\240\221 \345\220\216\345\272\217\351\201\215\345\216\206).md" "b/Algorithm/LeetCode/Graph/LeetCode-332.Reconstruct Itinerary(\351\207\215\346\226\260\345\256\211\346\216\222\350\241\214\347\250\213 \345\233\276\350\275\254\346\240\221 \345\220\216\345\272\217\351\201\215\345\216\206).md" similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Graph/LeetCode - 332. Reconstruct Itinerary(\351\207\215\346\226\260\345\256\211\346\216\222\350\241\214\347\250\213 \345\233\276\350\275\254\346\240\221 \345\220\216\345\272\217\351\201\215\345\216\206).md" rename to "Algorithm/LeetCode/Graph/LeetCode-332.Reconstruct Itinerary(\351\207\215\346\226\260\345\256\211\346\216\222\350\241\214\347\250\213 \345\233\276\350\275\254\346\240\221 \345\220\216\345\272\217\351\201\215\345\216\206).md" diff --git a/Algorithm/LeetCode/Graph/LeetCode-399.Evaluate Division.md b/Algorithm/LeetCode/Graph/LeetCode-399.Evaluate Division.md new file mode 100644 index 00000000..76498866 --- /dev/null +++ b/Algorithm/LeetCode/Graph/LeetCode-399.Evaluate Division.md @@ -0,0 +1,121 @@ +# LeetCode - 399. Evaluate Division(除法求值) + +#### [题目链接](https://leetcode-cn.com/problems/evaluate-division/) + +> https://leetcode-cn.com/problems/evaluate-division/ + +#### 题目 + +![399_t.png](images/399_t.png) + +### 解析 + +将这个过程看做一个图: + +例如下图求`a / d`,只需要将`a -> d`,路径上的值相乘即可。 + +图: + +![399_s.png](images/399_s.png) + +**DFS**解法: + +```java +import java.util.*; + +class Solution { + + private HashMap> G; // construct the Graph + private HashSet vis; + + public double[] calcEquation(String[][] equations, double[] values, String[][] queries) { + G = new HashMap<>(); + for (int i = 0; i < equations.length; i++) { + String from = equations[i][0]; + String to = equations[i][1]; +// G.putIfAbsent(from, new HashMap<>()); +// G.putIfAbsent(to, new HashMap<>()); +// G.get(from).put(to, values[i]); +// G.get(to).put(from, 1.0 / values[i]); + // 上面四行写成下面两行 + G.computeIfAbsent(from, m -> new HashMap<>()).put(to, values[i]); + G.computeIfAbsent(to, m -> new HashMap<>()).put(from, 1.0 / values[i]); + } + double[] res = new double[queries.length]; + for (int i = 0; i < queries.length; i++) { + String q1 = queries[i][0]; + String q2 = queries[i][1]; + if (!G.containsKey(q1) || !G.containsKey(q2)) { + res[i] = -1; + continue; + } + vis = new HashSet<>(); + res[i] = dfs(q1, q2); + } + return res; + } + + private double dfs(String A, String B) { + if(A.equals(B)) return 1.0; + vis.add(A); + for(Map.Entry mp : G.get(A).entrySet()){ + String to = mp.getKey(); + Double value = mp.getValue(); + if(vis.contains(to)) continue; + double d = dfs(to, B); // to / B + // A/B = A/to * to/B + if(d > 0) return value * d; + } + return -1; + } +} +``` + +`C++`代码: + +```cpp +class Solution { +public: + vector calcEquation(vector> equations, vector& values, vector> queries) { + unordered_map> g; + for (int i = 0; i < equations.size(); ++i) { + const string& A = equations[i].first; + const string& B = equations[i].second; + g[A][B] = values[i]; + g[B][A] = 1.0 / values[i]; + } + + vector res; + for (const auto& pair : queries) { + const string& q1 = pair.first; + const string& q2 = pair.second; + if (!g.count(q1) || !g.count(q2)) { + res.push_back(-1.0); + continue; + } + unordered_set vis; + res.push_back(dfs(q1, q2, g, vis)); + } + return res; + } +private: + // get result of A / B + double dfs(const string& A, const string& B, + unordered_map>& g, + unordered_set& vis) { + if (A == B) return 1.0; + vis.insert(A); + for (const auto& pair : g[A]) { + const string& to = pair.first; + if (vis.count(to)) continue; + double d = dfs(to, B, g, vis); // d = to / B + // A / B = to / B * A / to + if (d > 0) return d * g[A][to]; + } + return -1.0; + } +}; + +``` + +这题还可以用**并查集**来写,不过需要记录父亲(`parent : String`)和比值`ratio : Double`,比较麻烦。给一个并查集参考代码, [代码1](https://leetcode.com/problems/evaluate-division/discuss/180282/Java-Union-Find-solution-beats-100)。[代码2](https://leetcode.com/problems/evaluate-division/discuss/183185/Java-UnionFind-VS-DFS-andand-Time-Complexity-Analysis)。 diff --git "a/\345\210\267\351\242\230/LeetCode/Graph/images/332_s.png" b/Algorithm/LeetCode/Graph/images/332_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Graph/images/332_s.png" rename to Algorithm/LeetCode/Graph/images/332_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Graph/images/332_t.png" b/Algorithm/LeetCode/Graph/images/332_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Graph/images/332_t.png" rename to Algorithm/LeetCode/Graph/images/332_t.png diff --git a/Algorithm/LeetCode/Graph/images/399_s.png b/Algorithm/LeetCode/Graph/images/399_s.png new file mode 100644 index 00000000..b66d1c5f Binary files /dev/null and b/Algorithm/LeetCode/Graph/images/399_s.png differ diff --git a/Algorithm/LeetCode/Graph/images/399_t.png b/Algorithm/LeetCode/Graph/images/399_t.png new file mode 100644 index 00000000..ed2215e9 Binary files /dev/null and b/Algorithm/LeetCode/Graph/images/399_t.png differ diff --git a/Algorithm/LeetCode/Greedy/LeetCode-455.Assign Cookies.md b/Algorithm/LeetCode/Greedy/LeetCode-455.Assign Cookies.md new file mode 100644 index 00000000..e945eee6 --- /dev/null +++ b/Algorithm/LeetCode/Greedy/LeetCode-455.Assign Cookies.md @@ -0,0 +1,41 @@ +# LeetCode - 455. Assign Cookies + +#### [题目链接](https://leetcode.com/problems/assign-cookies/) + +> https://leetcode.com/problems/assign-cookies/ + +#### 题目 + +![1554955403026](assets/1554955403026.png) + +## 解析 + +很简单的贪心题。。 + +先对两个数组都排序,然后用两个指针跑。如果`s[j] >= g[i]`,就分配,否则就不行。 + +最后返回`g`数组的指针即可。 + +代码: + +```java +class Solution { + + // g是孩子的胃口,s是饼干大小, 如果s[j] >= g[i],就可以将s[j]分配给g[i]孩子 + public int findContentChildren(int[] g, int[] s) { + Arrays.sort(g); + Arrays.sort(s); + int i = 0, j = 0; + while (i < g.length && j < s.length) { + if (s[j] >= g[i]) { + i++; + j++; + } else { + j++; + } + } + return i; + } +} +``` + diff --git a/Algorithm/LeetCode/Greedy/LeetCode-56.Merge Intervals.md b/Algorithm/LeetCode/Greedy/LeetCode-56.Merge Intervals.md new file mode 100644 index 00000000..99954933 --- /dev/null +++ b/Algorithm/LeetCode/Greedy/LeetCode-56.Merge Intervals.md @@ -0,0 +1,83 @@ +# LeetCode - 56. Merge Intervals + +#### [题目链接](https://leetcode.com/problems/merge-intervals/) + +> https://leetcode.com/problems/merge-intervals/ + +#### 题目 +![在这里插入图片描述](images/56_t.png) +## 解析 + +这题一看就是贪心的题目: + + +* **对这些区间按照`start`升序排列(不同于活动安排问题(按照结束时间排序)),然后`start`相同的按照`end`排序**; +* 然后检查前一个的`end`和当前的`start`之间的关系,如果`cur.start <= pre.end`说明有交集,然后合并即可。但是一定要注意当`pre`包含`cur`区间的时候要特殊处理; + +图: + +

+ +代码: + +```java +class Solution { + + public List merge(List intervals) { + List res = new ArrayList<>(); + if (intervals == null || intervals.size() == 0) + return res; + Collections.sort(intervals, (o1, o2) -> { + if (o1.start == o2.start) + return o1.end - o2.end; + return o1.start - o2.start; + }); + Interval pre = intervals.get(0); + res.add(pre); + for (int i = 1; i < intervals.size(); i++) { + Interval cur = intervals.get(i); + if (pre.end >= cur.start) { + res.remove(res.size() - 1); + // should consider this special situation, such as [1, 4], [2, 3] + if (cur.start > pre.start && cur.end < pre.end) + res.add(pre); + else + res.add(new Interval(pre.start, intervals.get(i).end)); + } else { // directly add cur + res.add(cur); + } + pre = res.get(res.size() - 1); + } + return res; + } +} +``` +可以将上面的过程写的更加的简洁,每次更新一下`pre`的`end`即可,每次`res`都是添加`pre`。 +```java +class Solution { + public List merge(List intervals) { + List res = new ArrayList<>(); + if (intervals == null || intervals.size() == 0) + return res; + Collections.sort(intervals, (o1, o2) -> { + if (o1.start == o2.start) + return o1.end - o2.end; + return o1.start - o2.start; + }); + Interval pre = intervals.get(0); + for (Interval cur : intervals) { + if (pre.end >= cur.start) + pre.end = Math.max(pre.end, cur.end); // the same as above special situation, [1, 4]、[2, 3] + else { // no interval + res.add(pre); + pre = cur; + } + } + res.add(pre); + return res; + } +} +``` + + + diff --git a/Algorithm/LeetCode/Greedy/LeetCode-57.Insert Interval .md b/Algorithm/LeetCode/Greedy/LeetCode-57.Insert Interval .md new file mode 100644 index 00000000..c130b67c --- /dev/null +++ b/Algorithm/LeetCode/Greedy/LeetCode-57.Insert Interval .md @@ -0,0 +1,117 @@ +# LeetCode - 57. Insert Interval + +#### [题目链接](https://leetcode.com/problems/insert-interval/) + +> https://leetcode.com/problems/insert-interval/ + +#### 题目 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20190124193959530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +两种写法。 + +## 解析 + +第一种解法是和上一题`LeetCode - 56`一样的解法,**因为已经对所有的区间排过序了**, + +所以只需要在上一题的基础上,**先找到`newInterval`的合适插入位置**。 + +然后调用上一题的`merge`过程即可。 + +```java +class Solution { + public List insert(List intervals, Interval newInterval) { + List res = new ArrayList<>(); + if(intervals == null) // 注意这里不能加上 intervals.size() == 0 + return res; + // find the suitable position that the new interval should insert + int p = 0; + for(p = 0; p < intervals.size() && intervals.get(p).start < newInterval.start; ) + p++; + intervals.add(p, newInterval); + + // just like leetcode - 56. Merge Intervals + Interval pre = intervals.get(0); + for(Interval cur : intervals){ + if(pre.end >= cur.start) + pre.end = Math.max(pre.end, cur.end); // the same as above special situation, [1, 4]、[2, 3] + else { // no interval + res.add(pre); + pre = cur; + } + } + res.add(pre); + return res; + } +} +``` + +第二种方法: + +* 就是遍历一遍`intervals`,然后如果当前遍历的`cur`,如果和`newInterval`没有交集的话,就分别各自加到`left、right`(都是`List`集合)中; +* 如果有交集的话就需要一直维护一个最左端点`newStart`和最右端点`newEnd`的区间,具体看下面(题目的样例); + +![在这里插入图片描述](images/57_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Interval { + int start; + int end; + + Interval() { + start = 0; + end = 0; + } + + Interval(int s, int e) { + start = s; + end = e; + } + + @Override + public String toString() { + return "[" + start + + ", " + end + + ']'; + } +} + +class Solution { + + public List insert(List intervals, Interval newInterval) { + List res = new ArrayList<>(); + if (intervals == null) + return res; + int newStart = newInterval.start, newEnd = newInterval.end; + List L = new ArrayList<>(); + List R = new ArrayList<>(); + for (Interval cur : intervals) { + if (cur.end < newStart) // cur small than newInterval + L.add(cur); + else if (cur.start > newEnd)// cur bigger than newInterval + R.add(cur); + else { // have overlaps(intersect) --> get the final newInterval's left and right position + newStart = Math.min(newStart, cur.start); // the smallest + newEnd = Math.max(newEnd, cur.end); // the biggest + } + } + res.addAll(L); + res.add(new Interval(newStart, newEnd)); + res.addAll(R); + return res; + } + + public static void main(String[] args) { + List intervals = Arrays.asList(new Interval(1, 2), new Interval(3, 5), + new Interval(6, 7), new Interval(8, 10), new Interval(12, 16)); + Interval newInterval = new Interval(4, 8); + System.out.println(new Solution(). + insert(intervals, newInterval) + ); + } +} +``` \ No newline at end of file diff --git "a/Algorithm/LeetCode/Greedy/LeetCode-621.Task Scheduler(\350\264\252\345\277\203).md" "b/Algorithm/LeetCode/Greedy/LeetCode-621.Task Scheduler(\350\264\252\345\277\203).md" new file mode 100644 index 00000000..f03a931d --- /dev/null +++ "b/Algorithm/LeetCode/Greedy/LeetCode-621.Task Scheduler(\350\264\252\345\277\203).md" @@ -0,0 +1,95 @@ +# LeetCode - 621. Task Scheduler(贪心) + +#### [题目链接](https://leetcode.com/problems/task-scheduler/) + +> https://leetcode.com/problems/task-scheduler/ + +#### 题目 +![在这里插入图片描述](images/621_t.png) +#### 解析 + +贪心的大体想法就是: 要尽量将CPU均匀分配完,尽量减少CPU的空闲时间。 + +* 按照频率排序,记最大的频率是`maxFreq`,最大的结果最多是(`maxFreq * (n + 1)`); +* 然而最后一组如果有`p`个剩下的(也就是如果有`p`个和`maxFreq`相同的频率),则最后一组可以在同一个槽中; +* 所以按照上面的方法的计算结果是`(maxFreq - 1) * (n + 1) + p`,但是这个结果可能小于总的`tasks`的数量,那是因为在前面一直都填满了,没有空闲时间,此时答案就是`tasks.length`即可; + +题目中的案例: + +![在这里插入图片描述](images/621_s.png) + +![在这里插入图片描述](images/621_s2.png) + +没有空闲时间,直接取`task.length`的情况: + +![在这里插入图片描述](images/621_s3.png) + +按照上面的计算的方法: + +```java +class Solution { + public int leastInterval(char[] tasks, int n) { + HashMap counts = new HashMap<>(); + int maxFreq = 0; + for (char c : tasks) { + counts.put(c, 1 + counts.getOrDefault(c, 0)); + maxFreq = Math.max(maxFreq, counts.get(c)); + } + int res = (maxFreq - 1) * (n + 1); + for (Integer v : counts.values()) + if (v == maxFreq) + res++; + return Math.max(res, tasks.length); + } +} +``` + +```java +class Solution { + public int leastInterval(char[] tasks, int n) { + int[] freqs = new int[26]; + for (char c : tasks) + freqs[c - 'A']++; + Arrays.sort(freqs); + int count = 0; + for (int i = 25; i >= 0 && freqs[i] == freqs[25]; i--) + count++; + return Math.max((freqs[25] - 1) * (n + 1) + count, tasks.length); + } +} + +``` + +还有一种使用优先队列(按照**频率大的在堆顶**)模拟的写法: +```java +class Solution { + public int leastInterval(char[] tasks, int n) { + int[] freqs = new int[26]; + for (char c : tasks) + freqs[c - 'A']++; + // initialCapacity, descend Comparator + Queue maxHeap = new PriorityQueue<>(26, Collections.reverseOrder()); + for(int freq : freqs)if(freq != 0) + maxHeap.add(freq); + int res = 0; + while(!maxHeap.isEmpty()){ + Listtmp = new ArrayList<>(); + int i = 0; + while(i < n + 1 && !maxHeap.isEmpty()){ + if(maxHeap.peek() > 1) // freq > 1 , just update freq -= 1 + tmp.add(maxHeap.poll() - 1); // 现在 -1, 待会还要装回去 + else // == 1, 直接移除 + maxHeap.poll(); + res++; + i++; + } + if(tmp.size() > 0)// 这个就是在这个周期没有装满,需要待命 + res += n + 1 - i; + maxHeap.addAll(tmp); // 装回去 + } + return res; + } +} +``` + + diff --git a/Algorithm/LeetCode/Greedy/assets/1554955403026.png b/Algorithm/LeetCode/Greedy/assets/1554955403026.png new file mode 100644 index 00000000..09fdc462 Binary files /dev/null and b/Algorithm/LeetCode/Greedy/assets/1554955403026.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/56_s.png" b/Algorithm/LeetCode/Greedy/images/56_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/56_s.png" rename to Algorithm/LeetCode/Greedy/images/56_s.png diff --git a/Algorithm/LeetCode/Greedy/images/56_ss.png b/Algorithm/LeetCode/Greedy/images/56_ss.png new file mode 100644 index 00000000..fe606ef3 Binary files /dev/null and b/Algorithm/LeetCode/Greedy/images/56_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/56_t.png" b/Algorithm/LeetCode/Greedy/images/56_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/56_t.png" rename to Algorithm/LeetCode/Greedy/images/56_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/57_s.png" b/Algorithm/LeetCode/Greedy/images/57_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/57_s.png" rename to Algorithm/LeetCode/Greedy/images/57_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/621_s.png" b/Algorithm/LeetCode/Greedy/images/621_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/621_s.png" rename to Algorithm/LeetCode/Greedy/images/621_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/621_s2.png" b/Algorithm/LeetCode/Greedy/images/621_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/621_s2.png" rename to Algorithm/LeetCode/Greedy/images/621_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/621_s3.png" b/Algorithm/LeetCode/Greedy/images/621_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/621_s3.png" rename to Algorithm/LeetCode/Greedy/images/621_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Greedy/images/621_t.png" b/Algorithm/LeetCode/Greedy/images/621_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Greedy/images/621_t.png" rename to Algorithm/LeetCode/Greedy/images/621_t.png diff --git a/Algorithm/LeetCode/LeetCodeSolutionIndex.md b/Algorithm/LeetCode/LeetCodeSolutionIndex.md new file mode 100644 index 00000000..a8b9c4f4 --- /dev/null +++ b/Algorithm/LeetCode/LeetCodeSolutionIndex.md @@ -0,0 +1,1017 @@ +# 图解LeetCode + +说明: + +* 几乎每个题目(除极简单或者不好画图的题目)用**图的方式**结合部分文字解释展现出来。 +* 大部分题目我都尽我所能的总结了**尽可能多的解法**; +* **定位**到某个题目,`chrome`浏览器按`ctrl + f`然后搜索序号或者题目名字即可(非常实用,我一般都用这个方法定位到某个题目); +* 目前项目正在初期,后面会不断完善,很多题目我都写过了,正在完善一些图解(大概300题左右)。 +* 完善做过的之后,以后大概每天增加`2~4`题; +* 图片偶尔可能会加载不出来,因为`github`偶尔会比较慢,科学上网后不会存在这个问题; + +**如果觉得还不错的话,给个`star`哦** + +列表(**之前不是按照顺序写的,但是后面我会补上前面空缺的**) + +| 题号 | 题名和解题链接 | 分类 | 难度 | +| ---- | ----------------------------------------------------------------------- | --------- | ------------- | +| 1 | [Two Sum](DataStructure/Map/LeetCode-1.Two%20Sum.md) | Hash | Easy | +| 2 | [Add Two Numbers](DataStructure/List/LeetCode-2.Add%20Two%20Numbers.md) | List | Medium | +| 3 | [Longest Substring Without Repeating Characters](TwoPointer/LeetCode-3.Longest%20Substring%20Without%20Repeating%20Characters.md) | 滑动窗口 | Medium | +| 4 | [Median of Two Sorted Arrays](BinarySearch/LeetCode-4.Median%20of%20Two%20Sorted%20Arrays.md) | 二分 | Hard | +| 5 | [Longest Palindromic Substring](String/Manacher/LeetCode-5.Longest%20Palindromic%20Substring.md) | DP、Manacher | Medium | +| 6 | [ZigZag Conversion](Simulation/median/LeetCode-6.ZigZag%20Conversion.md) | 模拟 | Medium | +| 7 | [Reverse Integer](Simulation/easy/LeetCode-7.Reverse%20Integer.md) | 模拟 | Easy | +| 8 | [String to Integer(atoi)](Simulation/median/LeetCode-8.String%20to%20Integer.md) | 模拟 | Medium | +| 9 | [Palindrome Number](Simulation/median/LeetCode-9.Palindrome%20Number.md) | 模拟 | Medium | +| 10 | [Regular Expression Matching](DP/LeetCode-10.Regular%20Expression%20Matching.md) | DP | Hard | +| 11 | [Container With Most Water](TwoPointer/LeetCode-11.Container%20With%20Most%20Water.md) | 双指针 | Medium | +| 12 | [Integer to Roman](Simulation/easy/LeetCode-12.Integer%20to%20Roman.md) | 模拟 | Medium | +| 13 | [Roman to Integer](Simulation/median/LeetCode-13.Roman%20to%20Integer.md) | 模拟 | Easy | +| 14 | [Longest Comman Prefix](DataStructure/Trie/LeetCode-14.Longest%20Common%20Prefix.md) | 分治、二分、Trie | Easy | +| 15 | [3Sum](TwoPointer/LeetCode-15.3Sum.md) | 双指针 | Medium | +| 16 | [3Sum Closest](TwoPointer/LeetCode-16.3Sum%20Closest.md) | 双指针 | Medium | +| 17 | [Letter Combination of a Phone Number](Search/LeetCode-17.Letter%20Combinations%20of%20a%20Phone%20Number.md) | Search | Medium | +| 18 | [4Sum](TwoPointer/LeetCode-18.4Sum.md) | 双指针 | Medium | +| 19 | [Remove Nth Node From End of List](DataStructure/List/LeetCode-19.Remove%20Nth%20Node%20From%20End%20of%20List.md) | 快慢指针 | Medium | +| 20 | [Valid Parentheses](DataStructure/Stack/LeetCode-20.Valid%20Parentheses.md) | Stack | Easy | +| 21 | [Merge Two Sorted Lists](DataStructure/List/LeetCode-21.Merge%20Two%20Sorted%20Lists.md) | List | Easy | +| 22 | [Generate Parentheses](Search/LeetCode-22.Generate%20Parentheses.md) | Search | Medium | +| 23 | [Merge K Sorted Lists](DataStructure/List/LeetCode-23.Merge%20k%20Sorted%20Lists.md) | List | Hard | +| 24 | [Swap Nodes in Pairs](DataStructure/List/LeetCode-24.Swap%20Nodes%20in%20Pairs.md) | List | Medium | +| 25 | [Reverse Nodes in K-Group](DataStructure/List/LeetCode-25.Reverse%20Nodes%20in%20K-Group.md) | List | Hard | +| 26 | [Remove Duplicates from Sorted Array](TwoPointer/LeetCode-26.Remove%20Duplicates%20from%20Sorted%20Array.md) | Two Pointer | Easy | +| 27 | [Remove Element](TwoPointer/LeetCode-27.Remove%20Element.md) | Two Pointer | Easy | +| 28 | [Implement strStr()](String/KMP/LeetCode-28.Implement%20strStr().md) | String、KMP | Easy | +| 29 | [Divide Two Integers](Bit/LeetCode-29.Divide%20Two%20Integers.md) | Bit | Medium | +| 30 | [Substring with Concatenation of All Words](DataStructure/Map/LeetCode-30.Substring%20with%20Concatenation%20of%20All%20Words.md) | Map | Hard | +| 31 | [Next Permutation](Simulation/median/LeetCode-31.Next%20Permutation.md) | 模拟 | Medium | +| 32 | | | | +| 33 | | | | +| 34 | [Find First and Last Position of Element in Sorted Array](BinarySearch/LeetCode-34.Find%20First%20and%20Last%20Position%20of%20Element%20in%20Sorted%20Array.md) | 二分 | Medium | +| 35 | | | | +| 36 | | | | +| 37 | [Sudoku Solver](Search/LeetCode-37.Sudoku%20Solver.md) | Search | Hard | +| 38 | | | | +| 39 | [Combination Sum](Search/LeetCode-39.Combination%20Sum.md) | Search | Medium | +| 40 | [Combination Sum II](Search/LeetCode-40.Combination%20Sum%20II%20&&%20LeetCode%20-%20216.%20Combination%20Sum%20III.md) | Search | Medium | +| 41 | | | | +| 42 | | | | +| 43 | | | | +| 44 | | | | +| 45 | | | | +| 46 | [Permutations](Search/LeetCode-46.Permutations.md) | Search | Medium | +| 47 | [Permuations II](Search/LeetCode-47.Permutations%20II.md) | Search | Medium | +| 48 | [Rotate Image](Simulation/median/LeetCode-48.Rotate%20Image.md) | 模拟 | Medium | +| 49 | | | | +| 50 | [Pow(x, n)](../DataStructure/Math/乘法快速幂相关总结%20&%20LeetCode%20-%2050.%20Pow.md) | Math | Medium | +| 51 | [N-Queens](Search/LeetCode-51.N-Queens.md) | Search | Hard | +| 52 | [N-Queens II](Search/LeetCode-52.N-Queens%20II.md) | Search | Hard | +| 53 | [Maximum Subarray](DP/LeetCode-53.Maximum%20Subarray.md) | DP | Easy | +| 54 | [Spiral Matrix](Simulation/median/LeetCode-54.Spiral%20Matrix.md) | 模拟 | Medium | +| 55 | | | | +| 56 | [Merge Intervals](Greedy/LeetCode-56.Merge%20Intervals.md) | 贪心 | Medium | +| 57 | [Insert Interval](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Greedy/LeetCode%20-%2057.%20Insert%20Interval%20.md) | 贪心 | Hard | +| 58 | | | | +| 59 | | | | +| 60 | | | | +| 61 | | | | +| 62 | [Unique Paths](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2062.%20Unique%20Paths(%E4%B8%8D%E5%90%8C%E7%9A%84%E8%B7%AF%E5%BE%84%E6%95%B0%E9%87%8F)(%E7%AE%80%E5%8D%95dp).md) | DP | Medium | +| 63 | [Unique Paths II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2063.%20Unique%20Paths%20II(%E6%9C%89%E9%9A%9C%E7%A2%8D%E7%89%A9%E7%9A%84%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84).md) | DP | Medium | +| 64 | [Minimum Path Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2064.%20Minimum%20Path%20Sum(%E6%9C%80%E5%B0%8F%E8%B7%AF%E5%BE%84%E5%92%8C).md) | DP | Medium | +| 65 | | | | +| 66 | | | | +| 67 | | | | +| 68 | | | | +| 69 | | | | +| 70 | | | | +| 71 | | | | +| 72 | [Edit Distance](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2072.%20Edit%20Distance(%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%E9%97%AE%E9%A2%98)(%E4%B8%89%E4%B8%AA%E4%BE%9D%E8%B5%96%E7%9A%84%E6%BB%9A%E5%8A%A8%E4%BC%98%E5%8C%96).md) | DP | Hard | +| 73 | | | | +| 74 | | | | +| 75 | [Sort Colors](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Sort/LeetCode%20-%2075.%20Sort%20Colors(%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F%E5%92%8C%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%8F%98%E5%BD%A2).md) | Sort、双指针 | Medium | +| 76 | | | | +| 77 | | | | +| 78 | [Subsets](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2078.%20Subsets.md) | Search | Medium | +| 79 | [Word Search](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2079.%20Word%20Search(DFS).md) | Search | Medium | +| 80 | | | | +| 81 | | | | +| 82 | | | | +| 83 | | | | +| 84 | [Largest Rectangle in Histogram](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Stack/LeetCode%20-%2084.%20Largest%20Rectangle%20in%20Histogram(%E5%8D%95%E8%B0%83%E6%A0%88).md) | 单调栈 | Hard | +| 85 | [Maximal Rectangle](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Stack/LeetCode%20-%2085.%20Maximal%20Rectangle.md) | 单调栈 | Hard | +| 86 | | | | +| 87 | | | | +| 88 | | | | +| 89 | | | | +| 90 | [Subset II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2090.%20Subsets%20II.md) | Search | Medium | +| 91 | [Decode Ways](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%2091.%20Decode%20Ways.md) | DP | Medium | +| 92 | | | | +| 93 | | | | +| 94 | | | | +| 95 | | | | +| 96 | | | | +| 97 | | | | +| 98 | [Validate Binary Search Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%2098.%20Validate%20Binary%20Search%20Tree(%E5%88%A4%E6%96%AD%E6%98%AF%E4%B8%8D%E6%98%AF%E4%B8%80%E9%A2%97%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E4%BB%A5%E5%8F%8A%E5%88%A4%E6%96%AD%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91).md) | Tree | Medium | +| 99 | | | | +| 100 | [Same Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20100.%20Same%20Tree(%E5%88%A4%E6%96%AD%E4%B8%A4%E6%A3%B5%E6%A0%91%E6%98%AF%E5%90%A6%E5%AE%8C%E5%85%A8%E7%9B%B8%E5%90%8C)(%E7%AE%80%E5%8D%95%E9%A2%98).md) | Tree | Easy | +| 101 | [Symmetric Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20101.%20Symmetric%20Tree.md) | Tree | Easy | +| 102 | [Binary Tree Level Order Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20102.%20Binary%20Tree%20Level%20Order%20Traversal(%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86%E4%BF%9D%E5%AD%98).md) | Tree | Medium | +| 103 | [Binary Tree Zigzag Level Order Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20103.%20Binary%20Tree%20Zigzag%20Level%20Order%20Traversal.md) | Tree | Medium | +| 104 | [Maximum Depth of Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20104.%20Maximum%20Depth%20of%20Binary%20Tree.md) | Tree | Easy | +| 105 | [Construct Binary Tree from Preorder and Inorder Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20105.%20Construct%20Binary%20Tree%20from%20Preorder%20and%20Inorder%20Traversal.md) | Tree | Medium | +| 106 | [Construct Binary Tree from Inorder and Postorder Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20106.%20Construct%20Binary%20Tree%20from%20Inorder%20and%20Postorder%20Traversal.md) | Tree | Medium | +| 107 | | | | +| 108 | | | | +| 109 | | | | +| 110 | [Balanced Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20110.%20Balanced%20Binary%20Tree(%E5%88%A4%E6%96%AD%E4%B8%80%E6%A3%B5%E6%A0%91%E6%98%AF%E5%90%A6%E6%98%AF%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91).md) | Tree | Easy | +| 111 | | | | +| 112 | [Path Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20112.%20PathSum.md) | Tree | Easy | +| 113 | [Path Sum II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20113.%20Path%20Sum%20II.md) | Tree | Medium | +| 114 | | | | +| 115 | | | | +| 116 | | | | +| 117 | | | | +| 118 | | | | +| 119 | | | | +| 120 | [Traingle](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20120.%20Traingle%20%26%20Hdu%20-%202084.%20%E6%95%B0%E5%A1%94%E9%97%AE%E9%A2%98(%E7%AE%80%E5%8D%95dp).md) | DP | Medium | +| 121 | | | | +| 122 | | | | +| 123 | | | | +| 124 | [Binary Tree Maximum Path Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20124.%20Binary%20Tree%20Maximum%20Path%20Sum.md) | Tree | Hard | +| 125 | [Valid Palindrome](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20125.%20Valid%20Palindrome.md) | 双指针 | Easy | +| 126 | [Word Ladder II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20126.%20Word%20Ladder%20II.md) | Search | Hard | +| 127 | [Word Ladder](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20127.%20Word%20Ladder.md) | Search | Medium | +| 128 | [Longest Consecutive Sequence](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20128.%20Longest%20Consecutive%20Sequence%20(%E5%93%88%E5%B8%8C%E8%A1%A8).md) | Map | | +| 129 | | | | +| 130 | | | | +| 131 | | | | +| 132 | | | | +| 133 | | | | +| 134 | | | | +| 135 | | | | +| 136 | [Single Number](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Bit/LeetCode%20-%20136.%20Single%20Number.md) | Bit | Easy | +| 137 | [Single Number II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Bit/LeetCode%20-%20137.%20Single%20Number%20II.md) | Bit | Medium | +| 138 | [Copy List with Random Pointer](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20138.%20Copy%20List%20with%20Random%20Pointer(%E5%90%AB%E6%9C%89%E9%9A%8F%E6%9C%BA%E6%8C%87%E9%92%88%E7%9A%84%E9%93%BE%E8%A1%A8%E7%9A%84%E6%8B%B7%E8%B4%9D).md) | List | Medium | +| 139 | [Word Break](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20139.%20Word%20Break.md) | DP | Medium | +| 140 | [Word Break II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20140.%20Word%20Break%20II.md) | DP | Hard | +| 141 | [Linked List Cycle](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20141.%20Linked%20List%20Cycle.md) | List | Easy | +| 142 | [Linked List Cycle II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20142.%20Linked%20List%20Cycle%20II.md) | List | Medium | +| 143 | | | | +| 144 | | | | +| 145 | [Binary Tree Postorder Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20145.%20Binary%20Tree%20Postorder%20Traversal(%E5%AE%9E%E7%8E%B0%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86)(%E4%B8%89%E7%A7%8D%E9%9D%9E%E9%80%92%E5%BD%92%E6%96%B9%E5%BC%8F).md) | Tree | Hard | +| 146 | [LRU Cache](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20146.%20LRU%20Cache(LRU%E7%BC%93%E5%AD%98%E5%8F%98%E6%9B%B4%E7%AE%97%E6%B3%95)(LinkedHashMap%E5%BA%95%E5%B1%82).md) | List、Map | Hard | +| 147 | | | | +| 148 | | | | +| 149 | [Max Points on a Line](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Math/LeetCode%20-%20149.%20Max%20Points%20on%20a%20Line(%E5%93%88%E5%B8%8C%E8%A1%A8%20%E3%80%81%E6%95%B0%E5%AD%A6).md) | Math、Map | Hard | +| 150 | | | | +| 151 | | | | +| 152 | [Maximum Product Subarray](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20152.%20Maximum%20Product%20Subarray(%E5%AD%90%E6%95%B0%E7%BB%84%E6%9C%80%E5%A4%A7%E7%B4%AF%E4%B9%98%E7%A7%AF).md) | DP | Medium | +| 153 | | | | +| 154 | | | | +| 155 | | | | +| 156 | | | | +| 157 | | | | +| 158 | | | | +| 159 | | | | +| 160 | [Intersectin of Two Linked Lists](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20160.%20Intersectin%20of%20Two%20Linked%20Lists.md) | List | Easy | +| 161 | | | | +| 162 | | | | +| 163 | | | | +| 164 | [Maximum Gap](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Bucket/LeetCode%20-%20164.%20Maximum%20Gap(%E6%9C%80%E5%A4%A7%E9%97%B4%E8%B7%9D)(%E6%A1%B6).md) | Buckets | Hard | +| 165 | | | | +| 166 | | | | +| 167 | [Two Sum II - Input array is sorted](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20167.%20Two%20Sum%20II%20-%20Input%20array%20is%20sorted.md) | 双指针 | Easy | +| 168 | | | | +| 169 | [Majority Element](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DivideConquer/LeetCode%20-%20169.%20Majority%20Element%20(%E8%AE%A1%E6%95%B0%20%2B%20%E4%BD%8D%E8%BF%90%E7%AE%97%20%2B%20Partition%20%2B%20%E5%88%86%E6%B2%BB).md) | Bit、分治 | Easy | +| 170 | | | | +| 171 | | | | +| 172 | | | | +| 173 | | | | +| 174 | [Dungeon Game](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20174.%20Dungeon%20Game(%E5%9C%B0%E4%B8%8B%E5%9F%8E%E6%B8%B8%E6%88%8F)(dp).md) | DP | Hard | +| 175 | | | | +| 176 | | | | +| 178 | | | | +| 179 | | | | +| 180 | | | | +| 181 | | | | +| 182 | | | | +| 183 | | | | +| 184 | | | | +| 185 | | | | +| 186 | | | | +| 187 | | | | +| 188 | | | | +| 189 | | | | +| 190 | | | | +| 191 | | | | +| 192 | | | | +| 193 | | | | +| 194 | | | | +| 195 | | | | +| 196 | | | | +| 197 | | | | +| 198 | | | | +| 199 | | | | +| 200 | [Number of Islands](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/UnionFind/LeetCode%20-%20200.%20Number%20of%20Islands.md) | Search、并查集 | Medium | +| 201 | | | | +| 202 | | | | +| 203 | [Remove Linked List Elements](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20203.%20Remove%20Linked%20List%20Elements.md) | List | Easy | +| 204 | [Count Primes](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Math/LeetCode%20-%20204.%20Count%20Primes.md) | Math | Easy | +| 205 | | | | +| 206 | [Reverse Linked List](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20206.%20Reverse%20Linked%20List.md) | List | Easy | +| 207 | [Course Schedule](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Graph/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/Uva%20-%2010305.%20Ordering%20Tasks%20_%20LeetCode%20-%20207.%20Course%20Schedule.md#leetcode---207-course-schedule) | 拓扑排序 | Medium | +| 208 | [Implement Trie Prefix Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/Trie/LeetCode%20-%20208.%20Implement%20Trie%20(Prefix%20Tree)%E4%BB%A5%E5%8F%8A%E5%AE%9E%E7%8E%B0%E5%AD%97%E5%85%B8%E6%A0%91(%E5%89%8D%E7%BC%80%E6%A0%91).md) | Trie | Medium | +| 209 | [Minimum Size Subarray Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20209.%20Minimum%20Size%20Subarray%20Sum(%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3).md) | 滑动窗口 | Medium | +| 210 | | | | +| 211 | [Add and Search Word - Data structure design](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Trie/LeetCode%20-%20211.%20Add%20and%20Search%20Word%20-%20Data%20structure%20design(%E5%AD%97%E5%85%B8%E6%A0%91%E5%92%8C%E9%80%92%E5%BD%92).md) | Trie | Medium | +| 212 | | | | +| 213 | | | | +| 214 | [Shortest Palindrome](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/String/KMP/LeetCode%20-%20214.%20Shortest%20Palindrome(KMP%E5%92%8CManacher%E7%AE%97%E6%B3%95%E8%A7%A3%E5%86%B3).md) | KMP、Manacher | Hard | +| 215 | [Kth Largest Element in an Array](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Heap/LeetCode%20-%20215.%20Kth%20Largest%20Element%20in%20an%20Array.md) | Heap,Partition | Medium | +| 216 | [Combination Sum III](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2040.%20Combination%20Sum%20II%20%26%26%20LeetCode%20-%20216.%20Combination%20Sum%20III%20%20(DFS).md#leetcode---216-combination-sum-III) | Search、Bit | Medium | +| 217 | | | | +| 218 | | | | +| 219 | | | | +| 220 | | | | +| 221 | [Maximal Square](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20221.%20Maximal%20Square(%E6%B1%82%E6%9C%80%E5%A4%A7%E7%9A%84%E5%85%A8%E6%98%AF1%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2).md) | DP | Medium | +| 222 | [LeetCode - 222. Count Complete Tree Nodes](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20222.%20Count%20Complete%20Tree%20Nodes(%E7%BB%9F%E8%AE%A1%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%BB%93%E7%82%B9%E4%B8%AA%E6%95%B0).md) | Tree | Medium | +| 223 | | | | +| 224 | | | | +| 225 | | | | +| 226 | | | | +| 227 | [Basic Calculator II,系列计算器和栈的题目](https://github.com/ZXZxin/ZXBlog/tree/master/%E5%88%B7%E9%A2%98/Other/LintCode/Data%20Structure/Stack) | Stack | Medium | +| 228 | | | | +| 229 | [Majority Element II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/%E5%89%91%E6%8C%87Offer/%E5%89%91%E6%8C%87Offer%20-%2028%20-%20%E6%95%B0%E7%BB%84%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E8%B6%85%E8%BF%87%E4%B8%80%E5%8D%8A%E7%9A%84%E6%95%B0%E5%AD%97.md#leetcode---229-majorityelementii) | Array | Medium | +| 230 | | | | +| 231 | | | | +| 232 | | | | +| 233 | | | | +| 234 | [Palindrome Linked List](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20234.%20Palindrome%20Linked%20List(%E5%9B%9E%E6%96%87%E9%93%BE%E8%A1%A8).md) | List | Easy | +| 235 | | | | +| 236 | | | | +| 237 | | | | +| 238 | | | | +| 239 | | | | +| 240 | | | | +| 241 | [Different Ways to Add Parentheses](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20241.%20Different%20Ways%20to%20Add%20Parentheses(%E5%88%86%E6%B2%BB%E3%80%81dp).md) | 分治、DP | Medium | +| 242 | [Valid Anagram](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20242.%20Valid%20Anagram.md) | Map | Easy | +| 243 | | | | +| 244 | | | | +| 245 | | | | +| 246 | | | | +| 247 | | | | +| 248 | | | | +| 249 | | | | +| 250 | | | | +| 251 | | | | +| 252 | | | | +| 253 | | | | +| 254 | | | | +| 255 | | | | +| 256 | | | | +| 257 | | | | +| 258 | | | | +| 259 | | | | +| 260 | | | | +| 261 | | | | +| 262 | | | | +| 263 | [Ugly Number](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20263.%20Ugly%20Number.md) | 模拟 | Easy | +| 264 | [Ugly Number II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/%E5%89%91%E6%8C%87Offer/%E5%89%91%E6%8C%87Offer%20-%2033%20-%20%E4%B8%91%E6%95%B0.md) | 堆、DP | Medium | +| | | | | +| 266 | | | | +| 267 | | | | +| 268 | | | | +| 269 | | | | +| 270 | | | | +| 271 | | | | +| 272 | | | | +| 273 | | | | +| 274 | | | | +| 275 | | | | +| 276 | | | | +| 277 | | | | +| 278 | | | | +| 279 | | | | +| 280 | | | | +| 281 | | | | +| 282 | | | | +| 283 | [Move Zeroes](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20283.%20Move%20Zeroes(%E7%A7%BB%E5%8A%A8%E9%9B%B6)(%E7%AE%80%E5%8D%95%E9%A2%98)(%E4%B8%89%E7%A7%8D%E5%86%99%E6%B3%95).md) | 双指针 | Easy | +| 284 | | | | +| 285 | | | | +| 286 | | | | +| 287 | | | | +| 288 | | | | +| 289 | | | | +| 290 | | | | +| 291 | | | | +| 292 | | | | +| 293 | | | | +| 294 | | | | +| 295 | | | | +| 296 | | | | +| 297 | [Serialize and Deserialize Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20297.%20Serialize%20and%20Deserialize%20Binary%20Tree(%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96).md) | Tree | Medium | +| 298 | | | | +| 299 | | | | +| 300 | | | | +| 301 | | | | +| 302 | | | | +| 303 | [Range Sum Query - Immutable](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/SegmentTree/LeetCode%20-%20303.%20Range%20Sum%20Query%20-%20Immutable(%E7%AE%80%E5%8D%95dp%E6%88%96%E8%80%85%E7%BA%BF%E6%AE%B5%E6%A0%91).md) | DP、线段树 | Easy | +| 304 | [Range Sum Query 2D - Immutable](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20221.%20Maximal%20Square(%E6%B1%82%E6%9C%80%E5%A4%A7%E7%9A%84%E5%85%A8%E6%98%AF1%E7%9A%84%E6%AD%A3%E6%96%B9%E5%BD%A2).md#leetcode---304-range-sum-query-2d---immutable) | DP | Medium | +| 305 | | | | +| 306 | | | | +| 307 | [Range Sum Query - Mutable](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Data%20Structure/SegmentTree/%E7%BA%BF%E6%AE%B5%E6%A0%91%E6%80%BB%E7%BB%93%E4%BB%A5%E5%8F%8ALeetCode%20-%20307.%20Range%20Sum%20Query%20-%20Mutable.md#leetcode---307-range-sum-query---mutable) | 线段树 | Medium | +| 308 | | | | +| 309 | | | | +| 310 | | | | +| 311 | | | | +| 312 | [Burst Balloons](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20312.%20Burst%20Balloons(DP).md) | DP | Hard | +| 313 | | | | +| 314 | | | | +| 315 | | | | +| 316 | | | | +| 317 | | | | +| 318 | | | | +| 319 | | | | +| 320 | | | | +| 321 | | | | +| 322 | | | | +| 323 | | | | +| 324 | | | | +| 325 | | | | +| 326 | | | | +| 327 | | | | +| 328 | | | | +| 329 | | | | +| 330 | | | | +| 331 | | | | +| 332 | | | | +| 333 | | | | +| 334 | [Reverse String](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20344.%20Reverse%20String.md) | 双指针 | Easy | +| 335 | [Reverse Vowels of a String](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Two%20Pointer/LeetCode%20-%20345.%20Reverse%20Vowels%20of%20a%20String.md) | 双指针 | Easy | +| 336 | | | | +| 337 | [Top K Frequent Elements](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Heap/LeetCode%20-%20347.%20Top%20K%20Frequent%20Elements.md) | 堆 | Medium | +| 338 | | | | +| 339 | | | | +| 340 | | | | +| 341 | | | | +| 342 | | | | +| 343 | | | | +| 344 | | | | +| 345 | | | | +| 346 | | | | +| 347 | | | | +| 348 | | | | +| 349 | [Intersection of Two Arrays](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Set/LeetCode%20-%20349.%20Intersection%20of%20Two%20Arrays.md) | Set | Easy | +| 350 | [Intersection of Two Arrays II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20350.%20Intersection%20of%20Two%20Arrays%20II.md) | Map | Easy | +| 351 | | | | +| 352 | | | | +| 353 | | | | +| 354 | [Russian Doll Envelopes](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20354.%20Russian%20Doll%20Envelopes%E5%8F%8A%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97%E9%97%AE%E9%A2%98%E6%80%BB%E7%BB%93.md#leetcode---354-russian-doll-envelopes) | DP | Hard | +| 355 | | | | +| 356 | | | | +| 357 | | | | +| 358 | | | | +| 359 | | | | +| 360 | | | | +| 361 | | | | +| 362 | | | | +| 363 | | | | +| 364 | | | | +| 365 | | | | +| 366 | | | | +| 367 | | | | +| 368 | | | | +| 369 | | | | +| 370 | | | | +| 371 | | | | +| 372 | | | | +| 373 | | | | +| 374 | | | | +| 375 | | | | +| 376 | | | | +| 377 | | | | +| 378 | | | | +| 379 | | | | +| 380 | [Insert Delete GetRandom](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20380.%20Insert%20Delete%20GetRandom%20O(1)%20(%E5%B8%B8%E6%95%B0%E6%97%B6%E9%97%B4%E6%8F%92%E5%85%A5%E3%80%81%E5%88%A0%E9%99%A4%E5%92%8C%E8%8E%B7%E5%8F%96%E9%9A%8F%E6%9C%BA%E5%85%83%E7%B4%A0).md) | Map | Medium | +| 381 | [Insert Delete GetRandom O(1) - Duplicates allowed](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20381.%20Insert%20Delete%20GetRandom%20O(1)%20-%20Duplicates%20allowed(%E5%85%81%E8%AE%B8%E9%87%8D%E5%A4%8D).md) | Map | Hard | +| 382 | | | | +| 383 | | | | +| 384 | | | | +| 385 | | | | +| 386 | | | | +| 387 | | | | +| 388 | | | | +| 389 | | | | +| 390 | | | | +| 391 | | | | +| 392 | | | | +| 393 | | | | +| 394 | | | | +| 395 | | | | +| 396 | | | | +| 397 | | | | +| 398 | | | | +| 399 | [Evaluate Division](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Graph/LeetCode%20-%20399.%20Evaluate%20Division.md) | Graph | Medium | +| 400 | | | | +| 401 | | | | +| 402 | | | | +| 403 | | | | +| 404 | [Sum of Left Leaves](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20404.%20Sum%20of%20Left%20Leaves(%E5%B7%A6%E5%8F%B6%E5%AD%90%E7%BB%93%E7%82%B9%E4%B9%8B%E5%92%8C)(%E9%80%92%E5%BD%92%E5%92%8C%E9%9D%9E%E9%80%92%E5%BD%92).md) | Tree | Easy | +| 405 | | | | +| 406 | | | | +| 407 | | | | +| 408 | | | | +| 409 | | | | +| 410 | | | | +| 411 | | | | +| 412 | | | | +| 413 | | | | +| 414 | | | | +| 415 | | | | +| 416 | | | | +| 417 | | | | +| 418 | | | | +| 419 | | | | +| 420 | | | | +| 421 | | | | +| 422 | | | | +| 423 | | | | +| 424 | | | | +| 425 | | | | +| 426 | | | | +| 427 | | | | +| 428 | | | | +| 429 | | | | +| 430 | | | | +| 431 | | | | +| 432 | | | | +| 433 | | | | +| 434 | | | | +| 435 | | | | +| 436 | | | | +| 437 | | | | +| 438 | | | | +| 439 | | | | +| 440 | | | | +| 441 | | | | +| 442 | | | | +| 443 | | | | +| 444 | | | | +| 445 | | | | +| 446 | | | | +| 447 | | | | +| 448 | | | | +| 449 | [Serialize and Deserialize BST](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20297.%20Serialize%20and%20Deserialize%20Binary%20Tree(%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96).md#leetcode---449-serialize-and-deserialize-bst) | Tree | Medium | +| 450 | | | | +| 451 | | | | +| 452 | | | | +| 453 | | | | +| 454 | | | | +| 455 | [Assign Cookies](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Greedy/LeetCode%20-%20455.%20Assign%20Cookies.md) | Greedy | Easy | +| 456 | | | | +| 457 | | | | +| 458 | | | | +| 459 | | | | +| 460 | [LFU Cache](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20460.%20LFU%20Cache(LFU%E7%BC%93%E5%AD%98%E7%AE%97%E6%B3%95%EF%BC%8C%E4%BA%8C%E7%BB%B4%E9%93%BE%E8%A1%A8%E8%A7%A3%E5%86%B3).md) | List、Design | Hard | +| 461 | [Hamming Distance](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Bit/LeetCode%20-%20461.%20Hamming%20Distance(%E4%BD%8D%E8%BF%90%E7%AE%97).md) | Bit | Easy | +| 462 | | | | +| 463 | [Island Perimerter](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20463.%20Island%20Perimeter(%E5%B2%9B%E5%B1%BF%E7%9A%84%E5%91%A8%E9%95%BF)(%E8%A7%84%E5%BE%8B_DFS_BFS).md) | Search、规律 | Easy | +| 464 | | | | +| 465 | | | | +| 466 | | | | +| 467 | | | | +| 468 | | | | +| 469 | | | | +| 470 | | | | +| 471 | | | | +| 472 | | | | +| 473 | | | | +| 474 | | | | +| 475 | | | | +| 476 | | | | +| 477 | | | | +| 478 | | | | +| 479 | | | | +| 480 | | | | +| 481 | | | | +| 482 | | | | +| 483 | | | | +| 484 | | | | +| 485 | | | | +| 486 | [Predict the Winner](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20486.%20Predict%20the%20Winner(%E6%8E%92%E6%88%90%E4%B8%80%E6%9D%A1%E7%BA%BF%E7%9A%84%E7%BA%B8%E7%89%8C%E5%8D%9A%E5%BC%88%E9%97%AE%E9%A2%98).md) | DP | Medium | +| 487 | | | | +| 488 | [Zuma Game](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20488.%20Zuma%20Game%20(DFS).md) | Search | Hard | +| 489 | | | | +| 490 | | | | +| 491 | | | | +| 492 | | | | +| 493 | | | | +| 494 | | | | +| 495 | | | | +| 496 | | | | +| 497 | | | | +| 498 | | | | +| 499 | | | | +| 400 | | | | +| 501 | | | | +| 502 | [IPO](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Heap/LeetCode%20-%20502.%20IPO(%E8%B4%AA%E5%BF%83%20%2B%20%E5%A0%86).md) | Heap、Greedy | Hard | +| 503 | | | | +| 504 | [Base 7](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20504.%20Base%207.md) | 模拟 | Easy | +| 505 | | | | +| 506 | | | | +| 507 | | | | +| 508 | | | | +| 509 | | | | +| 510 | | | | +| 511 | | | | +| 512 | | | | +| 513 | | | | +| 514 | | | | +| 515 | | | | +| 516 | | | | +| 517 | | | | +| 518 | [Coin Change 2](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20518.%20Coin%20Change%202(%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II)(%E6%8D%A2%E9%92%B1%E7%9A%84%E6%96%B9%E6%B3%95%E6%95%B0%E9%97%AE%E9%A2%98).md) | DP | Medium | +| 519 | | | | +| 520 | | | | +| 521 | | | | +| 522 | | | | +| 523 | | | | +| 524 | | | | +| 525 | | | | +| 526 | | | | +| 527 | | | | +| 528 | | | | +| 529 | | | | +| 530 | | | | +| 531 | | | | +| 532 | | | | +| 533 | | | | +| 534 | | | | +| 535 | | | | +| 536 | | | | +| 537 | | | | +| 538 | | | | +| 539 | | | | +| 540 | | | | +| 541 | | | | +| 542 | | | | +| 543 | [Diameter of Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20687.%20Longest%20Univalue%20Path%20(%E6%A0%91%E7%9A%84%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84).md#leetcode---543-diameter-of-binary-tree) | Tree | Easy | +| 544 | | | | +| 545 | | | | +| 546 | | | | +| 547 | [Friend Circles](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/UnionFind/LeetCode%20-%20547.%20Friend%20Circles.md) | DFS、并查集 | Medium | +| 548 | | | | +| 549 | | | | +| 550 | | | | +| 551 | [Student Attendance Record I](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20551.%20Student%20Attendance%20Record%20I.md) | 模拟 | Easy | +| 552 | | | | +| 553 | | | | +| 554 | | | | +| 555 | | | | +| 556 | | | | +| 557 | | | | +| 558 | | | | +| 559 | | | | +| 560 | | | | +| 561 | [Array Partition I](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20561.%20Array%20Partition%20I(%E6%95%B0%E7%BB%84%E6%8B%86%E5%88%86%20I)(%E8%B4%AA%E5%BF%83%E5%92%8CHash%E6%80%9D%E6%83%B3).md) | Greedy、bucket | Easy | +| 562 | | | | +| 563 | | | | +| 564 | | | | +| 565 | | | | +| 566 | [Reshape the Matrix](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20566.%20Reshape%20the%20Matrix(%E9%87%8D%E5%A1%91%E7%9F%A9%E9%98%B5)(%E7%AE%80%E5%8D%95%E9%A2%98).md) | 模拟 | Easy | +| 567 | | | | +| 568 | | | | +| 569 | | | | +| 570 | | | | +| 571 | | | | +| 572 | | | | +| 573 | | | | +| 574 | | | | +| 575 | | | | +| 576 | | | | +| 577 | | | | +| 578 | | | | +| 579 | | | | +| 580 | | | | +| 581 | | | | +| 582 | | | | +| 583 | | | | +| 584 | | | | +| 585 | | | | +| 586 | | | | +| 587 | | | | +| 588 | | | | +| 589 | | | | +| 590 | | | | +| 591 | | | | +| 592 | | | | +| 593 | | | | +| 594 | | | | +| 595 | | | | +| 596 | | | | +| 597 | | | | +| 598 | | | | +| 599 | | | | +| 600 | | | | +| 601 | | | | +| 602 | | | | +| 603 | | | | +| 604 | | | | +| 605 | | | | +| 606 | [Construct String from Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20606.%20Construct%20String%20from%20Binary%20Tree(%E6%A0%B9%E6%8D%AE%E4%BA%8C%E5%8F%89%E6%A0%91%E7%94%9F%E6%88%90%E5%AD%97%E7%AC%A6%E4%B8%B2).md) | Tree | Easy | +| 607 | | | | +| 608 | | | | +| 609 | | | | +| 610 | | | | +| 611 | [Valid Triangle Number](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Math/LeetCode%20-%20611.%20Valid%20Triangle%20Number(%E8%B4%AA%E5%BF%83).md) | Greedy | Medium | +| 612 | | | | +| 613 | | | | +| 614 | | | | +| 615 | | | | +| 616 | | | | +| 617 | [Merge Two Binary Trees](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20617.%20Merge%20Two%20Binary%20Trees(%E5%90%88%E5%B9%B6%E4%B8%A4%E6%A3%B5%E4%BA%8C%E5%8F%89%E6%A0%91).md) | Tree | Easy | +| 618 | | | | +| 619 | | | | +| 620 | | | | +| 621 | [Task Scheduler](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Greedy/LeetCode%20-%20621.%20Task%20Scheduler(%E8%B4%AA%E5%BF%83).md) | Greedy | Medium | +| 622 | | | | +| 623 | | | | +| 624 | | | | +| 625 | | | | +| 626 | | | | +| 627 | | | | +| 628 | [Maximum Product of Three Numbers](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20628.%20Maximum%20Product%20of%20Three%20Numbers(%E6%95%B0%E7%BB%84%E4%B8%AD%E4%B8%89%E4%B8%AA%E6%95%B0%E7%9A%84%E6%9C%80%E5%A4%A7%E7%B4%AF%E6%88%90%E7%A7%AF)(%E7%AE%80%E5%8D%95%E9%A2%98).md) | 模拟 | Easy | +| 629 | | | | +| 630 | | | | +| 631 | | | | +| 632 | | | | +| 633 | [Sum of Square Numbers](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Math/LeetCode%20-%20633.%20Sum%20of%20Square%20Numbers(%E5%B9%B3%E6%96%B9%E6%95%B0%E4%B9%8B%E5%92%8C)(%E6%95%B0%E5%AD%A6_%E4%BA%8C%E5%88%86).md) | Math、双指针 | Easy | +| 634 | | | | +| 635 | | | | +| 636 | | | | +| 637 | [Average of Levels in Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20637.%20Average%20of%20Levels%20in%20Binary%20Tree(%E6%B1%82%E6%A0%91%E7%9A%84%E6%AF%8F%E4%B8%80%E5%B1%82%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC).md) | Tree | Easy | +| 638 | | | | +| 639 | [Decode Ways II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20639.%20Decode%20Ways%20II.md) | DP | Hard | +| 640 | | | | +| 641 | | | | +| 642 | | | | +| 643 | | | | +| 644 | | | | +| 645 | | | | +| 646 | | | | +| 647 | | | | +| 648 | | | | +| 649 | | | | +| 650 | | | | +| 651 | | | | +| 652 | | | | +| 653 | | | | +| 654 | [Maximum Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20654.%20Maximum%20Binary%20Tree(%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91).md) | Tree | Medium | +| 655 | [Print Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20655.%20Print%20Binary%20Tree(%E6%8C%89%E7%85%A7%E5%AD%97%E7%AC%A6%E7%9F%A9%E9%98%B5%E7%9A%84%E5%BD%A2%E5%BC%8F%E6%89%93%E5%8D%B0%E4%BA%8C%E5%8F%89%E6%A0%91)(%E4%BA%8C%E5%88%86%E5%92%8C%E9%80%92%E5%BD%92).md) | Tree | Medium | +| 656 | | | | +| 657 | [Robot Return to Origin](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20657.%20Robot%20Return%20to%20Origin.md) | 模拟 | Easy | +| 658 | | | | +| 659 | | | | +| 660 | | | | +| 661 | | | | +| 662 | | | | +| 663 | | | | +| 664 | [Strange Printer](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20664.%20Strange%20Printer(DP).md) | DP | Hard | +| 665 | | | | +| 666 | | | | +| 667 | | | | +| 668 | | | | +| 669 | | | | +| 670 | | | | +| 671 | [Second Minimum Node In a Binary Tree](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20671.%20Second%20Minimum%20Node%20In%20a%20Binary%20Tree(%E5%AF%BB%E6%89%BE%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%AC%AC%E4%BA%8C%E5%B0%8F%E7%9A%84%E7%BB%93%E7%82%B9).md) | Tree | Easy | +| 672 | | | | +| 673 | [Number of Longest Increasing Subsequence](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20673.%20Number%20of%20Longest%20Increasing%20Subsequence(%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97%E7%9A%84%E4%B8%AA%E6%95%B0).md) | DP | Medium | +| 674 | | | | +| 675 | [Cut Off Trees for Golf Event](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20675.%20Cut%20Off%20Trees%20for%20Golf%20Event%20(%E6%8E%92%E5%BA%8FBFS%E6%B1%82%E6%9C%80%E7%9F%AD%E8%B7%AF).md) | Search | Hard | +| 676 | [Implement Magic Dictionary](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Trie/LeetCode%20-%20676.%20Implement%20Magic%20Dictionary(%E5%AD%97%E5%85%B8%E6%A0%91).md) | Map、Trie | Medium | +| 677 | [Map Sum Pairs](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Trie/LeetCode%20-%20677.%20Map%20Sum%20Pairs(%E9%94%AE%E5%80%BC%E6%98%A0%E5%B0%84)(%E5%AD%97%E5%85%B8%E6%A0%91%E5%8F%98%E5%BD%A2).md) | Trie | Mediums | +| 678 | | | | +| 679 | | | | +| 680 | [Valid Palindrome](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/String/Easy/LeetCode%20-%20680.%20Valid%20Palindrome%20II(%E5%88%A0%E9%99%A4%E4%B8%80%E4%B8%AA%E5%AD%97%E7%AC%A6%E5%88%A4%E6%96%AD%E8%83%BD%E5%90%A6%E6%9E%84%E6%88%90%E5%9B%9E%E6%96%87).md) | String | Easy | +| 681 | | | | +| 682 | [Baseball Game](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20682.%20Baseball%20Game.md) | 模拟 | Easy | +| 683 | | | | +| 684 | [Redundant Connection](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/UnionFind/LeetCode%20-%20684.%20Redundant%20Connection.md) | DFS、并查集 | Medium | +| 685 | [Redundant Connection II](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/UnionFind/LeetCode%20-%20685.%20Redundant%20Connection%20II.md) | 并查集 | Hard | +| 686 | | | | +| 687 | [Longest Univalue Path](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20687.%20Longest%20Univalue%20Path%20(%E6%A0%91%E7%9A%84%E6%9C%80%E9%95%BF%E5%90%8C%E5%80%BC%E8%B7%AF%E5%BE%84).md) | Tree | Easy | +| 688 | [Knight Probability in Chessboard](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20688.%20Knight%20Probability%20in%20Chessboard%20(DP).md) | DP | Medium | +| 689 | | | | +| 690 | | | | +| 691 | | | | +| 692 | [Top K Frequent Words](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Heap/LeetCode%20-%20692.%20Top%20K%20Frequent%20Words.md) | Heap | Medium | +| 693 | | | | +| 694 | | | | +| 695 | | | | +| 696 | | | | +| 697 | | | | +| 698 | | | | +| 699 | | | | +| 700 | | | | +| 701 | | | | +| 702 | | | | +| 703 | | | | +| 704 | | | | +| 705 | | | | +| 706 | | | | +| 707 | | | | +| 708 | | | | +| 709 | | | | +| 710 | | | | +| 711 | | | | +| 712 | | | | +| 713 | | | | +| 714 | | | | +| 715 | | | | +| 716 | | | | +| 717 | | | | +| 718 | | | | +| 719 | [Find K-th Smallest Pair Distance](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/BinarySearch/LeetCode%20-%20719.%20Find%20K-th%20Smallest%20Pair%20Distance(%E6%9A%B4%E5%8A%9B%20_%20%E4%BA%8C%E5%88%86).md) | 二分、模拟 | Hard | +| 720 | [Longest Word in Dictionary](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Trie/LeetCode%20-%20720.%20Longest%20Word%20in%20Dictionary(%E5%AD%97%E5%85%B8%E6%A0%91).md) | Trie | Easy | +| 721 | | | | +| 722 | | | | +| 723 | | | | +| 724 | [Find Pivot Index](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20724.%20Find%20Pivot%20Index.md) | 模拟 | Easy | +| 725 | [Split Linked List in Parts](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/List/LeetCode%20-%20725.%20Split%20Linked%20List%20in%20Parts.md) | List | Medium | +| 726 | [Number of Atoms](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Other/LeetCode%20-%20726.%20Number%20of%20Atoms(TreeMap%20%2B%20%E9%80%92%E5%BD%92).md) | Map,递归 | Hard | +| 727 | | | | +| 728 | | | | +| 729 | [My Calendar I](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Map/LeetCode%20-%20729.%20My%20Calendar.md) | Map | Medium | +| 730 | [Count Different Palindromic Subsequences](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/DP/LeetCode%20-%20730.%20Count%20Different%20Palindromic%20Subsequences(%E7%BB%9F%E8%AE%A1%E4%B8%8D%E5%90%8C%E5%9B%9E%E6%96%87%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E9%87%8F).md) | DP | Hard | +| 731 | | | | +| 732 | | | | +| 733 | [Flood Fill](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%20733.%20Flood%20Fill.md) | Search | Easy | +| 734 | | | | +| 735 | [Asteroid Collision](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Simulation/easy/LeetCode%20-%20735.%20Asteroid%20Collision.md) | Stack | Medium | +| 736 | | | | +| 737 | | | | +| 738 | | | | +| 739 | | | | +| 740 | | | | +| 741 | | | | +| 742 | | | | +| 743 | | | | +| 744 | | | | +| 745 | | | | +| 746 | | | | +| 747 | | | | +| 748 | | | | +| 749 | | | | +| 750 | | | | +| 751 | | | | +| 752 | | | | +| 753 | | | | +| 754 | | | | +| 755 | | | | +| 756 | | | | +| 757 | | | | +| 758 | | | | +| 759 | | | | +| 760 | | | | +| 761 | | | | +| 762 | | | | +| 763 | | | | +| 764 | | | | +| 765 | | | | +| 766 | | | | +| 767 | | | | +| 768 | | | | +| 769 | | | | +| 770 | | | | +| 771 | | | | +| 772 | | | | +| 773 | | | | +| 774 | | | | +| 775 | | | | +| 776 | | | | +| 777 | | | | +| 778 | | | | +| 779 | | | | +| 780 | | | | +| 781 | | | | +| 782 | | | | +| 783 | | | | +| 784 | | | | +| 785 | | | | +| 786 | | | | +| 787 | | | | +| 788 | | | | +| 789 | | | | +| 790 | | | | +| 791 | | | | +| 792 | | | | +| 793 | | | | +| 794 | | | | +| 795 | | | | +| 796 | | | | +| 797 | | | | +| 798 | | | | +| 799 | | | | +| 800 | | | | +| 801 | | | | +| 802 | | | | +| 803 | | | | +| 804 | | | | +| 805 | | | | +| 806 | | | | +| 807 | | | | +| 808 | | | | +| 809 | | | | +| 810 | | | | +| 811 | | | | +| 812 | | | | +| 813 | | | | +| 814 | | | | +| 815 | | | | +| 816 | | | | +| 817 | | | | +| 818 | | | | +| 819 | | | | +| 820 | | | | +| 821 | | | | +| 822 | | | | +| 823 | | | | +| 824 | | | | +| 825 | | | | +| 826 | | | | +| 827 | | | | +| 828 | | | | +| 829 | | | | +| 830 | | | | +| 831 | | | | +| 832 | | | | +| 833 | | | | +| 834 | | | | +| 835 | | | | +| 836 | | | | +| 837 | | | | +| 838 | | | | +| 839 | | | | +| 840 | | | | +| 841 | | | | +| 842 | | | | +| 843 | | | | +| 844 | | | | +| 845 | | | | +| 846 | | | | +| 847 | | | | +| 848 | | | | +| 849 | | | | +| 850 | | | | +| 851 | | | | +| 852 | | | | +| 853 | | | | +| 854 | | | | +| 855 | | | | +| 856 | | | | +| 857 | | | | +| 858 | | | | +| 859 | | | | +| 860 | | | | +| 861 | | | | +| 862 | | | | +| 863 | | | | +| 864 | | | | +| 865 | | | | +| 866 | | | | +| 867 | | | | +| 868 | | | | +| 869 | | | | +| 870 | | | | +| 871 | | | | +| 872 | | | | +| 873 | | | | +| 874 | | | | +| 875 | | | | +| 876 | | | | +| 877 | | | | +| 878 | | | | +| 879 | | | | +| 880 | | | | +| 881 | | | | +| 882 | | | | +| 883 | | | | +| 884 | | | | +| 885 | | | | +| 886 | | | | +| 887 | | | | +| 888 | | | | +| 889 | | | | +| 890 | | | | +| 891 | | | | +| 892 | | | | +| 893 | | | | +| 894 | | | | +| 895 | | | | +| 896 | | | | +| 897 | | | | +| 898 | | | | +| 899 | | | | +| 900 | | | | +| 901 | | | | +| 902 | | | | +| 903 | | | | +| 904 | | | | +| 905 | | | | +| 906 | | | | +| 907 | | | | +| 908 | | | | +| 909 | | | | +| 910 | | | | +| 911 | | | | +| 912 | | | | +| 913 | | | | +| 914 | | | | +| 915 | | | | +| 916 | | | | +| 917 | | | | +| 918 | | | | +| 919 | | | | +| 920 | | | | +| 921 | | | | +| 922 | | | | +| 923 | | | | +| 924 | | | | +| 925 | | | | +| 926 | | | | +| 927 | | | | +| 928 | | | | +| 929 | | | | +| 930 | | | | +| 931 | | | | +| 932 | | | | +| 933 | | | | +| 934 | | | | +| 935 | | | | +| 936 | | | | +| 937 | | | | +| 938 | | | | +| 939 | | | | +| 940 | | | | +| 941 | | | | +| 942 | | | | +| 943 | | | | +| 944 | | | | +| 945 | | | | +| 946 | | | | +| 947 | | | | +| 948 | | | | +| 949 | | | | +| 950 | | | | +| 951 | | | | +| 952 | | | | +| 953 | | | | +| 954 | | | | +| 955 | | | | +| 956 | | | | +| 957 | | | | +| 958 | | | | +| 959 | | | | +| 960 | | | | +| 961 | | | | +| 962 | | | | +| 963 | | | | +| 964 | | | | +| 965 | | | | +| 966 | | | | +| 967 | | | | +| 968 | | | | +| 969 | | | | +| 970 | | | | +| 971 | | | | +| 972 | | | | +| 973 | | | | +| 974 | | | | +| 975 | | | | +| 976 | | | | +| 977 | | | | +| 978 | | | | +| 979 | | | | +| 980 | | | | +| 981 | | | | +| 982 | | | | +| 983 | | | | +| 984 | | | | +| 985 | | | | +| 986 | | | | +| 987 | | | | +| 988 | | | | +| 989 | | | | +| 990 | | | | +| 991 | | | | +| 992 | | | | +| 993 | | | | +| 994 | | | | +| 995 | | | | +| 996 | | | | +| 997 | | | | +| 998 | | | | +| 999 | | | | +| 1000 | | | | + diff --git "a/Algorithm/LeetCode/Math/LeetCode-149.Max Points on a Line(\345\223\210\345\270\214\350\241\250 \343\200\201\346\225\260\345\255\246).md" "b/Algorithm/LeetCode/Math/LeetCode-149.Max Points on a Line(\345\223\210\345\270\214\350\241\250 \343\200\201\346\225\260\345\255\246).md" new file mode 100644 index 00000000..79c62201 --- /dev/null +++ "b/Algorithm/LeetCode/Math/LeetCode-149.Max Points on a Line(\345\223\210\345\270\214\350\241\250 \343\200\201\346\225\260\345\255\246).md" @@ -0,0 +1,164 @@ + +## LeetCode - 149. Max Points on a Line(哈希表 、数学) + +#### [题目链接](https://leetcode.com/problems/max-points-on-a-line/) +> https://leetcode.com/problems/max-points-on-a-line/ + +#### 题目 + +![在这里插入图片描述](images/149_t.png) +#### 解析 +此题的做法其实就是暴力统计(`O(N^2)`): + +* 每次枚举一个点,看其他点和这个点构成的斜率(`(y2 - y1) / (x2 - x1) `)有多少个是相同的,如果有`k`个相同的斜率,则有`k+1`个这样的点; +* 但是这一题还需要额外**处理可能有相同的点**(使用`overlap`变量统计即可); + +图: + + +![在这里插入图片描述](images/149_s.png) + +这题比较精华的地方在于: + +* 我们不能简单的将斜率存到`HashMap`的`Key`中,因为这样会产生精度问题,想法就是直接存`y2 - y1`和`x2 - x1`的键值对(代码中用一个结构`Pair`表示)。这种想法避免了`double`产生的问题; +* 但是又由于数据可能很大,所以我们对`y2 - y1`和`x2 - x1`进行约分处理(`gcd`); +* 这里还有一点代码实现上的问题,如果我们的`HashMap<斜率,出现次数>`中的斜率是我们自定义的`Pair`,此时需要重写`Pair`的`equals`和`hashCode`方法; + +代码: + + +```java +import java.io.*; +import java.util.*; + +// 放在 HashMap中的自定义的对象一定要记得重写equals 和 hashCode() +// 博客讲解: https://blog.csdn.net/u014590757/article/details/79501332 +class Pair{ + int dx; + int dy; + + Pair(int dx, int dy){ + this.dx = dx; + this.dy = dy; + } + + @Override + public boolean equals(Object o) { + Pair pair = (Pair) o; + return dx == pair.dx && dy == pair.dy; + } + + @Override + public int hashCode() { + return 31 * dx + dy; + } +} + +class Solution { + + public int maxPoints(Point[] points) { + if(points == null || points.length == 0) + return 0; + int n = points.length; + int res = 0; + for(int i = 0; i < n; i++){ // 从每个点开始的最长直线 + HashMapcounts = new HashMap<>(); // 必须写在循环内 + int overlap = 0; // duplicated points + int maxP = 0; + for(int j = i + 1; j < n; j++){ + if(points[i].x == points[j].x && points[i].y == points[j].y) + overlap++; + else { + Pair pSlope = getSlope(points[i], points[j]); + counts.put(pSlope, 1 + counts.getOrDefault(pSlope, 0)); + maxP = Math.max(maxP, counts.get(pSlope)); + } + } + res = Math.max(res, maxP + 1 + overlap); + } + return res; + } + + private Pair getSlope(Point p1, Point p2){ + int dx = p2.x - p1.x; + int dy = p2.y - p1.y; + if(dy == 0) // horizontal line + return new Pair(0, p1.y); + if(dx == 0) // vertical line + return new Pair(p1.x, 0); + int divisor = gcd(dx, dy); + return new Pair(dx / divisor, dy / divisor); + } + + private int gcd(int a, int b){ + // return b == 0 ? a : gcd(b, a % b); + int r = 0; + while(b != 0){ + r = a % b; + a = b; + b = r; + } + return a; + } + + public static void main(String[] args){ + PrintStream out = System.out; + Point[] points = { + new Point(1, 1), + new Point(3, 2), + new Point(5, 3), + new Point(4, 1), + new Point(2, 3), + new Point(1, 4) + }; + out.println(new Solution(). + maxPoints(points) + ); + } +} +``` + +`C++`代码: + + +```java +class Solution{ +public: + int maxPoints(vector& points){ + int n = points.size(); + int res = 0; + for(int i = 0; i < n; i++){ + std::map, int> counts; + int overlap = 0; + int maxP = 0; + for(int j = i + 1; j < n; j++){ + if(points[i].x == points[j].x && points[i].y == points[j].y) + overlap++; + else + maxP = std::max(maxP, ++counts[getSlope(points[i], points[j])]); + } + res = std::max(res, 1 + maxP + overlap); + } + return res; + } +private: + std::pairgetSlope(const Point& p1, const Point& p2){ + int dx = p2.x - p1.x; + int dy = p2.y - p1.y; + if(dy == 0) return {0, p1.y}; + if(dx == 0) return {p1.x, 0}; + int divisor = gcd(dx, dy); + return { dx / divisor, dy / divisor }; + } + + int gcd(int a, int b){ + int r = 0; + while(b != 0){ + r = a % b; + a = b; + b = r; + } + return a; + } +}; +``` diff --git a/Algorithm/LeetCode/Math/LeetCode-204.Count Primes.md b/Algorithm/LeetCode/Math/LeetCode-204.Count Primes.md new file mode 100644 index 00000000..f66ae056 --- /dev/null +++ b/Algorithm/LeetCode/Math/LeetCode-204.Count Primes.md @@ -0,0 +1,61 @@ +## LeetCode - 204. Count Primes + +#### [题目链接](https://leetcode.com/problems/count-primes/) + +> https://leetcode.com/problems/count-primes/ + +#### 题目 +筛选`0~n`(`[0,n)`)之间的素数个数。 + +![在这里插入图片描述](images/204_t.png) + +#### 解析 +如果用经典的判断素数,时间复杂度为`O(n*sqrt(n))`,会超时。 + +于是使用经典的[**埃拉托斯特尼筛法(有动图演示)**](https://zh.wikipedia.org/wiki/%E5%9F%83%E6%8B%89%E6%89%98%E6%96%AF%E7%89%B9%E5%B0%BC%E7%AD%9B%E6%B3%95),有关素数也可以看我[另一篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Math/Hdu%20-%201431%E7%B4%A0%E6%95%B0%E5%9B%9E%E6%96%87%E4%BB%A5%E5%8F%8A%E7%B4%A0%E6%95%B0%E7%9B%B8%E5%85%B3%E6%80%BB%E7%BB%93.md)讲解。 + +超时: +```java +class Solution { + //TLE + public boolean isPrime(int nums){ + if(nums == 0 || nums == 1) + return false; + for(int i = 2; i <= (int)Math.sqrt(nums); i++){ // <= not < + if(nums%i == 0) + return false; + } + return true; + } + + public int countPrimes(int n) { + int res = 0; + for(int i = 0; i < n; i++){ + if(isPrime(i)) + res++; + } + return res; + } +} +``` +正解: +```java +class Solution { + public int countPrimes(int n) { + if(n < 3) + return 0; + int res = 0; + boolean[] isPrime = new boolean[n]; + Arrays.fill(isPrime,true); + isPrime[0] = isPrime[1] = false; + for(int i = 2; i < n; i++){ + if(isPrime[i]){ + res++; + for(int j = 2*i; j < n; j+=i) //筛选 + isPrime[j] = false; + } + } + return res; + } +} +``` diff --git "a/Algorithm/LeetCode/Math/LeetCode-611.Valid Triangle Number(\350\264\252\345\277\203).md" "b/Algorithm/LeetCode/Math/LeetCode-611.Valid Triangle Number(\350\264\252\345\277\203).md" new file mode 100644 index 00000000..f796a042 --- /dev/null +++ "b/Algorithm/LeetCode/Math/LeetCode-611.Valid Triangle Number(\350\264\252\345\277\203).md" @@ -0,0 +1,71 @@ +## LeetCode - 611. Valid Triangle Number + +#### [题目链接](https://leetcode.com/problems/valid-triangle-number/) + +> https://leetcode.com/problems/valid-triangle-number/ + +#### 题目大意 +给定一个包含非负整数的数组,统计其中可以组成三角形三条边的三元组个数。 + +![在这里插入图片描述](images/611_t.png) + +#### 解析 +很巧妙的方法,使用贪心: + +* 先对数组排序(升序降序都可以,这里按照升序排列); +* 然后初始化`c=num.length - 1`,`b = c-1`,`a = 0`,然后如果`arr[a] + arr[b] > arr[c]`,那么所有`arr[a ~ b-1]`和`arr[b]`、`arr[c]`之间都可以构成三角形,所以可以加上`b-a`个; +* 否则说明`a`小了,就让`a++`,知道`a == b`退出; + +图: + +![在这里插入图片描述](images/611_s.png) + +代码: + +```java +class Solution { + public int triangleNumber(int[] nums) { + if(nums == null || nums.length < 3) + return 0; + int res = 0; + Arrays.sort(nums); + for(int c = nums.length-1; c >= 2; c--){ + for(int b = c-1; b >= 1; b--){ + int a = 0; + while(a < b){ + if(nums[a] + nums[b] > nums[c]){ + res += b-a; + break; + } + a++; + } + } + } + return res; + } +} +``` + +```java +class Solution { + // more fast + public int triangleNumber(int[] nums) { + if(nums == null || nums.length < 3) + return 0; + int res = 0; + Arrays.sort(nums); + for(int c = nums.length-1; c >= 2; c--){ + int a = 0,b = c-1; + while(a < b){ + if(nums[a] + nums[b] > nums[c]){ + res += b-a; + b--; + }else a++; + } + } + return res; + } +} +``` + +*** diff --git "a/Algorithm/LeetCode/Math/LeetCode-633.Sum of Square Numbers(\345\271\263\346\226\271\346\225\260\344\271\213\345\222\214)(\346\225\260\345\255\246_\344\272\214\345\210\206).md" "b/Algorithm/LeetCode/Math/LeetCode-633.Sum of Square Numbers(\345\271\263\346\226\271\346\225\260\344\271\213\345\222\214)(\346\225\260\345\255\246_\344\272\214\345\210\206).md" new file mode 100644 index 00000000..a4f59c82 --- /dev/null +++ "b/Algorithm/LeetCode/Math/LeetCode-633.Sum of Square Numbers(\345\271\263\346\226\271\346\225\260\344\271\213\345\222\214)(\346\225\260\345\255\246_\344\272\214\345\210\206).md" @@ -0,0 +1,147 @@ +# LeetCode - 633. Sum of Square Numbers(平方数之和)(数学/二分) +* [方法一-数学](#方法一-数学) +* [方法二-Hash表](#方法二-hash表) +* [方法三-二分](#方法三-二分) + +*** +#### [题目链接](https://leetcode.com/problems/sum-of-square-numbers/) + +> https://leetcode.com/problems/sum-of-square-numbers/ + +#### 题目 +![在这里插入图片描述](images/633_t.png) +### 方法一-数学 +首先想到肯定不会是双重循环的枚举,只需要枚举一个数`a`,去检查`b`是否满足即可,又因为是平方数,所以最对只需要枚举`sqrt( c )`即可: + +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + for (int a = 0; a <= Math.sqrt(c); a++) { + int b = (int) Math.sqrt(c - a * a); + if (c == a * a + b * b) + return true; + } + return false; + } +} +``` +或者这样,原理是一样的: + +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + for (int a = 0; a <= Math.sqrt(c); a++) { + int b2 = c - a * a; + int b = (int) Math.sqrt(b2); + if (b * b == b2) + return true; + } + return false; + } +} +``` +这里有个问题就是,当我们把`for`循环中的`a <= Math.sqrt( c ) `改一下,改成下面的代码,就会超时: + +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + for (int a = 0; a * a <= c; a++) { + int b2 = c - a * a; + int b = (int) Math.sqrt(b2); + if (b * b == b2) + return true; + } + return false; + } +} +``` +这是为什么呢?因为当 `c `接近于`Math.Integer`的时候,`a*a`有可能会发生溢出,于是枚举的时候将`a`改成`long`类型: +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + for (long a = 0; a * a <= c; a++) { + long b2 = c - a * a; + long b = (long) Math.sqrt(b2); + if (b * b == b2) + return true; + } + return false; + } +} +``` +*** +### 方法二-Hash表 +这题也可以和`LeetCode - 1. Two Sum`一样,使用`Hash`表存储答案的一边,然后遍历去寻找另一边: + +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + HashSet set = new HashSet<>(); + for (int a = 0; a <= Math.sqrt(c); a++) + set.add(a * a); + for (int b = 0; b <= Math.sqrt(c); b++) { + if (set.contains(c - b * b)) + return true; + } + return false; + } +} +``` +同样可以从`2*N`优化到`N`,只需要遍历一遍数组 : +但是这里要注意: +`set.add(c - a * a);`这一句和那个第一题不同,这个要放在判断的前面,因为像`1 * 1 + 1 * 1 = 2`这种情况,就要这样判断。 +```java +class Solution { + public boolean judgeSquareSum(int c) { + if (c == 0) + return true; + HashSet set = new HashSet<>(); + for (int a = 0; a <= Math.sqrt(c); a++) { + set.add(c - a * a); //这个要放在前面,因为像 1*1 + 1*1 = 2就是这种例子 + if (set.contains(a * a)) + return true; + } + return false; + } +} +``` +*** +### 方法三-二分 +还有一个很巧妙的方法: + +* 因为`a`和`b`都一定在`[0,sqrt( c )]`之间; +* 我们可以在` [0,sqrt( c )]`,设置两个指针`L,R`,每次看这两个指针对应的平方和是否` = c`; +* 如果等于就返回`true`; +* 如果 `>` ,说明右边值要缩小才有可能` = ` ,所以`R--`; +* 如果 `<` ,说明左边值要增加才有可能` = ` ,所以`L++`; + +代码: + +```java +class Solution { + public boolean judgeSquareSum(int c) { + int L = 0, R = (int)Math.sqrt(c); + while(L <= R){ + int cur = L*L + R*R; + if(cur == c) + return true; + else if(cur > c) + R--; + else + L++; + } + return false; + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/149_s.png" b/Algorithm/LeetCode/Math/images/149_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/149_s.png" rename to Algorithm/LeetCode/Math/images/149_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/149_t.png" b/Algorithm/LeetCode/Math/images/149_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/149_t.png" rename to Algorithm/LeetCode/Math/images/149_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/204_t.png" b/Algorithm/LeetCode/Math/images/204_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/204_t.png" rename to Algorithm/LeetCode/Math/images/204_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/611_s.png" b/Algorithm/LeetCode/Math/images/611_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/611_s.png" rename to Algorithm/LeetCode/Math/images/611_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/611_t.png" b/Algorithm/LeetCode/Math/images/611_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/611_t.png" rename to Algorithm/LeetCode/Math/images/611_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Math/images/633_t.png" b/Algorithm/LeetCode/Math/images/633_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Math/images/633_t.png" rename to Algorithm/LeetCode/Math/images/633_t.png diff --git "a/Algorithm/LeetCode/Other/LeetCode-726.Number of Atoms(TreeMap + \351\200\222\345\275\222).md" "b/Algorithm/LeetCode/Other/LeetCode-726.Number of Atoms(TreeMap + \351\200\222\345\275\222).md" new file mode 100644 index 00000000..c5ea3245 --- /dev/null +++ "b/Algorithm/LeetCode/Other/LeetCode-726.Number of Atoms(TreeMap + \351\200\222\345\275\222).md" @@ -0,0 +1,101 @@ +## LeetCode - 726. Number of Atoms(TreeMap + 递归) + +#### [题目链接](https://leetcode.com/problems/number-of-atoms/) + +> https://leetcode.com/problems/number-of-atoms/ + +#### 题目 + +![](images/726_t.png) + +> 注意: +> +> - 所有原子的第一个字母为大写,剩余字母都是小写。 +> - `formula`的长度在`[1, 1000]`之间。 +> - `formula`只包含字母、数字和圆括号,并且题目中给定的是合法的化学式。 + +#### 解析 + +这题是一个比较有意思的递归求解的题目: + +* 当没有括号的时候,直接计算,先得到原子的字符串(第一个字母大写,后面的小写),然后得到后面的个数(如果是`1`就忽略,否则要计算数目);(`getCount()和getName()`函数) +* 如果遇到`(`,就去递归求解,返回的是一个`Map`(这里用`TreeMap`,因为要按照字典序),`Map`的`key`就是原子,`value`对应的就是原子的数量;然后返回的`Map`的每一项会和右边的`)`外面的那个数字相乘; +* 否则就不是`(`和`)`,就是正常的原子,就计算`getName`和`getCount`(普通原子的数量); + +图: + +![img.png](images/726_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private int pos; + private char[] chs; + + public String countOfAtoms(String formula) { + if (formula == null || formula.length() == 0) + return ""; + chs = formula.toCharArray(); + pos = 0; + StringBuilder res = new StringBuilder(); + rec().forEach((k, v) -> { + res.append(k); + if (v > 1) res.append(String.valueOf(v)); + }); + return res.toString(); + } + + private TreeMap rec() { + TreeMap res = new TreeMap<>(); + while (pos != chs.length) { + if (chs[pos] == '(') { // 递归 + pos++; + TreeMap tmpRes = rec(); + int count = getCount(); + for (Map.Entry entry : tmpRes.entrySet()) + res.put(entry.getKey(), res.getOrDefault(entry.getKey(), 0) + entry.getValue() * count); + } else if (chs[pos] == ')') { //返回这一层 + pos++; + return res; + } else { + String name = getName(); + res.put(name, res.getOrDefault(name, 0) + getCount()); + } + } + return res; + } + + private String getName() { + StringBuilder res = new StringBuilder(); + res.append(chs[pos++]); + while (pos < chs.length && Character.isLowerCase(chs[pos])) + res.append(chs[pos++]); + return res.toString(); + } + + private int getCount() { + int count = 0; + while (pos < chs.length && '0' <= chs[pos] && chs[pos] <= '9') + count = count * 10 + (chs[pos++] - '0'); + return count == 0 ? 1 : count; +// String res = ""; +// while(Character.isDigit(chs[pos])) +// res += chs[pos++]; +// return res.isEmpty() ? 1 : Integer.parseInt(res); // 没有数字就是1(省略) + } + + public static void main(String[] args) { + Scanner cin = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + String formual = "K4(ON(SO3)2)2"; + System.out.println(new Solution().countOfAtoms(formual)); + } +} + +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/Other/images/726_s.png" b/Algorithm/LeetCode/Other/images/726_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Other/images/726_s.png" rename to Algorithm/LeetCode/Other/images/726_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Other/images/726_t.png" b/Algorithm/LeetCode/Other/images/726_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Other/images/726_t.png" rename to Algorithm/LeetCode/Other/images/726_t.png diff --git a/Algorithm/LeetCode/Search/LeetCode-126.Word Ladder II.md b/Algorithm/LeetCode/Search/LeetCode-126.Word Ladder II.md new file mode 100644 index 00000000..ed490532 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-126.Word Ladder II.md @@ -0,0 +1,177 @@ +# LeetCode - 126. Word LadderII + +#### [题目链接](https://leetcode.com/problems/word-ladder-ii/) + +> https://leetcode.com/problems/word-ladder-ii/ + +#### 题目 +![在这里插入图片描述](images/127_t.png) + +## 解析 +单向`BFS`解法: + +* 记录当前扩展的`next`的`parent`是`cur`即可; +* 但是这里每个`next`可能有多个`parent`,所以在循环中,不能先移除`dict.remve(next)`,而是使用一个`set`集合`used`数组先标记,退出这次循环之后再一次性移除; +* 最后从`endWord`往前`DFS`求出路径即可(中间集合使用`LinkedList`(可以支持头部插入)); + +图: + +![在这里插入图片描述](images/127_s.png) + +使用单向`BFS + DFS`: +```java +class Solution { + + public List> findLadders(String beginWord, String endWord, List wordList) { + List> res = new ArrayList<>(); + if (wordList == null || wordList.isEmpty()) + return res; + Set dict = new HashSet<>(wordList); + if (!dict.contains(endWord)) + return res; + dict.remove(beginWord); + + Set used = new HashSet<>(); //不能像前一题一样,找到一个就移除一个,而是搞完一层,才一次性移除 + Map> parents = new HashMap<>(); //当前节点(单词)的所有的父亲 + Queue queue = new LinkedList<>(); + queue.add(beginWord); + boolean found = false; + while (!queue.isEmpty() && !found) { + int qsize = queue.size(); + for (int size = 0; size < qsize; size++) { + String cur = queue.poll(); + for (int i = 0; i < cur.length(); i++) { + char[] chs = cur.toCharArray(); + for (char c = 'a'; c <= 'z'; c++) { + if (c == chs[i]) + continue; + chs[i] = c; + String next = new String(chs); + if (!dict.contains(next)) + continue; + if (next.equals(endWord)) // 就算找到了,还是要做完当前这一层 + found = true; + queue.add(next); + parents.computeIfAbsent(next, k -> new HashSet<>()).add(cur);// next的其中一个父亲为cur + used.add(next);// 只是记录,而不是直接dict.remove(next) + } + } + } + dict.removeAll(used); // 不能像前一题单向那样直接在循环里面移除 next, 而是在这里(这一层结束之后),才移除,不然会丢失路径 例如 : dog->cog, log->cog + used.clear(); + } + if (!found) + return res; + + //DFS从节点的parent集合中求解答案 + LinkedList list = new LinkedList<>(); // 中间变量 + list.add(endWord); + dfs(parents, res, endWord, beginWord, list); //backtrack from the endWord(child) to beginWord(parent) + return res; + } + + private void dfs(Map> parents, List> res, + String curStr, String beginWord, LinkedList list) { + if (curStr.equals(beginWord)) { //到了起点, 找到一条了 + res.add(new ArrayList<>(list)); + return; + } + for (String parent : parents.get(curStr)) { + list.addFirst(parent); // 注意是加到开头 + dfs(parents, res, parent, beginWord, list); + list.removeFirst(); // backtrack + } + } +} +``` + +另外可以添加一个`Map steps`记录每一个节点的最短路径长度(层数) (**这题不需要**): + +测试: + +```java +import java.util.*; + +public class Solution { + + public List> findLadders(String beginWord, String endWord, List wordList) { + List> res = new ArrayList<>(); + if (wordList == null || wordList.isEmpty()) + return res; + Set dict = new HashSet<>(wordList); + if (!dict.contains(endWord)) + return res; + dict.remove(beginWord); + + Set used = new HashSet<>(); //不能像前一题一样,找到一个就移除一个,而是搞完一层,才一次性移除 + Map> parents = new HashMap<>(); //当前单词的所有的父亲 + Map steps = new HashMap<>(); // 扩展到当前单词所需要的最小步数 + Queue queue = new LinkedList<>(); + queue.add(beginWord); + boolean found = false; + int step = 0; + while (!queue.isEmpty() && !found) { + int qsize = queue.size(); + ++step; + for (int size = 0; size < qsize; size++) { + String cur = queue.poll(); + for (int i = 0; i < cur.length(); i++) { + char[] chs = cur.toCharArray(); + for (char c = 'a'; c <= 'z'; c++) { + if (c == chs[i]) + continue; + chs[i] = c; + String next = new String(chs); + if (!dict.contains(next)) + continue; + if (next.equals(endWord)) // 就算找到了,还是要做完当前这一层 + found = true; + queue.add(next); + steps.put(next, step + 1); + parents.computeIfAbsent(next, k -> new HashSet<>()).add(cur); + used.add(next); + } + } + } + dict.removeAll(used); // 不能像前一题那样直接在循环里面移除 next, 而是在这里(这一层结束之后),才移除,不然会丢失路径 例如 : dog->cog, log->cog + used.clear(); + } + if (!found) + return res; + LinkedList list = new LinkedList<>(); // 中间变量 + list.add(endWord); + dfs(parents, res, endWord, beginWord, list); //backtrack from the endWord(child) to beginWord(parent) + + System.out.println(steps); //输出 steps + return res; + } + + private void dfs(Map> parents, List> res, + String curStr, String beginWord, LinkedList list) { + if (curStr.equals(beginWord)) { //到了起点, 找到一条了 + res.add(new ArrayList<>(list)); + return; + } + for (String parent : parents.get(curStr)) { + list.addFirst(parent); // 注意是加到开头 + dfs(parents, res, parent, beginWord, list); + list.removeFirst(); // backtrack + } + } + + + public static void main(String[] args) { + String beginWord = "hit"; + String endWord = "cog"; + List wordList = Arrays.asList("hot", "dot", "dog", "lot", "log", "cog"); + + System.out.println(new Solution().findLadders(beginWord, endWord, wordList)); + } +} +``` +输出: + +```java +{lot=3, log=4, dot=3, cog=5, hot=2, dog=4} +[[hit, hot, lot, log, cog], [hit, hot, dot, dog, cog]] +``` \ No newline at end of file diff --git a/Algorithm/LeetCode/Search/LeetCode-127.Word Ladder.md b/Algorithm/LeetCode/Search/LeetCode-127.Word Ladder.md new file mode 100644 index 00000000..026fbddc --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-127.Word Ladder.md @@ -0,0 +1,247 @@ +# LeetCode - 127. Word Ladder + + +#### [题目链接](https://leetcode.com/problems/word-ladder/) + +> https://leetcode.com/problems/word-ladder/ + +#### 题目 +![在这里插入图片描述](images/126_t.png) +## 1、单向BFS + +两种解法: 单向`BFS`和双向`BFS`(`Bidrectional BFS`)。 + +单向BFS: + +* 首先将`wordDict`转换成`Set`集合(记为`dict`),这样查找更快,不然会`TLE`; +* 每次尝试更改一个字符(`a ~ z`),然后去判断是否这个在`dict`中,如果可以,加入队列; + +图: + +![1554773481209](assets/1554773481209.png) + +第一版: + +```java +class Solution { + + private class State { + public String word; + public int step; + + public State(String word, int step) { + this.word = word; + this.step = step; + } + } + + public int ladderLength(String beginWord, String endWord, List wordList) { + HashSet dict = new HashSet<>(wordList); // 将wordList转换成HashSet查找更快 + return bfs(new State(beginWord, 0), new State(endWord, 0), dict) + 1; + } + + private int bfs(State begin, State end, Set dict) { + Queue queue = new LinkedList<>(); + queue.add(begin); + HashMap visMap = new HashMap<>(); + visMap.put(begin.word, true); + while (!queue.isEmpty()) { + State cur = queue.poll(); + if (cur.word.equals(end.word)) + return cur.step; + for (int i = 0; i < cur.word.length(); i++) { + StringBuilder sb = new StringBuilder(cur.word); + for (char c = 'a'; c <= 'z'; c++) { + if (cur.word.charAt(i) == c) + continue; + sb.setCharAt(i, c); + if (visMap.get(sb.toString()) == null && dict.contains(sb.toString())) { + visMap.put(sb.toString(), true); + queue.add(new State(sb.toString(), cur.step + 1)); + } + } + } + } + return -1; + } +} +``` +第二版: 没有使用`visMap`,而是当一个字符串(节点),已经被访问过了之后,就从`dict`中移除即可。 + +```java +class Solution { + + private class State { + public String word; + public int step; + + public State(String word, int step) { + this.word = word; + this.step = step; + } + } + + public int ladderLength(String beginWord, String endWord, List wordList) { + HashSet dict = new HashSet<>(wordList); // 将wordList转换成HashSet查找 + return bfs(new State(beginWord, 0), new State(endWord, 0), dict) + 1; + } + + private int bfs(State begin, State end, Set dict) { + Queue queue = new LinkedList<>(); + queue.add(begin); + while (!queue.isEmpty()) { + State cur = queue.poll(); + if (cur.word.equals(end.word)) + return cur.step; + for (int i = 0; i < cur.word.length(); i++) { + StringBuilder sb = new StringBuilder(cur.word); + for (char c = 'a'; c <= 'z'; c++) { + if (cur.word.charAt(i) == c) + continue; + sb.setCharAt(i, c); + if (dict.contains(sb.toString())) { + queue.add(new State(sb.toString(), cur.step + 1)); + dict.remove(sb.toString()); // 直接移除这个就好,不需要使用一个HashMap + } + } + } + } + return -1; + } +} +``` +第三版: 将结构体去掉。 +```java +class Solution { + public int ladderLength(String beginWord, String endWord, List wordList) { + HashSet dict = new HashSet<>(wordList); + // start bfs + Queue queue = new LinkedList<>(); + queue.add(beginWord); + int level = 0;// 层数 + while (!queue.isEmpty()) { + level++; + int qsize = queue.size(); // 一定要定义一个变量,不能在下面的循环中直接写queue.size(),因为会变化 + for(int size = 0; size < qsize; size++){ //当前这一层的节点的数目 + String cur = queue.poll(); + for (int i = 0; i < cur.length(); i++) { + StringBuilder sb = new StringBuilder(cur); + for (char c = 'a'; c <= 'z'; c++) { + if (cur.charAt(i) == c) + continue; + sb.setCharAt(i, c); + if (dict.contains(sb.toString())) { + if(sb.toString().equals(endWord)) + return level + 1; + queue.add(sb.toString()); + dict.remove(sb.toString()); + } + } + } + } + } + return 0; + } +} +``` + +## 2、双向BFS + +思路: + + +* 用两个集合`Set`,分别从两边开始搜索(代码上是从一边,但是交换两个集合); +* 这样的话,返回的条件就是当前扩展的节点如果在`endSet`中有就返回; + +注意` dict.removeAll(beginSet);`这一样,是将`beginSet`原来的单词从`dict`中移除掉,而不能在循环中`dict.remove(next); `,因为可能会移除掉对面`endSet`中的元素,导致失败。 + +图: + +![1554773205403](assets/1554773205403.png) + +代码: + +```java +class Solution{ + public int ladderLength(String beginWord, String endWord, List wordList) { + HashSet dict = new HashSet<>(wordList); + if (!dict.contains(endWord)) + return 0; + HashSet beginSet = new HashSet<>(); + HashSet endSet = new HashSet<>(); + + beginSet.add(beginWord); + endSet.add(endWord); + int step = 0; + while (!beginSet.isEmpty() && !endSet.isEmpty()) { + ++step; + if (beginSet.size() > endSet.size()) { + HashSet hs = beginSet; + beginSet = endSet; + endSet = hs; + } + HashSet nextSet = new HashSet<>(); + for (String cur : beginSet) { + char[] chs = cur.toCharArray(); + for (int i = 0; i < chs.length; i++) { + char old = chs[i]; + for (char c = 'a'; c <= 'z'; c++) { + chs[i] = c; + String next = String.valueOf(chs); + if (dict.contains(next)) { + if (endSet.contains(next)) + return step + 1; + nextSet.add(next); + // dict.remove(next); //不能这样 + } + } + chs[i] = old; + } + } + dict.removeAll(beginSet); + beginSet = nextSet; + } + return 0; + } +} +``` +也可以将上面的方法改成递归: + +```java +class Solution { + public int ladderLength(String beginWord, String endWord, List wordList) { + Set beginSet = new HashSet<>(); + Set endSet = new HashSet<>(); + beginSet.add(beginWord); + endSet.add(endWord); + Set dict = new HashSet<>(wordList); + if(!dict.contains(endWord)) + return 0; + return bfs(beginSet, endSet, dict, 0); + } + + private int bfs(Set beginSet, Set endSet, Set dict, int step){ + if(beginSet.isEmpty() || endSet.isEmpty()) + return 0; + step++; + dict.removeAll(beginSet); + Set nextSet = new HashSet<>(); + for(String str : beginSet){ + char[] chs = str.toCharArray(); + for(int i = 0; i < chs.length; i++){ + char old = chs[i]; + for(char c = 'a'; c <= 'z'; c++){ + chs[i] = c; + String next = new String(chs); + if(!dict.contains(next)) continue; + if(endSet.contains(next)) return step + 1; + nextSet.add(next); + } + chs[i] = old; + } + } + return nextSet.size() > endSet.size() ? bfs(endSet, nextSet, dict, step) : bfs(nextSet, endSet, dict, step); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-17.Letter Combinations of a Phone Number.md b/Algorithm/LeetCode/Search/LeetCode-17.Letter Combinations of a Phone Number.md new file mode 100644 index 00000000..5a308811 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-17.Letter Combinations of a Phone Number.md @@ -0,0 +1,79 @@ +# LeetCode - 17. Letter Combination of a Phone Number + +#### [题目链接](https://leetcode.com/problems/letter-combinations-of-a-phone-number/) + +> https://leetcode.com/problems/letter-combinations-of-a-phone-number/ + +#### 题目 + +![1555053287232](assets/1555053287232.png) + +## 解析 + +比较明显的搜索题目。可以用dfs和bfs。 + +DFS的思路: + +* 递归函数需要一个当前状态的字符串的值,记为`curr`; +* 还需要一个当前字符串的长度,我们搜索到`l == dights.length`就可以了; +* 然后记得在搜索完之后要回溯,删除当前`append`的字符; + +图: + +![1555054067126](assets/1555054067126.png) + +dfs代码: + +```java +class Solution { + + private List res; + private String[] ds = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; + + public List letterCombinations(String digits) { + res = new ArrayList<>(); + if (digits == null || digits.length() == 0) return res; + dfs(0, digits.toCharArray(), new StringBuilder()); + return res; + } + + private void dfs(int p, char[] digits, StringBuilder curr) { + if(p == digits.length){ + res.add(curr.toString()); + return; + } + for(char c : ds[digits[p] - '0'].toCharArray()){ + curr.append(c); + dfs(p + 1, digits, curr); + curr.deleteCharAt(curr.length() - 1); + } + } +} +``` + +bfs代码: + +就是在前一个状态`res`的基础上`append`当前字符串的所有字符,就得到下一个状态。 + +```java +class Solution { + + public List letterCombinations(String digits) { + List res = new ArrayList<>(); + if (digits == null || digits.length() == 0) return res; + String[] ds = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; + res.add(""); + for (char c : digits.toCharArray()) { + List tmp = new ArrayList<>(); + for (String s : res) { //在原有的基础上 + 当前ds[c - '0']的所有字符 + for (char c2 : ds[c - '0'].toCharArray()) + tmp.add(s + c2); + } + res = tmp; + } + return res; + } +} + +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-22.Generate Parentheses.md b/Algorithm/LeetCode/Search/LeetCode-22.Generate Parentheses.md new file mode 100644 index 00000000..6aadff7e --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-22.Generate Parentheses.md @@ -0,0 +1,46 @@ +## LeetCode - 22. Generate Parentheses + +#### [题目链接](https://leetcode.com/problems/generate-parentheses/) + +> https://leetcode.com/problems/generate-parentheses/ + +#### 题目 + +![1556957924483](assets/1556957924483.png) + +### 解析 + +看一个`n == 3`的例子: + +![1556957791373](assets/1556957791373.png) + +思路: + +* 思路就是,当`L < n`的时候,都可以尝试添加; +* 当时当`R >= L`的时候,就不能添加右括号,所以上面灰色就是那种情况,所以添加右括号的时候,注意`R < L`; +* 边界就是只要当右括号`R == n`就添加到结果,因为上面保证了`R <= L`; + +使用`String`会使代码更加简洁: + +```java +class Solution { + + private Listres; + + public List generateParenthesis(int n) { + res = new ArrayList<>(); + dfs("", n, 0, 0); + return res; + } + + private void dfs(String curr, int n, int L, int R){ + if(R == n){ + res.add(curr); + return; + } + if(L < n) dfs(curr + "(", n, L+1, R); + if(R < L) dfs(curr + ")", n, L, R+1); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-37.Sudoku Solver.md b/Algorithm/LeetCode/Search/LeetCode-37.Sudoku Solver.md new file mode 100644 index 00000000..3fab6aa9 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-37.Sudoku Solver.md @@ -0,0 +1,89 @@ +## LeetCode - 37. Sudoku Solver (DFS、回溯) +#### [题目链接](https://leetcode.com/problems/sudoku-solver/) + +> https://leetcode.com/problems/sudoku-solver/ + +#### 题目 +![在这里插入图片描述](images/37_t.png) +#### 解析 +这个题目和[**N皇后问题**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2051.%20N-Queens(N%E7%9A%87%E5%90%8E%E9%97%AE%E9%A2%98).md)很像: + +* `N`皇后问题的数组标记法中用三个数组标记**列、主对角线、副对角线**是否已经摆法了皇后,这里同样也需要用三个二维数组来标记之前是否已经摆放(求解)了数字。 +* 使用三个数组标记之后,然后就是递归去尝试求解了。**一开始将不是`.`的先标记已经放置了(置为`true`),然后递归求解即可,记得递归之后回溯**; + +图: + +![在这里插入图片描述](images/37_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private boolean[][] rows, cols, boxes; // record position is used + + public void solveSudoku(char[][] board) { + if(board == null || board.length == 0 || board[0].length == 0) + return; + rows = new boolean[9][10]; + cols = new boolean[9][10]; + boxes = new boolean[9][10]; + for(int i = 0; i < 9; i++){ + for(int j = 0; j < 9; j++){ + if(board[i][j] == '.') continue; + int num = board[i][j] - '0'; + rows[i][num] = true; // i row have num + cols[j][num] = true; // j col have num + // 9宫格 一维和坐标的对应关系 boxKey = (r/3)*3 + c/3 + boxes[i/3 * 3 + j/3][num] = true; // which box have the num + } + } + dfs(board, 0, 0); // recursive to fill the board + } + + private boolean dfs(char[][] board, int r, int c){ + if(r == 9) // 0~8, when r = 9, already fill all the grid + return true; + // next position + int nc = (c + 1) % 9; + int nr = (nc == 0) ? r + 1 : r; // new line or the old line + // cur position don't need to fill, just go new position + if(board[r][c] != '.') + return dfs(board, nr, nc); + // cur position should fill + for(int i = 1; i <= 9; i++){ // try all possibile num + int boxKey = r/3 * 3 + c/3; // boxKey = (r/3)*3 + c/3 + if(rows[r][i] || cols[c][i] || boxes[boxKey][i]) + continue; + rows[r][i] = cols[c][i] = boxes[boxKey][i] = true; + board[r][c] = (char)('0' + i); // fill + if(dfs(board, nr, nc)) + return true; + board[r][c] = '.'; // backtrack + rows[r][i] = cols[c][i] = boxes[boxKey][i] = false; // backtrack + } + return false; + } + //test + public static void main(String[] args){ + PrintStream out = System.out; + char[][] board = { + {'5','3','.','.','7','.','.','.','.'}, + {'6','.','.','1','9','5','.','.','.'}, + {'.','9','8','.','.','.','.','6','.'}, + {'8','.','.','.','6','.','.','.','3'}, + {'4','.','.','8','.','3','.','.','1'}, + {'7','.','.','.','2','.','.','.','6'}, + {'.','6','.','.','.','.','2','8','.'}, + {'.','.','.','4','1','9','.','.','5'}, + {'.','.','.','.','8','.','.','7','9'} + }; + new Solution().solveSudoku(board); + out.println(Arrays.deepToString(board)); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-39.Combination Sum.md b/Algorithm/LeetCode/Search/LeetCode-39.Combination Sum.md new file mode 100644 index 00000000..cbf093cb --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-39.Combination Sum.md @@ -0,0 +1,348 @@ +# LeetCode - 39. Combination Sum (组合总和 | dfs) + +#### [题目链接](https://leetcode.com/problems/combination-sum/) + +> https://leetcode.com/problems/combination-sum/ + +#### 题目 +![在这里插入图片描述](images/39_t.png) + +## 解析 + + +**先看通过`dfs`求解排列数和组合数的代码**。 + +①`dfs`求解排列数 + +之前也做过一个求排列数的例题。 +[**LeetCode-46. Permutations用dfs排列数**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2046.%20Permutations(%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95).md) + +图: + +![39_ss.png](images/39_ss.png) + +代码: + + +```java +import java.io.*; +import java.util.*; + +public class Main{ + + static List>res; + + static void dfs(int[] nums, int d, int n, boolean[] used, ArrayListcurr){ + if(d == n){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = 0; i < nums.length; i++){ + if(!used[i]){ // 第i个元素没有使用过 + used[i] = true; + curr.add(nums[i]); + dfs(nums, d + 1, n, used, curr); + curr.remove(curr.size()-1); + used[i] = false; + } + } + } + + public static void main(String[] args) { + PrintStream out = System.out; + + int[] arr = {2, 1, 3}; + + res = new ArrayList<>(); + dfs(arr, 0, 3, new boolean[arr.length], new ArrayList<>()); // 从3个数中取3个数的排列 + out.println(res); + + out.println("-------------------------------"); + + res = new ArrayList<>(); + dfs(arr, 0, 2, new boolean[arr.length], new ArrayList<>());// 从3个数中取2个数的排列 + out.println(res); + } +} +``` + +输出: +```c +[[2, 1, 3], [2, 3, 1], [1, 2, 3], [1, 3, 2], [3, 2, 1], [3, 1, 2]] +------------------------------- +[[2, 1], [2, 3], [1, 2], [1, 3], [3, 2], [3, 1]] +``` + +②`dfs`求解组合数 + +注意这里的`d`和`cur`意义不同: + +* `d`表示的还是层数,当`d == n`时,就达到了`n`个数,就可以将中间结果加入结果集; +* 而`cur`存在意义就是因为组合和排列不同,不需要判断前面是否使用过(不会考虑顺序),而是只会选取指定的数,而不考虑顺序,所以`cur`表示的是当前已经选取到了这个位置,下面的选择是从`cur ~ arr.length`选择即可。这样一定不会选择重复的元素; +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static List> res; + + static void dfs(int[] arr, int depth, int cur, int n, ArrayList curr) { + if (depth == n) { + res.add(new ArrayList<>(curr)); + return; + } + for (int i = cur; i < arr.length; i++) { + curr.add(arr[i]); //当前 d 层元素为nums[i] + dfs(arr, depth + 1, i + 1, n, curr); //去考虑d+1以及后面的层,注意下次直接从i+1开始,不会有重复的 + curr.remove(curr.size() - 1); + } + } + + public static void main(String[] args) { + PrintStream out = System.out; + + int[] arr = {2, 1, 3}; + + res = new ArrayList<>(); + dfs(arr, 0, 0, 3, new ArrayList<>()); // 从3个数中取3个数的组合(不是排列) + out.println(res); + + out.println("-------------------------------"); + + res = new ArrayList<>(); + dfs(arr, 0, 0, 2, new ArrayList<>());// 从3个数中取2个数的组合(不是排列) + out.println(res); + } +} +``` + +输出: + +```c +[[2, 1, 3]] +------------------------------- +[[2, 1], [2, 3], [1, 3]] +``` + +**本题和求解组合数有几分类似,但是却不是完全相同**: + +* 本题可以有重复元素,所以在递归的时候不能选择`i+1`(下一个元素),而是需要选择当前的`i`; +* 那这样不就死循环了吗?,所以我们在选择当前`nums[i]`的时候,先检查当前累加`curSum + nums[i] > target`,如果满足,则当前`nums[i]`就不考虑了,考虑下一个,所以这样就不会死循环; +* 且这里的递归终止条件就不是元素个数了,而是当前的累加和`curSum == target`; + +代码: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List>res; + + private void dfs(int[] nums, int target, int curSum, int cur, Listcurr){ + if(curSum == target){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(curSum + nums[i] > target) continue; // 一定要有终止条件,不然会死循环 + curr.add(nums[i]); + dfs(nums, target, curSum + nums[i], i, curr); // 注意不是i+1,因为可以有重复的元素 + curr.remove(curr.size()-1); + } + } + + public List> combinationSum(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + dfs(candidates, target, 0, 0, new ArrayList<>()); + return res; + } + + + public static void main(String[] args){ + PrintStream out = System.out; + + int[] nums = {2, 3, 6, 7}; + int target = 7; + + out.println(new Solution(). + combinationSum(nums, target) + ); + } +} +``` + +**剪枝,先对数组排序,然后递归函数循环中可以将`continue`改成`break`,这样速度快了很多**: + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List>res; + + private void dfs(int[] nums, int target, int curSum, int cur, Listcurr){ + if(curSum == target){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(curSum + nums[i] > target) break; // 因为排序了,所以这里是break,不是continue,这样可以加速(剪枝) + curr.add(nums[i]); + dfs(nums, target, curSum + nums[i], i, curr); // 注意不是i+1,因为可以有重复的元素 + curr.remove(curr.size()-1); + } + } + + public List> combinationSum(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + Arrays.sort(candidates); //先排序 + dfs(candidates, target, 0, 0, new ArrayList<>()); + return res; + } + + + public static void main(String[] args){ + PrintStream out = System.out; + + int[] nums = {2, 3, 6, 7}; + int target = 7; + + out.println(new Solution(). + combinationSum(nums, target) + ); + } +} +``` + +也可以将`curSum`累加和去掉,直接从`target ~ 0`去递归: + + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List>res; + + private void dfs(int[] nums, int target, int cur, Listcurr){ + if(0 == target){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(nums[i] > target) break; //也是因为排序,所以不是continue而是break + curr.add(nums[i]); + dfs(nums, target-nums[i], i, curr); // 注意不是i+1,因为可以有重复的元素 + curr.remove(curr.size()-1); + } + } + + public List> combinationSum(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + Arrays.sort(candidates); + dfs(candidates, target, 0, new ArrayList<>()); + return res; + } + + + public static void main(String[] args){ + PrintStream out = System.out; + + int[] nums = {2, 3, 6, 7}; + int target = 7; + + out.println(new Solution(). + combinationSum(nums, target) + ); + } +} +``` + +**另外,如果题目要求结果`res`里面的顺序是从短到长的,应该怎么做呢?** + +例如第二个样例如果答案必须是第二种样子: + + +输入: `candidates = [2,3,5], target = 8` +解集为: + +① +```c +[ + [2,2,2,2], + [2,3,3], + [3,5] +] +``` +②(如果题目需要这样输出) +```c +[ + [3,5], + [2,3,3], + [2,2,2,2] +] +``` + + +其实也不难,只需要在递归函数中增加两个参数`d、n`,和之前的组合数一样,`d`表示的当前深度,`n`表示的是需要凑多少个数,然后在主函数中,依次从`n = 1、n = 2,n = 3.....`去递归,这样求出的结果的集合的大小就是从小到大的。 + + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List>res; + + private void dfs(int[] nums, int target, int d, int n, int cur, Listcurr){ + if(d == n){ + if(target == 0) + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(nums[i] > target) break; //也是因为排序,所以不是continue而是break + curr.add(nums[i]); + dfs(nums, target-nums[i], d+1, n, i, curr); // 注意不是i+1,因为可以有重复的元素 + curr.remove(curr.size()-1); + } + } + + public List> combinationSum(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + Arrays.sort(candidates); + for(int n = 1; n <= target / candidates[0]; n++)// 大小从小到大 + dfs(candidates, target, 0, n, 0, new ArrayList<>()); + return res; + } + + + public static void main(String[] args){ + PrintStream out = System.out; + + int[] nums = {2, 3, 6, 7}; + int target = 7; + + out.println(new Solution(). + combinationSum(nums, target) + ); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-40.Combination Sum II && LeetCode - 216. Combination Sum III.md b/Algorithm/LeetCode/Search/LeetCode-40.Combination Sum II && LeetCode - 216. Combination Sum III.md new file mode 100644 index 00000000..3b42e6c1 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-40.Combination Sum II && LeetCode - 216. Combination Sum III.md @@ -0,0 +1,207 @@ + +# LeetCode - 40. Combination Sum II && LeetCode - 216. Combination Sum III (DFS) +* [LeetCode - 40. Combination Sum II](#leetcode---40-combination-sum-ii) +* [LeetCode - 216. Combination Sum III](#leetcode---216-combination-sum-III) + +*** +**做这题之前可以先做[LeetCode - 39.Combination Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2039.%20Combination%20Sum%20(%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20_%20dfs).md),并知道`dfs`求解组合数的模板**。 + +## LeetCode - 40. Combination Sum II +#### [题目链接](https://leetcode.com/problems/combination-sum-ii/) + +> https://leetcode.com/problems/combination-sum-ii/ + +#### 题目 +![在这里插入图片描述](images/40_t.png) +#### 解析 +这题和[LeetCode - 39.Combination Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2039.%20Combination%20Sum%20(%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20_%20dfs).md)不同的地方在于: + +* [LeetCode - 39.Combination Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2039.%20Combination%20Sum%20(%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20_%20dfs).md)数组**没有重复的元素,但是你可以使用同一个位置的元素去组成`target`**; +* 而此题,数组中**可能存在重复的元素,但是你不能使用同一个位置的元素**; +* 由于这一题可以使用不同位置上值相同的元素,所以`dfs`得到的结果`res`中可能存在重复的答案,例如第一个示例中,如果不去重,答案会是`[1, 7], [1, 7], [1, 2, 5], [1, 2, 5], [2, 6], [1, 1, 6]`,可以看到有两个重复的答案。所以需要去重。下面给出两种代码实现去重的方式。 + + +①其实只需要在[LeetCode - 39.Combination Sum](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2039.%20Combination%20Sum%20(%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20_%20dfs).md)的基础上加上一行代码,当有连续重复(在排序之后)的值时,跳过即可,即`while(i+1 < nums.length && nums[i] == nums[i+1]) i++;`。 +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List>res; + + //不同点: 不能使用同一个位置上的元素,但是不同位置上可能有相同值的元素,所以-->需要考虑重复答案的问题 + public List> combinationSum2(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + Arrays.sort(candidates); //先排序 ,既可以剪枝,由导致相邻相等的元素在一起 + dfs(candidates, target, 0, 0, new ArrayList<>()); + return res; + } + + private void dfs(int[] nums, int target, int curSum, int cur, Listcurr){ + if(curSum == target){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(curSum + nums[i] > target) + break; // 因为排序了,所以这里是break,不是continue,剪枝 + curr.add(nums[i]); + dfs(nums, target, curSum + nums[i], i+1, curr); // notice is i+1 + curr.remove(curr.size()-1); + while(i+1 < nums.length && nums[i] == nums[i+1]) i++; //only add this; + } + } + + public static void main(String[] args){ + PrintStream out = System.out; + int[] candidates = {10, 1, 2, 7, 6, 1, 5}; + int target = 8; + out.println(new Solution(). + combinationSum2(candidates, target) + ); + } +} +``` +②或者在递归之前判断一下和前面的`nums[i-1]`是不是相同,即:` if(i != cur && nums[i] == nums[i-1]) continue;` + +```java +class Solution { + + private List>res; + + //不同点: 不能使用同一个位置上的元素,但是不同位置上可能有相同值的元素,所以-->需要考虑重复答案的问题 + public List> combinationSum2(int[] candidates, int target) { + res = new ArrayList<>(); + if(candidates == null || candidates.length == 0) + return res; + Arrays.sort(candidates); //先排序 ,既可以剪枝,由导致相邻相等的元素在一起 + dfs(candidates, target, 0, 0, new ArrayList<>()); + return res; + } + + private void dfs(int[] nums, int target, int curSum, int cur, Listcurr){ + if(curSum == target){ + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i < nums.length; i++){ + if(curSum + nums[i] > target) + break; + if(i != cur && nums[i] == nums[i-1])// judge this + continue; + curr.add(nums[i]); + dfs(nums, target, curSum + nums[i], i+1, curr); // notice is i+1 + curr.remove(curr.size()-1); + } + } + +} +``` + +*** + +## LeetCode - 216. Combination Sum III +#### [题目链接](https://leetcode.com/problems/combination-sum-iii/) + +> https://leetcode.com/problems/combination-sum-iii/ + +#### 题目 +![在这里插入图片描述](images/216_t.png) +#### 解析 + +这题其实也挺简单的,还是组合数的模板稍微改动一下。只不过将枚举数组中的元素改成`for(int i = cur; i <= 9; i++){ `,即枚举 +`cur ~ 9`,而`cur`一开始自然是从`1`开始的。 +```java +import java.io.*; +import java.util.*; + +class Solution { + + private List> res; + + public List> combinationSum3(int k, int n) { + res = new ArrayList<>(); + dfs(0, k, 1, 0, n, new ArrayList<>());// d is depth + return res; + } + + private void dfs(int d, int k, int cur, int curSum, int target, Listcurr){ + if(d == k){ + if(curSum == target) + res.add(new ArrayList<>(curr)); + return; + } + for(int i = cur; i <= 9; i++){ + if(curSum + i > target) + break; + curr.add(i); + dfs(d + 1, k, i + 1, curSum + i, target, curr); + curr.remove(curr.size() - 1); + } + } + + public static void main(String[] args){ + PrintStream out = System.out; + int k = 3, n = 9; + out.println(new Solution(). + combinationSum3(3, 9) + ); + } +} + +``` + +因为这里的数只从`1 ~ 9`,所以这题还有一种做法就是利用二进制枚举`2 ^ 9`种可能。 + +* 其中如果二进制(总共`9`位)某位`i`上值为`1`,则说明取了`i+1`(因为二进制位数从`0`开始,但是这里是`1~9`(即从`1`开始));如果`i`位置上为`0`,则没有取`i+1`; +* 枚举所有的二进制数(`0 ~ 2^9`),然后每个数,对应哪些位置上是有数的(值为`1`),如果有,就累加,最后看是不是等于`n`即可; + +例如: + +|二进制(`0 ~ 2^9`)|对应集合| +|-|-| +|000000000| [ ] | +|000000001| [ 1 ]| +|000100011| [ 1, 2, 6]| +|010101101| [ 1, 3, 4, 6, 8]| +|111111110|[ 2, 3, 4, 5, 6, 7, 8, 9]| +|111111111|[1, 2, 3, 4, 5, 6, 7, 8, 9]| + +代码: +```java +import java.io.*; +import java.util.*; + +class Solution { + + public List> combinationSum3(int k, int n) { + List> res = new ArrayList<>(); + + for(int mask = 0; mask < (1 << 9); mask++){ + Listcur = new ArrayList<>(); + int sum = 0; + for(int i = 1; i <= 9; i++) + // if( (mask & (1 << (i-1))) != 0){ + if( (( mask >> (i-1) ) & 1) == 1){// same as above + sum += i; + cur.add(i); + } + if(sum == n && cur.size() == k) + res.add(new ArrayList<>(cur)); + } + return res; + } + + public static void main(String[] args){ + PrintStream out = System.out; + int k = 3, n = 9; + out.println(new Solution(). + combinationSum3(3, 9) + ); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-46.Permutations.md b/Algorithm/LeetCode/Search/LeetCode-46.Permutations.md new file mode 100644 index 00000000..a57b8387 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-46.Permutations.md @@ -0,0 +1,150 @@ +# LeetCode - 46. Permutations + +#### [题目链接](https://leetcode.com/problems/permutations/description/) + +> https://leetcode.com/problems/permutations/description/ + +#### 题目 + +![在这里插入图片描述](images/46_t.png) + +### 1、经典排列 +![在这里插入图片描述](images/46_s.png) + +这种方法采用的是位置两两交换,交换后出现一种新的组合。 + +将这种新的组合添加到中间集,再将中间集添加到结果集中。 + +另外,[这篇博客](https://blog.csdn.net/summerxiachen/article/details/60579623)讲的不错。 + +```java +class Solution { + public List> permute(int[] nums) { + List> res = new ArrayList<>(); + dfs(res, nums, 0); + return res; + } + + public void dfs(List> res, int[] nums, int cur) { + if (cur == nums.length) { + List temp = new ArrayList<>(); + for (Integer item : nums) + temp.add(item); + res.add(temp); + } else for (int i = cur; i < nums.length; i++) { + swap(nums, cur, i); + dfs(res, nums, cur + 1); + swap(nums, cur, i); //这里一定要交换回来,因为不是和C++一样是数组的拷贝,Java中是数组的引用,不能改变,不然得不到正确的结果 + } + } + + public void swap(int[] arr, int a, int b) { + int temp = arr[a]; + arr[a] = arr[b]; + arr[b] = temp; + } +} +``` +*** +### 2、`dfs`记录是否使用 +这种方法就是使用一个`bool`数组,记录是否填过,如果没有填过就填(向中间集添加元素)。 + +然后递归,填到`n`个数就得到一个排列,时间复杂度O(`n* n``n-1`)。 + +![这里写图片描述](images/39_ss.png) + +

+代码: + +```java +class Solution { + + private List> res; + + public List> permute(int[] nums) { + res = new ArrayList<>(); + dfs(new ArrayList<>(), nums, new boolean[nums.length]); + return res; + } + + public void dfs(List curr, int[] arr, boolean[] used) { + if (curr.size() == arr.length) { + //注意不能直接添加temp + res.add(new ArrayList<>(curr)); //以一个集合或数组初始化ArrayList al = new ArrayList(a);//a为集合或数组 + return; + } + for (int i = 0; i < arr.length; i++) { + if (!used[i]) {//记录某个下标的数是否被使用过 + curr.add(arr[i]); + used[i] = true; + dfs(curr, arr, used); + curr.remove(curr.size() - 1); //移除最后一个 + used[i] = false; + } + } + } +} + +``` +*** +### 3、非递归实现 + +这个方法就是每次在已经有的集合中,每个集合的每个可以放置的位置,我们都放置一遍,并且把原来的移除掉,这样就能得到所有的排列。 +看下图,如果集合中有两个子集合`[1,2]`,和`[2,1]`,就可以分别生成下面的集合 + +![这里写图片描述](images/46_ss2.png) + +代码: + +```java +import java.util.*; + +class Solution { + + public List> permute(int[] nums) { + List> res = new ArrayList<>(); + if (nums == null || nums.length == 0) + return res; + res.add(Arrays.asList(nums[0])); + List> newRes; + for (int i = 1; i < nums.length; i++) { + newRes = new ArrayList<>(); + for (List list : res) { + for (int j = 0; j <= list.size(); j++) { //注意这里是<= 有这么多个位置可以插入 + List temp = new ArrayList<>(list); + temp.add(j, nums[i]); //在j位置放入nums[i] + newRes.add(temp); + } + } + res = newRes; + } + return res; + } +} +``` +非递归简单优化。 + +上面的非递归可以简写成下面的形式,意思是差不多的,每次都全部取出来。 + +```java +import java.util.*; + +class Solution { + + public List> permute(int[] nums) { + LinkedList> res = new LinkedList>(); + res.add(new ArrayList<>()); + for (int i = 0; i < nums.length; i++) { + for (int size = res.size(); size > 0; size--) { + List item = res.pollFirst(); //每次取出来一个并且弹出 + for (int j = 0; j <= item.size(); j++) { + List temp = new ArrayList<>(item); + temp.add(j, nums[i]); + res.add(temp); + } + } + } + return res; + } +} +``` diff --git "a/Algorithm/LeetCode/Search/LeetCode-463.Island Perimeter(\345\262\233\345\261\277\347\232\204\345\221\250\351\225\277)(\350\247\204\345\276\213_DFS_BFS).md" "b/Algorithm/LeetCode/Search/LeetCode-463.Island Perimeter(\345\262\233\345\261\277\347\232\204\345\221\250\351\225\277)(\350\247\204\345\276\213_DFS_BFS).md" new file mode 100644 index 00000000..7a82a922 --- /dev/null +++ "b/Algorithm/LeetCode/Search/LeetCode-463.Island Perimeter(\345\262\233\345\261\277\347\232\204\345\221\250\351\225\277)(\350\247\204\345\276\213_DFS_BFS).md" @@ -0,0 +1,242 @@ +## LeetCode - 463. Island Perimeter(岛屿的周长)(规律/DFS/BFS) +* 找规律 +* BFS +* DFS + +*** +#### [题目链接](https://leetcode.com/problems/island-perimeter/) + +> https://leetcode.com/problems/island-perimeter/ + +#### 题目 +![在这里插入图片描述](images/463_t.png) +### 找规律 +这个方法的思路就是: + +* 首先看如果没有相邻的方格的话,就是`4 * (grid[i][j] == 1)`的数量 ,记为`island`; +* 如果有相邻的,我们只需要遍历每一个,上下左右`4`个方向,有相邻的统计即可,最后用`island - 这个数量`即可; + +图: + +

+ +代码: + +```java +class Solution { + public int islandPerimeter(int[][] grid) { + if (grid == null || grid.length == 0) + return 0; + int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + int island = 0, neighbor = 0; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1) { + island++; + for (int k = 0; k < 4; k++) { + if (judge(grid, i + dir[k][0], j + dir[k][1])) + neighbor++; + } + } + } + } + return 4 * island - neighbor; + } + + private boolean judge(int[][] grid, int i, int j) { + if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) + return false; + return grid[i][j] == 1; + } +} +``` +这种写法一点小小的改进就是,只判断四个方向中的两个,这样就可以少判断一个岛屿,但是`neighbor`就要`*2` + +```java +class Solution { + public int islandPerimeter(int[][] grid) { + if (grid == null || grid.length == 0) + return 0; + int island = 0, neighbor = 0; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1) { + island++; + if (i - 1 >= 0 && grid[i - 1][j] == 1)//检查上 + neighbor++; + if (j + 1 < grid[0].length && grid[i][j + 1] == 1) + neighbor++; + } + } + } + return 4 * island - neighbor * 2; //注意这里是 * 2 + } +} +``` +*** +### BFS +这题也可以使用`BFS`,但是这里要注意: + +* **在判断`check`函数中,没有加入`vis[i][j]`的判断,因为这会影响`neighbor`的统计**; +* **因为`neighbor`的统计不依赖于 是否访问过这个方格,而是只看这个方格是否有相邻的方格**; + +代码: + +```java +class Solution { + + private class Coordinate { + public int x; + public int y; + + public Coordinate(int x, int y) { + this.x = x; + this.y = y; + } + } + + public int islandPerimeter(int[][] grid) { + if (grid == null || grid.length == 0) + return 0; + int[] x = {-1, 0, 1, 0}; + int[] y = {0, 1, 0, -1}; + Queue queue = new LinkedList<>(); + boolean[][] vis = new boolean[grid.length][grid[0].length]; + int res = 0; + Coordinate cur = null, next = new Coordinate(-1, -1); + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 0 || vis[i][j]) + continue; + queue.add(new Coordinate(i, j)); // add the start + vis[i][j] = true; + // begin the BFS + while (!queue.isEmpty()) { + cur = queue.poll(); + res += 4; + int neighbor = 0; + for (int k = 0; k < 4; k++) { + next.x = cur.x + x[k]; + next.y = cur.y + y[k]; + if (check(grid, next.x, next.y, vis)) { + if (!vis[next.x][next.y]) { + vis[next.x][next.y] = true; + queue.add(new Coordinate(next.x, next.y)); + } + neighbor++;//无论是否 vis 都要++ + } + } + res -= neighbor; + } + } + } + return res; + } + + //注意这里 没有判断 !vis[i][j] 因为那样不能正确统计 count + private boolean check(int[][] grid, int i, int j, boolean[][] vis) { + return i >= 0 && i < grid.length && j >= 0 && + j < grid[0].length && grid[i][j] == 1; + } +} +``` +*** +### DFS +但是也要和`BFS`一样,注意: + +* 虽然访问过了,但是还是要减去`1`的情况,也就是`else if`中的; +* 没访问过,是`1`,当然也要减去`1`; + +代码: + + +```java +class Solution { + + int[] dirx = {-1, 0, 1, 0}; + int[] diry = {0, 1, 0, -1}; + boolean[][] vis; + + public int islandPerimeter(int[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) + return 0; + int res = 0; + vis = new boolean[grid.length][grid[0].length]; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1 && !vis[i][j]) + res += dfs(grid, i, j); + } + } + return res; + } + + private int dfs(int[][] grid, int x, int y) { + vis[x][y] = true; + int edges = 4; + for (int i = 0; i < 4; i++) { + int nx = x + dirx[i]; + int ny = y + diry[i]; + if (check(grid, nx, ny)) { + if (grid[nx][ny] == 1 && !vis[nx][ny]) { + edges += dfs(grid, nx, ny); + edges--; // 因为有1也是相邻,所以要 -1 注意不是-2,因为只负责一边 + } else if (vis[nx][ny]) { // grid[nx][ny] == 1 && vis[nx][ny] --> 这个也要,不要忘了 + edges--; + } + } + } + return edges; + } + + private boolean check(int[][] grid, int i, int j) { + return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length; + } +} +``` +改一下`check`函数,改写一下,和`BFS`更像,也阔以: + +```java +class Solution { + + int[] dirx = {-1, 0, 1, 0}; + int[] diry = {0, 1, 0, -1}; + boolean[][] vis; + + public int islandPerimeter(int[][] grid) { + if (grid == null || grid.length == 0 || grid[0].length == 0) + return 0; + int res = 0; + vis = new boolean[grid.length][grid[0].length]; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1 && !vis[i][j]) + res += dfs(grid, i, j); + } + } + return res; + } + + private int dfs(int[][] grid, int x, int y) { + vis[x][y] = true; + int edges = 4; + for (int i = 0; i < 4; i++) { + int nx = x + dirx[i]; + int ny = y + diry[i]; + if (check(grid, nx, ny)) { + if (!vis[nx][ny]) { + edges += dfs(grid, nx, ny); + } + edges--; //无论是否访问过都要 减去 1, -1 注意不是-2,因为只负责一边 + } + } + return edges; + } + + //这里改了 + private boolean check(int[][] grid, int i, int j) { + return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] == 1; + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-47.Permutations II.md b/Algorithm/LeetCode/Search/LeetCode-47.Permutations II.md new file mode 100644 index 00000000..00327d0a --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-47.Permutations II.md @@ -0,0 +1,181 @@ +# LeetCode - 47. Permutations II(不重复全排列)(四种方式实现) + +#### [题目链接](https://leetcode.com/problems/permutations-ii/description/) + +> https://leetcode.com/problems/permutations-ii/description/ + + +#### 题目 +![在这里插入图片描述](images/47_t.png) + +## 解析 + +**做这个题目之前,先做[LeetCode46-Permutations](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2046.%20Permutations(%E4%B8%89%E7%A7%8D%E6%96%B9%E6%B3%95).md)**。 + +### 1、使用普通全排列并使用`List`判断去重 + + 这种方式很简单,就是在将中间结果集添加到最终结果集之前,判断一下中间集在结果集中之前是否出现过即可。 + +这种方法使用`list`的`contains`去重,会很慢。 + +```java +import java.util.*; + +class Solution { + + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + if (nums == null || nums.length == 0) return res; + dfs(res, nums, new ArrayList<>(), 0); + return res; + } + + private void dfs(List> res, int[] arr, List temp, int cur) { + if (cur == arr.length) { + for (int i = 0; i < arr.length; i++) + temp.add(arr[i]); + if (!res.contains(temp))//去重 ,很慢 + res.add(new ArrayList<>(temp)); + temp.clear(); + return; + } + for (int i = cur; i < arr.length; i++) { + swap(arr, cur, i); + dfs(res, arr, temp, cur + 1); + swap(arr, cur, i); + } + } + + private void swap(int[] arr, int a, int b) { + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } +} +``` + +*** +### 2、使用排序和判断去重 + +这个方法比较玄乎: +* 使用的是先要排序,而且在调用`dfs`函数的时候,我们要进行行数组的拷贝(`Arrays.copyof()`),不能使用原来的数组(`C++`中传递的就是数组的拷贝); +* 而且使用数组的复制品还不能交换回来,这样还可以得到整个序列的字典序,去重的时候使用` if(cur != i && newNums[i] == newNums[cur])continue; `去重。 + +代码: + +```java +import java.util.*; + +class Solution { + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + if (nums == null || nums.length == 0) return res; + Arrays.sort(nums); //必须要排序,不然也不能去重 ,为了让重复的元素相邻 + dfs(res, nums, 0); + return res; + } + + public void dfs(List> res, int[] nums, int cur) { + int[] newNums = Arrays.copyOf(nums,nums.length);//因为Java中的数组是引用,不像C++中一样是数组的拷贝 + if (cur == newNums.length) { + List temp = new ArrayList<>(); + for (Integer item : newNums) temp.add(item); + res.add(temp); //这里不需要再次判断重复 + } else for (int i = cur; i < newNums.length; i++) { + if (cur != i && newNums[i] == newNums[cur]) continue; //去重 + swap(newNums, cur, i); + dfs(res, newNums, cur + 1); +// swap(newNums,cur,i); //不能交换 不然也不能去重 + } + } + + private void swap(int[] arr, int a, int b) { + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } +} +``` +*** +### 3、使用`HashSet`判断去重 + 另一种方法就是每次递归的时候使用一个`HashSet`来判断去重。 + +```java +import java.util.*; + +class Solution { + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + if (nums == null || nums.length == 0) + return res; + dfs(res, nums, 0); + return res; + } + + private void dfs(List> res, int[] nums, int cur) { + if (cur == nums.length) { + List temp = new ArrayList<>(); + for (Integer num : nums) temp.add(num); + res.add(temp); + } else { + Set set = new HashSet<>();//去重的 + for (int i = cur; i < nums.length; i++) { + if (!set.contains(nums[i])) { + set.add(nums[i]); + swap(nums, cur, i); + dfs(res, nums, cur + 1); + swap(nums, cur, i); + } + } + } + } + + private void swap(int[] arr, int a, int b) { + int t = arr[a]; + arr[a] = arr[b]; + arr[b] = t; + } +} +``` +*** +### 4、使用`dfs` + 排序去重 + 这个方法和`LeetCode - 46`那个`dfs`也差不多: + * 主要是如何判断重复,这里要先排序,保证重复的元素会贴在一块。 + * 对与重复的元素循环时跳过递归的调用只对第一个未被使用的进行递归,那么这一次的结果将会唯一出现在结果集中,而后重复的元素将会被略过; + * 如果第一个重复元素还没在当前结果中,那么我们就不需要进行递归。 + +图: + +

+ +代码: + +```java +import java.util.*; + +class Solution { + public List> permuteUnique(int[] nums) { + List> res = new ArrayList<>(); + if (nums == null || nums.length == 0) return res; + Arrays.sort(nums); //先排序,必须保证相邻的元素在一块 + dfs(res, new ArrayList<>(), nums, new boolean[nums.length]); + return res; + } + + private void dfs(List> res, ArrayList temp, int[] arr, boolean[] used) { + if (temp.size() == arr.length) { + res.add(new ArrayList<>(temp)); + return; + } + for (int i = 0; i < arr.length; i++) { + if (used[i] || (i > 0 && !used[i - 1] && arr[i] == arr[i - 1])) + continue; + used[i] = true; + temp.add(arr[i]); + dfs(res, temp, arr, used); + temp.remove(temp.size() - 1); + used[i] = false; + } + } +} +``` diff --git a/Algorithm/LeetCode/Search/LeetCode-488.Zuma Game (DFS).md b/Algorithm/LeetCode/Search/LeetCode-488.Zuma Game (DFS).md new file mode 100644 index 00000000..0ac82f34 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-488.Zuma Game (DFS).md @@ -0,0 +1,171 @@ + +# LeetCode - 488. Zuma Game (DFS) + +#### [题目链接](https://leetcode.com/problems/zuma-game/) + +> https://leetcode.com/problems/zuma-game/ + +#### 题目 +![在这里插入图片描述](images/488_t.png) + +#### 解析 + +看题目中的三个例子: + +![在这里插入图片描述](images/488_s.png) + +DFS过程: + +* 先用一个`map`保存`hand`字符串中每种颜色的个数; +* `dfs`过程,遍历当前`board`字符串,逐个去寻找一段连续的相同的球,如果这段相同的球在`map`中还可以提供足够的消去的球,就先消去,这里需要消耗`3 - (j - i)`个球(具体看代码); +* 然后先去递归消去剩下的(由`eliminate`函数求出消去之后剩下的字符串(连环消)),然后这种情况下消去的总消耗是`3 - (j - i) + dfs(剩下的串)`,然后更新最小值即可; + + +```java +import java.io.*; +import java.util.*; + +class Solution { + + private HashMaphMap; // hand中每个字符的数量 + + public int findMinStep(String board, String hand) { + if(board == null || hand == null) + return 0; + hMap = new HashMap<>(); +// for(char c : hand.toCharArray()){ +// if(!hMap.containsKey(c)) +// hMap.put(c, 1); +// else +// hMap.put(c, hMap.get(c) + 1); +// } + for(char c : hand.toCharArray()) + hMap.put(c, 1 + hMap.getOrDefault(c, 0)); + return dfs(board); + } + + private int dfs(String s){ + if("".equals(s)) + return 0; + int res = 2 * s.length() + 1; // worst case, every character need 2 characters + for(int i = 0; i < s.length(); ){ + char ch = s.charAt(i); + int j = i+1; // continuous same length + while(j < s.length() && ch == s.charAt(j)) + j++; + // s[i] ~ s[j-1] have the same value + int num = 3 - (j - i); + Integer count = hMap.get(ch); + if(count != null && count >= num){ // can eliminate + String ns = eliminate(s.substring(0, i) + s.substring(j)); + hMap.put(ch, count - num); // update the map + int r = dfs(ns); + hMap.put(ch, hMap.get(ch) + num); // backtrack + if(r != -1) + res = Math.min(res, r + num); + } + i = j; // next the same string + } + return res == 2 * s.length() + 1 ? -1 : res; + } + + // example : "YWWRRRWWYY" -> "YWWWWYY" -> "YYY" --> "" + private String eliminate(String s){ + for(int i = 0; i < s.length(); ){ + int j = i + 1; + while(j < s.length() && s.charAt(i) == s.charAt(j)) + j++; + if(j - i >= 3){ //subtract + s = s.substring(0, i) + s.substring(j); + i = 0; // notice + }else + i++; + } + return s; + } + + public static void main(String[] args){ + PrintStream out = System.out; + String board = "WWRRBBWW"; + String hand = "WRBRW"; + out.println(new Solution(). + findMinStep(board, hand) + ); + } +} + +``` + +将`map`改成数组: +```java +import java.io.*; +import java.util.*; + +class Solution { + + private int[] counts; + + public int findMinStep(String board, String hand) { + if(board == null || hand == null) + return 0; + counts = new int[128]; + for(char c : hand.toCharArray()) + counts[c]++; + return dfs(board); + } + + private int dfs(String s){ + if("".equals(s)) + return 0; + int res = 2 * s.length() + 1; // worst case, every character need 2 character s;= + for(int i = 0; i < s.length(); ){ + char ch = s.charAt(i); + int j = i+1; // continuous same length + while(j < s.length() && ch == s.charAt(j)) + j++; + // s[i] ~ s[j-1] have the same value + int num = 3 - (j - i); + if(counts[ch] >= num) { // can eliminate + String ns = eliminate(s.substring(0, i) + s.substring(j)); + counts[ch] -= num; + int r = dfs(ns); + counts[ch] += num; + if(r != -1) + res = Math.min(res, r + num); + } + i = j; // next the same string + } + return res == 2 * s.length() + 1 ? -1 : res; + } + + // example : "YWWRRRWWYY" -> "YWWWWYY" -> "YYY" --> "" + private String eliminate(String s){ + for(int i = 0; i < s.length(); ){ + int j = i + 1; + while(j < s.length() && s.charAt(i) == s.charAt(j)) + j++; + if(j - i >= 3){ //subtract + s = s.substring(0, i) + s.substring(j); + i = 0; // notice + }else + i++; + } + return s; + } + + public static void main(String[] args){ + PrintStream out = System.out; + String board = "WWRRBBWW"; + String hand = "WRBRW"; + out.println(new Solution(). + findMinStep(board, hand) + ); + } +} +``` + +注意其实真正的祖玛游戏和这个题目有点偏差,这种方法我们每次都是如果`map`中有足够的球,就消去,也就是说,我们每次都会消去,但是实际游戏是我们可以在中间部分不消去,而是只摆法一个球,后面可以消去而节省步数。看下面的例子: + +如果按照这种方式枚举每一个位置,会超时: + +![在这里插入图片描述](images/488_s2.png) \ No newline at end of file diff --git a/Algorithm/LeetCode/Search/LeetCode-51.N-Queens.md b/Algorithm/LeetCode/Search/LeetCode-51.N-Queens.md new file mode 100644 index 00000000..b34671e4 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-51.N-Queens.md @@ -0,0 +1,186 @@ +## LeetCode - 51. N-Queens(N皇后问题) + + - 数组标记法 + - 下标判断法 + +*** +#### [题目链接](https://leetcode.com/problems/n-queens/) + +> https://leetcode.com/problems/n-queens/ + +#### 题目 +![在这里插入图片描述](images/51_t.png) + + +### 1、数组标记法 + + - 这里使用三个数组分别标记那些不能访问的列,主对角线,和副对角线。注意`cols[c] = true`表示`c`列已经有了皇后; + - **而`d1[]`表示的是副对角线,拿`8`皇后来说,我们把`n`正方形划分成`14`根对角线,每个对角线上有一个值,就是这根对角线上任意一个点的`x + y`的值(从右上角到左下角);** + - **而`d2[]`表示的是主对角线,对于`8`皇后来说,也是划分成`14`根,不过对应关系是 `id2 = x - y + (n-1)`(从左下角到右上角);** + - 通过上面的对应关系,我们就可以用一个数组来存这个下标,看这个点通过`(x + y)`或者`(x-y+(n-1))`求出的下标上面是不是`true`,如果是,说明这根对角线上有皇后,所以不能放,否则可以放; + +![在这里插入图片描述](images/51_s.png) + +代码: + +```java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class Solution { + + private int N; + private boolean[] cols, d1, d2; + private List tmp; + private List> res; + + public List> solveNQueens(int n) { + res = new ArrayList<>(); + if (n == 0) + return res; + cols = new boolean[n]; + d1 = new boolean[n * 2 - 1]; + d2 = new boolean[n * 2 - 1]; + tmp = new ArrayList<>(); + N = n; + + dfs(0); + return res; + } + + public void dfs(int r) { + if (r == N) { + res.add(new ArrayList<>(tmp)); + return; + } + for (int c = 0; c < N; c++) { //考察每一列 + int id1 = c + r; //主对角线 + int id2 = r - c + N - 1;//副对角线对应的 id值 + if (cols[c] || d1[id1] || d2[id2]) continue; + cols[c] = d1[id1] = d2[id2] = true; + char[] help = new char[N]; //每一个temp是一个解 而每一个temp中又有n行String + Arrays.fill(help, '.'); + help[c] = 'Q'; + tmp.add(new String(help)); + dfs(r + 1); + cols[c] = d1[id1] = d2[id2] = false; //递归之后还原 + tmp.remove(tmp.size() - 1); + } + } +} +``` +或者改装一下,使用一个二维字符数组存储: +```java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +class Solution { + + private int N; + private boolean[] cols, d1, d2; + private List> res; + private char[][] board; + + public List> solveNQueens(int n) { + res = new ArrayList<>(); + if (n == 0) + return res; + cols = new boolean[n]; + d1 = new boolean[n * 2 - 1]; + d2 = new boolean[n * 2 - 1]; + N = n; + + board = new char[n][n]; + for (char[] row : board) + Arrays.fill(row, '.'); + + dfs(0); + return res; + } + + public void dfs(int r) { + if (r == N) { + res.add(convert(board)); + return; + } + for (int c = 0; c < N; c++) { //考察每一列 + int id1 = c + r; + int id2 = r - c + N - 1; + if (cols[c] || d1[id1] || d2[id2]) continue; + + cols[c] = d1[id1] = d2[id2] = true; + board[r][c] = 'Q'; + + dfs(r + 1); + + cols[c] = d1[id1] = d2[id2] = false; + board[r][c] = '.'; + } + } + + private List convert(char[][] board) { + List ans = new ArrayList<>(); + for (char[] row : board) + ans.add(new String(row)); + return ans; + } +} +``` +*** +### 2、下标判断法 +这个就是通过下标对应关系: + + - `cols`表示的意义和上面的不同,`cols[i] = j`,表示的是`[i,j]`上面放了一个皇后; + - **重点是判断我这一行和之前已经放置的所有行`(0....r-1)`的主副对角线充不冲突;** + - 而判断这个的方式就是看看主对角线和父对角线之间的下标对应关系; + - 主对角线方向满足,行之差等于列之差:`i-j == cols[i] - cols[j]`; + - 副对角线方向满足, 行之差等于列之差的相反数:`i-j == cols[j]-cols[i]`; + +代码: + +

+ +代码: + +```java +class Solution { + public List> solveNQueens(int n) { + List> res = new ArrayList<>(); + if (n == 0) + return res; + dfs(0, n, new int[n], res); + return res; + } + + public void dfs(int r, int n, int[] cols, List> res) { + if (r == n) { + List temp = new ArrayList<>(); + for (int i = 0; i < cols.length; i++) { + char[] help = new char[n]; + Arrays.fill(help, '.'); + help[cols[i]] = 'Q'; + temp.add(new String(help)); + } + res.add(temp); + return; + } + for (int c = 0; c < n; c++) { //考察每一列 + if (!isValid(cols, r, c)) + continue; + cols[r] = c; //第r行放在c列 + dfs(r + 1, n, cols, res); + } + } + + private boolean isValid(int[] cols, int r, int c) { + for (int i = 0; i < r; i++) + if (c == cols[i] || r - i == c - cols[i] || r - i == cols[i] - c) return false; + return true; + } +} +``` + + + diff --git a/Algorithm/LeetCode/Search/LeetCode-52.N-Queens II.md b/Algorithm/LeetCode/Search/LeetCode-52.N-Queens II.md new file mode 100644 index 00000000..8dc1e855 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-52.N-Queens II.md @@ -0,0 +1,88 @@ +# LeetCode - 52. N-Queens + +#### [题目链接](https://leetcode.com/problems/n-queens-ii/) + +> https://leetcode.com/problems/n-queens-ii/ + +#### 题目 + +![52_t.png](images/52_t.png) + +## 解析 + +比上一题还要简单,只要你求方法数。。具体看上一题`LeetCode - 51`的解析吧,递归到`N`层的时候,累加结果就行了。 + +![51_ss3.png](images/51_ss3.png) + +代码: + +```java +class Solution { + + private int res; + private boolean[] cols, d1, d2; + + public int totalNQueens(int n) { + if (n == 0) + return res; + cols = new boolean[n]; + d1 = new boolean[n * 2 - 1]; + d2 = new boolean[n * 2 - 1]; + dfs(0, n); + return res; + } + + public void dfs(int r, int n) { + if (r == n) { + res++; + return; + } + for (int c = 0; c < n; c++) { //考察每一列 + int id1 = c + r; + int id2 = r - c + n - 1; + if (cols[c] || d1[id1] || d2[id2]) continue; + cols[c] = d1[id1] = d2[id2] = true; + dfs(r + 1, n); + cols[c] = d1[id1] = d2[id2] = false; + } + } +} +``` + +也是第二种方法统计数目就可以了。 + +

+代码: + +```java +public class Solution { + + private int[] cols; + + public int totalNQueens(int n) { + if (n < 1) return 0; + cols = new int[n]; + return dfs(0, n); + } + + public int dfs(int r, int n) { + if (r == n) + return 1; + int res = 0; + for (int c = 0; c < n; c++) { + if (!isValid(r, c)) continue; + cols[r] = c; + res += dfs(r + 1, n); + } + return res; + } + + private boolean isValid(int r, int c) { + for (int i = 0; i < r; i++) + if (c == cols[i] || r - i == c - cols[i] || r - i == cols[i] - c) + return false; + return true; + } +} +``` + diff --git "a/Algorithm/LeetCode/Search/LeetCode-675.Cut Off Trees for Golf Event (\346\216\222\345\272\217BFS\346\261\202\346\234\200\347\237\255\350\267\257).md" "b/Algorithm/LeetCode/Search/LeetCode-675.Cut Off Trees for Golf Event (\346\216\222\345\272\217BFS\346\261\202\346\234\200\347\237\255\350\267\257).md" new file mode 100644 index 00000000..ae66b871 --- /dev/null +++ "b/Algorithm/LeetCode/Search/LeetCode-675.Cut Off Trees for Golf Event (\346\216\222\345\272\217BFS\346\261\202\346\234\200\347\237\255\350\267\257).md" @@ -0,0 +1,187 @@ + +# LeetCode - 675. Cut Off Trees for Golf Event (排序BFS求最短路) +#### [题目链接](https://leetcode.com/problems/cut-off-trees-for-golf-event/) + +> https://leetcode.com/problems/cut-off-trees-for-golf-event/ + +#### 题目 +![在这里插入图片描述](images/675_t.png) +## 解析 +比较典型的BFS题目。 + +看下面一个例子: + +![在这里插入图片描述](images/675_s.png) + +* 因为题目必须要按照树的高度来砍(访问), 所以我们只需要将所有树按照高度`height`排序,然后进行对按照顺序`bfs`访问所有的树即可; +* 结果就是所有`bfs`结果的和; + +代码: + +```java +class Solution { + + private class Tuple implements Comparable { + public int height; + public int x; + public int y; + + public Tuple(int height, int x, int y) { + this.height = height; + this.x = x; + this.y = y; + } + + @Override + public int compareTo(Tuple o) { + return height - o.height; + } + } + + private class State { + public int x; + public int y; + public int step; + + public State(int x, int y, int step) { + this.x = x; + this.y = y; + this.step = step; + } + } + + + private int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + private boolean checkBoundary(int x, int y, List> forest){ + return x >= 0 && x < forest.size() && y >= 0 && y < forest.get(0).size(); + } + + private int bfs(State start, State end, List> forest) { + Queue queue = new LinkedList<>(); + boolean[][] vis = new boolean[forest.size()][forest.get(0).size()]; + queue.add(start); + vis[start.x][start.y] = true; + while (!queue.isEmpty()) { + State cur = queue.poll(); + if (cur.x == end.x && cur.y == end.y) { + return cur.step; + } + for (int i = 0; i < 4; i++) { + int nx = cur.x + dir[i][0]; + int ny = cur.y + dir[i][1]; + if (checkBoundary(nx, ny, forest) && !vis[nx][ny] && forest.get(nx).get(ny) > 0) { + vis[nx][ny] = true; + queue.add(new State(nx, ny, cur.step + 1)); + } + } + } + return -1; + } + + + public int cutOffTree(List> forest) { + if (forest == null || forest.size() == 0) + return 0; + int n = forest.size(); + int m = forest.get(0).size(); + ArrayList lists = new ArrayList<>(); + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (forest.get(i).get(j) > 1) { + lists.add(new Tuple(forest.get(i).get(j), i, j)); + } + } + } + // sort by height + Collections.sort(lists); + + int sx = 0, sy = 0; + int res = 0; + + for (int i = 0; i < lists.size(); i++) { + int ex = lists.get(i).x; + int ey = lists.get(i).y; + int step = bfs(new State(sx, sy, 0), new State(ex, ey, 0), forest); + if (step == -1) + return -1; + res += step; +// forest.get(sx).set(sy, 1); // do it or not do both ok + sx = ex; + sy = ey; + } + return res; + } +} +``` + +另一种用数组替代类的方法: + +```java +class Solution { + + private int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + private boolean checkBoundary(int x, int y, List> forest){ + return x >= 0 && x < forest.size() && y >= 0 && y < forest.get(0).size(); + } + + private int bfs(int sx, int sy, int ex, int ey, List> forest) { + Queue queue = new LinkedList<>(); + boolean[][] vis = new boolean[forest.size()][forest.get(0).size()]; + queue.add(new int[]{sx, sy, 0}); + vis[sx][sy] = true; + while (!queue.isEmpty()) { + int[] cur = queue.poll(); + int curx = cur[0]; + int cury = cur[1]; + if (curx == ex && cury == ey) { + return cur[2]; + } + for (int i = 0; i < 4; i++) { + int nx = curx + dir[i][0]; + int ny = cury + dir[i][1]; + if (checkBoundary(nx, ny, forest) && !vis[nx][ny] && forest.get(nx).get(ny) > 0) { + vis[nx][ny] = true; + queue.add(new int[]{nx, ny, cur[2]+1}); + } + } + } + return -1; + } + + + public int cutOffTree(List> forest) { + if (forest == null || forest.size() == 0) + return 0; + int n = forest.size(); + int m = forest.get(0).size(); + PriorityQueuepq = new PriorityQueue<>((o1, o2) -> o1[2] - o2[2]); + // PriorityQueuepq = new PriorityQueue<>(Comparator.comparingInt(o -> o[2])); //按照height排序 + + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (forest.get(i).get(j) > 1) { + pq.add(new int[]{i, j, forest.get(i).get(j)}); + } + } + } + int sx = 0, sy = 0; + int res = 0; + + while(!pq.isEmpty()){ + int[] cur = pq.poll(); + int ex = cur[0]; + int ey = cur[1]; + int step = bfs(sx, sy, ex, ey, forest); + if (step == -1) + return -1; + res += step; + sx = ex; + sy = ey; + } + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-733.Flood Fill.md b/Algorithm/LeetCode/Search/LeetCode-733.Flood Fill.md new file mode 100644 index 00000000..a8000bf1 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-733.Flood Fill.md @@ -0,0 +1,48 @@ +# LeetCode - 733. Flood Fill + +#### [题目链接](https://leetcode.com/problems/flood-fill/) + +> https://leetcode.com/problems/flood-fill/ + +#### 题目 + +![1555045719085](assets/1555045719085.png) + +## 解析 + +入门DFS题目。 + +```java +import java.util.Arrays; + +public class Solution { + + final int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + private int startColor; + + public int[][] floodFill(int[][] image, int sr, int sc, int newColor) { + startColor = image[sr][sc]; + dfs(image, sr, sc, newColor); + return image; + } + + private void dfs(int[][] image, int x, int y, int newColor) { + if (image[x][y] == newColor) return; + image[x][y] = newColor; + for (int i = 0; i < 4; i++) { + int nx = x + dir[i][0]; + int ny = y + dir[i][1]; + if (nx >= 0 && nx < image.length && ny >= 0 && ny < image[0].length && image[nx][ny] == startColor) + dfs(image, nx, ny, newColor); + } + + } + + public static void main(String[] args) { + int[][] arr = {{1, 1, 1}, {1, 1, 0}, {1, 0, 1}}; + System.out.println(Arrays.deepToString(new Solution().floodFill(arr, 1, 1, 2))); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-78.Subsets.md b/Algorithm/LeetCode/Search/LeetCode-78.Subsets.md new file mode 100644 index 00000000..60cef80d --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-78.Subsets.md @@ -0,0 +1,207 @@ +# LeetCode - 78. Subsets + +#### [题目链接](https://leetcode-cn.com/problems/subsets/) + +> https://leetcode-cn.com/problems/subsets/ + +#### 题目 + +![1554641481809](assets/1554641481809.png) + +### 1、当前位置选或者不选 + +这个方法我个人觉得也是最容易理解的。我们要求的是所有的子集的可能,于是我们考虑每一位取或者不取,这不就是考虑了所有子集的可能吗?。 + +图: + +![1554642531024](assets/1554642531024.png) + +可以看到对于案例`[1, 2, 3]`子集的结果总共有`8`个,即`1`个红色的加上`7`个绿色的。 + +代码: + +```java +class Solution { + + private boolean[] bit; + private List> res; + + public List> subsets(int[] nums) { + res = new ArrayList<>(); + bit = new boolean[nums.length]; + dfs(0, nums); + return res; + } + + private void dfs(int cur, int[] arr) { + if (cur == arr.length) { + List tmp = new ArrayList<>(); + for (int i = 0; i < cur; i++) if (bit[i]) tmp.add(arr[i]); + res.add(new ArrayList<>(tmp)); + return; + } + dfs(cur + 1, arr); // 不取当前数字 + bit[cur] = true; // 取当前数字 + dfs(cur + 1, arr); + bit[cur] = false; + } +} +``` + +也可以使用一个`List`来存储当前的位置。和上面的`boolean`数组是一样的。 + +```java +class Solution { + + private List> res; + + public List> subsets(int[] nums) { + res = new ArrayList<>(); + dfs(0, nums, new ArrayList<>()); + return res; + } + + private void dfs(int cur, int[] arr, ArrayList curr) { + if (cur == arr.length) { + res.add(new ArrayList<>(curr)); + return; + } + dfs(cur + 1, arr, curr); // 不取当前数 + + curr.add(arr[cur]); + dfs(cur + 1, arr, curr);//取当前数 + curr.remove(curr.size() - 1); + } +} +``` + +### 2、二进制子集枚举 + +这种情况和上面那种其实是一样的,只不过这个是用,数组长度这么多位,来枚举每一位`0`和`1`的情况。 + +

+ +代码: + +```java +class Solution { + public List> subsets(int[] nums) { + List> res = new ArrayList<>(); + for(int mask = 0; mask < (1 << nums.length); mask++){ + List tmp = new ArrayList<>(); + for(int i = 0; i < nums.length; i++) + if( ( (mask >> i) & 1) == 1) tmp.add(nums[i]); + res.add(tmp); + } + return res; + } +} +``` + +### 3、经典写法 + +这种写法的思想是每一次取了当前的数,然后递归去考虑剩下的数`[cur ~ arr.length]`,而且每次都会从下一个开始,也就是说每次是递增构造的,说不太清,看图吧。。。 + +![1554643596288](assets/1554643596288.png) + +代码: + +```java +class Solution { + + private List> res; + + public List> subsets(int[] nums) { + res = new ArrayList<>(); + dfs(0, nums, new ArrayList<>()); + return res; + } + + private void dfs(int cur, int[] arr, ArrayList curr) { + res.add(new ArrayList<>(curr)); //任何一个结果都要加入结果集 + for(int i = cur; i < arr.length; i++) { + curr.add(arr[i]); + dfs(i + 1, arr, curr); // 从当前的下一个数开始取 + curr.remove(curr.size() - 1); + } + } +} +``` + + + +这里要区分我们的**排列数、组合数**的dfs写法。 + +排列数: + +```java +class Solution { + + private List> res; + + public List> permute(int[] nums) { + res = new ArrayList<>(); + dfs(new ArrayList<>(), nums, new boolean[nums.length]); + return res; + } + + public void dfs(List curr, int[] arr, boolean[] used) { + if (curr.size() == arr.length) { + //注意不能直接添加temp + res.add(new ArrayList<>(curr)); //以一个集合或数组初始化ArrayList al = new ArrayList(a);//a为集合或数组 + return; + } + for (int i = 0; i < arr.length; i++) { + if (!used[i]) {//记录某个下标的数是否被使用过 + curr.add(arr[i]); + used[i] = true; + dfs(curr, arr, used); + curr.remove(curr.size() - 1); //移除最后一个 + used[i] = false; + } + } + } +} + +``` + +组合数: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static List> res; + + static void dfs(int[] arr, int depth, int cur, int n, ArrayList curr) { + if (depth == n) { + res.add(new ArrayList<>(curr)); + return; + } + for (int i = cur; i < arr.length; i++) { + curr.add(arr[i]); //当前 d 层元素为nums[i] + dfs(arr, depth + 1, i + 1, n, curr); //去考虑d+1以及后面的层,注意下次直接从i+1开始,不会有重复的 + curr.remove(curr.size() - 1); + } + } + + public static void main(String[] args) { + PrintStream out = System.out; + + int[] arr = {2, 1, 3}; + + res = new ArrayList<>(); + dfs(arr, 0, 0, 3, new ArrayList<>()); // 从3个数中取3个数的组合(不是排列) + out.println(res); + + out.println("-------------------------------"); + + res = new ArrayList<>(); + dfs(arr, 0, 0, 2, new ArrayList<>());// 从3个数中取2个数的组合(不是排列) + out.println(res); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/LeetCode-79.Word Search.md b/Algorithm/LeetCode/Search/LeetCode-79.Word Search.md new file mode 100644 index 00000000..53289692 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-79.Word Search.md @@ -0,0 +1,173 @@ +# LeetCode - 79. Word Search(DFS) + +#### [题目链接](https://leetcode.com/problems/word-search/) + +> https://leetcode.com/problems/word-search/ + +#### 题目 +![在这里插入图片描述](images/79_t.png) + +## 解析 +这个题目很明显是使用搜索来做,这里使用`DFS`来做会比较方便: + + +* 递归函数要记录一个`dist`变量,表示当前搜索的深度,或者已经找到的字符串的长度,当`dist == word.length -1 `的时候,说明已经找到了这个字符串,就可以返回`true`; +* 四个方向只需要一个即可,遍历每一个位置,从每个位置开始`DFS`即可,注意边界的判断; +* 这里在递归的时候使用`board[i][j] = '#'`,然后递归,就标识了已经访问了,就省去了一个`vis`数组,当然也可以使用`vis`数组; +* 时间复杂度O(n * m * 4`word.length()`);其中`n = board.length`,`m = board[0].length`; + +图: + +![1554651307629](assets/1554651307629.png) + +代码: + +```java +class Solution { + public boolean exist(char[][] board, String word) { + if(board == null || board.length == 0 || board[0].length == 0) + return false; + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(search(board, i, j, 0, word)) + return true; + } + } + return false; + } + + private boolean search(char[][] board, int i, int j, int dist, String word){ + if(!checkBoundary(board, i, j) || board[i][j] != word.charAt(dist)) + return false; + if(dist == word.length() - 1) // already find the last + return true; + + char tmp = board[i][j]; + board[i][j] = '#'; // set it to be a specific symbol that can't reach + boolean success = search(board, i+1, j, dist+1, word) + ||search(board, i, j+1, dist+1, word) + ||search(board, i, j-1, dist+1, word) + ||search(board, i-1, j, dist+1, word); + board[i][j] = tmp; // backTrack, restore it + return success; + + } + + private boolean checkBoundary(char[][] board, int i, int j){ + return i >= 0 && i < board.length && j >=0 && j < board[0].length; + } +} +``` + +使用`vis`数组: +```java +class Solution { + public boolean exist(char[][] board, String word) { + if(board == null || board.length == 0 || board[0].length == 0) + return false; + + boolean[][] vis = new boolean[board.length][board[0].length]; + + for(int i = 0; i < board.length; i++){ + for(int j = 0; j < board[0].length; j++){ + if(search(board, i, j, 0, word, vis)) + return true; + } + } + return false; + } + + private boolean search(char[][] board, int i, int j, int dist, String word, boolean[][] vis){ + if(!checkBoundary(board, i, j) || board[i][j] != word.charAt(dist)) + return false; + if(vis[i][j]) + return false; + if(dist == word.length() - 1) // already find the last + return true; + + vis[i][j] = true; + boolean success = search(board, i+1, j, dist+1, word, vis) + ||search(board, i, j+1, dist+1, word, vis) + ||search(board, i, j-1, dist+1, word, vis) + ||search(board, i-1, j, dist+1, word, vis); + vis[i][j] = false; + return success; + } + + private boolean checkBoundary(char[][] board, int i, int j){ + return i >= 0 && i < board.length && j >=0 && j < board[0].length; + } +} +``` + +*** + +其他代码: + +`C++`代码: + +```cpp +class Solution { +public: + bool exist(vector>& board, string word) { + if(board.size() == 0) + return false; + + for(int i = 0; i < board.size(); i++){ + for(int j = 0; j < board[0].size(); j++){ + if(search(board, word, i, j, 0)) + return true; + } + } + return false; + } +private: + bool search(vector>& board, const string& word, int i, int j, int dist){ + if(i < 0 || i >= board.size() || j < 0 || j >= board[0].size() || board[i][j] != word[dist]) + return false; + if(dist == word.length() - 1) + return true; + + char tmp = board[i][j]; + board[i][j] = '#'; + bool success = search(board, word, i+1, j, dist + 1) + ||search(board, word, i, j+1, dist + 1) + ||search(board, word, i-1, j, dist + 1) + ||search(board, word, i, j-1, dist + 1); + board[i][j] = tmp; + return success; + } +}; +``` + + +`Python`: + +```python +class Solution: + def exist(self, board, word): + if len(board) == 0: + return False + for i in range(len(board)): + for j in range(len(board[0])): + if self.search(board, word, i, j, 0): + return True + return False + + def search(self, board, word, i, j, dist): + if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]) or board[i][j] != word[dist]: + return False + if dist == len(word)-1: + return True + tmp = board[i][j] + board[i][j] = '#' + if self.search(board, word, i+1, j, dist+1)\ + or self.search(board, word, i, j+1, dist+1)\ + or self.search(board, word, i-1, j, dist+1)\ + or self.search(board, word, i, j-1, dist+1): + return True + board[i][j] = tmp + return False +``` + +> 给出一个`Python`的BFS[链接](https://leetcode.com/problems/word-search/discuss/195834/Two-python-solutions)。 \ No newline at end of file diff --git a/Algorithm/LeetCode/Search/LeetCode-90.Subsets II.md b/Algorithm/LeetCode/Search/LeetCode-90.Subsets II.md new file mode 100644 index 00000000..5def6312 --- /dev/null +++ b/Algorithm/LeetCode/Search/LeetCode-90.Subsets II.md @@ -0,0 +1,91 @@ +# LeetCode - 90. Subsets II + +#### [题目链接]() + +#### 题目 + +![1554645822682](assets/1554645822682.png) + +## 解析 + +在[LeetCode - 78. Subsets](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2078.%20Subsets.md)的基础上加上去重就好了。 + +方法: + +* 首先要对数组排序,因为我们要对相邻的重复元素进行去重; +* 然后我们去重的条件是`if(i > 0 && !used[i-1] && arr[i] == arr[i-1]) continue;`,也是就说当我们当前元素的前一个元素没有被用过,而且当前元素`==`前一个元素的时候,就不要考虑当前元素了。 + +案例`[1, 2, 2]`,注意在下图中,第二个`2`我用的是`2‘`表示。 + +可以看到下面的蓝色部分就是重复的解。 + +![1554645724343](assets/1554645724343.png) + +这里提供两种写法,一种是[LeetCode - 78. Subsets](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2078.%20Subsets.md)的第三种增量写法,然后还要标记每一个元素是否被使用了。 + +```java +class Solution { + + private List> res; + + private boolean[] used; + + public List> subsetsWithDup(int[] nums) { + res = new ArrayList<>(); + used = new boolean[nums.length]; + Arrays.sort(nums); + dfs(0, nums, new ArrayList<>()); + return res; + } + + private void dfs(int cur, int[] arr, ArrayList curr) { + res.add(new ArrayList<>(curr)); + for(int i = cur; i < arr.length; i++) { + if(i > 0 && !used[i-1] && arr[i] == arr[i-1]) continue; + curr.add(arr[i]); + used[i] = true; + dfs(i + 1, arr, curr); + used[i] = false; + curr.remove(curr.size() - 1); + } + } + + public static void main(String[] args){ + System.out.println(new Solution().subsetsWithDup(new int[]{1, 2, 3})); + } +} +``` + +然后就是改进[LeetCode - 78. Subsets](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Search/LeetCode%20-%2078.%20Subsets.md)的第一种写法,我们这里可以省略一个`used`数组,直接使用一个`preTaken`变量表示前一个有没有使用过即可。 + +```java +class Solution { + + private List> res; + + public List> subsetsWithDup(int[] nums) { + res = new ArrayList<>(); + Arrays.sort(nums); + dfs(0, false, nums, new ArrayList<>()); + return res; + } + + public void dfs(int cur, boolean preTaken, int[] arr, ArrayList curr) { + if(cur == arr.length){ + res.add(new ArrayList<>(curr)); + return; + } + dfs(cur+1, false, arr, curr); // 前面的 + if(cur > 0 && !preTaken && arr[cur] == arr[cur-1]) return; + curr.add(arr[cur]); + dfs(cur+1, true, arr, curr); + curr.remove(curr.size() - 1); + } + + + public static void main(String[] args){ + System.out.println(new Solution().subsetsWithDup(new int[]{1, 2, 2})); + } +} +``` + diff --git a/Algorithm/LeetCode/Search/assets/1554641348023.png b/Algorithm/LeetCode/Search/assets/1554641348023.png new file mode 100644 index 00000000..215eca1d Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554641348023.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554641481809.png b/Algorithm/LeetCode/Search/assets/1554641481809.png new file mode 100644 index 00000000..1690aea1 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554641481809.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554642531024.png b/Algorithm/LeetCode/Search/assets/1554642531024.png new file mode 100644 index 00000000..7ab3399f Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554642531024.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554643123413.png b/Algorithm/LeetCode/Search/assets/1554643123413.png new file mode 100644 index 00000000..37121703 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554643123413.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554643596288.png b/Algorithm/LeetCode/Search/assets/1554643596288.png new file mode 100644 index 00000000..5c64b876 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554643596288.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554645724343.png b/Algorithm/LeetCode/Search/assets/1554645724343.png new file mode 100644 index 00000000..c7a7a7be Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554645724343.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554645822682.png b/Algorithm/LeetCode/Search/assets/1554645822682.png new file mode 100644 index 00000000..f8c88e7b Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554645822682.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554651307629.png b/Algorithm/LeetCode/Search/assets/1554651307629.png new file mode 100644 index 00000000..d0fb7274 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554651307629.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554773205403.png b/Algorithm/LeetCode/Search/assets/1554773205403.png new file mode 100644 index 00000000..350b5a83 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554773205403.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554773481209.png b/Algorithm/LeetCode/Search/assets/1554773481209.png new file mode 100644 index 00000000..270fec1a Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554773481209.png differ diff --git a/Algorithm/LeetCode/Search/assets/1554961801999.png b/Algorithm/LeetCode/Search/assets/1554961801999.png new file mode 100644 index 00000000..427aa0f0 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1554961801999.png differ diff --git a/Algorithm/LeetCode/Search/assets/1555045719085.png b/Algorithm/LeetCode/Search/assets/1555045719085.png new file mode 100644 index 00000000..deec5f60 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1555045719085.png differ diff --git a/Algorithm/LeetCode/Search/assets/1555053287232.png b/Algorithm/LeetCode/Search/assets/1555053287232.png new file mode 100644 index 00000000..f9b5233e Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1555053287232.png differ diff --git a/Algorithm/LeetCode/Search/assets/1555054067126.png b/Algorithm/LeetCode/Search/assets/1555054067126.png new file mode 100644 index 00000000..1c484f79 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1555054067126.png differ diff --git a/Algorithm/LeetCode/Search/assets/1556957791373.png b/Algorithm/LeetCode/Search/assets/1556957791373.png new file mode 100644 index 00000000..fc50be22 Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1556957791373.png differ diff --git a/Algorithm/LeetCode/Search/assets/1556957924483.png b/Algorithm/LeetCode/Search/assets/1556957924483.png new file mode 100644 index 00000000..383ff99b Binary files /dev/null and b/Algorithm/LeetCode/Search/assets/1556957924483.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/126_s.png" b/Algorithm/LeetCode/Search/images/126_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/126_s.png" rename to Algorithm/LeetCode/Search/images/126_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/126_t.png" b/Algorithm/LeetCode/Search/images/126_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/126_t.png" rename to Algorithm/LeetCode/Search/images/126_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/127_s.png" b/Algorithm/LeetCode/Search/images/127_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/127_s.png" rename to Algorithm/LeetCode/Search/images/127_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/127_t.png" b/Algorithm/LeetCode/Search/images/127_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/127_t.png" rename to Algorithm/LeetCode/Search/images/127_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/216_t.png" b/Algorithm/LeetCode/Search/images/216_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/216_t.png" rename to Algorithm/LeetCode/Search/images/216_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/37_s.png" b/Algorithm/LeetCode/Search/images/37_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/37_s.png" rename to Algorithm/LeetCode/Search/images/37_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/37_t.png" b/Algorithm/LeetCode/Search/images/37_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/37_t.png" rename to Algorithm/LeetCode/Search/images/37_t.png diff --git a/Algorithm/LeetCode/Search/images/39_ss.png b/Algorithm/LeetCode/Search/images/39_ss.png new file mode 100644 index 00000000..713e63b8 Binary files /dev/null and b/Algorithm/LeetCode/Search/images/39_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/39_t.png" b/Algorithm/LeetCode/Search/images/39_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/39_t.png" rename to Algorithm/LeetCode/Search/images/39_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/40_t.png" b/Algorithm/LeetCode/Search/images/40_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/40_t.png" rename to Algorithm/LeetCode/Search/images/40_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/463_t.png" b/Algorithm/LeetCode/Search/images/463_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/463_t.png" rename to Algorithm/LeetCode/Search/images/463_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/46_s.png" b/Algorithm/LeetCode/Search/images/46_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/46_s.png" rename to Algorithm/LeetCode/Search/images/46_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/46_s2.png" b/Algorithm/LeetCode/Search/images/46_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/46_s2.png" rename to Algorithm/LeetCode/Search/images/46_s2.png diff --git a/Algorithm/LeetCode/Search/images/46_ss.png b/Algorithm/LeetCode/Search/images/46_ss.png new file mode 100644 index 00000000..064e6b4a Binary files /dev/null and b/Algorithm/LeetCode/Search/images/46_ss.png differ diff --git a/Algorithm/LeetCode/Search/images/46_ss2.png b/Algorithm/LeetCode/Search/images/46_ss2.png new file mode 100644 index 00000000..84ac2438 Binary files /dev/null and b/Algorithm/LeetCode/Search/images/46_ss2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/46_t.png" b/Algorithm/LeetCode/Search/images/46_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/46_t.png" rename to Algorithm/LeetCode/Search/images/46_t.png diff --git a/Algorithm/LeetCode/Search/images/47_ss.png b/Algorithm/LeetCode/Search/images/47_ss.png new file mode 100644 index 00000000..23266d8d Binary files /dev/null and b/Algorithm/LeetCode/Search/images/47_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/47_t.png" b/Algorithm/LeetCode/Search/images/47_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/47_t.png" rename to Algorithm/LeetCode/Search/images/47_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/488_s.png" b/Algorithm/LeetCode/Search/images/488_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/488_s.png" rename to Algorithm/LeetCode/Search/images/488_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/488_s2.png" b/Algorithm/LeetCode/Search/images/488_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/488_s2.png" rename to Algorithm/LeetCode/Search/images/488_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/488_t.png" b/Algorithm/LeetCode/Search/images/488_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/488_t.png" rename to Algorithm/LeetCode/Search/images/488_t.png diff --git a/Algorithm/LeetCode/Search/images/51_s.png b/Algorithm/LeetCode/Search/images/51_s.png new file mode 100644 index 00000000..5df83622 Binary files /dev/null and b/Algorithm/LeetCode/Search/images/51_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/51_s2.png" b/Algorithm/LeetCode/Search/images/51_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/51_s2.png" rename to Algorithm/LeetCode/Search/images/51_s2.png diff --git a/Algorithm/LeetCode/Search/images/51_ss.png b/Algorithm/LeetCode/Search/images/51_ss.png new file mode 100644 index 00000000..0eb6927d Binary files /dev/null and b/Algorithm/LeetCode/Search/images/51_ss.png differ diff --git a/Algorithm/LeetCode/Search/images/51_ss3.png b/Algorithm/LeetCode/Search/images/51_ss3.png new file mode 100644 index 00000000..87dc4d39 Binary files /dev/null and b/Algorithm/LeetCode/Search/images/51_ss3.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/51_t.png" b/Algorithm/LeetCode/Search/images/51_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/51_t.png" rename to Algorithm/LeetCode/Search/images/51_t.png diff --git a/Algorithm/LeetCode/Search/images/52_t.png b/Algorithm/LeetCode/Search/images/52_t.png new file mode 100644 index 00000000..720f1666 Binary files /dev/null and b/Algorithm/LeetCode/Search/images/52_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/675_s.png" b/Algorithm/LeetCode/Search/images/675_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/675_s.png" rename to Algorithm/LeetCode/Search/images/675_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/675_t.png" b/Algorithm/LeetCode/Search/images/675_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/675_t.png" rename to Algorithm/LeetCode/Search/images/675_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Search/images/79_t.png" b/Algorithm/LeetCode/Search/images/79_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Search/images/79_t.png" rename to Algorithm/LeetCode/Search/images/79_t.png diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-12.Integer to Roman.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-12.Integer to Roman.md new file mode 100644 index 00000000..78b915f3 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-12.Integer to Roman.md @@ -0,0 +1,70 @@ +# LeetCode - 12. Integer to Roman + +#### [题目链接](https://leetcode-cn.com/problems/integer-to-roman/) + +> https://leetcode-cn.com/problems/integer-to-roman/ + +#### 题目 + +![12_t.png](images/12_t.png) + +输入输出示例 + +```java +示例 1: + +输入: 3 +输出: "III" +示例 2: + +输入: 4 +输出: "IV" +示例 3: + +输入: 9 +输出: "IX" +示例 4: + +输入: 58 +输出: "LVIII" +解释: L = 50, V = 5, III = 3. +示例 5: + +输入: 1994 +输出: "MCMXCIV" +解释: M = 1000, CM = 900, XC = 90, IV = 4. +``` + +## 解析 + +这题不难,每个人按照自己的想法写就可以了。这里给出一个代码比较**简洁**的写法。 + + + +先将可能需要用的所有组合先打表到两个数组中,**一个数组是对应的罗马字符串,一个是对应的基础数字**。 + +然后就从大的数到小的数累加即可。 + +

+代码: + +```java +class Solution { + public String intToRoman(int num) { + String[] roman = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}; + int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + while (num >= values[i]) { + num -= values[i]; + sb.append(roman[i]); + } + if (num <= 0) return sb.toString(); + } + return sb.toString(); + } +} +``` + + + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-263.Ugly Number.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-263.Ugly Number.md new file mode 100644 index 00000000..39669d88 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-263.Ugly Number.md @@ -0,0 +1,30 @@ +# LeetCode - 263. Ugly Number + +#### [题目链接](https://leetcode.com/problems/ugly-number/) + +> https://leetcode.com/problems/ugly-number/ + +#### 题目 + +![1554870970424](assets/1554870970424.png) + +## 解析 + +很简单的题目,相信谁都会做。 + +```java +class Solution { + public boolean isUgly(int num) { + if (num <= 0) return false; + if (num == 1) return true; + while (num != 1) { + if (num % 2 == 0) num /= 2; + else if (num % 3 == 0) num /= 3; + else if (num % 5 == 0) num /= 5; + else return false; + } + return true; + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/easy/LeetCode - 451. Sort Characters By Frequency(\346\214\211\347\205\247\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\346\216\222\345\272\217(\347\256\200\345\215\225\351\242\230)).md" "b/Algorithm/LeetCode/Simulation/easy/LeetCode-451.Sort Characters By Frequency(\346\214\211\347\205\247\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\346\216\222\345\272\217(\347\256\200\345\215\225\351\242\230)).md" similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/easy/LeetCode - 451. Sort Characters By Frequency(\346\214\211\347\205\247\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\346\216\222\345\272\217(\347\256\200\345\215\225\351\242\230)).md" rename to "Algorithm/LeetCode/Simulation/easy/LeetCode-451.Sort Characters By Frequency(\346\214\211\347\205\247\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\346\216\222\345\272\217(\347\256\200\345\215\225\351\242\230)).md" diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-504.Base 7.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-504.Base 7.md new file mode 100644 index 00000000..f14ce195 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-504.Base 7.md @@ -0,0 +1,69 @@ +# LeetCode - 504. Base 7 + +#### [题目链接](https://leetcode.com/problems/base-7/) + +> https://leetcode.com/problems/base-7/ + +#### 题目 + +![1554994509158](assets/1554994509158.png) + +## 解析 + +比较简单的题目,既然知道十进制转二进制是每次除2取余,**所以转成`7`进制就是每次除`7`取余**。 + +处理的话我们可以用一个字符串存结果,最后翻转即可。注意符号。 + +代码: + +```java +class Solution { + + public String convertToBase7(int num) { + if(num == 0) return "0"; + boolean isNegative = num < 0; + num = isNegative ? -num : num; + StringBuilder sb = new StringBuilder(); + while(num > 0){ + sb = sb.append((num % 7)); + num /= 7; + } + if(isNegative) sb = sb.append("-"); + return sb.reverse().toString(); + } +} +``` + +字符串也可以从前面累加。 + +```java +class Solution { + public String convertToBase7(int num) { + if (num == 0) return "0"; + boolean isNegative = num < 0; + num = isNegative ? -num : num; + String str = ""; + while (num > 0) { + str = (num % 7) + str; + num /= 7; + } + if (isNegative) str = "-" + str; + return str; + } +} +``` + +讨论区看到有人用递归。很好的思路。 + +递归三行代码: + +```java +class Solution { + public String convertToBase7(int num) { + if (num < 0) return "-" + convertToBase7(-num); + if (num < 7) return String.valueOf(num); + return convertToBase7(num / 7) + String.valueOf(num % 7); //前面的先求出结果加上当前位的 + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-551.Student Attendance Record I.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-551.Student Attendance Record I.md new file mode 100644 index 00000000..f79cdf18 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-551.Student Attendance Record I.md @@ -0,0 +1,34 @@ +# LeetCode - 551. Student Attendance Record I + +#### [题目链接](https://leetcode.com/problems/student-attendance-record-i/) + +> https://leetcode.com/problems/student-attendance-record-i/ + +#### 题目 + +![1555028703651](assets/1555028703651.png) + +## 解析 + +简单题,用两个变量统计一下就可以了。如果`L`不是连续就置为`0`。 + +代码: + +```java +class Solution { + public boolean checkRecord(String s) { + int a = 0, l = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == 'A') a++; + if (c == 'L') + l++; + else //没有连续就置为0 + l = 0; + if (a > 1 || l > 2) return false; + } + return true; + } +} +``` + diff --git "a/Algorithm/LeetCode/Simulation/easy/LeetCode-566.Reshape the Matrix(\351\207\215\345\241\221\347\237\251\351\230\265)(\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/LeetCode/Simulation/easy/LeetCode-566.Reshape the Matrix(\351\207\215\345\241\221\347\237\251\351\230\265)(\347\256\200\345\215\225\351\242\230).md" new file mode 100644 index 00000000..43830217 --- /dev/null +++ "b/Algorithm/LeetCode/Simulation/easy/LeetCode-566.Reshape the Matrix(\351\207\215\345\241\221\347\237\251\351\230\265)(\347\256\200\345\215\225\351\242\230).md" @@ -0,0 +1,63 @@ +# LeetCode - 566. Reshape the Matrix(重塑矩阵)(简单题) + + - 直接填充 + - 坐标对应 + +*** +#### [题目链接](https://leetcode.com/problems/reshape-the-matrix/description/) +> https://leetcode.com/problems/reshape-the-matrix/description/ + +#### 题目 + +![在这里插入图片描述](images/566_t.png) + +比较简单的题目。 + +## 1、直接填充 +这个方法很简单,遍历`nums`中的元素,一个一个填充到新数组即可。 +```java +class Solution { + + public int[][] matrixReshape(int[][] nums, int r, int c) { + if (nums.length == 0 || nums[0].length == 0) + return nums; + int n = nums.length; + int m = nums[0].length; + if (n * m != r * c) return nums; + + int[][] res = new int[r][c]; + int p = 0, q = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + res[p][q] = nums[i][j]; + if (++q == c) { + ++p; + q = 0; + } + } + } + return res; + } +} +``` +## 2、坐标对应 +这个是考虑一重循环,找坐标的对应的关系,在矩阵中,一维遍历的`nums[i]`在二维中横坐标为`i/c`,纵坐标为`i % c`,所以可以用一个一重循环遍历。 + +```java +class Solution { + + public int[][] matrixReshape(int[][] nums, int r, int c) { + if (nums.length == 0 || nums[0].length == 0) + return nums; + int n = nums.length; + int m = nums[0].length; + if (n * m != r * c) + return nums; + int[][] res = new int[r][c]; + for (int i = 0; i < n * m; i++) + res[i / c][i % c] = nums[i / m][i % m]; + return res; + } +} + +``` diff --git "a/Algorithm/LeetCode/Simulation/easy/LeetCode-628.Maximum Product of Three Numbers(\346\225\260\347\273\204\344\270\255\344\270\211\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\347\264\257\346\210\220\347\247\257)(\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/LeetCode/Simulation/easy/LeetCode-628.Maximum Product of Three Numbers(\346\225\260\347\273\204\344\270\255\344\270\211\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\347\264\257\346\210\220\347\247\257)(\347\256\200\345\215\225\351\242\230).md" new file mode 100644 index 00000000..8526fc9d --- /dev/null +++ "b/Algorithm/LeetCode/Simulation/easy/LeetCode-628.Maximum Product of Three Numbers(\346\225\260\347\273\204\344\270\255\344\270\211\344\270\252\346\225\260\347\232\204\346\234\200\345\244\247\347\264\257\346\210\220\347\247\257)(\347\256\200\345\215\225\351\242\230).md" @@ -0,0 +1,107 @@ +# LeetCode - 628. Maximum Product of Three Numbers(数组中三个数的最大累成积)(简单题) +* 排序方法 +* O(N)方法 + +*** +#### [题目链接](https://leetcode.com/problems/maximum-product-of-three-numbers/) + +> https://leetcode.com/problems/maximum-product-of-three-numbers/ + +#### 题目 +![在这里插入图片描述](images/628_t.png) + +也不是很难的题目。 + +### 排序方法 + +很容易想到最大的累成积只有可能是**最大的三个数相乘`(max1 * max2 * max3)`**或者**`最大数(max1) * 最小的数(min1) * 次小的数(min2)`**。 + +于是第一种方法就是排序,然后找出这些数即可。 + +```java +class Solution { + public int maximumProduct(int[] nums) { + Arrays.sort(nums); + return Math.max(nums[0] * nums[1] * nums[nums.length - 1], + nums[nums.length - 1] * nums[nums.length - 2] * nums[nums.length - 3] + ); + } +} +``` + +*** +### O(N)的方法 +很明显这题不会是用排序`N * logN`的复杂度,**其实只需要记录几个变量就可以找出这五个值**,于是下面的代码是很容易写出来的,遍历三次,每次`O(N)`,显然这种办法有点累赘,可以从 `3 * N`优化到`N`。 + +```java +class Solution { + public int maximumProduct(int[] nums) { + int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; + int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE; + int min1I = -1, max1I = -1, max2I = -1; + + for (int i = 0; i < nums.length; i++) { + if (nums[i] < min1) { + min1 = nums[i]; + min1I = i; + } + if (nums[i] > max1) { + max1 = nums[i]; + max1I = i; + } + } + for (int i = 0; i < nums.length; i++) { + if (i == min1I || i == max1I) + continue; + if (nums[i] < min2) { + min2 = nums[i]; + } + if (nums[i] > max2) { + max2 = nums[i]; + max2I = i; + } + } + for (int i = 0; i < nums.length; i++) { + if (i == max1I || i == max2I) //注意这里不要多余的加上 i == min1I 不然出错 + continue; + if (nums[i] > max3) { + max3 = nums[i]; + } + } + return Math.max(max1 * max2 * max3, max1 * min1 * min2); + } +} +``` +注意到其中的**层次关系和更新的关系**,即可写出下面的代码: + +```java +class Solution { + public int maximumProduct(int[] nums) { + int min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE; + int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE; + + for (int i = 0; i < nums.length; i++) { + //judge max + if (nums[i] > max1) { + max3 = max2; + max2 = max1; + max1 = nums[i]; + } else if (nums[i] > max2) { + max3 = max2; + max2 = nums[i]; + } else if (nums[i] > max3) { + max3 = nums[i]; + } + // judge min + if (nums[i] < min1) { + min2 = min1; + min1 = nums[i]; + } else if (nums[i] < min2) { + min2 = nums[i]; + } + } + return Math.max(max1 * max2 * max3, max1 * min1 * min2); + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-657.Robot Return to Origin.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-657.Robot Return to Origin.md new file mode 100644 index 00000000..5b9d4d9b --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-657.Robot Return to Origin.md @@ -0,0 +1,31 @@ +# LeetCode - 657. Robot Return to Origin + +#### [题目链接](https://leetcode.com/problems/robot-return-to-origin/) + +> https://leetcode.com/problems/robot-return-to-origin/ + +#### 题目 + +![1555036240365](assets/1555036240365.png) + +#### 解析 + +简单题。 + +```java +class Solution { + public boolean judgeCircle(String moves) { + int x = 0,y = 0; + for(int i = 0; i < moves.length(); i++){ + switch (moves.charAt(i)){ + case 'L': x--; break; + case 'R': x++; break; + case 'U': y++; break; + case 'D': y--; break; + } + } + return x == 0 && y == 0; + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-682.Baseball Game.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-682.Baseball Game.md new file mode 100644 index 00000000..a3a7eef9 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-682.Baseball Game.md @@ -0,0 +1,48 @@ +# LeetCode - 682. Baseball Game + +#### [题目链接](https://leetcode.com/problems/baseball-game/) + +> https://leetcode.com/problems/baseball-game/ + +#### 题目 + +![1555038200773](assets/1555038200773.png) + +## 解析 + +纯模拟题。按照思路写就可以了。 + +用一个栈保存数据即可。 + +```java +import java.util.*; + +class Solution { + public int calPoints(String[] ops) { + if (ops == null || ops.length == 0) return 0; + Stack stack = new Stack<>(); + int res = 0; + for (int i = 0; i < ops.length; i++) { + if (ops[i].equals("+")) { + int pre = stack.pop(); + int prepre = stack.peek(); + res += pre + prepre; + stack.push(pre); + stack.push(pre + prepre); + } else if (ops[i].equals("D")) { + int pre = stack.peek(); + res += 2 * pre; + stack.push(2 * pre); + } else if (ops[i].equals("C")) { + res -= stack.pop(); + } else { + res += Integer.valueOf(ops[i]); + stack.push(Integer.valueOf(ops[i])); + } + } + return res; + } + +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-7.Reverse Integer.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-7.Reverse Integer.md new file mode 100644 index 00000000..74b9795c --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-7.Reverse Integer.md @@ -0,0 +1,37 @@ +# LeetCode - 7. Reverse Integer + +#### [题目链接](https://leetcode.com/problems/reverse-integer/) + +> https://leetcode.com/problems/reverse-integer/ + +#### 题目 + +![7_t.png](images/7_t.png) + +## 解析 + +这题很简单,我就不画图了。 + +注意负数取摸也是一样的,不需要特殊考虑,例如`-123 % 10 = -3`。 + +然后就是溢出的问题,如果`newRev = rev * 10 + x % 10`发生了溢出,那么`(newRev - x%10)/10`一定不会等于原来的`rev`。 + +> `2^31-1=2147483647, -2^31=-2147483648`。 + +代码: + +```java +class Solution { + public int reverse(int x) { + int rev = 0; + while(x != 0){ // 不能写成>=0, 考虑负数 + int newRev = rev * 10 + x % 10; + if( (newRev-x%10)/10 != rev) return 0; //判断溢出 + rev = newRev; + x /= 10; + } + return rev; + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-724.Find Pivot Index.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-724.Find Pivot Index.md new file mode 100644 index 00000000..02d3cf1b --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-724.Find Pivot Index.md @@ -0,0 +1,28 @@ +# LeetCode - 724. Find Pivot Index + +#### [题目链接](https://leetcode.com/problems/find-pivot-index/) + +> https://leetcode.com/problems/find-pivot-index/ + +#### 题目 + +![1555044107618](assets/1555044107618.png) + +## 解析 + +比较简单的模拟题。前面的累加,然后算出两边是否相等即可。 + +```java +class Solution { + public int pivotIndex(int[] nums) { + int sum = 0, L = 0; + for (int i = 0; i < nums.length; i++) sum += nums[i]; + for (int i = 0; i < nums.length; i++) { + if (L == sum - L - nums[i]) return i; + L += nums[i]; + } + return -1; + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/LeetCode-735.Asteroid Collision.md b/Algorithm/LeetCode/Simulation/easy/LeetCode-735.Asteroid Collision.md new file mode 100644 index 00000000..8a1f87e8 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/easy/LeetCode-735.Asteroid Collision.md @@ -0,0 +1,49 @@ +## LeetCode - 735. Asteroid Collision + +#### [题目链接](https://leetcode.com/problems/asteroid-collision/) + +> https://leetcode.com/problems/asteroid-collision/ + +#### 题目 + +![735_t.png](images/735_t.png) + +### 解析 + +用栈模拟即可。注意**只有一个向右一个向左才会碰撞**。 + +```java +import java.util.*; + +class Solution { + + public int[] asteroidCollision(int[] asteroids) { + Stack stack = new Stack<>(); + for (int n : asteroids) { + if (stack.isEmpty()) { + stack.push(n); + continue; + } + boolean ok = false; + while (!stack.isEmpty() && n < 0 && stack.peek() > 0) { + if (Math.abs(n) > Math.abs(stack.peek())) { + stack.pop(); + } else if (Math.abs(n) == Math.abs(stack.peek())) { + stack.pop(); + ok = true; + break; + } else { // n < stack.pop() + ok = true; + break; + } + } + if (!ok) stack.push(n); + } + int[] res = new int[stack.size()]; + int p = stack.size() - 1; + while (!stack.isEmpty()) res[p--] = stack.pop(); + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1554870970424.png b/Algorithm/LeetCode/Simulation/easy/assets/1554870970424.png new file mode 100644 index 00000000..5b8b6ae9 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1554870970424.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1554994509158.png b/Algorithm/LeetCode/Simulation/easy/assets/1554994509158.png new file mode 100644 index 00000000..2fbc371f Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1554994509158.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1554994686103.png b/Algorithm/LeetCode/Simulation/easy/assets/1554994686103.png new file mode 100644 index 00000000..2fbc371f Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1554994686103.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1555028703651.png b/Algorithm/LeetCode/Simulation/easy/assets/1555028703651.png new file mode 100644 index 00000000..494e87b3 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1555028703651.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1555036240365.png b/Algorithm/LeetCode/Simulation/easy/assets/1555036240365.png new file mode 100644 index 00000000..8cc5b4e1 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1555036240365.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1555038200773.png b/Algorithm/LeetCode/Simulation/easy/assets/1555038200773.png new file mode 100644 index 00000000..99b32228 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1555038200773.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/assets/1555044107618.png b/Algorithm/LeetCode/Simulation/easy/assets/1555044107618.png new file mode 100644 index 00000000..5ad1be18 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/assets/1555044107618.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/images/12_s.png b/Algorithm/LeetCode/Simulation/easy/images/12_s.png new file mode 100644 index 00000000..c35c79f2 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/images/12_s.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/images/12_t.png b/Algorithm/LeetCode/Simulation/easy/images/12_t.png new file mode 100644 index 00000000..ea662051 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/images/12_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/easy/images/451_t.png" b/Algorithm/LeetCode/Simulation/easy/images/451_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/easy/images/451_t.png" rename to Algorithm/LeetCode/Simulation/easy/images/451_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/easy/images/566_t.png" b/Algorithm/LeetCode/Simulation/easy/images/566_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/easy/images/566_t.png" rename to Algorithm/LeetCode/Simulation/easy/images/566_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/easy/images/628_t.png" b/Algorithm/LeetCode/Simulation/easy/images/628_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/easy/images/628_t.png" rename to Algorithm/LeetCode/Simulation/easy/images/628_t.png diff --git a/Algorithm/LeetCode/Simulation/easy/images/735_t.png b/Algorithm/LeetCode/Simulation/easy/images/735_t.png new file mode 100644 index 00000000..359ba9e1 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/images/735_t.png differ diff --git a/Algorithm/LeetCode/Simulation/easy/images/7_t.png b/Algorithm/LeetCode/Simulation/easy/images/7_t.png new file mode 100644 index 00000000..8cef21f4 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/easy/images/7_t.png differ diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-13.Roman to Integer.md b/Algorithm/LeetCode/Simulation/median/LeetCode-13.Roman to Integer.md new file mode 100644 index 00000000..3507393a --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-13.Roman to Integer.md @@ -0,0 +1,80 @@ +# LeetCode - 13. Roman to Integer + +#### [题目链接](https://leetcode-cn.com/problems/integer-to-roman/) + +> https://leetcode.com/problems/integer-to-roman/ + +#### 题目 + +![13_t.png](images/13_t.png) + +样例: + +```java +示例 1: + +输入: "III" +输出: 3 +示例 2: + +输入: "IV" +输出: 4 +示例 3: + +输入: "IX" +输出: 9 +示例 4: + +输入: "LVIII" +输出: 58 +解释: L = 50, V= 5, III = 3. +示例 5: + +输入: "MCMXCIV" +输出: 1994 +解释: M = 1000, CM = 900, XC = 90, IV = 4. +``` + +## 解析 + +这题要考虑处理好`900、400、90、40、9、4`这些情况,用什么方法呢?不能和上一题一样,因为有些组合是一个字符串。 + +能不能找到更加有效的方法呢? + +我们可以先用哈希表存储题目中给定的罗马字符和整数的对应关系,然后对于不是`900、400、90、40、9、4`的这些情况,我们直接在哈希表中找到对应的值累加即可。 + +但是如果是那六种特殊情况呢? 我们也可以找到对应的处理办法: + +* 我们从字符串**后面开始处理**; +* 这样当我们遇到那些特殊情况的时候,后面那一位先累加; +* 然后对应的前面一位就要减去自身对应的值; + +具体看图和例子: + +![12_s.png](images/12_s.png) + +代码: + +```java +class Solution { + + public int romanToInt(String s) { + char[] chs = s.toCharArray(); + char[] roman = {'I', 'V', 'X', 'L', 'C', 'D', 'M'}; + int[] values = {1, 5, 10, 50, 100, 500, 1000}; + HashMap map = new HashMap<>(); + for(int i = 0; i < roman.length; i++) map.put(roman[i], values[i]); + int res = map.get(chs[chs.length - 1]); + for(int i = chs.length - 2; i >= 0; i--){ + // 说明是那种特殊情况,比如900, 此时chs[i] == C(100), < 前面的1000 + if(map.get(chs[i]) < map.get(chs[i+1])){ + res -= map.get(chs[i]); + }else { + res += map.get(chs[i]); + } + } + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-31.Next Permutation.md b/Algorithm/LeetCode/Simulation/median/LeetCode-31.Next Permutation.md new file mode 100644 index 00000000..32141ace --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-31.Next Permutation.md @@ -0,0 +1,60 @@ +## LeetCode - 31. Next Permutation(下一个排列) +#### [题目链接](https://leetcode.com/problems/next-permutation/description/) + +> https://leetcode.com/problems/next-permutation/description/ + +#### 题目 +![在这里插入图片描述](images/31_t.png) + + + +#### 解析 + + 可以分为三种情况 : + + - 一开始就是升序的,此时我们只需要交换最后两个位置,就可以得到下一个排列; + - 一开始就是降序的,我们只需要把整个数组反转就可以得到一个升序的排列,也就是下一个排列; + - 普通的情况的做法就是: + - 从数组的后面往前面开始找,直到找到第一个**当前数不比前一个数小**的位置,记为`nums[pivot]`。 + - 然后再从后面始往前找到第一个比`nums[pivot]`大的位置,交换这个两个位置的数。 + - 然后从`[pivot~nums.length-1]`之间的数翻转过来即可。 + +图: + +

+代码如下 : + +```java +class Solution { + + public void nextPermutation(int[] nums) { + if (nums.length == 0 || nums == null) return; + int pivot = nums.length - 1; + for (; pivot - 1 >= 0 && nums[pivot] <= nums[pivot - 1]; pivot--) ; + if (pivot == 0) { //全部降序 + reverse(nums, 0, nums.length - 1); + return; + } + int mini = nums.length - 1; + for (int i = nums.length - 1; i >= pivot; i--) { + if (nums[i] > nums[pivot-1]) { // nums[pivot-1]是那个从后往前第一个降序的 + mini = i; + break; + } + } + swap(nums, mini, pivot - 1); + reverse(nums, pivot, nums.length - 1); + } + + + public void reverse(int[] arr, int s, int e) { + while (s < e) swap(arr, s++, e--); + } + + public void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } +} +``` diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/LeetCode - 48. Rotate Image(\345\260\206\346\255\243\346\226\271\345\275\242\347\237\251\351\230\265\346\227\213\350\275\25490\345\272\246).md" b/Algorithm/LeetCode/Simulation/median/LeetCode-48.Rotate Image.md similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/LeetCode - 48. Rotate Image(\345\260\206\346\255\243\346\226\271\345\275\242\347\237\251\351\230\265\346\227\213\350\275\25490\345\272\246).md" rename to Algorithm/LeetCode/Simulation/median/LeetCode-48.Rotate Image.md diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-54.Spiral Matrix.md b/Algorithm/LeetCode/Simulation/median/LeetCode-54.Spiral Matrix.md new file mode 100644 index 00000000..fa4893fa --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-54.Spiral Matrix.md @@ -0,0 +1,49 @@ +## LeetCode - 54. Spiral Matrix(圈圈打印矩阵) +#### [题目链接](https://leetcode.com/problems/spiral-matrix/description/) + +> https://leetcode.com/problems/spiral-matrix/description/ + +#### 题意 +![在这里插入图片描述](images/54_t.png) + +#### 解析 + 题目不难,但是如果去一个个的打印的话会比较的麻烦。 +* 好的方法是使用**矩阵分圈处理**的方式,在矩阵中使用`(ar,ac)`表示左上角,`(br,bc)`表示矩阵的右下角; +* 每次只需要通过这四个变量打印一个矩阵,然后用一个**宏观的函数来调用**打印的局部的函数,这样调理更加清晰; + +看图很清晰: + +![54_ss.png](images/54_ss.png) + +代码: + +```java +class Solution { + + private List res; + + public List spiralOrder(int[][] matrix) { + res = new ArrayList<>(); + if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) + return res; + int ar = 0,ac = 0; + int br = matrix.length - 1,bc = matrix[0].length - 1; + //直到ar > br 或者 ac > bc才不打印了 + while (ar <= br && ac <= bc) edgePrint(matrix, ar++, ac++, br--, bc--); + return res; + } + + public void edgePrint(int[][] matrix, int ar, int ac, int br, int bc) { + if (ar == br) { //只有一行 + for (int i = ac; i <= bc; i++) res.add(matrix[ar][i]); + } else if (ac == bc) { // 只有一列 + for (int i = ar; i <= br; i++) res.add(matrix[i][ac]); + } else { + for (int i = ac; i <= bc - 1; i++) res.add(matrix[ar][i]); + for (int i = ar; i <= br - 1; i++) res.add(matrix[i][bc]); + for (int i = bc; i >= ac + 1; i--) res.add(matrix[br][i]); + for (int i = br; i >= ar + 1; i--) res.add(matrix[i][ac]); + } + } +} +``` diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-6.ZigZag Conversion.md b/Algorithm/LeetCode/Simulation/median/LeetCode-6.ZigZag Conversion.md new file mode 100644 index 00000000..b71e556e --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-6.ZigZag Conversion.md @@ -0,0 +1,88 @@ +# LeetCode - 6. ZigZag Conversion(N字型打印字符) + +#### [题目链接](https://leetcode.com/problems/zigzag-conversion/) + +> https://leetcode.com/problems/zigzag-conversion/ + +#### 题目 + +![6_t.png](images/6_t.png) + +## 一、方法一 + +每一行生成一个`String`,所以我们要准备一个`String[]`数组。直接模拟先从上往上走,然后走到底了就往上走。 + +可以使用当前行(`curRow`)和当前方向`down`这两个变量对合适的行进行跟踪。 + +图: + +

+代码: + +```java +class Solution { + public String convert(String s, int numRows) { + if (numRows <= 1) return s; + StringBuilder[] sb = new StringBuilder[numRows]; + for (int i = 0; i < numRows; i++) sb[i] = new StringBuilder(); + int curRow = 0; + boolean down = false; // 注意一开始必须是false,因为等下会down = !down + for (char c : s.toCharArray()) { + sb[curRow].append(c); + if (curRow == numRows - 1 || curRow == 0) down = !down; + if (down) + curRow += 1; + else + curRow -= 1; + } + StringBuilder res = new StringBuilder(); + for (StringBuilder row : sb) res.append(row); + return res.toString(); + } +} +``` + +## 二、方法二 + + +第二种方法是一行一行的去找每个一行的每一个位置在源字符串中对应的下标在哪。 + +分三种情况: + +* 第一行的时候,某个字符位于`idx * (2 * numRow - 2)`。 +* 最后一行的时候,某个字符位于`idx * (2 * numRow - 2) + numRow - 1`。 +* 中间行的时候,每一次要搞定两个字符,位置分别是`idx * (2 * numRow - 2) + i`和`(idx+1) * (2 * numRow - 2) - i`。 + +每种情况的图: + +

+最后一行的: + +

+中间的时候: + +

+代码 + +```java +class Solution { + public String convert(String s, int numRows) { + if (numRows <= 1) return s; + int n = s.length(); + char[] chs = s.toCharArray(); + StringBuilder res = new StringBuilder(); + int cycle = 2 * numRows - 2; + + for (int i = 0; i < numRows; i++) { + for (int j = 0; j + i < n; j += cycle) { + res.append(chs[i + j]); + if(i == 0 || i == numRows - 1) continue; + // 不是第一行和最后行,每次要append两个 + if (j + cycle - i < n) res.append(chs[j + cycle - i]); //注意最后可能不足 + } + } + return res.toString(); + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-8.String to Integer.md b/Algorithm/LeetCode/Simulation/median/LeetCode-8.String to Integer.md new file mode 100644 index 00000000..22d8cf96 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-8.String to Integer.md @@ -0,0 +1,56 @@ +# LeetCode - 8. String to Integer(atoi) + +#### [题目链接](https://leetcode.com/problems/string-to-integer-atoi/) + +> https://leetcode.com/problems/string-to-integer-atoi/ + +#### 题目 + +![8_t.png](images/8_t.png) + +![8_t_2.png](images/8_t_2.png) + +## 解析 + +也不难,只是要注意符号和溢出的问题。 + +关于溢出的判断,我们在当前位,只需要判断前面的乘积`ret > Integer.MAX_VALUE/10`或者`ret == Integer.MAX_VALUE && s[i] > 7`即可。 + +

+ +代码: + +```java +class Solution { + public int myAtoi(String str) { + if (str == null || str.length() == 0) return 0; + char[] s = str.toCharArray(); + int sign = 1, i = 0, ret = 0; + while (i < s.length && s[i] == ' ') i++;//去掉前面的空格 + if (i == s.length) return 0;//判断越界 + if (s[i] == '-') { + sign = -1; + i++; + } else if (s[i] == '+') { + i++; + } + while (i < s.length && s[i] >= '0' && s[i] <= '9') { + // max : 2147483647, min : -2147483648 + // Integer.MAX_VALUE/10 = 214748364 + if (ret > Integer.MAX_VALUE / 10 + || (Integer.MAX_VALUE / 10 == ret && s[i] > '7')) { +// || (Integer.MAX_VALUE / 10 == ret && ( (sign == 1 && s[i] > '7') || (sign == -1 && s[i] > '8')))){ + return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; + } + ret = ret * 10 + s[i++] - '0'; + } + return ret * sign; + } + + public static void main(String[] args) { + System.out.println(new Solution().myAtoi("-2147483648")); //-2147483648 + System.out.println(new Solution().myAtoi("2147483648")); //21474836487 + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/median/LeetCode-9.Palindrome Number.md b/Algorithm/LeetCode/Simulation/median/LeetCode-9.Palindrome Number.md new file mode 100644 index 00000000..1ad7bd98 --- /dev/null +++ b/Algorithm/LeetCode/Simulation/median/LeetCode-9.Palindrome Number.md @@ -0,0 +1,63 @@ +# LeetCode - 9. Palindrome Number + +#### [题目链接](https://leetcode.com/problems/palindrome-number/) + +> https://leetcode.com/problems/palindrome-number/ + +#### 题目 + +![9_t.png](images/9_t.png) + +### 解析 + +直接处理成字符串就不说了。 + +一遍循环逆序过来也比较简单,**但是也要考虑溢出问题**。 + +`O( len(N) ) ` + +```java +class Solution { + public boolean isPalindrome(int x) { + if (x < 0) return false; + int rev = 0; + int tmp = x; + while (tmp > 0) { + int newRev = rev * 10 + tmp % 10; + if( (newRev-tmp%10)/10 != rev) return false; //判断溢出 + rev = newRev; + tmp /= 10; + } + return x == rev; + } +} +``` + +更好的方法: + +* **考虑只反转数字的一半**,如果该数字是回文,**其后半部分反转后应该与原始数字的前半部分相同**。 +* 循环条件是剩余的`x`要大于`rev`(累乘的反转数);(也就是一半的边界) +* 然后最后判断是否相同的条件要分奇偶,奇数的话将`rev/10`即可。因为奇数的话`rev`会比`x`多出来一位; + +`O(len(N)/2)` + +图: + +

+代码: + +```java +class Solution { + public boolean isPalindrome(int x) { + if (x < 0) return false; + if(x%10 == 0 && x != 0) return false;// must add this + int rev = 0; + while(x > rev){// 处理一半 + rev = rev * 10 + x % 10; + x /= 10; + } + return x == rev || x == rev/10;// 1221, 12321,分别是奇数和偶数的情况 + } +} +``` + diff --git a/Algorithm/LeetCode/Simulation/median/images/12_s.png b/Algorithm/LeetCode/Simulation/median/images/12_s.png new file mode 100644 index 00000000..2d1b7686 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/12_s.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/13_t.png b/Algorithm/LeetCode/Simulation/median/images/13_t.png new file mode 100644 index 00000000..a420ce7e Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/13_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/31_s.png" b/Algorithm/LeetCode/Simulation/median/images/31_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/31_s.png" rename to Algorithm/LeetCode/Simulation/median/images/31_s.png diff --git a/Algorithm/LeetCode/Simulation/median/images/31_ss.png b/Algorithm/LeetCode/Simulation/median/images/31_ss.png new file mode 100644 index 00000000..c53f9e58 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/31_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/31_t.png" b/Algorithm/LeetCode/Simulation/median/images/31_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/31_t.png" rename to Algorithm/LeetCode/Simulation/median/images/31_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_s.png" b/Algorithm/LeetCode/Simulation/median/images/48_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_s.png" rename to Algorithm/LeetCode/Simulation/median/images/48_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_t.png" b/Algorithm/LeetCode/Simulation/median/images/48_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_t.png" rename to Algorithm/LeetCode/Simulation/median/images/48_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_t2.png" b/Algorithm/LeetCode/Simulation/median/images/48_t2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/48_t2.png" rename to Algorithm/LeetCode/Simulation/median/images/48_t2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/54_s.png" b/Algorithm/LeetCode/Simulation/median/images/54_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/54_s.png" rename to Algorithm/LeetCode/Simulation/median/images/54_s.png diff --git a/Algorithm/LeetCode/Simulation/median/images/54_ss.png b/Algorithm/LeetCode/Simulation/median/images/54_ss.png new file mode 100644 index 00000000..3ad425c3 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/54_ss.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Simulation/median/images/54_t.png" b/Algorithm/LeetCode/Simulation/median/images/54_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Simulation/median/images/54_t.png" rename to Algorithm/LeetCode/Simulation/median/images/54_t.png diff --git a/Algorithm/LeetCode/Simulation/median/images/6_s.png b/Algorithm/LeetCode/Simulation/median/images/6_s.png new file mode 100644 index 00000000..9771dadb Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/6_s.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/6_s_2.png b/Algorithm/LeetCode/Simulation/median/images/6_s_2.png new file mode 100644 index 00000000..e5b43723 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/6_s_2.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/6_s_3.png b/Algorithm/LeetCode/Simulation/median/images/6_s_3.png new file mode 100644 index 00000000..f32321aa Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/6_s_3.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/6_s_4.png b/Algorithm/LeetCode/Simulation/median/images/6_s_4.png new file mode 100644 index 00000000..5e612534 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/6_s_4.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/6_t.png b/Algorithm/LeetCode/Simulation/median/images/6_t.png new file mode 100644 index 00000000..b7efe19a Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/6_t.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/8_s.png b/Algorithm/LeetCode/Simulation/median/images/8_s.png new file mode 100644 index 00000000..45c2434e Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/8_s.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/8_t.png b/Algorithm/LeetCode/Simulation/median/images/8_t.png new file mode 100644 index 00000000..105e7858 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/8_t.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/8_t_2.png b/Algorithm/LeetCode/Simulation/median/images/8_t_2.png new file mode 100644 index 00000000..3ade20cd Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/8_t_2.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/9_s.png b/Algorithm/LeetCode/Simulation/median/images/9_s.png new file mode 100644 index 00000000..d87bdd8b Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/9_s.png differ diff --git a/Algorithm/LeetCode/Simulation/median/images/9_t.png b/Algorithm/LeetCode/Simulation/median/images/9_t.png new file mode 100644 index 00000000..49a20976 Binary files /dev/null and b/Algorithm/LeetCode/Simulation/median/images/9_t.png differ diff --git "a/Algorithm/LeetCode/String/Easy/LeetCode-680.Valid Palindrome II(\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\210\244\346\226\255\350\203\275\345\220\246\346\236\204\346\210\220\345\233\236\346\226\207).md" "b/Algorithm/LeetCode/String/Easy/LeetCode-680.Valid Palindrome II(\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\210\244\346\226\255\350\203\275\345\220\246\346\236\204\346\210\220\345\233\236\346\226\207).md" new file mode 100644 index 00000000..96dc194e --- /dev/null +++ "b/Algorithm/LeetCode/String/Easy/LeetCode-680.Valid Palindrome II(\345\210\240\351\231\244\344\270\200\344\270\252\345\255\227\347\254\246\345\210\244\346\226\255\350\203\275\345\220\246\346\236\204\346\210\220\345\233\236\346\226\207).md" @@ -0,0 +1,59 @@ + +# LeetCode - 680. Valid Palindrome II(删除一个字符判断能否构成回文) +#### [题目链接](https://leetcode.com/problems/valid-palindrome-ii/) + +> https://leetcode.com/problems/valid-palindrome-ii/ + +#### 题目 + +就是给你一个字符串,问你是否能删除最多一个字符(可以不用删除) ,得到一个回文串。 + +![在这里插入图片描述](images/680_t.png) + +#### 解析 + +暴力枚举删除每一个位置的方法肯定是行不通的。 + +这里需要用到**回文串的性质**。 + +看下面两个例子: + +案例一: `e a d c b c a e` + +![在这里插入图片描述](images/680_s.png) + +另一种情况:  + +`e a b f f b e a e` + +![在这里插入图片描述](images/680_s2.png) + + +所以解题步骤: + + +设置两个指针,往中间靠拢,然后当遇到`s[L] != s[R]`的时候,就判断`[L + 1, R]`或者`[L, R-1]`部分可以不可以构成回文串,只要有其中一个可以,就返回`true`即可。 +```java +class Solution { + public boolean validPalindrome(String s) { + int L = 0, R = s.length() - 1; + while(L < R){ + if(s.charAt(L) != s.charAt(R)){ + return isPalindrome(s, L+1, R) || isPalindrome(s, L, R-1); + }else { + L++; + R--; + } + } + return true; + } + + private boolean isPalindrome(String s, int L, int R){ + while(L < R) + if(s.charAt(L++) != s.charAt(R--)) + return false; + return true; + } +} +``` + diff --git "a/\345\210\267\351\242\230/LeetCode/String/Easy/images/680_s.png" b/Algorithm/LeetCode/String/Easy/images/680_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Easy/images/680_s.png" rename to Algorithm/LeetCode/String/Easy/images/680_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/Easy/images/680_s2.png" b/Algorithm/LeetCode/String/Easy/images/680_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Easy/images/680_s2.png" rename to Algorithm/LeetCode/String/Easy/images/680_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/Easy/images/680_t.png" b/Algorithm/LeetCode/String/Easy/images/680_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Easy/images/680_t.png" rename to Algorithm/LeetCode/String/Easy/images/680_t.png diff --git "a/Algorithm/LeetCode/String/KMP/LeetCode-214.Shortest Palindrome(KMP\345\222\214Manacher\347\256\227\346\263\225\350\247\243\345\206\263).md" "b/Algorithm/LeetCode/String/KMP/LeetCode-214.Shortest Palindrome(KMP\345\222\214Manacher\347\256\227\346\263\225\350\247\243\345\206\263).md" new file mode 100644 index 00000000..6258a4e8 --- /dev/null +++ "b/Algorithm/LeetCode/String/KMP/LeetCode-214.Shortest Palindrome(KMP\345\222\214Manacher\347\256\227\346\263\225\350\247\243\345\206\263).md" @@ -0,0 +1,178 @@ +## LeetCode - 214. Shortest Palindrome(KMP和Manacher算法解决) + + - KMP算法解决 + - 使用Manacher解决以及扩展 + - 递归解决 + +*** +#### [题目链接](https://leetcode.com/problems/shortest-palindrome/description/) + +> https://leetcode.com/problems/shortest-palindrome/description/ + +#### 题目 + +![](images/214_t.png) + +### KMP算法解决 +**KMP基础可以先看一下[这篇文章](https://blog.csdn.net/zxzxzx0119/article/details/81430392)**。 + +* KMP解决算法就是我们**先在原始的串后面加上一个`'#'`,然后在加上原串的倒置串**; +* 然后对整个串求出它的`next`数组(`next[i]`代表的是在`0 ~ i-1` 中,必须以`str[i-1]`结尾的后缀子串(不能包含`str[0]`)与必须以`str[0]`开头的前缀子串(不能包含`str[i-1]`)的最大匹配长度) ; +* 然后我们要的就是整个串的最长公共前缀和后缀,然后再进行字符串的处理即可,处理的方面看代码。 + + **注意下面画的图所有的例子都是 `12321cba` 这个字符串**。 + +![1554862260986](assets/1554862260986.png) + +代码如下: + +```java +class Solution { + public String shortestPalindrome(String s) { + String temp = s + "#" + new StringBuilder(s).reverse().toString(); + int[] next = getNext(temp); //获得 每个位置 前面的最长公共前缀和最长公共后缀的长度 + return new StringBuilder(s.substring(next[next.length - 1])).reverse().toString() + s; + } + + + //获取next数组 + private int[] getNext(String str) { + int[] next = new int[str.length() + 1]; + next[0] = -1; + next[1] = 0; + int cn = 0; + for (int i = 2; i <= str.length(); ) { //注意没有i++ + if (str.charAt(i - 1) == str.charAt(cn)) { + next[i++] = ++cn; + } else { + if (cn > 0) { + cn = next[cn]; + } else { + next[i++] = 0; + } + } + } + return next; + } +} +``` +*** +### 使用Manacher解决 + +manacher算法基础可以先看一下[这篇文章](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/String/Manacher/LeetCode%20-%205.%20Longest%20Palindromic%20Substring(%E4%B8%89%E7%A7%8D%E8%A7%A3%E6%B3%95%E5%8F%8AManacher%E7%AE%97%E6%B3%95%E8%AF%A6%E8%A7%A3).md)。 + + +* 如果能够使用`Manacher`算法求出以`str[0]`开头的最长回文半径,如 ` 12321cba` ,如果我们可以求出`r[0]`的最大值(以`r[0]`开头的最长回文子串),在`12321cba `这个字符串中就是`12321`; +* 那么我们的问题只需要把后面的`cba`反过来放到前面就就可以; +* 而这就是进行下标的变化得到结果(code的细节)。(下图的第二种情况) + +这个问题变化问题,就是我们可以在后面添加最少字符使得变成回文串,这个是一样的(而且更简单),看下图的第一种情况。 + +![在这里插入图片描述](images/214_s2.png) + +代码: + +```java +class Solution { + + public String shortestPalindrome(String s) { + if (s == null || s.length() < 2) + return s; + char[] chs = manacherString(s); + int[] r = new int[chs.length]; + int R = -1, C = -1; + int max = Integer.MIN_VALUE; + for (int i = 0; i < chs.length; i++) { + r[i] = R > i ? Math.min(r[2 * C - i], R - i) : 1; + for (; i + r[i] < chs.length && i - r[i] >= 0 && chs[i - r[i]] == chs[i + r[i]]; ) r[i]++; + if (i + r[i] > R) { + R = i + r[i]; + C = i; + } + if (i + 1 - r[i] == 0) { //起点是 s[0]的回文子串 + max = Math.max(max, r[i]); + } + } + char[] res = new char[s.length() - max + 1]; //画一个例子试试 + for (int i = 0; i < res.length; i++) { + res[res.length - 1 - i] = chs[chs.length - 1 - 2 * (res.length - 1 - i) - 1];//这个画一个图抠一下 + } + return String.valueOf(res) + s; + } + + public char[] manacherString(String str) { + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i < res.length; i++) { + res[i] = ((i & 1) == 0) ? '#' : str.charAt(index++); + } + return res; + } + +} +``` + +上面第一种情况(在后面补)(问题的变式): + +```java +class Solution { + + /** + * 在后面补 + * 算出以 s[0] 开头的最长回文 ,剩下的最补在 s[0]的前面 + */ + public String shortestPalindromeAfter(String s) { + if (s == null || s.length() < 2) return s; + char[] chs = manacherString(s); + int[] r = new int[chs.length]; + int R = -1, C = -1; + int max = Integer.MIN_VALUE; + for (int i = 0; i < chs.length; i++) { + r[i] = R > i ? Math.min(r[2 * C - i], R - i) : 1; + for (; i + r[i] < chs.length && i - r[i] >= 0 && chs[i - r[i]] == chs[i + r[i]]; ) r[i]++; + if (i + r[i] > R) { + R = i + r[i]; + C = i; + } + if (R == chs.length) { //扩充到最后一个位置,注意 不是chs.lenght-1,因为每个r[i]包括了自己 + max = r[i]; + break; + } + } + char[] res = new char[s.length() - max + 1]; //画一个例子试试 + for (int i = 0; i < res.length; i++) { + res[res.length - 1 - i] = chs[2 * i + 1]; + } + return s + String.valueOf(res); + } + + public char[] manacherString(String str) { + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i < res.length; i++) { + res[i] = ((i & 1) == 0) ? '#' : str.charAt(index++); + } + return res; + } +} +``` +*** +### 递归解决 +[**discuss**](https://leetcode.com/problems/shortest-palindrome/discuss/60098/My-7-lines-recursive-Java-solution)中看到的。 + +```java +class Solution { + public String shortestPalindrome(String s) { + if (s.length() <= 1) + return s; //递归条件 + int start = 0; + StringBuilder sb = new StringBuilder(); + for (int i = s.length() - 1; i >= 0; i--) + if (s.charAt(start) == s.charAt(i)) start++; + if (start == s.length()) return s; + for (int i = s.length() - 1; i >= start; i--) + sb.append(s.charAt(i)); + return sb.toString() + shortestPalindrome(s.substring(0, start)) + s.substring(start); + } +} +``` diff --git a/Algorithm/LeetCode/String/KMP/LeetCode-28.Implement strStr().md b/Algorithm/LeetCode/String/KMP/LeetCode-28.Implement strStr().md new file mode 100644 index 00000000..a2167c3b --- /dev/null +++ b/Algorithm/LeetCode/String/KMP/LeetCode-28.Implement strStr().md @@ -0,0 +1,96 @@ +# LeetCode - 28. Implement strStr() + +#### [题目链接](https://leetcode.com/problems/implement-strstr/) + +> https://leetcode.com/problems/implement-strstr/ + +#### 题目 + +![1557825269693](assets/1557825269693.png) + +### 解析 + +经典的字符串匹配问题。 + +暴力解法和KMP解法。 + +暴力匹配的话就是直接对`s1`的每一个位置,都匹配一次`s2`即可。 + +
+ +代码: + +```java +class Solution { + // 暴力匹配 + public int strStr(String haystack, String needle) { + char[] s = haystack.toCharArray(); + char[] p = needle.toCharArray(); + int i = 0, j = 0; + for(i = 0; i < s.length; i++){ + if(j == p.length) return i - p.length; + if(s[i] == p[j]) j++; // 继续匹配 + else { + i -= j; // i回到前面 + j = 0; // j回到开始 + } + } + if(j == p.length) return i - p.length; + return -1; + } +} +``` + +KMP算法请移步[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/String/KMP/Hdu%20-%201711.%20Number%20Sequence%E4%BB%A5%E5%8F%8AKMP%E7%AE%97%E6%B3%95%E6%80%BB%E7%BB%93.md)。 + +注意这一题要判断一下`needle.length==0`的情况下返回`0`。 + +代码: + +```java +class Solution { + + public int strStr(String haystack, String needle) { + if(needle.length() == 0) return 0; // 必须要加上这个 + return kmp(haystack.toCharArray(), needle.toCharArray()); + } + + private int kmp(char[] s, char[] p) { + if (s == null || p == null || p.length < 1 || s.length < p.length) return -1; + + int i1 = 0, i2 = 0; //甲乙 + int[] next = getNext(p); + while (i1 < s.length && i2 < p.length) { + if (s[i1] == p[i2]) { //能配上,继续 + i1++; + i2++; + } else { + if (next[i2] == -1) { //我str2到了第一个你都配不上(第一个位置都配不上),那你str1就下一个吧 + i1++; + } else {//逻辑概念是str2往右边推 + i2 = next[i2]; //来到next数组指示(最长公共前缀后缀) + } + } + } + return i2 == p.length ? i1 - i2 : -1;//返回匹配的第一个位置 + } + + private int[] getNext(char[] p) { + if (p.length == 1) return new int[]{-1}; + int[] next = new int[p.length]; + next[0] = -1; + next[1] = 0; + int cn = 0; + for (int i = 2; i < p.length; ) { + if (p[i - 1] == p[cn]) { + next[i++] = ++cn; //就是cn+1 + } else { + if (cn > 0) cn = next[cn];//往前面跳 + else next[i++] = 0; + } + } + return next; + } +} +``` + diff --git a/Algorithm/LeetCode/String/KMP/assets/1554862260986.png b/Algorithm/LeetCode/String/KMP/assets/1554862260986.png new file mode 100644 index 00000000..228be33b Binary files /dev/null and b/Algorithm/LeetCode/String/KMP/assets/1554862260986.png differ diff --git a/Algorithm/LeetCode/String/KMP/assets/1557825269693.png b/Algorithm/LeetCode/String/KMP/assets/1557825269693.png new file mode 100644 index 00000000..c52c67a8 Binary files /dev/null and b/Algorithm/LeetCode/String/KMP/assets/1557825269693.png differ diff --git a/Algorithm/LeetCode/String/KMP/assets/1557830960766.png b/Algorithm/LeetCode/String/KMP/assets/1557830960766.png new file mode 100644 index 00000000..f27f43e4 Binary files /dev/null and b/Algorithm/LeetCode/String/KMP/assets/1557830960766.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/String/KMP/images/214_s.png" b/Algorithm/LeetCode/String/KMP/images/214_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/KMP/images/214_s.png" rename to Algorithm/LeetCode/String/KMP/images/214_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/KMP/images/214_s2.png" b/Algorithm/LeetCode/String/KMP/images/214_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/KMP/images/214_s2.png" rename to Algorithm/LeetCode/String/KMP/images/214_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/KMP/images/214_t.png" b/Algorithm/LeetCode/String/KMP/images/214_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/KMP/images/214_t.png" rename to Algorithm/LeetCode/String/KMP/images/214_t.png diff --git a/Algorithm/LeetCode/String/Manacher/LeetCode-5.Longest Palindromic Substring.md b/Algorithm/LeetCode/String/Manacher/LeetCode-5.Longest Palindromic Substring.md new file mode 100644 index 00000000..033227b3 --- /dev/null +++ b/Algorithm/LeetCode/String/Manacher/LeetCode-5.Longest Palindromic Substring.md @@ -0,0 +1,247 @@ +# LeetCode - 5. Longest Palindromic Substring(三种解法及Manacher算法详解) + - 中心扩展(普通方法) + - 动态规划`O(N^2)` + - Manacher算法`O(N)` +*** +#### [题目链接](https://leetcode.com/problems/longest-palindromic-substring/description/) + +> https://leetcode.com/problems/longest-palindromic-substring/description/ + +#### 题目 + +![images/m3.png](images/m3.png) + +## 一、中心扩展方法 +这个方法也是最容易想到的: + + - 从`i`位置开始,**每个位置都要往左右两边开始扩张**,相等就继续扩,不相等就停止,**并记录,注意这里的下标的变化,以及边界的处理**; + - 这里要注意一个问题就是要**同时处理奇回文(如`cbabd`) 和偶回文(如`abbccd`),只需要在扩展的时候扩展两次就可以了**。 + +图: + +

+代码: + +```java +class Solution { + + private int len = 0; //记录最长回文的长度 + private int begin = 0; // 记录最长回文的起始位置 + + public String longestPalindrome(String s) { + if (s == null || s.length() < 2) return s; + char[] chs = s.toCharArray(); + for (int i = 0; i < chs.length; i++) { + expand(chs, i, i); //奇回文 例如 cbabd + expand(chs, i, i + 1); //偶数回文 例如abbccd + } + return s.substring(begin, begin + len); + } + + private void expand(char[] chs, int L, int R) { + while (L >= 0 && R < chs.length && chs[L] == chs[R]) { // 往两边扩展 + L--; + R++; + } + // 为什么是r-l-1, 因为上面的判断条件中, l或者r超出了范围或者不满足条件 + // 比如 aabac, 此时L = 0, R = 4, 长度为 R - L - 1,也就是中间的3 + if (R - L - 1 > len) { + len = R - L - 1; + begin = L + 1; + } + } +} +``` +**至于为什么是`r - l - 1 `因为上面的判断条件中是判断当前的`chs[l] == chs[r]` 如果不等才退出`while`,所以包含了不等的,这个自己画一个例子就明白了**。 + + +## 二、动态规划 +**`dp[i][j]` 表示的是 ` i` 到 `j`这段是不是回文子串**。 + +能画出二维`dp`表,能搞清计算顺序就`ok`。 + +

+代码: + +```java +class Solution { + + public String longestPalindrome(String s) { + if (s == null || s.length() < 2) return s; + boolean[][] dp = new boolean[s.length()][s.length()]; + int begin = 0, end = 0; + for (int i = s.length() - 2; i >= 0; i--) { + for (int j = i + 1; j <= s.length() - 1; j++) { + if (s.charAt(i) == s.charAt(j) && (j - i <= 2 || dp[i + 1][j - 1])) { + dp[i][j] = true; + if (j - i + 1 > end - begin + 1) { + begin = i; + end = j; + } + } + + } + } + return s.substring(begin, end + 1); + } +} +``` + +## 三、Manacher算法 + +Mancher解决的是求**最长回文子串的长度**的问题,这里有一个原题是[Hdu - 3068](http://acm.hdu.edu.cn/showproblem.php?pid=3068)。 + +这方法有点难度,可以选择性的看。 + +看下图(为了处理奇回文和偶回文,在原来的字符串基础上处理成中间和两边都要带上`#`的字符串) + +

+首先给出四个概念: + + - **回文直径,以一个中心开始,向两边开始扩,能扩出来的范围,以及回文半径(回文直径的一半)**; + - **回文半径数组`r[]` ,每个位置能扩出来的回文半径的长度**; + - **`pR` : 所有回文半径中,最靠右的位置的下一个位置**,一开始最右边界`pR = -1`; + - **`C` : 当前的回文右边界(最右的)的最早的中心在哪** ; + +看一个简单的示例 +

+ 首先我们的目的就是要求出所有的`r[]`数组(每个位置的最长回文半径,然后取最大的即可),Manacher算法分为大的两种情况 + + - 第一种可能性: `i`不在回文右边界(`pR`, 也就是`i < pR`)里,直接暴力扩(向两边); + - 第二种可能性: `i`在回文右边界(`pR`)里,这种情况又可以**分为三种小情况**,看下面的三种情况的图; + - `i`关于`C`对称的位置`i'`在`L,R`内,(`L`是`pR`关于`C`的对称点);则`r[i] = r[i']`; (`r[i']`前面已经求解过) + - `i`关于`C`对称的位置`i'`在`L,R`外,则`r[i] = R - i`; + - `i`关于`C`对称的位置`i'`的回文半径左边界和`L`相等(压线), 则从`pR`的右边开始暴力扩; + +下面看**第二种可能性中**的三种情况(注意`i'`代表的就是`i`关于`C`对称的点) + +第一种情况 + +

+第二种情况 + +

+第三种情况 + +

+上面的图片解释了三种情况下分别的做法,具体的做法看代码的注释。 + +注意其中第一种情况和第二种情况取最小值。 + +`Manacher`实现代码以及[Hdu - 3068](http://acm.hdu.edu.cn/showproblem.php?pid=3068)代码 + +```java +import java.io.BufferedInputStream; +import java.util.Scanner; + +public class Main { + + /** + * 获取指定格式的字符串(中间和两边都带有#) 这样可以处理偶回文 + * 例如 : 如果是abc -->#a#b#c# + * 如果是abcd -->#a#b#c#d# + */ + static char[] manacherString(String str) { + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i < res.length; i++) + res[i] = ((i & 1) == 0) ? '#' : str.charAt(index++); + return res; + } + + static int manacher(String s) { + if (s == null || s.length() == 0) return 0; + char[] chs = manacherString(s); + int[] r = new int[chs.length]; //记录每个位置的最长回文半径,注意是chs的长度 + int pR = -1, C = -1; //分别代表目前的最长回文右边界,和它的中心 + int max = Integer.MIN_VALUE; //记录结果 + for (int i = 0; i < chs.length; i++) { + r[i] = pR > i ? Math.min(r[2 * C - i], pR - i) : 1; //这句代码包含三种情况 第一种大情况,和二种中的(1)(2)情况 + while (i + r[i] < chs.length && i - r[i] >= 0) { //不越界 //注意这包括了四种情况,都要扩一下,为了节省代码 + if (chs[i + r[i]] == chs[i - r[i]]) { + r[i]++; + } else { //扩不动了 + break; + } + } + if (i + r[i] > pR) { //更新最右边界和它的中心 + pR = i + r[i]; + C = i; + } + max = Math.max(max, r[i]); //取最大的r[i] (r[i]记录的是每个位置的最长回文半径) + } + return max - 1; //求出来的是加了'#'的 + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + while (in.hasNext()) { + String s = in.next(); + System.out.println(manacher(s)); + } + } +} +``` +**关于返回值`max-1`的例子解释** + +计算出`r`数组之后,顺便记录了`r`数组的最大值,假设位置`i`的回文半径最大,即`r[i] = max`。 + +但`max`只是`chs`数组的最大回文半径,我们还要对应回原来的字符串。 + +比如原字符串为`121`,处理成`chs`之后为`#1#2#1#`,在`chs`中位置`3`的回文半径最大,最大值为`4`(即`r[3] = 4`),对应原来的字符串的最大回文子串为`4 - 1 = 3`。 + + + +上面已经说了`Manacher`求得回文子串的长度,这里只需要记录一些求得**回文子串长度的下标**,就可以通过相应的关系还原出来。 + +有两种方式还原,至于下标怎么抠的,我也是写代码的时候自己画的例子,具体看代码。 + +```java +class Solution { + /** + * 方法三: 使用Manacher方法 + * 获取指定格式的字符串(中间和两边都带有#) 这样可以处理偶回文 + * 例如 : 如果是abc -->#a#b#c# + * 如果是abcd -->#a#b#c#d# + */ + public char[] manacherString(String str) { + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i < res.length; i++) { + res[i] = ((i & 1) == 0) ? '#' : str.charAt(index++); + } + return res; + } + + public String longestPalindrome(String s) { + if (s == null || s.length() == 0) return ""; + char[] chs = manacherString(s); + int[] r = new int[chs.length]; //记录每个位置的最长回文半径,注意是chs的长度 + int pR = -1, C = -1; //分别代表目前的最长回文右边界,和它的中心 + int max = Integer.MIN_VALUE; //记录结果 + int maxi = 0; + for (int i = 0; i < chs.length; i++) { + r[i] = pR > i ? Math.min(r[2 * C - i], pR - i) : 1; //这句代码包含三种情况 第一种大情况,和二种中的(1)(2)情况 + //不越界 //注意这包括了四种情况,都要扩一下,为了节省代码 + while (i + r[i] < chs.length && i - r[i] >= 0 && chs[i + r[i]] == chs[i - r[i]]) r[i]++; + if (i + r[i] > pR) { //更新最右边界和它的中心 + pR = i + r[i]; + C = i; + } + if (r[i] > max) { + maxi = i; + max = r[i]; + } + } + //如果使用下面注释的方法,从这一行开始都可以不要 + StringBuilder res = new StringBuilder(); + for (int i = maxi - (max - 1); i <= maxi + (max - 1); i++) + if (chs[i] != '#') res.append(chs[i]); + return res.toString(); + + //这里要抠下标的话,自己画两个例子就知道了 +// return maxi % 2 == 0 ? s.substring(maxi / 2 - ((max) / 2), maxi / 2 + (max / 2)) +// : s.substring(maxi / 2 - ((max - 1) / 2), maxi / 2 + ((max - 1) / 2) + 1); + } +} +``` diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s.png b/Algorithm/LeetCode/String/Manacher/images/5_s.png new file mode 100644 index 00000000..f747d60c Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_2.png b/Algorithm/LeetCode/String/Manacher/images/5_s_2.png new file mode 100644 index 00000000..0d180b70 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_2.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_3.png b/Algorithm/LeetCode/String/Manacher/images/5_s_3.png new file mode 100644 index 00000000..0b162229 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_3.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_4.png b/Algorithm/LeetCode/String/Manacher/images/5_s_4.png new file mode 100644 index 00000000..ab065b7e Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_4.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_5.png b/Algorithm/LeetCode/String/Manacher/images/5_s_5.png new file mode 100644 index 00000000..bdef1381 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_5.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_6.png b/Algorithm/LeetCode/String/Manacher/images/5_s_6.png new file mode 100644 index 00000000..a581a94f Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_6.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/5_s_7.png b/Algorithm/LeetCode/String/Manacher/images/5_s_7.png new file mode 100644 index 00000000..fb11705c Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/5_s_7.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/String/Manacher/images/m1.png" b/Algorithm/LeetCode/String/Manacher/images/m1.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Manacher/images/m1.png" rename to Algorithm/LeetCode/String/Manacher/images/m1.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/Manacher/images/m2.png" b/Algorithm/LeetCode/String/Manacher/images/m2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Manacher/images/m2.png" rename to Algorithm/LeetCode/String/Manacher/images/m2.png diff --git "a/\345\210\267\351\242\230/LeetCode/String/Manacher/images/m3.png" b/Algorithm/LeetCode/String/Manacher/images/m3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/String/Manacher/images/m3.png" rename to Algorithm/LeetCode/String/Manacher/images/m3.png diff --git a/Algorithm/LeetCode/String/Manacher/images/m4.png b/Algorithm/LeetCode/String/Manacher/images/m4.png new file mode 100644 index 00000000..dffb5e14 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/m4.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/m5.png b/Algorithm/LeetCode/String/Manacher/images/m5.png new file mode 100644 index 00000000..ac85ca41 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/m5.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/m6.png b/Algorithm/LeetCode/String/Manacher/images/m6.png new file mode 100644 index 00000000..fe33ca0c Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/m6.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/m7.png b/Algorithm/LeetCode/String/Manacher/images/m7.png new file mode 100644 index 00000000..37bc859e Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/m7.png differ diff --git a/Algorithm/LeetCode/String/Manacher/images/m8.png b/Algorithm/LeetCode/String/Manacher/images/m8.png new file mode 100644 index 00000000..234e1dd8 Binary files /dev/null and b/Algorithm/LeetCode/String/Manacher/images/m8.png differ diff --git "a/Algorithm/LeetCode/Tree/LeetCode-100.Same Tree(\345\210\244\346\226\255\344\270\244\346\243\265\346\240\221\346\230\257\345\220\246\345\256\214\345\205\250\347\233\270\345\220\214)(\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/LeetCode/Tree/LeetCode-100.Same Tree(\345\210\244\346\226\255\344\270\244\346\243\265\346\240\221\346\230\257\345\220\246\345\256\214\345\205\250\347\233\270\345\220\214)(\347\256\200\345\215\225\351\242\230).md" new file mode 100644 index 00000000..a9766d18 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-100.Same Tree(\345\210\244\346\226\255\344\270\244\346\243\265\346\240\221\346\230\257\345\220\246\345\256\214\345\205\250\347\233\270\345\220\214)(\347\256\200\345\215\225\351\242\230).md" @@ -0,0 +1,67 @@ +# LeetCode - 100. Same Tree(判断两棵树是否完全相同)(简单题) +* 递归 +* 非递归 + +*** +#### [题目链接](https://leetcode.com/problems/same-tree/) + +> https://leetcode.com/problems/same-tree/ + +#### 题目 +![在这里插入图片描述](images/100_t.png) +## 1、递归 +这题比较简单。分为五种情况: + +* `p、q`都为空,返回`true`; +* `p == null && q != null `返回`false`; +* `p != null && q == null`返回`false`; +* `p.val != q.val` 返回`false`; +* `p.val == q.val` ---> 去判断他们的左右子树是否相等(递归); + +图: ![1554697533547](assets/1554697533547.png) + +代码: + +```java +class Solution { + public boolean isSameTree(TreeNode p, TreeNode q) { + if (p == null && q == null) + return true; + if (p == null || q == null) + return false; + return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right); + } +} +``` + +## 2、非递归 + +这个也很简单: + +* 栈中保存两棵树的节点即可(使用数组),当然也可以使用两个栈; +* 然后利用前序非递归类似的方式非递归,判断条件还是和递归一样,只不过改成了非递归而已。 +```java +class Solution { + public boolean isSameTree(TreeNode p, TreeNode q) { + if (p == null && q == null) + return true; + if (p == null || q == null) + return false; + Stack stack = new Stack<>(); + stack.push(new TreeNode[]{p, q}); + while (!stack.isEmpty()) { + TreeNode[] top = stack.pop(); + if (top[0] == null && top[1] == null) + continue; + if (top[0] == null || top[1] == null) + return false; + if (top[0].val != top[1].val) + return false; + stack.push(new TreeNode[]{top[0].left, top[1].left}); + stack.push(new TreeNode[]{top[0].right, top[1].right}); + } + return true; + } +} +``` + diff --git a/Algorithm/LeetCode/Tree/LeetCode-101.Symmetric Tree.md b/Algorithm/LeetCode/Tree/LeetCode-101.Symmetric Tree.md new file mode 100644 index 00000000..7cd8fcc4 --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-101.Symmetric Tree.md @@ -0,0 +1,92 @@ +# LeetCode - 101. Symmetric Tree + +#### [题目链接](https://leetcode.com/problems/symmetric-tree/) + +> https://leetcode.com/problems/symmetric-tree/ + +#### 题目 + +![1554698074830](assets/1554698074830.png) + +## 解析 + + + +递归思路。 + +* 首先根节点,只要`pRoot.left`和`pRoot.right`对称即可; + +* 左右节点的**值相等**且对称子树`left.left 和 right.right对称` ,且`left.rigth和right.left也对称`。 + +

+ +递归: + +```java +public class Solution { + + public boolean isSymmetric(TreeNode root){ + return root == null ? true : mirror(root.left, root.right); + } + + private boolean mirror(TreeNode left, TreeNode right) { + if(left == null && right == null) return true; + if(left == null || right == null) return false; + return left.val == right.val + && mirror(left.left, right.right) + && mirror(left.right, right.left); + } +} +``` + +非递归: + +层次遍历即可,注意队列中要成对成对的取。 + +```java +public class Solution { + + boolean isSymmetric(TreeNode root) { + if (root == null) return true; + Queue queue = new LinkedList<>(); + queue.add(root.left); + queue.add(root.right); + while (!queue.isEmpty()) { + TreeNode right = queue.poll(); + TreeNode left = queue.poll(); + if (left == null && right == null) continue; + if (left == null || right == null) return false; + if (left.val != right.val) return false; + //成对插入 + queue.add(left.left); queue.add(right.right); + queue.add(left.right); queue.add(right.left); + } + return true; + } +} +``` + +栈也可以: + +```java +public class Solution { + + boolean isSymmetric(TreeNode root) { + if (root == null) return true; + Stack s = new Stack<>(); + s.push(root.left); + s.push(root.right); + while (!s.isEmpty()) { + TreeNode right = s.pop(); + TreeNode left = s.pop(); + if (left == null && right == null) continue; + if (left == null || right == null) return false; + if (left.val != right.val) return false; + //成对插入 + s.push(left.left); s.push(right.right); + s.push(left.right); s.push(right.left); + } + return true; + } +} +``` diff --git "a/Algorithm/LeetCode/Tree/LeetCode-102.Binary Tree Level Order Traversal(\345\261\202\346\254\241\351\201\215\345\216\206\344\277\235\345\255\230).md" "b/Algorithm/LeetCode/Tree/LeetCode-102.Binary Tree Level Order Traversal(\345\261\202\346\254\241\351\201\215\345\216\206\344\277\235\345\255\230).md" new file mode 100644 index 00000000..dba6ef51 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-102.Binary Tree Level Order Traversal(\345\261\202\346\254\241\351\201\215\345\216\206\344\277\235\345\255\230).md" @@ -0,0 +1,134 @@ +## LeetCode - 102. Binary Tree Level Order Traversal(层次遍历保存) + - 非递归 + - 递归 +*** +#### [题目链接](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) + +> https://leetcode.com/problems/binary-tree-level-order-traversal/description/ + +#### 题目 +![在这里插入图片描述](images/102_t.png) + +这个题目和 [LeetCode - 637](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20637.%20Average%20of%20Levels%20in%20Binary%20Tree(%E6%B1%82%E6%A0%91%E7%9A%84%E6%AF%8F%E4%B8%80%E5%B1%82%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC).md)基本一样,这个可以说更简单。 + +## 1、非递归 + +很容易想到的解法就是层次遍历,每次处理一层,**先得到队列中所有元素的个数,然后全部处理完,然后处理下一层**。  + +

+ +代码: + +```java +class Solution { + public List> levelOrder(TreeNode root) { + List> res = new ArrayList<>(); + if (root == null) + return res; + Queue queue = new LinkedList<>(); + queue.add(root); + while (!queue.isEmpty()) { + int n = queue.size(); + List temp = new ArrayList<>(); + for (int i = 0; i < n; i++) { + TreeNode top = queue.poll(); + temp.add(top.val); + if (top.left != null) + queue.add(top.left); + if (top.right != null) + queue.add(top.right); + } + res.add(temp); + } + return res; + } +} +``` + +## 2、递归 +先序: +* 也是要注意分为两种情况,第一种是现在所处的高度`level`是`>=`当前结果集`res`的大小的,此时要临时开辟一个`List`中间集来存储; +* 第二种情况就是当前所处高度`<`当前结果集`res`的`size`,此时就不需要开辟,因为之前已经有了。 + +图: + +

+ +代码: + +```java +class Solution { + public List> levelOrder(TreeNode root) { + List> res = new ArrayList<>(); + if (root == null) + return res; + pre(root, 0, res); + return res; + } + + // pre + private void pre(TreeNode node, int level, List> res) { + if (node == null) + return; + if (level >= res.size()) { // 下面的新的高度 + List temp = new ArrayList<>(); + temp.add(node.val); + res.add(temp); + } else { // 之前的 + res.get(level).add(node.val); + } + pre(node.left, level + 1, res); + pre(node.right, level + 1, res); + } +} +``` + +中序: + +中序遍历和前序遍历不同的是,先左子树走到底,然后回来根,然后再右子树,所以我们要预先给`list`中添加一个默认值。 + +```java +class Solution { + public List> levelOrder(TreeNode root) { + + List> res = new ArrayList<>(); + if (root == null) + return res; + in(root, 0, res); + return res; + } + + private void in(TreeNode node, int level, List> res) { + if (node == null) + return; + if (level >= res.size()) //直接都建出中间集 + res.add(new ArrayList<>()); + in(node.left, level + 1, res); + res.get(level).add(node.val); + in(node.right, level + 1, res); + } +} +``` +后序: + +```java +class Solution { + public List> levelOrder(TreeNode root) { + List> res = new ArrayList<>(); + if (root == null) + return res; + post(root, 0, res); + return res; + } + + private void post(TreeNode node, int level, List> res) { + if (node == null) + return; + if (level >= res.size()) + res.add(new ArrayList<>()); + post(node.left, level + 1, res); + post(node.right, level + 1, res); + res.get(level).add(node.val); + } +} +``` diff --git a/Algorithm/LeetCode/Tree/LeetCode-103.Binary Tree Zigzag Level Order Traversal.md b/Algorithm/LeetCode/Tree/LeetCode-103.Binary Tree Zigzag Level Order Traversal.md new file mode 100644 index 00000000..30975e24 --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-103.Binary Tree Zigzag Level Order Traversal.md @@ -0,0 +1,115 @@ +# LeetCode - 103. Binary Tree Zigzag Level Order Traversal + +#### [题目链接](https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/) + +> https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/ + +#### 题目 + +![1554705962248](assets/1554705962248.png) + +## 解析 + +非递归写法,有几种写法。 + +第一种,最简单的方法。直接在[LeetCode - 102](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20102.%20Binary%20Tree%20Level%20Order%20Traversal(%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86%E4%BF%9D%E5%AD%98).md)的基础上,把偶数层反转一下即可。 + +```java +class Solution { + + public List> zigzagLevelOrder(TreeNode root) { + List> res = new ArrayList<>(); + if (root == null) + return res; + Queue queue = new LinkedList<>(); + queue.add(root); + boolean ok = false; + ArrayList list = new ArrayList<>(); + while (!queue.isEmpty()) { + int n = queue.size(); + ArrayList tmp = new ArrayList<>(); + for (int i = 0; i < n; i++) { + TreeNode cur = queue.poll(); + tmp.add(cur.val); + if (cur.left != null) queue.add(cur.left); + if (cur.right != null) queue.add(cur.right); + } + if (ok) Collections.reverse(tmp); + ok = !ok; + res.add(tmp); + } + return res; + } +} +``` + +第二种,用到双向队列,或者自己维护一下是在左边添加还是在右边添加。 + +

+ +代码: + +```java +class Solution { + public List> zigzagLevelOrder(TreeNode root) { + List> res = new ArrayList<>(); + if (root == null) + return res; + LinkedList queue = new LinkedList<>(); + queue.add(root); + boolean ok = false; + while (!queue.isEmpty()) { + ArrayList tmp = new ArrayList<>(); + int n = queue.size(); + if (!ok) { // 从左往右 + for (int i = 0; i < n; i++) { + TreeNode cur = queue.removeFirst(); + tmp.add(cur.val); + if(cur.left != null) queue.addLast(cur.left); + if(cur.right != null) queue.addLast(cur.right); + } + } else { + for (int i = 0; i < n; i++) { //从右往左 + TreeNode cur = queue.removeLast(); + tmp.add(cur.val); + if(cur.right != null) queue.addFirst(cur.right);//从右往左要先添加右孩子 + if(cur.left != null) queue.addFirst(cur.left); + } + } + ok = !ok; + res.add(tmp); + } + return res; + } +} +``` + +递归写法。 + +和上一题[LeetCode - 102](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20102.%20Binary%20Tree%20Level%20Order%20Traversal(%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86%E4%BF%9D%E5%AD%98).md)的一样,只不过判断一下层数的奇偶即可。 + +```java +class Solution { + + List> res; + + public List> zigzagLevelOrder(TreeNode root) { + res = new ArrayList<>(); + if (root != null) pre(root, 0); + return res; + } + + public void pre(TreeNode node, int level) { + if (node == null) return; + if (level >= res.size()) + res.add(new ArrayList<>()); + if (level % 2 == 0) + res.get(level).add(node.val); + else + res.get(level).add(0, node.val); + pre(node.left, level + 1); + pre(node.right, level + 1); + } +} +``` + diff --git a/Algorithm/LeetCode/Tree/LeetCode-104.Maximum Depth of Binary Tree.md b/Algorithm/LeetCode/Tree/LeetCode-104.Maximum Depth of Binary Tree.md new file mode 100644 index 00000000..f0c501f0 --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-104.Maximum Depth of Binary Tree.md @@ -0,0 +1,67 @@ +# LeetCode - 104. Maximum Depth of Binary Tree + +#### [题目链接](https://leetcode.com/problems/maximum-depth-of-binary-tree/) + +> https://leetcode.com/problems/maximum-depth-of-binary-tree/ + +#### 题目 + +![1554707691045](assets/1554707691045.png) + +## 解析 + +递归的思路很简单。当前结点为根的树的高度 = 左右子树中高的那个 + 1 (自己)。 + +

+ +代码: + +```java +class Solution { + public int maxDepth(TreeNode root) { + if (root == null) + return 0; + return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); + } +} +``` + +方法二。 + +可以利用层次遍历。来求树的层数(高度)。 + +* 每一层的数量用一个变量`count`统计,总的层数用`depth`统计; +* 同时,我们在当前层的时候,可以得知下一层的节点的数量(通过`queue.size()`); +* 然后在到了下一层的时候, 就判断统计的数量`count == nextLevelSize`,如果等于,就加一层`depth++`; + +图: + +![1554708569102](assets/1554708569102.png) + +代码: + +```java +class Solution { + public int maxDepth(TreeNode root) { + if (root == null) + return 0; + Queue queue = new LinkedList<>(); + queue.add(root); + int count = 0, nextLevelSize = 1; + int depth = 0; + while (!queue.isEmpty()) { + TreeNode cur = queue.poll(); + count++; + if (cur.left != null) queue.add(cur.left); + if (cur.right != null) queue.add(cur.right); + if (count == nextLevelSize) { + count = 0; + depth++; + nextLevelSize = queue.size(); //下一层的节点的个数 + } + } + return depth; + } +} +``` + diff --git a/Algorithm/LeetCode/Tree/LeetCode-105.Construct Binary Tree from Preorder and Inorder Traversal.md b/Algorithm/LeetCode/Tree/LeetCode-105.Construct Binary Tree from Preorder and Inorder Traversal.md new file mode 100644 index 00000000..16995338 --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-105.Construct Binary Tree from Preorder and Inorder Traversal.md @@ -0,0 +1,43 @@ +# LeetCode - 105. Construct Binary Tree from Preorder and Inorder Traversal + +#### [题目链接](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) + +> https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ + +#### 题目 + +## 解析 + +思路: + + - 根据前序和中序建树时,**前序遍历的第一个结点就是根,在中序遍历中找到根所在的位置**,计算的左子树长度(左边孩子的个数`lLen`)(可以得出右子树的长度 = 总长度-左子树长度-1); + - 这样在中序遍历中就确定了根节点的位置,且在`pre`数组中`pre[pL+1, pL+lLen]`之间都是根节点的左孩子;在`in`数组中`in[iL, iL + lLen - 1]`位置也都是根节点的左孩子,利用这个重新递归构造根节点的左子树即可; + - 同理,在`pre`数组中`pre[pL + lLen + 1 , pR]`都是当前根节点的右孩子,在`in`数组中`in[iL + lLen + 1 , iR]`也都是当前根节点的右孩子,利用这两段重新构造根节点的右子树即可; + +> 注意根据**前序遍历和中序遍历,中序遍历和后续遍历都可以建立一颗二叉树**,**但是根据前序遍历和后续遍历不可以确定一颗二叉树**,前序和后序在本质上都只是**将子节点和父节点**分离,没有指明左右子树的能力。 + +图: + +

+ +代码: + +```java +class Solution { + public TreeNode buildTree(int[] preorder, int[] inorder) { + return rec(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); + } + + public TreeNode rec(int[] pre, int pL, int pR, int[] in, int iL, int iR) { + if (pL > pR || iL > iR) + return null; + TreeNode root = new TreeNode(pre[pL]); //根 + int lLen = 0; //左子树 数组长度 (在in数组中找到pre[pL](根)) + for (int i = iL; i <= iR && in[i] != pre[pL]; i++, lLen++) ; + root.left = rec(pre, pL + 1, pL + lLen, in, iL, iL + lLen - 1); //pre[pL]和in[iL + iLen]是根 + root.right = rec(pre, pL + lLen + 1, pR, in, iL + lLen + 1, iR); + return root; + } +} +``` + diff --git a/Algorithm/LeetCode/Tree/LeetCode-106.Construct Binary Tree from Inorder and Postorder Traversal.md b/Algorithm/LeetCode/Tree/LeetCode-106.Construct Binary Tree from Inorder and Postorder Traversal.md new file mode 100644 index 00000000..d93389ca --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-106.Construct Binary Tree from Inorder and Postorder Traversal.md @@ -0,0 +1,38 @@ +# LeetCode - 106. Construct Binary Tree from Inorder and Postorder Traversal + +#### [题目链接](https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) + +> https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/ + +#### 题目 + +![1554711156778](assets/1554711156778.png) + +## 解析 + +和上一题[LeetCode - 105. Construct Binary Tree from Preorder and Inorder Traversal](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20105.%20Construct%20Binary%20Tree%20from%20Preorder%20and%20Inorder%20Traversal.md)类似,我们只需要每次递归的时候在`in`数组中找到`post`数组的最后一个元素,然后递归建立左右子树即可。 + +图: + +![1554711504651](assets/1554711504651.png) + +代码: + +```java +class Solution { + public TreeNode buildTree(int[] inorder, int[] postorder) { + return rec(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1); + } + + public TreeNode rec(int[] in, int iL, int iR, int[] post, int poL, int poR) { + if (iL > iR || poL > poR) return null; + TreeNode root = new TreeNode(post[poR]);//最后一个是根 + int lLen = 0; // 左子树长度, 在in[]中找到 post[posR]的位置 + for (int i = iL; i <= iR && in[i] != post[poR]; i++, lLen++) ; + root.left = rec(in, iL, iL + lLen - 1, post, poL, poL + lLen - 1); + root.right = rec(in, iL + lLen + 1, iR, post, poL + lLen, poR - 1); + return root; + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-110.Balanced Binary Tree(\345\210\244\346\226\255\344\270\200\346\243\265\346\240\221\346\230\257\345\220\246\346\230\257\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221).md" "b/Algorithm/LeetCode/Tree/LeetCode-110.Balanced Binary Tree(\345\210\244\346\226\255\344\270\200\346\243\265\346\240\221\346\230\257\345\220\246\346\230\257\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221).md" new file mode 100644 index 00000000..fb985d47 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-110.Balanced Binary Tree(\345\210\244\346\226\255\344\270\200\346\243\265\346\240\221\346\230\257\345\220\246\346\230\257\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221).md" @@ -0,0 +1,167 @@ +## LeetCode - 110. Balanced Binary Tree(判断一棵树是否是平衡二叉树) + - O(N*logN)解法 + - O(n)解法 + +### [题目链接](https://leetcode.com/problems/balanced-binary-tree/description/) + +> https://leetcode.com/problems/balanced-binary-tree/description/ + +#### 题目 +![在这里插入图片描述](images/110_t.png) + +这个题目**剑指offer**中也出现过。 + +#### 解析 +### O(N*logN)解法 + * 首先我们知道平衡二叉树**是一棵空树或它的左右两个子树的高度差的绝对值不超过`1`,并且左右两个子树都是一棵平衡二叉树**; + * 我们可以使用一个获取树的高度的函数。然后递归比较左右子树是不是平衡二叉树且左右子树的高度不超过`1`即可。 + +![在这里插入图片描述](images/110_s.png) + +代码: + +```java +class Solution { + public boolean isBalanced(TreeNode root) { + if(root == null) + return true; + return isBalanced(root.left) && isBalanced(root.right) && Math.abs( height(root.left)-height(root.right) ) <= 1; + } + + private int height(TreeNode node){ + if(node == null) + return 0; + return Math.max(height(node.left),height(node.right)) + 1; + } +} +``` + +*** +### O(n)解法 + 所以假如我们要判断一个以`node`开头的结点是否为一颗平衡二叉树,则要满足以下几点 : + - 它的左子树要给它一个左子树本身是不是平衡二叉树的信息; + - 它的右子树要给它一个右子树本身是不是平衡二叉树的信息; + - 它的左子树要给它左子树高度的信息; + - 它的右子树要给它右子树高度的信息; + +所以我们知道上面的几点之后,可以完全按照上面写出一个递归结构函数,因为子树返回的信息中既包含高度信息,又包含子树是不是也是一颗平衡二叉树的信息,所以这里把这个信息封装成一个结构。 + +**这里和`O(n*logn)`方法不同的是,我们求的是一个结构体,递归函数同时返回了两个信息: 高度和子树是否平衡,如果不平衡,我们就不再判断直接`false`了)**。 + +```java +class Solution { + //返回两个值 高度和子树是否平衡 + private class ReturnData{ + public int h; + public boolean isB; + public ReturnData(int h,boolean isB){ + this.h = h; + this.isB = isB; + } + } + + public boolean isBalanced(TreeNode root) { + if(root == null) + return true; + return balanced(root).isB; + } + + private ReturnData balanced(TreeNode node){ + if(node == null){ + return new ReturnData(0,true); + } + ReturnData L = balanced(node.left); + if(!L.isB) + return new ReturnData(0,false); + ReturnData R = balanced(node.right); + if(!R.isB) + return new ReturnData(0,false); + return new ReturnData( Math.max(L.h,R.h)+1, Math.abs(L.h - R.h) <= 1 ); + } +} +``` + +如下面的树,其中的返回类型的变化,以及子树的遍历。 + +![1554714590525](assets/1554714590525.png) + +或者简化: + +```java +class Solution { + + public boolean isBalanced(TreeNode root) { + if(root == null) + return true; + boolean[] res = new boolean[1]; + res[0] = true; + height(root,res); + return res[0]; + } + + private int height(TreeNode node,boolean[] res){ + if(node == null) + return 0; + int LH = height(node.left,res); + if(!res[0]) + return 0; + int RH = height(node.right,res); + if(!res[0]) + return 0; + if(Math.abs(LH-RH) > 1) + res[0] = false; + return Math.max(LH,RH) + 1; + } +} +``` +或者写成全局变量: + +```java +class Solution { + + public boolean res; + + public boolean isBalanced(TreeNode root) { + if(root == null) + return true; + res = true; + height(root); + return res; + } + + private int height(TreeNode node){ + if(node == null) + return 0; + int LH = height(node.left); + if(!res) + return 0; + int RH = height(node.right); + if(!res) + return 0; + if(Math.abs(LH-RH) > 1) + res = false; + return Math.max(LH,RH) + 1; + } +} +``` + +再简化: + +```java +class Solution { + public boolean isBalanced(TreeNode root) { + if(root == null) + return true; + return height(root) > -1; + } + private int height(TreeNode node){ + if(node == null) + return 0; + int LH = height(node.left); + int RH = height(node.right); + if(Math.abs(LH-RH) > 1 || LH == -1 || RH == -1) + return -1; + return Math.max(LH,RH) + 1; + } +} +``` diff --git a/Algorithm/LeetCode/Tree/LeetCode-112.PathSum.md b/Algorithm/LeetCode/Tree/LeetCode-112.PathSum.md new file mode 100644 index 00000000..ed4ad135 --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-112.PathSum.md @@ -0,0 +1,151 @@ +# LeetCode - 112. PathSum + +#### [题目链接](https://leetcode.com/problems/path-sum/) + +> https://leetcode-cn.com/problems/path-sum/ + +#### 题目 +![在这里插入图片描述](images/112_t.png) +## 1、递归 + +两种写法,从`sum`的角度去看,可以从`sum`减,也可以从`0`加到`sum`。 + +![1554739372795](assets/1554739372795.png) + +第一种(减的思路): + +```java +class Solution { + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) + return false; + if (root.left == null && root.right == null) + return root.val == sum; + int newSum = sum - root.val; + return hasPathSum(root.left, newSum) || hasPathSum(root.right, newSum); + } +} +``` +第二种(加的思路): +```java +class Solution { + + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) + return false; + return helper(root, 0, sum); + } + + private boolean helper(TreeNode node, int curSum, int sum) { + if (node == null) + return false; + if (node.left == null && node.right == null) + return curSum + node.val == sum; + curSum += node.val; + return helper(node.left, curSum, sum) || helper(node.right, curSum, sum); + } +} +``` + +## 2、非递归 +非递归,如果只使用一个栈(或者不使用辅助结构)的话,需要修改原来的树的结构: + +```java +class Solution { + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) + return false; + Stack stack = new Stack<>(); + stack.push(root); + TreeNode cur = null; + while (!stack.isEmpty()) { + cur = stack.pop(); + + if (cur.left == null && cur.right == null && cur.val == sum) + return true; + + if (cur.right != null) { + cur.right.val += cur.val; + stack.push(cur.right); + } + + if (cur.left != null) { + cur.left.val += cur.val; + stack.push(cur.left); + } + } + return false; + } +} +``` +写法可以有很多种,也可以使用一个栈存数,这样就不要改变树的结构: + +```java +class Solution { + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) + return false; + Stack nodeStack = new Stack<>(); + Stack sumStack = new Stack<>();//存上到当前节点的数的值 + nodeStack.push(root); + sumStack.push(sum); + while (!nodeStack.isEmpty()) { + TreeNode curNode = nodeStack.pop(); + int curSum = sumStack.pop(); + + if (curNode.left == null && curNode.right == null && curNode.val == curSum) + return true; + + if (curNode.right != null) { + nodeStack.push(curNode.right); + sumStack.push(curSum - curNode.val); + } + + if (curNode.left != null) { + nodeStack.push(curNode.left); + sumStack.push(curSum - curNode.val); + } + } + return false; + } +} +``` + +这里也可以使用类似`BFS`(层序)的遍历: + +写法真的可以说是多种多样吧,层序使用队列即可,代码和`BFS`差别不大,但是我这里使用了另一个类,没有使 + +用多余的两个队列,也保证没有修改树的结构。 + +```java +class Solution { + + private class Comb { + public int curSum; + public TreeNode node; + + public Comb(TreeNode node, int curSum) { + this.node = node; + this.curSum = curSum; + } + } + + public boolean hasPathSum(TreeNode root, int sum) { + if (root == null) + return false; + Queue queue = new LinkedList<>(); + queue.add(new Comb(root, 0)); //注意对应的关系 + while (!queue.isEmpty()) { + Comb cur = queue.poll(); + if (cur.node.left == null && cur.node.right == null && cur.curSum + cur.node.val == sum) + return true; + if (cur.node.left != null) + queue.add(new Comb(cur.node.left, cur.curSum + cur.node.val)); + if (cur.node.right != null) + queue.add(new Comb(cur.node.right, cur.curSum + cur.node.val)); + } + return false; + } +} +``` + diff --git a/Algorithm/LeetCode/Tree/LeetCode-113.Path Sum II.md b/Algorithm/LeetCode/Tree/LeetCode-113.Path Sum II.md new file mode 100644 index 00000000..a73ae85f --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-113.Path Sum II.md @@ -0,0 +1,97 @@ +# LeetCode - 113. Path Sum II + +#### [题目链接](https://leetcode.com/problems/path-sum-ii/) + +> https://leetcode.com/problems/path-sum-ii/ + +#### 题目 +![在这里插入图片描述](images/113_t.png) +## 解析 + +递归,和上面唯一的不同,就是需要记录路径,递归写法很简单:  + + +* 就是每次先将当前节点加入中间集合(`path`),然后深度优先遍历; +* 遍历完记得回溯的时候要在`path`集合中移除当前节点; +* 注意递归条件哪里一定不要`return `; + +图: + +![1554740023967](assets/1554740023967.png) + +代码: + + +```java +class Solution { + + private List> res; + + public List> pathSum(TreeNode root, int sum) { + res = new ArrayList<>(); + if (root == null) return res; + helper(root, 0, sum, new ArrayList<>()); + return res; + } + + private void helper(TreeNode node, int curSum, int sum, List path) { + if (node == null) return; + path.add(node.val); + if (node.left == null && node.right == null && curSum + node.val == sum) { + // why do we need new arrayList here?if we are using the same path variable path + // path will be cleared after the traversal + res.add(new ArrayList<>(path)); + // return ; // can't do this + } + helper(node.left, curSum + node.val, sum, path); + helper(node.right, curSum + node.val, sum, path); + path.remove(path.size() - 1); + } +} +``` + +非递归写法: + +* 当前节点`cur`只要不为空,先走到树的最左边节点(第一个`while`循环); +* 然后取栈顶元素,但是此时还要继续判断栈顶的右孩子的左子树,此时不能`pop()`,因为有孩子还有可能也是有左子树的; +* `pre`节点的作用是为了回溯,记录前一个访问的节点,如果`cur.right == pre`,则说明右子树正在回溯,下面的已经访问完了; + +代码: + +```java +class Solution { + public List> pathSum(TreeNode root, int sum) { + List> res = new ArrayList<>(); + if (root == null) + return res; + + Stack stack = new Stack<>(); + ArrayList path = new ArrayList<>(); + TreeNode cur = root, pre = null; + int curSum = 0; + while (cur != null || !stack.isEmpty()) { + while (cur != null) { //先到最左边 + stack.push(cur); + curSum += cur.val; + path.add(cur.val); + cur = cur.left; + } + cur = stack.peek(); //此时cur = 最左边的没有左孩子的节点 + + //此时已经到了最左边,但是这个节点还是有可能有右孩子,且这个右孩子又有自己的左子树 + if (cur.right != null && cur.right != pre) { //有孩子不为空且没有被访问过 + cur = cur.right; + } else { // 右孩子为空 或者 已经访问过 此时先判断是否叶子 然后 开始回溯 + if (cur.left == null && cur.right == null && curSum == sum) + res.add(new ArrayList<>(path)); + stack.pop();//出栈 + pre = cur; // 更新pre + path.remove(path.size() - 1); + curSum -= cur.val; + cur = null;//把当前的节点置为空,然后继续从栈中取别的节点 + } + } + return res; + } +} +``` \ No newline at end of file diff --git a/Algorithm/LeetCode/Tree/LeetCode-124.Binary Tree Maximum Path Sum.md b/Algorithm/LeetCode/Tree/LeetCode-124.Binary Tree Maximum Path Sum.md new file mode 100644 index 00000000..b7ae24ed --- /dev/null +++ b/Algorithm/LeetCode/Tree/LeetCode-124.Binary Tree Maximum Path Sum.md @@ -0,0 +1,56 @@ +# LeetCode - 124. Binary Tree Maximum Path Sum + +#### [题目链接](https://leetcode.com/problems/binary-tree-maximum-path-sum/) + +> https://leetcode.com/problems/binary-tree-maximum-path-sum/ + +#### 题目 + +![1554770636440](assets/1554770636440.png) + +## 解析 + +也是很明显的递归题目。 + +* 当前递归的节点`node`,先求出左右子树的最大路径和。 +* 全局最大值的可能情况有很多。 + * 可能是当前节点和左右子树的最大路径和之和; + * 也有可能是左右子树其中一个最大和 和 当前节点的和; + * 也有可能就是自己节点本身就是最大的; +* 然后递归返回给上一层,有两种情况,一种是当左右子树中最大路径和**最大的那个**大于0的时候,就返回`max + node.val`,否则返回`node.val`; + + +图: + +![1554771306876](assets/1554771306876.png) + + +代码: + +```java +class Solution { + + private int res; // 需要用一个全局最大值更新 + + public int maxPathSum(TreeNode root) { + if (root == null) + return 0; + res = Integer.MIN_VALUE; + recur(root); + return res; + } + + public int recur(TreeNode node) { + if (node == null) + return 0; + int L = recur(node.left); + int R = recur(node.right); + res = Math.max(res, L + R + node.val); + res = Math.max(res, Math.max(L, R) + node.val); + res = Math.max(res, node.val); + int max = Math.max(L, R); + return max > 0 ? max + node.val : node.val; + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-145.Binary Tree Postorder Traversal(\345\256\236\347\216\260\345\220\216\345\272\217\351\201\215\345\216\206)(\344\270\211\347\247\215\351\235\236\351\200\222\345\275\222\346\226\271\345\274\217).md" "b/Algorithm/LeetCode/Tree/LeetCode-145.Binary Tree Postorder Traversal(\345\256\236\347\216\260\345\220\216\345\272\217\351\201\215\345\216\206)(\344\270\211\347\247\215\351\235\236\351\200\222\345\275\222\346\226\271\345\274\217).md" new file mode 100644 index 00000000..6ac2c8c1 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-145.Binary Tree Postorder Traversal(\345\256\236\347\216\260\345\220\216\345\272\217\351\201\215\345\216\206)(\344\270\211\347\247\215\351\235\236\351\200\222\345\275\222\346\226\271\345\274\217).md" @@ -0,0 +1,212 @@ +# LeetCode - 145. Binary Tree Postorder Traversal + +#### [题目链接](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) + +> https://leetcode.com/problems/binary-tree-postorder-traversal/description/ + +#### 题目 +![在这里插入图片描述](images/145_t.png) + +## 1、递归 + +第一种方法,递归。这个很简单。 +```cpp +class Solution { + + private List res; + + public List postorderTraversal(TreeNode root) { + res = new ArrayList<>(); + rec(root); + return res; + } + + public void rec(TreeNode root) { + if (root == null) + return; + rec(root.left); + rec(root.right); + res.add(root.val); + } +} +``` + +当然也可以使用类似函数式编程的方式: + +```java +class Solution { + public List postorderTraversal(TreeNode root) { + if (root == null) + return new ArrayList<>(); + List res = new ArrayList<>(); + List L = postorderTraversal(root.left); + List R = postorderTraversal(root.right); + res.addAll(L); // left + res.addAll(R); // right + res.add(root.val); // middle + return res; + } +} +``` +`C++`写法: + +```cpp +class Solution { +public: + vector postorderTraversal(TreeNode* root) { + if(!root) + return {}; + vectorres; + vectorl = postorderTraversal(root->left); + vectorr = postorderTraversal(root->right); + res.insert(res.end(), l.begin(), l.end()); + res.insert(res.end(), r.begin(), r.end()); + res.push_back(root->val); + return res; + } +}; +``` + +## 2、双栈法 + +思路: + + - 我们前序遍历是:`中->左->右`,所以压栈的顺序是 `右->左`; + - 我们可以实现遍历: `中->右->左`,所以压栈的顺序是 `左->右`; + - 利用上面这个 ,我们遍历的时候是`中->右->左`,但是我们不遍历,将**这个压入另一个栈,最后逆序回来,就能得到左->右->中**; + +图: + +

+ +注意: 本来后面还需要用一个栈来逆序的,但是由于`List`有一个`addFirst`方法,所以可以省掉一个栈。两个栈的可以看[这篇博客](https://blog.csdn.net/zxzxzx0119/article/details/79808127#t4)。 +```java +class Solution { + /** + * 前序非递归: 中左右 入栈: 右左 + * 我们可以实现 : 中右左 入栈: 左右(1) + * 后续非递归: 左右中 + * 上一个(1)位置该打印的时候不打印,压入到另一栈就是后续遍历 + */ + public List postorderTraversal(TreeNode root) { + LinkedList res = new LinkedList<>(); + if (root == null) + return res; + Stack stack = new Stack<>(); + stack.push(root); + TreeNode cur = null; + while (!stack.isEmpty()) { + cur = stack.pop(); + //下面这一步就是 reverse(print(root), visit(right), visit(left)) + res.addFirst(cur.val);//代替了另一个栈 + if (cur.left != null) + stack.push(cur.left); + if (cur.right != null) + stack.push(cur.right); + } + return res; + } +} +``` + +## 3、设置`pre`结点 + +思路: + + - 对于任一结点`p`,先将其入栈。若`p`不存在左孩子和右孩子,则可以直接访问它。或者`p`存在左孩子或者右孩子,但是左孩子和右孩子都已经被访问过了,则可以直接访问该结点。 + - 若非上述两种情况,则将右孩子和左孩子依次入栈。这样可以保证每次取栈顶元素时,左孩子在右孩子前面被访问,根结点在左孩子和右孩子访问之后被访问。 + +代码: + +```java +class Solution { + public List postorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) + return res; + Stack stack = new Stack<>(); + stack.push(root); + TreeNode cur = null, pre = null; + while (!stack.isEmpty()) { + cur = stack.peek(); // 不能直接pop + if ((cur.left == null && cur.right == null) || ((pre != null) && (pre == cur.left || pre == cur.right))) { + res.add(cur.val); + pre = cur; + stack.pop(); + } else { // otherwise, can't visis directly + if (cur.right != null) + stack.push(cur.right); + if (cur.left != null) + stack.push(cur.left); + } + } + return res; + } +} +``` + +## 4、morris遍历 + +`morris` 遍历看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B9%8BMorris%E9%81%8D%E5%8E%86.md)。 + +```java +class Solution { + //morris后续 + public List postorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) + return res; + TreeNode cur = root; + TreeNode mostRight = null; + + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { //第二次来的时候 + mostRight.right = null; + printEdge(cur.left, res); //打印左子树的右边界 + } + } + cur = cur.right; + } + printEdge(root, res); //最后打印整棵树的右边界 + return res; + } + + //打印边界 + private void printEdge(TreeNode head, List res) { + //先逆序边界 + TreeNode tail = reverseEdge(head); + //打印 + TreeNode cur = tail; + while (cur != null) { + res.add(cur.val); + cur = cur.right; + } + //再逆序回来 + reverseEdge(tail); + } + + //有点类似链表的逆序 + private TreeNode reverseEdge(TreeNode cur) { + TreeNode pre = null; + TreeNode next = null; + while (cur != null) { + next = cur.right;//先保存下一个 + cur.right = pre; + pre = cur; + cur = next; + } + return pre; + } +} +``` diff --git "a/Algorithm/LeetCode/Tree/LeetCode-222.Count Complete Tree Nodes(\347\273\237\350\256\241\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\347\273\223\347\202\271\344\270\252\346\225\260).md" "b/Algorithm/LeetCode/Tree/LeetCode-222.Count Complete Tree Nodes(\347\273\237\350\256\241\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\347\273\223\347\202\271\344\270\252\346\225\260).md" new file mode 100644 index 00000000..e2ca81f6 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-222.Count Complete Tree Nodes(\347\273\237\350\256\241\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221\347\232\204\347\273\223\347\202\271\344\270\252\346\225\260).md" @@ -0,0 +1,82 @@ +## LeetCode - 222. Count Complete Tree Nodes(统计完全二叉树的结点个数) +#### [题目链接](https://leetcode.com/problems/count-complete-tree-nodes/description/) + +> https://leetcode.com/problems/count-complete-tree-nodes/description/ + +#### 题目 +![在这里插入图片描述](images/222_t.png) + +### 解析 + 遍历整个二叉树的方法是不行的,因为时间复杂度要求小于`O(n)`。 + + 具体求法如下 : + + - 先求树的高度,也就是看树的最左结点能到哪一层,层数记为`h`。 + - 然后下面求解要分两种情况,都是递归过程,`node`表示当前结点,`level`表示当前结点所在的层数(根节点为第一层),`h`是整棵树的层数。 + +先看第一种情况 : 当`node`的右孩子的左边界到最后一层: + +![这里写图片描述](assets/1554865040622.png) + +再看第二种情况,这种情况是`node`的右子树没有来到最后一层 : + +![这里写图片描述](assets/1554865255792.png) + + +所以经过上面的分析 ,可以写出下面的代码 : + +```java +class Solution { + public int countNodes(TreeNode root) { + if(root == null) + return 0; + return ct(root,1,mostLeftLevel(root,1)); + } + + private int ct(TreeNode node, int level, int h) { + if(level == h) //叶子结点,只有一个结点 + return 1; + + if(mostLeftLevel(node.right,level + 1) == h) //如果右孩子的最左边界到了最后一层 + return (1 << (h-level)) + ct(node.right,level+1,h); + else + return (1 << (h-level-1)) + ct(node.left,level+1,h); + } + + //求某个结点最左边的层数 + private int mostLeftLevel(TreeNode node, int level) { + while(node != null){ + level++; + node = node.left; + } + return --level; + } +} +``` + +稍微改动一下,不用记录层数,用下面的写法意思是一样的 : + +```java +class Solution { + public int countNodes(TreeNode root) { + if(root == null) + return 0; + int LH = mostLeftLevel(root.left); //求出左结点 最左边高度 + int RH = mostLeftLevel(root.right); //求出右结点 最左边高度 + + if(LH == RH){ + return (1 << LH) + countNodes(root.right); + }else { //高度right = left - 1 + return (1 << RH) + countNodes(root.left); + } + } + + //求某个结点最左边的层数 递归 + private int mostLeftLevel(TreeNode node) { + if(node == null) + return 0; + return mostLeftLevel(node.left) + 1; + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-297.Serialize and Deserialize Binary Tree(\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226).md" "b/Algorithm/LeetCode/Tree/LeetCode-297.Serialize and Deserialize Binary Tree(\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226).md" new file mode 100644 index 00000000..87111d1a --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-297.Serialize and Deserialize Binary Tree(\344\272\214\345\217\211\346\240\221\347\232\204\345\272\217\345\210\227\345\214\226\345\222\214\345\217\215\345\272\217\345\210\227\345\214\226).md" @@ -0,0 +1,261 @@ +## LeetCode - 297. Serialize and Deserialize Binary Tree(二叉树的序列化和反序列化) + + - [LeetCode - 297. Serialize and Deserialize Binary Tree](#leetcode---297-serialize-and-deserialize-binary-tree) + - [LeetCode - 449. Serialize and Deserialize BST](#leetcode---449-serialize-and-deserialize-bst) +*** +### LeetCode - 297. Serialize and Deserialize Binary Tree +#### [题目链接](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) + +> https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/ + +#### 题意 +![在这里插入图片描述](images/297_t.png) + +当然上面这个是按层序列化,我们也可以按前序序列化,只要能通过序列化的结果还原二叉树即可。 + +### 解析 + **前序序列化,就是将当前的树按照前序的方式生成一个字符串,如果结点不为空,就是结点的`value`,如果结点为`null`,就用`”null“`来代替**。 + +逻辑很简单,直接上前序序列化代码 + +由于说最好不要使用全局变量,所以这里反序列化的时候使用了一个`idx`数组(大小为`1`)。 + +

+ +代码: + +```java +public class Codec { + + public String serialize(TreeNode root) { + StringBuilder res = new StringBuilder(); + serHelper(root, res); + res.deleteCharAt(res.length() - 1); // notice, delete the last + return res.toString(); + } + + public void serHelper(TreeNode root, StringBuilder sb) { + if (root == null) { + sb.append("null,"); + return; + } + sb.append(root.val + ","); + serHelper(root.left, sb); + serHelper(root.right, sb); + } + + public TreeNode deserialize(String data) { + if (data == null || data.length() == 0) + return null; + String[] arr = data.split(","); + int[] idx = new int[1]; + return desHelper(arr, idx); + } + + private TreeNode desHelper(String[] arr, int[] idx) { + if (idx[0] >= arr.length) + return null; + String val = arr[idx[0]]; + if (val.equals("null")) + return null; + TreeNode root = new TreeNode(Integer.valueOf(val)); + idx[0]++; + root.left = desHelper(arr, idx); + idx[0]++; + root.right = desHelper(arr, idx); + return root; + } +} +``` +或者使用一个存储容器: + +```java +public class Codec { + + public String serialize(TreeNode root) { + StringBuilder res = new StringBuilder(); + serHelper(root, res); + res.deleteCharAt(res.length() - 1); + return res.toString(); + } + + public void serHelper(TreeNode root, StringBuilder sb) { + if (root == null) { + sb.append("null,"); + return; + } + sb.append(root.val + ","); + serHelper(root.left, sb); + serHelper(root.right, sb); + } + + public TreeNode deserialize(String data) { + if (data == null || data.length() == 0) + return null; + LinkedList nodes = new LinkedList<>(Arrays.asList(data.split(",")));// 用Queue也行 + return desHelper(nodes); + } + + private TreeNode desHelper(LinkedList nodes) { + if (nodes.size() == 0) + return null; + String val = nodes.get(0); + nodes.removeFirst(); + if (val.equals("null")) + return null; + TreeNode root = new TreeNode(Integer.valueOf(val)); + root.left = desHelper(nodes); + root.right = desHelper(nodes); + return root; + } +} +``` +也可以使用层序序列化和层序反序列化。 + +其实和前序差不多,会层序遍历就差不多,一个逻辑。 + +```java +public class Codec { + + public String serialize(TreeNode root) { + StringBuilder res = serHelper(root); + res.deleteCharAt(res.length() - 1); + return res.toString(); + } + + /** level order */ + public StringBuilder serHelper(TreeNode root) { + StringBuilder res = new StringBuilder(); + if (root == null) { + res.append("null,"); + return res; + } + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + while (!queue.isEmpty()) { + top = queue.poll(); + if (top != null) { + res.append(top.val + ","); + queue.add(top.left); + queue.add(top.right); + } else { + res.append("null,"); + } + } + return res; + } + + public TreeNode deserialize(String data) { + String[] arr = data.split(","); + int idx = 0; + TreeNode root = recon(arr[idx++]); + if (root != null) { + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + while (!queue.isEmpty()) { + top = queue.poll(); + top.left = recon(arr[idx++]); + top.right = recon(arr[idx++]); + if (null != top.left) + queue.add(top.left); + if (null != top.right) + queue.add(top.right); + } + } + return root; + } + + private TreeNode recon(String str) { + return str.equals("null") ? null : new TreeNode(Integer.valueOf(str)); + } +} +``` +其中`serHelper`方法也可以这样写: + +```java +public StringBuilder serHelper(TreeNode root) { + StringBuilder res = new StringBuilder(); + if (root == null) { + res.append("null,"); + return res; + } + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + res.append(root.val + ","); //注意这样写的话 要先添加这个 + while (!queue.isEmpty()) { + top = queue.poll(); + if (top.left != null) { + queue.add(top.left); + res.append(top.left.val + ","); + } else + res.append("null,"); + if (top.right != null) { + queue.add(top.right); + res.append(top.right.val + ","); + } else + res.append("null,"); + } + return res; +} +``` +*** +### LeetCode - 449. Serialize and Deserialize BST + +#### [题目链接](https://leetcode.com/problems/serialize-and-deserialize-bst/) + +> https://leetcode.com/problems/serialize-and-deserialize-bst/ + +#### 题目 + +> 和上一个题目一样,但是给你的是一个二叉搜索树。 +#### 解析 + +唯一的不同就是可以在反序列化的时候可以利用`BST`的性质来优化这个过程。 + +* 例如下面的`7`去反序列话的时候,递归左子树的时候给一个限定,如果当前数值`>7`,就肯定不行,返回`null`; +* 同理递归右子树的时候,结点的值不能`>7`,否则返回`null`; + +图: + +![在这里插入图片描述](images/449_s.png) + +代码: + +```java +public class Codec { + + public String serialize(TreeNode root) { + if (root == null) + return ""; + StringBuilder sb = new StringBuilder(); + sb.append(root.val + ","); + sb.append(serialize(root.left)); + sb.append(serialize(root.right)); + return sb.toString(); + } + + public TreeNode deserialize(String data) { + if (data.equals("")) + return null; + LinkedList nodes = new LinkedList<>(Arrays.asList(data.split(","))); + return desHelper(nodes, Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + private TreeNode desHelper(LinkedList nodes, int L, int R) { + if (nodes.size() == 0) + return null; + int cur = Integer.parseInt(nodes.get(0)); + if (cur < L || cur > R) // not + return null; + TreeNode root = new TreeNode(cur); + nodes.removeFirst(); + root.left = desHelper(nodes, L, cur); + root.right = desHelper(nodes, cur, R); + return root; + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-404.Sum of Left Leaves(\345\267\246\345\217\266\345\255\220\347\273\223\347\202\271\344\271\213\345\222\214)(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" "b/Algorithm/LeetCode/Tree/LeetCode-404.Sum of Left Leaves(\345\267\246\345\217\266\345\255\220\347\273\223\347\202\271\344\271\213\345\222\214)(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" new file mode 100644 index 00000000..3e1b9cfa --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-404.Sum of Left Leaves(\345\267\246\345\217\266\345\255\220\347\273\223\347\202\271\344\271\213\345\222\214)(\351\200\222\345\275\222\345\222\214\351\235\236\351\200\222\345\275\222).md" @@ -0,0 +1,140 @@ +# LeetCode - 404. Sum of Left Leaves(左叶子结点之和)(递归和非递归) + - 递归 + - 非递归 + +#### [题目链接](https://leetcode.com/problems/sum-of-left-leaves/description/) + +> https://leetcode.com/problems/sum-of-left-leaves/description/ + +#### 题目 +![在这里插入图片描述](images/404_t.png) +*** +#### 递归 + - **当我们访问一个结点的时候,不是判断结点本身是不是左叶子结点(无法判断),而是去判断它的左孩子是不是左叶子结点**。 + - 而以一个结点为头的左叶子结点的数量是它本身能不能发现左叶子结点,以及它的左右孩子总共得到的左叶子结点的数量之和。 + +图: + +![这里写图片描述](assets/1554954837788.png) + +代码: + + +```java +class Solution { + public int sumOfLeftLeaves(TreeNode root) { + if (root == null) + return 0; + int sum = 0; + if (root.left != null && root.left.left == null && root.left.right == null) + sum = root.left.val;//检查左叶子结点 + return sum + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right); + } +} +``` +**如果一个结点它能检测到左叶子结点,那么它的左边一定只有一个,所以可以优化**。 +```java +class Solution { + public int sumOfLeftLeaves(TreeNode root) { + if (root == null) + return 0; + int sum = 0; + if (root.left != null) { + if (root.left.left == null && root.left.right == null) { + sum = root.left.val; + } else { + sum = sumOfLeftLeaves(root.left); + } + } + return sum + sumOfLeftLeaves(root.right); + } +} + +``` + +**上面的方法是统计以访问的结点出发检测下面有没有叶子结点,这个方法是检测自己本身是不是左叶子,方法就是递归的时候,左边的孩子用一个`isLeft`标记`true`,然后只要同时满足`isLeft && node.right == null && node.left == null` 就可以判断是左叶子**。 +```java +class Solution { + + private int sum; + + public int sumOfLeftLeaves(TreeNode root) { + if (root == null) return 0; + sum = 0; + rec(root, false); //根不是叶子 + return sum; + } + + public void rec(TreeNode root, boolean isLeaf) { + if (root == null) return; + if (root.left == null && root.right == null && isLeaf) { + sum += root.val; + return; + } + rec(root.left, true); + rec(root.right, false); + } +} + +``` +*** +### 非递归 +非递归只是把访问改成了非递归,判断左叶子的逻辑没有变化。 + +前序访问: +```java +class Solution { + + public int sumOfLeftLeaves(TreeNode root) { + if (root == null) + return 0; + int sum = 0; + Stack stack = new Stack<>(); + stack.push(root); + TreeNode cur; + while (!stack.isEmpty()) { + cur = stack.pop(); + if (cur.left == null && cur.right == null) + continue; + if (cur.left != null && cur.left.left == null && cur.left.right == null) + sum += cur.left.val; + if (cur.right != null) + stack.push(cur.right); + if (cur.left != null) + stack.push(cur.left); + } + return sum; + } +} + +``` +同样可以优化: +```java +class Solution { + + public int sumOfLeftLeaves(TreeNode root) { + if (root == null) return 0; + int sum = 0; + Stack stack = new Stack<>(); + stack.push(root); + TreeNode cur; + while (!stack.isEmpty()) { + cur = stack.pop(); + if (cur.right != null) { + if (cur.right.left != null || cur.right.right != null) { + stack.push(cur.right); + } + } + if (cur.left != null) { + if (cur.left.left == null && cur.left.right == null) { + sum += cur.left.val; + } else { + stack.push(cur.left); + } + } + } + return sum; + } +} + +``` diff --git "a/Algorithm/LeetCode/Tree/LeetCode-606.Construct String from Binary Tree(\346\240\271\346\215\256\344\272\214\345\217\211\346\240\221\347\224\237\346\210\220\345\255\227\347\254\246\344\270\262).md" "b/Algorithm/LeetCode/Tree/LeetCode-606.Construct String from Binary Tree(\346\240\271\346\215\256\344\272\214\345\217\211\346\240\221\347\224\237\346\210\220\345\255\227\347\254\246\344\270\262).md" new file mode 100644 index 00000000..86288287 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-606.Construct String from Binary Tree(\346\240\271\346\215\256\344\272\214\345\217\211\346\240\221\347\224\237\346\210\220\345\255\227\347\254\246\344\270\262).md" @@ -0,0 +1,143 @@ +# LeetCode - 606. Construct String from Binary Tree(根据二叉树生成字符串) +* 递归解法 +* 非递归解法 + +*** +#### [题目链接](https://leetcode.com/problems/construct-string-from-binary-tree/) + +> https://leetcode.com/problems/construct-string-from-binary-tree/ + +#### 题目 +![在这里插入图片描述](images/606_t.png) +## 1、递归解法 +注意这个题目的意思 + +* 如果结点`node`的左右子树为空,则可以省略空的括号`"()"`; +* 如果结点`node`的右子树为空,也可以省略右子树的括号`"()"`; +* **但是如果结点`node`的左子树为空就不能省略,因为如果省略就不能唯一的标识一颗二叉树** ; + +递归做法比较简单,具体如下: +* 对于当前结点,生成一个`String`类型的`res`变量,先加上自己结点的值; +* 如果左右子树都为`null`,直接返回`res`; +* 如果左子树不为空,则加上左子树,而且在加上左子树的时候,两边加上括号。如果左子树为空,则加上一对空的字符串; +* 右子树的如果不为空,就类似处理,**但是如果为空,不需要加上`"()"`**; + +图: + +![1555031050417](assets/1555031050417.png) + +代码: + +```java +class Solution { + public String tree2str(TreeNode t) { + if (t == null) + return ""; + String res = String.valueOf(t.val); + if (t.left == null && t.right == null) //左右子树都为空的话可以省略 + return res; + if (t.left != null) { + res += "("; + res += tree2str(t.left); + res += ")"; + } else { //注意左子树的括号不能省略 + res += "()"; + } + if (t.right != null) { + res += "("; + res += tree2str(t.right); + res += ")"; + } //else 右子树可以省略 + return res; + } +} +``` +显然使用`StringBuilder`可以加快速度。 +```java +class Solution { + + public String tree2str(TreeNode t) { + StringBuilder sb = new StringBuilder(); + helper(t, sb); + return sb.toString(); + } + + private void helper(TreeNode node, StringBuilder sb) { + if (node == null) { + sb.append(""); + return; + } + sb.append(String.valueOf(node.val)); + if (node.left == null && node.right == null)// case 1 + return; + if (node.right == null) {// case 2 + sb.append("("); + helper(node.left, sb); + sb.append(")"); + return; + } + // general case + sb.append("("); + helper(node.left, sb); + sb.append(")("); + helper(node.right, sb); + sb.append(")"); + } +} +``` + +## 2、非递归解法 +非递归写法稍微有一点特殊。 + +* 不能只是单纯的每次`pop()`掉栈里面的东西,因为后面还要加上一个右括号`")"`; + +* 要访问一个结点两次,这里使用一个类似`vis`数组的`set`集合来记录之前是否已经访问过了一次这个结点; + +* 如果是第一次访问就`append("(")`和结点的`val`,并且进行相应的子结点的判断; + +* 子结点判断还是类似递归的做法,如果是左右结点都为空,直接不需要处理。否则,因为是前序,压栈的时候先处理右子树,对于右子树,只要不空就压栈,因为右子树如果为空也不需要加上多余的`"()"`,而左子树需要特殊处理,因为左子树为空的时候,我们需要加上`"()"`; + +* 如果是第二次访问,我们就可以填上右括号了,然后那个结点也没用了,所以可以弹出来了; + +* **最后记得去除根结点对应的左右括号,然后返回结果**; + +代码: + +```java +class Solution { + public String tree2str(TreeNode t) { + if (t == null) + return ""; + StringBuilder sb = new StringBuilder(); + Stack stack = new Stack<>(); + HashSet set = new HashSet<>(); + stack.push(t); + TreeNode cur = null; + while (!stack.isEmpty()) { + cur = stack.peek(); + if (set.contains(cur)) { + sb.append(")");//第二次来到这里 + stack.pop(); + } else {// 第一次 !set.contains(cur) + set.add(cur);//设置已经访问了 + sb.append("(" + cur.val); + if (cur.left == null && cur.right == null) + continue; + //前序先处理右子树,后处理左子树 + + //right 这里注意如果right == null也不要额外的 sb.append("()"); 因为可以省略 + if (cur.right != null) + stack.push(cur.right); + //left + if (cur.left == null) + sb.append("()");//这个要加上 + else + stack.push(cur.left); + } + } + //最后要去除根结点的左右两个括号 + return sb.substring(1, sb.length() - 1).toString();//注意 substring(s,e)取的是[s,e)之间的字符串 + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-617.Merge Two Binary Trees(\345\220\210\345\271\266\344\270\244\346\243\265\344\272\214\345\217\211\346\240\221).md" "b/Algorithm/LeetCode/Tree/LeetCode-617.Merge Two Binary Trees(\345\220\210\345\271\266\344\270\244\346\243\265\344\272\214\345\217\211\346\240\221).md" new file mode 100644 index 00000000..264971b0 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-617.Merge Two Binary Trees(\345\220\210\345\271\266\344\270\244\346\243\265\344\272\214\345\217\211\346\240\221).md" @@ -0,0 +1,195 @@ +# LeetCode - 617. Merge Two Binary Trees(合并两棵二叉树) +* 递归 +* 递归优化(改变原有的二叉树结构) +* 非递归前序 +* BFS(层序) + +*** +#### [题目链接](https://leetcode.com/problems/merge-two-binary-trees/description/) + +> https://leetcode.com/problems/merge-two-binary-trees/description/ + +#### 题目 +![在这里插入图片描述](images/617_t.png) +### 递归 +递归的想法很简单,当前的结点的值是`t1.val + t2.val`,然后当前`root.left`和`right`去递归的求解。 +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + TreeNode root = new TreeNode(t1.val + t2.val); + root.left = mergeTrees(t1.left, t2.left); + root.right = mergeTrees(t1.right, t2.right); + return root; + } +} +``` +*** +### 递归优化(改变原有的二叉树结构) +这个优化在于我们不需要每次递归的时候都创建一个`TreeNode `对象(`Java`堆中): + +而是只声明一个`TreeNode`的引用(在栈中): +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + TreeNode root = t1; + root.val += t2.val; + root.left = mergeTrees(t1.left, t2.left); + root.right = mergeTrees(t1.right, t2.right); + return root; + } +} +``` +*** +### 非递归前序 +既然写出了递归的前序遍历,自然想到非递归的前序遍历,于是我一开始写出了下面的代码: + +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + Stack stack = new Stack<>();// 使用数组可以在栈中操作结点 + + //前序非递归处理 因为前序是 中 -> 左 -> 右,压栈的顺序就是 右 -> 左 + stack.add(new TreeNode[]{t1, t2}); + while (!stack.isEmpty()) { + TreeNode[] tops = stack.pop(); + if (tops[1] == null) + continue; + if (tops[0] == null) { + tops[0] = tops[1];//这里看似处理了t1,但是这个tops[0]本来是null,没有和它的父亲连接 + continue; + } + // tops[0] != null && tops[1] != null + tops[0].val += tops[1].val; + stack.add(new TreeNode[]{tops[0].right, tops[1].right}); + stack.add(new TreeNode[]{tops[0].left, tops[1].left}); + + } + return t1; + } +} +``` +但是上面显然是错误的。 +为什么呢,经过调试,发现虽然看似处理了当前`t1`为`null`的情况,但是当前`t1`却没有和它的父亲连接起来,也就是没有在树的体系结构中,看下图: + +![在这里插入图片描述](images/617_s.png) + +**所以处理的办法就是** + +* **当前的`t1`直接判断自己的`left`和`right`,如果为空,就设置成`t2`的结点**; +* **如果不为空,就加入栈中,进行前序非递归的步骤**; +* 这样的话,当前的`t1(tops[0])`一定不为`null`; + +代码: + +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + Stack stack = new Stack<>();// 使用数组可以在栈中操作结点 + + //前序非递归处理 因为前序是 中 -> 左 -> 右,压栈的顺序就是 右 -> 左 + stack.add(new TreeNode[]{t1, t2}); + while (!stack.isEmpty()) { + TreeNode[] tops = stack.pop(); + if (tops[1] == null) + continue; + //这里注意tops[0]一定不会 = null,因为下面判断了tops[0]的空值 + tops[0].val += tops[1].val; + // right + if (tops[0].right == null) + tops[0].right = tops[1].right; + else + stack.add(new TreeNode[]{tops[0].right, tops[1].right}); + //left + if (tops[0].left == null) + tops[0].left = tops[1].left; + else + stack.add(new TreeNode[]{tops[0].left, tops[1].left}); + + } + return t1; + } +} +``` +因为不一定非要先遍历左孩子,再右孩子(不一定需要按照前序),所以`left`和`right`颠倒也是可以的: + +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + Stack stack = new Stack<>();// 使用数组可以在栈中操作结点 + + //前序非递归处理 因为前序是 中 -> 左 -> 右,压栈的顺序就是 右 -> 左 + stack.add(new TreeNode[]{t1, t2}); + while (!stack.isEmpty()) { + TreeNode[] tops = stack.pop(); + if (tops[1] == null) + continue; + //这里注意tops[0]一定不会 = null,因为下面判断了tops[0]的空值 + tops[0].val += tops[1].val; + //left + if (tops[0].left == null) + tops[0].left = tops[1].left; + else stack.add(new TreeNode[]{tops[0].left, tops[1].left}); + // right + if (tops[0].right == null) + tops[0].right = tops[1].right; + else + stack.add(new TreeNode[]{tops[0].right, tops[1].right}); + } + return t1; + } +} +``` +*** +### BFS +这题当然也可以层序合并求解。 + +```java +class Solution { + public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) + return t2; + if (t2 == null) + return t1; + Queue queue = new LinkedList<>();// 使用数组可以在栈中操作结点 + queue.add(new TreeNode[]{t1, t2}); + while (!queue.isEmpty()) { + TreeNode[] tops = queue.poll(); + if (tops[1] == null) + continue; + tops[0].val += tops[1].val; + if (tops[0].left == null) + tops[0].left = tops[1].left; + else + queue.add(new TreeNode[]{tops[0].left, tops[1].left}); + if (tops[0].right == null) + tops[0].right = tops[1].right; + else + queue.add(new TreeNode[]{tops[0].right, tops[1].right}); + } + return t1; + } +} +``` + + + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-637.Average of Levels in Binary Tree(\346\261\202\346\240\221\347\232\204\346\257\217\344\270\200\345\261\202\347\232\204\345\271\263\345\235\207\345\200\274).md" "b/Algorithm/LeetCode/Tree/LeetCode-637.Average of Levels in Binary Tree(\346\261\202\346\240\221\347\232\204\346\257\217\344\270\200\345\261\202\347\232\204\345\271\263\345\235\207\345\200\274).md" new file mode 100644 index 00000000..abd2849d --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-637.Average of Levels in Binary Tree(\346\261\202\346\240\221\347\232\204\346\257\217\344\270\200\345\261\202\347\232\204\345\271\263\345\235\207\345\200\274).md" @@ -0,0 +1,151 @@ +## LeetCode - 637. Average of Levels in Binary Tree(求树的每一层的平均值) + - BFS(层次) + - DFS(前序和中序递归) +*** + #### [题目链接](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/) + +> https://leetcode.com/problems/average-of-levels-in-binary-tree/description/ + +#### 题目 +![在这里插入图片描述](images/637_t.png) + +### BFS(层次) +很容易想到的解法就是层次遍历,每次处理一层,先得到队列中所有元素的个数,然后全部处理完,然后处理下一层。 + +图: + +![这里写图片描述](images/637_s.png) + +代码: + +```java +class Solution { + public List averageOfLevels(TreeNode root) { + List res = new ArrayList<>(); + if (root == null) + return res; + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + double sum = 0; + while (!queue.isEmpty()) { + int n = queue.size(); + sum = 0; + for (int i = 0; i < n; i++) { + top = queue.poll(); + sum += top.val; + if (top.left != null) + queue.add(top.left); + if (top.right != null) + queue.add(top.right); + } + res.add(sum / n); + } + return res; + } +} +``` + +*** +### DFS(前序和中序递归) +递归的做法就是 记录一个层数`level`,用两个`List`保存和以及个数,每次遍历到这一层的时候: + + - 或者是这一层第一次来,那就加上第一次`val`,次数变为`1`; + - 或者不是第一次,不是第一层就累加这一层的和以及个数; + - **因为前序是先根,再左,再右,所以可以通过层数`level`和`list`的大写来判断上面的情况**; + +图: + +![这里写图片描述](images/637_s2.png) + +前序: + +```java +class Solution { + + public List averageOfLevels(TreeNode root) { + List res = new ArrayList<>(); + List sum = new ArrayList<>(); + List count = new ArrayList<>(); + + pre(root, 0, sum, count); + + for (int i = 0; i < sum.size(); i++) { + res.add(sum.get(i) / count.get(i)); + } + return res; + } + + private void pre(TreeNode root, int level, List sum, List count) { + if (root == null) return; + if (level < sum.size()) { //回去的 + sum.set(level, sum.get(level) + root.val); + count.set(level, count.get(level) + 1); + } else { //新的高度 + sum.add(1.0 * root.val); //添加第一个 + count.add(1); + } + pre(root.left, level + 1, sum, count); + pre(root.right, level + 1, sum, count); + } +} +``` +中序: + +中序遍历和前序遍历不同的是,先左子树走到底,然后回来根,然后再右子树,所以我们要预先给`list`中添加一个默认值,添加一个`0`即可。后序也是: + +```java +class Solution { + public List averageOfLevels(TreeNode root) { + List res = new ArrayList<>(); + List sum = new ArrayList<>(); + List count = new ArrayList<>(); + in(root, 0, sum, count); + for (int i = 0; i < sum.size(); i++) { + res.add(sum.get(i) / count.get(i)); + } + return res; + } + + private void in(TreeNode root, int level, List sum, List count) { + if (root == null) + return; + if (level >= sum.size()) { + sum.add(0.0); + count.add(0); + } + in(root.left, level + 1, sum, count); + sum.set(level, sum.get(level) + root.val); + count.set(level, count.get(level) + 1); + in(root.right, level + 1, sum, count); + } +} +``` +后序: + +```java +class Solution { + public List averageOfLevels(TreeNode root) { + List res = new ArrayList<>(); + List sum = new ArrayList<>(); + List count = new ArrayList<>(); + pos(root, 0, sum, count); + for (int i = 0; i < sum.size(); i++) { + res.add(sum.get(i) / count.get(i)); + } + return res; + } + + private void pos(TreeNode root, int level, List sum, List count) { + if (root == null) return; + if (level >= sum.size()) { + sum.add(0.0); + count.add(0); + } + pos(root.left, level + 1, sum, count); + pos(root.right, level + 1, sum, count); + sum.set(level, sum.get(level) + root.val); + count.set(level, count.get(level) + 1); + } +} +``` diff --git "a/Algorithm/LeetCode/Tree/LeetCode-654.Maximum Binary Tree(\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221).md" "b/Algorithm/LeetCode/Tree/LeetCode-654.Maximum Binary Tree(\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221).md" new file mode 100644 index 00000000..012f12d4 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-654.Maximum Binary Tree(\346\234\200\345\244\247\344\272\214\345\217\211\346\240\221).md" @@ -0,0 +1,198 @@ +## LeetCode - 654. Maximum Binary Tree(最大二叉树) +* 递归 +* 非递归 + +*** +#### [题目链接](https://leetcode.com/problems/maximum-binary-tree/) + +> https://leetcode.com/problems/maximum-binary-tree/ + +#### 题目 +![在这里插入图片描述](images/654_t.png) + + +### 递归 + +这个题目很经典,使用类似**分治**的思想,每次在数组的某个区间操作: +递归函数写法: + +* 先找出分界点,也就是这段区间`[L, R]`内的最大值`maxx`,以及它的索引`maxi`; +* 然后分治,这个最大值作为根,左右去递归,作为它的左右孩子即可; + +图: + +![在这里插入图片描述](images/654_s.png) + +代码: + +```java +class Solution { + public TreeNode constructMaximumBinaryTree(int[] nums) { + if(nums == null || nums.length == 0) + return null; + return helper(nums, 0, nums.length-1); + } + + private TreeNode helper(int[] nums, int L, int R){ + if(L > R) //notice section is [L, R] + return null; + int maxx = nums[L]; + int maxi = L; + for(int i = L+1; i <= R; i++){ + if(nums[i] > maxx){ + maxx = nums[i]; + maxi = i; + } + } + TreeNode root = new TreeNode(maxx); + root.left = helper(nums, L, maxi-1); + root.right = helper(nums, maxi+1, R); + return root; + } +} +``` + +*** +### 非递归 +递归的写法没有想出来,类似**单调栈**主要思想是维持一个栈,这个栈里面的元素是要从**栈底到栈顶保持递减**的: +过程: +* 扫描数组,将每个元素建立一个节点`cur`; +* 每次都要判断当前元素是否比栈顶元素大,**如果大,就要一直弹出元素**,同时,要将当前元素的左孩子设置成弹出的节点: `cur.left = stack.pop()`; +* 弹完栈之后,此时栈中的元素都比cur大,此时我们让栈顶的节点的右孩子指向`cur`; +* 然后压栈当前元素; +* 最后返回的是栈底的元素(最大的元素作为根); + +图: + +![在这里插入图片描述](images/654_s2.png) + +代码: + +```java +class Solution { + public TreeNode constructMaximumBinaryTree(int[] nums) { + if(nums == null || nums.length == 0) + return null; + Stackstack = new Stack<>(); + for(int i = 0; i < nums.length; i++){ + TreeNode cur = new TreeNode(nums[i]); + while(!stack.isEmpty() && stack.peek().val < nums[i]) + cur.left = stack.pop(); + if(!stack.isEmpty()) + stack.peek().right = cur; + stack.push(cur); + } + if(stack.isEmpty()) + return null; + while(1 != stack.size()) + stack.pop(); + return stack.peek(); + } +} +``` +可以改成双端队列`Deque`写法: + +```java +class Solution { + public TreeNode constructMaximumBinaryTree(int[] nums) { + if(nums == null || nums.length == 0) + return null; + Dequestack = new LinkedList<>(); + for(int i = 0; i < nums.length; i++){ + TreeNode cur = new TreeNode(nums[i]); + while(!stack.isEmpty() && stack.peek().val < nums[i]) + cur.left = stack.pop(); + if(!stack.isEmpty()) + stack.peek().right = cur; + stack.push(cur); + } + return stack.isEmpty() ? null : stack.removeLast(); + } +} +``` + +*** +其他代码: + +`C++` + +```cpp +class Solution { +public: + TreeNode* constructMaximumBinaryTree(vector& nums) { + return helper(nums, 0, nums.size());// not nums.size()-1 + } +private: + TreeNode* helper(const vector& nums, int L, int R){ + if(L >= R) // because below is compute(calculate) section of [L, R), instead [L, R] + return nullptr; + auto it = std::max_element(nums.begin() + L, nums.begin() + R);//computer [L,R)'s max value, return position iterator; + TreeNode* root = new TreeNode(*it); // it is a Iterator + int idx = it - nums.begin(); + + root->left = helper(nums, L , idx); //idx postition will not figure in + root->right = helper(nums, idx+1, R); + return root; + } +}; +``` + +注意这个和`Java`的相反,使用的`vector`,这里从后面操作,最后返回的是第一个元素,道理都差不多。 +```cpp +class Solution { +public: + TreeNode* constructMaximumBinaryTree(vector& nums) { + if(nums.size() == 0) + return nullptr; + vectorstack; + for(int i = 0; i < nums.size(); i++){ + TreeNode* cur = new TreeNode(nums[i]); + while(!stack.empty() && stack.back()->val < nums[i]){ + cur->left = stack.back(); + stack.pop_back(); //pop last element + } + if(!stack.empty()) + stack.back()->right = cur; + stack.push_back(cur); + } + return stack.empty() ? nullptr : stack.front(); + } +}; +``` + +`Python`: + +```python +class Solution: + def constructMaximumBinaryTree(self, nums): + if len(nums) == 0: + return None + return self.helper(nums, 0, len(nums) - 1) + + def helper(self, nums, L, R): + if L > R: + return None + maxi = nums.index(max(nums[L:R + 1])) # max(list)'s section is [L, R), so is R+1 + root = TreeNode(nums[maxi]) + root.left = self.helper(nums, L, maxi-1) + root.right = self.helper(nums, maxi+1, R) + return root +``` + +```python +class Solution: + def constructMaximumBinaryTree(self, nums): + if len(nums) == 0: + return None + stack = [] # a simple list to be a stack + for i in range(len(nums)): + cur = TreeNode(nums[i]) + while stack and stack[-1].val < nums[i]: + cur.left = stack.pop() + if stack: + stack[-1].right = cur + stack.append(cur) + return None if not stack[0] else stack[0] +``` + + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-655.Print Binary Tree(\346\214\211\347\205\247\345\255\227\347\254\246\347\237\251\351\230\265\347\232\204\345\275\242\345\274\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221)(\344\272\214\345\210\206\345\222\214\351\200\222\345\275\222).md" "b/Algorithm/LeetCode/Tree/LeetCode-655.Print Binary Tree(\346\214\211\347\205\247\345\255\227\347\254\246\347\237\251\351\230\265\347\232\204\345\275\242\345\274\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221)(\344\272\214\345\210\206\345\222\214\351\200\222\345\275\222).md" new file mode 100644 index 00000000..e43982ed --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-655.Print Binary Tree(\346\214\211\347\205\247\345\255\227\347\254\246\347\237\251\351\230\265\347\232\204\345\275\242\345\274\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221)(\344\272\214\345\210\206\345\222\214\351\200\222\345\275\222).md" @@ -0,0 +1,84 @@ +## LeetCode - 655. Print Binary Tree(按照字符矩阵的形式打印二叉树)(二分和递归) +#### [题目链接](https://leetcode.com/problems/print-binary-tree/description/) + +> https://leetcode.com/problems/print-binary-tree/description/ + +#### 题目 +![在这里插入图片描述](images/655_t.png) +#### 解析 +找出对应的下标,然后二分递归遍历填充,先求出高度`h`,然后求出宽度为**w = 2h-1**,然后填充一个`h`行`w`列的字符矩阵即可,上下递归和左右二分夹杂在一起的感觉。具体看下图: + +图: + +![这里写图片描述](images/655_s.png) + +图: + +```java +class Solution { + + private List> res; + + public List> printTree(TreeNode root) { + res = new ArrayList<>(); + int h = height(root); + int w = (1 << h) - 1; + List temp = new ArrayList<>(); + for (int i = 0; i < w; i++) temp.add(""); + for (int i = 0; i < h; i++) + res.add(new ArrayList<>(temp)); //这个不能直接写成temp必须要写成new ArrayList + rec(root, 0, 0, w - 1); + return res; + } + + public void rec(TreeNode root, int level, int l, int r) { + if (root == null) return; + int m = l + (r - l) / 2; + res.get(level).set(m, String.valueOf(root.val)); + rec(root.left, level + 1, l, m - 1); + rec(root.right, level + 1, m + 1, r); + } + + public int height(TreeNode root) { + if (root == null) + return 0; + return Math.max(height(root.left), height(root.right)) + 1; + } +} +``` +或者使用二维字符矩阵: +```java +class Solution { + + private String[][] str; + + public List> printTree(TreeNode root) { + int height = height(root); + str = new String[height][(1 << height) - 1]; + for (String[] arr : str) + Arrays.fill(arr, ""); + + rec(root, 0, 0, str[0].length); + + List> res = new ArrayList<>(); + for (String[] arr : str) + res.add(Arrays.asList(arr)); //asList()将一个数组转换成容器 + return res; + } + + public void rec(TreeNode root, int level, int l, int r) { + if (root == null) + return; + int m = l + (r - l) / 2; + str[level][m] = "" + root.val; + rec(root.left, level + 1, l, m - 1); + rec(root.right, level + 1, m + 1, r); + } + + public int height(TreeNode root) { + if (root == null) + return 0; + return Math.max(height(root.left), height(root.right)) + 1; + } +} +``` diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/LeetCode - 669. Trim a Binary Search Tree(\345\210\240\351\231\244\346\220\234\347\264\242\346\240\221\344\270\255\344\270\215\345\234\250[L\357\274\214R]\350\214\203\345\233\264\345\206\205\347\232\204\350\212\202\347\202\271).md" "b/Algorithm/LeetCode/Tree/LeetCode-669.Trim a Binary Search Tree(\345\210\240\351\231\244\346\220\234\347\264\242\346\240\221\344\270\255\344\270\215\345\234\250[L\357\274\214R]\350\214\203\345\233\264\345\206\205\347\232\204\350\212\202\347\202\271).md" similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/LeetCode - 669. Trim a Binary Search Tree(\345\210\240\351\231\244\346\220\234\347\264\242\346\240\221\344\270\255\344\270\215\345\234\250[L\357\274\214R]\350\214\203\345\233\264\345\206\205\347\232\204\350\212\202\347\202\271).md" rename to "Algorithm/LeetCode/Tree/LeetCode-669.Trim a Binary Search Tree(\345\210\240\351\231\244\346\220\234\347\264\242\346\240\221\344\270\255\344\270\215\345\234\250[L\357\274\214R]\350\214\203\345\233\264\345\206\205\347\232\204\350\212\202\347\202\271).md" diff --git "a/Algorithm/LeetCode/Tree/LeetCode-671.Second Minimum Node In a Binary Tree(\345\257\273\346\211\276\344\272\214\345\217\211\346\240\221\344\270\255\347\254\254\344\272\214\345\260\217\347\232\204\347\273\223\347\202\271).md" "b/Algorithm/LeetCode/Tree/LeetCode-671.Second Minimum Node In a Binary Tree(\345\257\273\346\211\276\344\272\214\345\217\211\346\240\221\344\270\255\347\254\254\344\272\214\345\260\217\347\232\204\347\273\223\347\202\271).md" new file mode 100644 index 00000000..a0cfb099 --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-671.Second Minimum Node In a Binary Tree(\345\257\273\346\211\276\344\272\214\345\217\211\346\240\221\344\270\255\347\254\254\344\272\214\345\260\217\347\232\204\347\273\223\347\202\271).md" @@ -0,0 +1,187 @@ +# LeetCode - 671. Second Minimum Node In a Binary Tree(寻找二叉树中第二小的结点) +* 非递归 +* 递归 + +*** +#### [题目链接](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/) + +> https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/ + +#### 题目 +![在这里插入图片描述](images/671_t.png) + + +### 1、非递归 + +* 使用一个变量`min`来记录比`root.val`大的数,而且这个数将会是这些比`root.val`大的数中的最小的数; +* 直接使用`BFS`遍历即可,但是这个题目有个性质,这可以让我们优化这个题目,**即任意一个结点的孩子结点都不会小于这个结点,所以,当我们取到某个结点,这个结点比`root.val`大的时候,我们不需要将这个结点加入到队列中并去遍历它的孩子结点了,而是直接跳过即可**。 + +图: + +![在这里插入图片描述](images/671_s.png) + + +`Java`代码: + +```java +class Solution { + public int findSecondMinimumValue(TreeNode root) { + if(root == null) + return -1; + Queuequeue = new LinkedList<>(); + queue.add(root); + int min = Integer.MAX_VALUE; + while(!queue.isEmpty()){ + TreeNode top = queue.poll(); + if(top.val > root.val && top.val < min){ + min = top.val; + continue; // Optimization,Needn't to add sub-node to queue + } + if(top.left == null) // special tree + continue; + queue.add(top.left); + queue.add(top.right); + } + if(min != Integer.MAX_VALUE) + return min; + return -1; + } +} +``` + +*** +## 2、递归 + +* 递归的方式也很简单,先判断边界条件,然后当当前遍历结点`node`, 如果当前`node.val > root.val `直接返回`node.val`; +* 否则,先递归求出左右子树的答案,返回的是左右子树中的最小的结果(如果只有一边满足的话,就返回这一边); + + +`Java`代码: + + +```java +class Solution { + public int findSecondMinimumValue(TreeNode root) { + return dfs(root, root.val); + } + private int dfs(TreeNode node,int rootVal){ + if(node == null) + return -1; + if(node.val > rootVal) + return node.val; //Needn't to visit sub-node, itself is the second-largest + int L = dfs(node.left, rootVal); + int R = dfs(node.right,rootVal); + if(L == -1) + return R; + if(R == -1) + return L; + return Math.min(L,R); + } +} +``` + +*** +其他代码: + +`C++`: + +```cpp +class Solution { +public: + int findSecondMinimumValue(TreeNode* root) { + if(!root) + return -1; + queueq; + int min = INT_MAX; // second-min + q.push(root); + while(!q.empty()){ + TreeNode* now = q.front(); + q.pop(); + if(now->val > root->val && now->val < min){ + min = now->val; + continue; + } + if(!now->left) + continue; + q.push(now->left); + q.push(now->right); + } + if(min == INT_MAX) + return -1; + return min; + } +}; + +``` + +```cpp +class Solution { +public: + int findSecondMinimumValue(TreeNode* root) { + return dfs(root, root->val); + } +private: + int dfs(TreeNode* root, int rootVal){ + if(!root) + return -1; + if(root->val > rootVal) + return root->val; + int L = dfs(root->left, rootVal); + int R = dfs(root->right, rootVal); + if(L == -1) + return R; + if(R == -1) + return L; + return min(L, R); // maybe -1 + } +}; +``` + + +`Python`: + +```python +from queue import Queue + +class Solution: + def findSecondMinimumValue(self, root): + if root is None: + return -1 + q = Queue() + q.put(root) + minn = float('inf') + while not q.empty(): + node = q.get() + if root.val < node.val < minn: + minn = node.val + continue + if not node.left: + continue + q.put(node.left) + q.put(node.right) + if minn == float('inf'): + return -1 + return minn + +``` + +```python +class Solution: + + def findSecondMinimumValue(self, root): + return self.dfs(root, root.val) + + def dfs(self, node, root_val): + if not node: + return -1 + if node.val > root_val: + return node.val + l_res = self.dfs(node.left, root_val) + r_res = self.dfs(node.right, root_val) + if l_res == -1: + return r_res + if r_res == -1: + return l_res + return min(l_res, r_res) +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-687.Longest Univalue Path (\346\240\221\347\232\204\346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204).md" "b/Algorithm/LeetCode/Tree/LeetCode-687.Longest Univalue Path (\346\240\221\347\232\204\346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204).md" new file mode 100644 index 00000000..543d7efc --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-687.Longest Univalue Path (\346\240\221\347\232\204\346\234\200\351\225\277\345\220\214\345\200\274\350\267\257\345\276\204).md" @@ -0,0 +1,241 @@ +# LeetCode - 687. Longest Univalue Path (树的最长同值路径) + +这三个题目有点类似,就放在一起了。 + +* [LeetCode - 687. Longest Univalue Path](#leetcode---687-longest-univalue-path) +* [LeetCode - 543. Diameter of Binary Tree](#leetcode---543-diameter-of-binary-tree) +* [LeetCode - 124. Binary Tree Maximum Path Sum](#leetcode---124-binary-tree-maximum-path-sum) + +## LeetCode - 687. Longest Univalue Path + +#### [题目链接](https://leetcode.com/problems/longest-univalue-path/) + +> https://leetcode.com/problems/longest-univalue-path/ + +#### 题目 +![在这里插入图片描述](images/687_t.png) + + +#### 解析 + +这个题目虽然不难,但是一开始还是没有注意细节: + +* 一开始递归函数就是考虑先递归求出左右孩子的最长同值路径; +* 然后如果左孩子和右孩子和自己相同,就更新当前节点返回的最长同值路径; +* 最终答案就是返回以根节点的最长同值路径; + +但是这种思路对于部分情况是错误的,见下图,因为这里的路径是**类似只能一笔画,而不能往回走**。所以我们需要在递归的时候记录最长路径,而返回的时候只能返回左右孩子的其中一个。 + + +即递归函数需要改变定义: `recur(node)`,返回的是**当前最长连续的同值路径** + + +* 如果当前节点为空或者没有左右孩子,就返回`0`; +* 如果当前节点只有左孩子,如果左孩子的值`node.left.val`等于当前节点的值`node.val`,就先更新最大值,然后返回`L + 1`,如果不等于,就返回`0`; +* 如果当前节点只有右孩子,如果右孩子的值`node.right.val`等于当前节点的值`node.val`,就先更新最大值,然后返回`R + 1`,如果不等于,就返回`0`; +* 否则就是左右孩子都全,如果左右孩子的值都等于`node.val`,就要更新`res = Math.max(res, L + R + 2)`,但是返回的是`Math.max(L, R) + 1`,其他情况和上面类似,具体看代码。 + +图: + +![在这里插入图片描述](images/687_s.png) + +**一开始的错误的代码**: +```java +class Solution { + public int longestUnivaluePath(TreeNode root) { + return recur(root); + } + + private int recur(TreeNode node){ + if(node == null) + return 0; + if(node.left == null && node.right == null) + return 0; + if(node.left == null){ + int R = recur(node.right); + if(node.val == node.right.val) + return 1 + R; + else + return R; + } + if(node.right == null){ + int L = recur(node.left); + if(node.val == node.left.val) + return 1 + L; + else + return L; + } + int L = recur(node.left); + int R = recur(node.right); + if(node.val == node.left.val && node.val == node.right.val) + return 2 + L + R; + else if(node.val == node.left.val && node.val != node.right.val) + return Math.max(R, L + 1); + else if(node.val != node.left.val && node.val == node.right.val) + return Math.max(L, R + 1); + else + return Math.max(L, R); + } +} +``` + + +**修正**之后的代码: +```java +class Solution { + + private int res; + + public int longestUnivaluePath(TreeNode root) { + recur(root); + return res; + } + + private int recur(TreeNode node){ + if(node == null) + return 0; + if(node.left == null && node.right == null) + return 0; + int L = recur(node.left); + int R = recur(node.right); + if(node.left == null){ // means node.right != null + if(node.val == node.right.val){ + res = Math.max(res, R + 1); + return R + 1; + } + else + return 0; + } + if(node.right == null){ //means node.left != null + if(node.val == node.left.val){ + res = Math.max(res, L + 1); + return L + 1; + } + else + return 0; + } + if(node.val == node.left.val && node.val == node.right.val){ + res = Math.max(res, L + R + 2); + return Math.max(L, R) + 1; //只能返回其中一个的 + }else if(node.val == node.left.val && node.val != node.right.val){ + res = Math.max(res, L + 1); + return L + 1; + }else if(node.val != node.left.val && node.val == node.right.val){ + res = Math.max(res, R + 1); + return R + 1; + }else { + return 0; + } + } +} +``` + +简写的代码: + +```java +class Solution { + + private int res; + + public int longestUnivaluePath(TreeNode root) { + recur(root); + return res; + } + + private int recur(TreeNode node){ + if(node == null) + return 0; + int L = recur(node.left); + int R = recur(node.right); + int nL = 0, nR = 0; + if(node.left != null && node.val == node.left.val) + nL = L + 1; + if(node.right != null && node.val == node.right.val) + nR = R + 1; + res = Math.max(res, nL + nR); + return Math.max(nL, nR); + } +} +``` + +*** + +## LeetCode - 543. Diameter of Binary Tree + +附: [**LeetCode-543. Diameter of Binary Tree**](https://leetcode.com/problems/diameter-of-binary-tree/)是这个题目的弱化版本。 + +![在这里插入图片描述](images/543_t.png) + +代码: + +```java +class Solution { + + private int res; + + private int recur(TreeNode node){ + if(node == null) + return 0; + int L = recur(node.left); + int R = recur(node.right); + if(node.left != null && node.right != null){ + res = Math.max(res, 2 + L + R); + return Math.max(L, R) + 1; + }else if(node.left != null && node.right == null){ + res = Math.max(res, 1 + L); + return 1 + L; + }else if(node.left == null && node.right != null){ + res = Math.max(res, 1 + R); + return 1 + R; + }else + return 0; + } + + public int diameterOfBinaryTree(TreeNode root) { + if(root == null) + return 0; + recur(root); + return res; + } +} +``` + +*** +## LeetCode - 124. Binary Tree Maximum Path Sum + +附: [**LeetCode - 124. Binary Tree Maximum Path Sum**](https://leetcode.com/problems/binary-tree-maximum-path-sum/)也是类似的题目。 + +![在这里插入图片描述](images/124_t.png) + +代码: + +```java +class Solution { + + private int res; + + public int maxPathSum(TreeNode root) { + if(root == null) + return 0; + res = Integer.MIN_VALUE; + recur(root); + return res; + } + + public int recur(TreeNode node){ + if(node == null) + return 0; + int L = recur(node.left); + int R = recur(node.right); + res = Math.max(res, L + R + node.val); + res = Math.max(res, Math.max(L, R) + node.val); + res = Math.max(res, node.val); + int maxx = Math.max(L, R); + if(maxx > 0) + return maxx + node.val; + else + return node.val; + } +} +``` + diff --git "a/Algorithm/LeetCode/Tree/LeetCode-98.Validate Binary Search Tree(\345\210\244\346\226\255\346\230\257\344\270\215\346\230\257\344\270\200\351\242\227\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\273\245\345\217\212\345\210\244\346\226\255\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221).md" "b/Algorithm/LeetCode/Tree/LeetCode-98.Validate Binary Search Tree(\345\210\244\346\226\255\346\230\257\344\270\215\346\230\257\344\270\200\351\242\227\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\273\245\345\217\212\345\210\244\346\226\255\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221).md" new file mode 100644 index 00000000..a0f5477c --- /dev/null +++ "b/Algorithm/LeetCode/Tree/LeetCode-98.Validate Binary Search Tree(\345\210\244\346\226\255\346\230\257\344\270\215\346\230\257\344\270\200\351\242\227\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\273\245\345\217\212\345\210\244\346\226\255\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221).md" @@ -0,0 +1,345 @@ +# LeetCode - 98. Validate Binary Search Tree(判断是不是一颗二叉搜索树以及判断完全二叉树) + + - LeetCode - 98. Validate Binary Search Tree (判断一颗二叉树是不是一颗二叉搜索树) + - 判断一颗二叉树是不是一颗完全二叉树 + - 完整测试代码 + +*** + + +## 1、LeetCode - 98. Validate Binary Search Tree (判断一颗二叉树是不是一颗二叉搜索树) +#### [题目链接](https://leetcode.com/problems/validate-binary-search-tree/) + +> https://leetcode.com/problems/validate-binary-search-tree/ + +#### 题目 + +![在这里插入图片描述](images/98_t.png) + + 首先要知道什么是二叉搜索树(二叉排序树)(二叉查找树) : + + - 任意节点的左子树不空,则左子树上**所有**结点的值均小于它的根结点的值; + - 任意节点的右子树不空,则右子树上**所有**结点的值均大于它的根结点的值; + - 任意节点的左、右子树也分别为二叉搜索树; + - 一般没有键值相等的节点。 + +例如下面的树就是一颗二叉搜索树 : + +![1554693055382](assets/1554693055382.png) + +怎么判断一颗二叉树是不是搜索二叉树呢? 其实很简单,只要这颗二叉树的**中序遍历的顺序是升序的**,那么就是一颗二叉搜索树,因为中序遍历的顺序是 **左->中->右** ,所以当中序遍历升序的时候,就有**左<中<右**,所以就可以判断。 + +```java +class Solution { + public boolean isValidBST(TreeNode root) { + if (root == null) return true; + Stack stack = new Stack<>(); + TreeNode cur = root; + TreeNode pre = null; + while (!stack.isEmpty() || cur != null) { + if (cur != null) { + stack.push(cur); + cur = cur.left; + } else { + cur = stack.pop(); + if (pre != null && cur.val <= pre.val) + return false; + pre = cur; + cur = cur.right; + } + } + return true; + } +} +``` + +代码中使用的是非递归的中序遍历,不懂的可以看[**这个博客**](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%84%E7%A7%8D%E6%93%8D%E4%BD%9C(%E9%80%92%E5%BD%92%E5%92%8C%E9%9D%9E%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86%2C%E6%A0%91%E6%B7%B1%E5%BA%A6%2C%E7%BB%93%E7%82%B9%E4%B8%AA%E6%95%B0%E7%AD%89%E7%AD%89).md#1%E9%80%92%E5%BD%92%E4%B8%AD%E5%BA%8F)。 + +当然也可以使用中序的递归,记录一个全局的`pre`变量: + +```java +class Solution { + private TreeNode pre; + public boolean isValidBST(TreeNode root) { + if(root == null) + return true; + pre = null; + return inOrder(root); + } + + private boolean inOrder(TreeNode node){ + if(node == null) + return true; + if(!inOrder(node.left)) + return false; + if(pre != null && node.val <= pre.val) + return false; + pre = node; + if(!inOrder(node.right)) + return false; + return true; + } +} +``` + +还有一种解法就是利用左右两边的`min`和`max`: + +* 递归函数有三个值,当前结点`node`,`node`在中序遍历中的前一个结点`pre`的值`min`,`node`在中序遍历中的后一个节点`next`的值`max`; +* 在递归的过程中,先判断当前的结点是否满足`min < node.val && node.val < max`,如果不满足就返回`false`; +* 判断完之后,还要判断孩子是否满足; + +图: + +```java +class Solution { + public boolean isValidBST(TreeNode root) { + if(root == null) + return true; + return helper(root,null,null); + } + + //min、max 也可以看做是 pre、next 就是root的前一个、后一个 + private boolean helper(TreeNode node,Integer min,Integer max){ + if(node == null) + return true; + //先判断自己 + if( (min != null && node.val <= min) || (max != null && node.val >= max) ) + return false; + // 判断左孩子 + if( !helper(node.left,min,node.val)) + return false; + //判断右孩子 + if( !helper(node.right,node.val,max)) + return false; + return true; + } +} +``` + +这里还需要注意当`node = Integer.MIN_VALUE`或者`Integer.MAX_VALUE`这两个值的影响。 + +*** +## 2、判断一颗二叉树是不是一颗完全二叉树 + + 首先知道什么是完全二叉树: + + - 完全二叉树是由满二叉树(一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。即如果一个二叉树的层数为`K`,且结点总数是(2k) -1
,则它就是满二叉树)而引出来的。对于深度为`K`的,有`n`个结点的二叉树,当且仅当其每一个结点都与深度为`K`的满二叉树中编号从`1`至`n`的结点一一对应时称之为完全二叉树。 + - 若设二叉树的深度为`h`,除第 `h` 层外,其它各层 `(1~h-1) `的结点数都达到最大个数,第` h` 层所有的结点都连续**集中在最左边**,这就是完全二叉树。 + - 或者说: 一棵二叉树至多只有最下面的一层上的结点的**度数**可以小于`2`,并且最下层上的结点都集中在该层最左边的若干位置上,而在最后一层上,右边的若干结点缺失的二叉树,则此二叉树成为完全二叉树。 + + 那么如何判断一棵二叉树是否为完全二叉树呢? 按照下面的标准 : + + - **按照**层次遍历**的顺序遍历二叉树,每一层从左到右;** + - **如果当前结点有右孩子但没有左孩子,直接返回`false`;** + - 如果当前结点**不是左右孩子都全**(包括两种情况),那之后的结点必须都为叶节点,否则返回`false`; + - **遍历过程中如果没有返回`false`,就返回`true`;** + +怎么理解上面的过程呢? 图。 + + - 如果当前结点有右孩子但没有左孩子,按照完全二叉树的定义,不符合,如下面的情况: + +

+ + - 当前结点不是左右孩子都全包括两种情况(前面的那种已经判断了),第一,有左孩子没有右孩子,那么后面的结点必须全部为叶子结点,如果出现非叶子结点,就返回`false`。如下: + +

+ +第二种情况就是左右孩子都没有,也要开启判断后面的结点必须全部都是叶子结点的过程,如下: + +

+ +所以综上,我们就可以写出代码,使用层次遍历,用一个`bool`型变量`leaf`记录是否遇到了左右孩子是不是不全的情况(这时就开始判断后面的结点是不是叶子结点),代码如下 : + +```java +public class Main { + + //判断一棵二叉树是不是完全二叉树 + static boolean isCBT(TreeNode root) { + if (root == null) + return true; + Queue queue = new LinkedList<>(); + boolean leaf = false; //如果碰到了 某个结点孩子不全就开始 判断是不是叶子这个过程 + queue.add(root); + TreeNode top = null, L = null, R = null; + while (!queue.isEmpty()) { + top = queue.poll(); + L = top.left; + R = top.right; + //第一种情况 + if ((R != null && L == null)) + return false; + //第二种情况 开启了判断叶子的过程 而且又不是叶子 就返回false + if (leaf && (L != null || R != null)) //以后的结点必须是 左右孩子都是null + return false; + if (L != null) + queue.add(L); + //准确的说是 只要孩子不全就开启leaf, + //但是前面已经否定了有右无左的情况,这里只要判断一下右孩子是不是为空就可以了(如果为空就开启leaf) + if (R != null) + queue.add(R); + else + leaf = true; + } + return true; + } +} +``` + + + +## 3、完整测试代码 + +测试样例是上面的例子 + +```java +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +/** + * 判断一棵二叉树是否为搜索二叉树 + * 判断一棵二叉树是否为完全二叉树 + */ +public class IsBSTAndCBT { + + static class TreeNode{ + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int value) { + this.val = value; + } + } + + static boolean isBST(TreeNode root){ + if(root == null) + return true; + Stackstack = new Stack<>(); + TreeNode cur = root; + TreeNode pre = null; + while(!stack.isEmpty() || cur != null){ + if(cur != null){ + stack.push(cur); + cur = cur.left; + }else { + cur = stack.pop(); + if(pre != null && cur.val <= pre.val) + return false; + pre = cur; + cur = cur.right; + } + } + return true; + } + + //判断一棵二叉树是不是完全二叉树 + static boolean isCBT(TreeNode root){ + if(root == null) + return true; + Queuequeue = new LinkedList<>(); + boolean leaf = false; //如果碰到了 某个结点孩子不全就开始 判断是不是叶子这个过程 + queue.add(root); + TreeNode top = null,L = null,R = null; + while(!queue.isEmpty()){ + top = queue.poll(); + L = top.left; R = top.right; + //第一种情况 + if((R != null && L == null)) + return false; + //第二种情况 开启了判断叶子的过程 而且又不是叶子 就返回false + if(leaf && (L != null || R != null)) //以后的结点必须是 左右孩子都是null + return false; + + if(L != null) + queue.add(L); + + //准确的说是 只要孩子不全就开启leaf, + //但是前面已经否定了有右无左的情况,这里只要判断一下右孩子是不是为空就可以了(如果为空就开启leaf) + if(R != null) + queue.add(R); + else + leaf = true; + } + return true; + } + + + public static void main(String[] args) { + + System.out.println("===============test for BST================"); + System.out.println("---------test1--------------"); + TreeNode head = new TreeNode(1); + System.out.println(isBST(head));//true + + System.out.println("---------test2--------------"); + head = new TreeNode(2); + head.left = new TreeNode(1); + head.right = new TreeNode(3); + System.out.println(isBST(head));//true + + System.out.println("---------test3--------------"); + head = new TreeNode(2); + head.left = new TreeNode(1); + head.right = new TreeNode(0); + System.out.println(isBST(head));//false + + System.out.println("---------test4(for example)--------------"); + head = new TreeNode(5); + head.left = new TreeNode(3); + head.left.left = new TreeNode(2); + head.left.right = new TreeNode(4); + head.left.left.left = new TreeNode(1); + head.right = new TreeNode(8); + head.right.left = new TreeNode(6); + head.right.left.right = new TreeNode(7); + head.right.right = new TreeNode(10); + head.right.right.left = new TreeNode(9); + System.out.println(isBST(head)); + + System.out.println(); + + System.out.println("===============test for CBT================"); + head = new TreeNode(1); + head.left = new TreeNode(2); + head.left.left = new TreeNode(4); + head.left.right = new TreeNode(5); + head.left.left.left = new TreeNode(8); + head.left.left.right = new TreeNode(9); + head.left.right.right = new TreeNode(10); //false + head.right = new TreeNode(3); + head.right.left = new TreeNode(6); + head.right.right = new TreeNode(7); + System.out.println(isCBT(head)); + + head = new TreeNode(1); + head.left = new TreeNode(2); + head.left.left = new TreeNode(4); + head.left.right = new TreeNode(5); + head.left.left.left = new TreeNode(8); + head.left.left.right = new TreeNode(9); + head.left.right.left = new TreeNode(10); //true + head.right = new TreeNode(3); + head.right.left = new TreeNode(6); + head.right.right = new TreeNode(7); + System.out.println(isCBT(head)); + + head = new TreeNode(1); + head.left = new TreeNode(2); + head.left.left = new TreeNode(4); + head.left.right = new TreeNode(5); + head.left.left.left = new TreeNode(8); + head.left.left.right = new TreeNode(9); + // head.left.right.left = new TreeNode(10); //true + head.right = new TreeNode(3); + head.right.left = new TreeNode(6); + head.right.right = new TreeNode(7); + System.out.println(isCBT(head)); + } +} + +``` + +运行结果 + +![这里写图片描述](https://img-blog.csdn.net/20180719111408832?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) \ No newline at end of file diff --git a/Algorithm/LeetCode/Tree/assets/1554693055382.png b/Algorithm/LeetCode/Tree/assets/1554693055382.png new file mode 100644 index 00000000..19f8b185 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554693055382.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554694076458.png b/Algorithm/LeetCode/Tree/assets/1554694076458.png new file mode 100644 index 00000000..6b5d98dd Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554694076458.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554694222712.png b/Algorithm/LeetCode/Tree/assets/1554694222712.png new file mode 100644 index 00000000..b3ae1c25 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554694222712.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554694295539.png b/Algorithm/LeetCode/Tree/assets/1554694295539.png new file mode 100644 index 00000000..7aeee48c Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554694295539.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554697533547.png b/Algorithm/LeetCode/Tree/assets/1554697533547.png new file mode 100644 index 00000000..f68622e5 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554697533547.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554698074830.png b/Algorithm/LeetCode/Tree/assets/1554698074830.png new file mode 100644 index 00000000..c0b6f733 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554698074830.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554698812545.png b/Algorithm/LeetCode/Tree/assets/1554698812545.png new file mode 100644 index 00000000..5c3e8df2 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554698812545.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554704180283.png b/Algorithm/LeetCode/Tree/assets/1554704180283.png new file mode 100644 index 00000000..a7dd7260 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554704180283.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554704536202.png b/Algorithm/LeetCode/Tree/assets/1554704536202.png new file mode 100644 index 00000000..5b83fcb0 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554704536202.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554705962248.png b/Algorithm/LeetCode/Tree/assets/1554705962248.png new file mode 100644 index 00000000..c738f581 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554705962248.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554707366520.png b/Algorithm/LeetCode/Tree/assets/1554707366520.png new file mode 100644 index 00000000..a42e8a3b Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554707366520.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554707691045.png b/Algorithm/LeetCode/Tree/assets/1554707691045.png new file mode 100644 index 00000000..42715e4d Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554707691045.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554708168720.png b/Algorithm/LeetCode/Tree/assets/1554708168720.png new file mode 100644 index 00000000..557a5b9e Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554708168720.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554708569102.png b/Algorithm/LeetCode/Tree/assets/1554708569102.png new file mode 100644 index 00000000..000217fe Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554708569102.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554710957308.png b/Algorithm/LeetCode/Tree/assets/1554710957308.png new file mode 100644 index 00000000..9ebd3dc8 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554710957308.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554711156778.png b/Algorithm/LeetCode/Tree/assets/1554711156778.png new file mode 100644 index 00000000..13ef495a Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554711156778.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554711504651.png b/Algorithm/LeetCode/Tree/assets/1554711504651.png new file mode 100644 index 00000000..d33827bb Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554711504651.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554714590525.png b/Algorithm/LeetCode/Tree/assets/1554714590525.png new file mode 100644 index 00000000..d23df2a9 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554714590525.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554739372795.png b/Algorithm/LeetCode/Tree/assets/1554739372795.png new file mode 100644 index 00000000..1007d91e Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554739372795.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554740023967.png b/Algorithm/LeetCode/Tree/assets/1554740023967.png new file mode 100644 index 00000000..6595cf9e Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554740023967.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554770636440.png b/Algorithm/LeetCode/Tree/assets/1554770636440.png new file mode 100644 index 00000000..4dd6e3e1 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554770636440.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554771306876.png b/Algorithm/LeetCode/Tree/assets/1554771306876.png new file mode 100644 index 00000000..5ec9622e Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554771306876.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554865040622.png b/Algorithm/LeetCode/Tree/assets/1554865040622.png new file mode 100644 index 00000000..20cc09a6 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554865040622.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554865255792.png b/Algorithm/LeetCode/Tree/assets/1554865255792.png new file mode 100644 index 00000000..a0320209 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554865255792.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554877901005.png b/Algorithm/LeetCode/Tree/assets/1554877901005.png new file mode 100644 index 00000000..e1333e21 Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554877901005.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1554954837788.png b/Algorithm/LeetCode/Tree/assets/1554954837788.png new file mode 100644 index 00000000..c9bcdc6e Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1554954837788.png differ diff --git a/Algorithm/LeetCode/Tree/assets/1555031050417.png b/Algorithm/LeetCode/Tree/assets/1555031050417.png new file mode 100644 index 00000000..e311418d Binary files /dev/null and b/Algorithm/LeetCode/Tree/assets/1555031050417.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/100_t.png" b/Algorithm/LeetCode/Tree/images/100_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/100_t.png" rename to Algorithm/LeetCode/Tree/images/100_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/102_t.png" b/Algorithm/LeetCode/Tree/images/102_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/102_t.png" rename to Algorithm/LeetCode/Tree/images/102_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/110_s.png" b/Algorithm/LeetCode/Tree/images/110_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/110_s.png" rename to Algorithm/LeetCode/Tree/images/110_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/110_s2.png" b/Algorithm/LeetCode/Tree/images/110_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/110_s2.png" rename to Algorithm/LeetCode/Tree/images/110_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/110_t.png" b/Algorithm/LeetCode/Tree/images/110_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/110_t.png" rename to Algorithm/LeetCode/Tree/images/110_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/112_t.png" b/Algorithm/LeetCode/Tree/images/112_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/112_t.png" rename to Algorithm/LeetCode/Tree/images/112_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/113_t.png" b/Algorithm/LeetCode/Tree/images/113_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/113_t.png" rename to Algorithm/LeetCode/Tree/images/113_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/124_t.png" b/Algorithm/LeetCode/Tree/images/124_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/124_t.png" rename to Algorithm/LeetCode/Tree/images/124_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/145_s.png" b/Algorithm/LeetCode/Tree/images/145_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/145_s.png" rename to Algorithm/LeetCode/Tree/images/145_s.png diff --git a/Algorithm/LeetCode/Tree/images/145_s2.png b/Algorithm/LeetCode/Tree/images/145_s2.png new file mode 100644 index 00000000..525b9510 Binary files /dev/null and b/Algorithm/LeetCode/Tree/images/145_s2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/145_t.png" b/Algorithm/LeetCode/Tree/images/145_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/145_t.png" rename to Algorithm/LeetCode/Tree/images/145_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/222_s.png" b/Algorithm/LeetCode/Tree/images/222_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/222_s.png" rename to Algorithm/LeetCode/Tree/images/222_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/222_s2.png" b/Algorithm/LeetCode/Tree/images/222_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/222_s2.png" rename to Algorithm/LeetCode/Tree/images/222_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/222_t.png" b/Algorithm/LeetCode/Tree/images/222_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/222_t.png" rename to Algorithm/LeetCode/Tree/images/222_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/297_t.png" b/Algorithm/LeetCode/Tree/images/297_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/297_t.png" rename to Algorithm/LeetCode/Tree/images/297_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/404_s.png" b/Algorithm/LeetCode/Tree/images/404_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/404_s.png" rename to Algorithm/LeetCode/Tree/images/404_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/404_t.png" b/Algorithm/LeetCode/Tree/images/404_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/404_t.png" rename to Algorithm/LeetCode/Tree/images/404_t.png diff --git a/Algorithm/LeetCode/Tree/images/449_s.png b/Algorithm/LeetCode/Tree/images/449_s.png new file mode 100644 index 00000000..63761017 Binary files /dev/null and b/Algorithm/LeetCode/Tree/images/449_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/543_t.png" b/Algorithm/LeetCode/Tree/images/543_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/543_t.png" rename to Algorithm/LeetCode/Tree/images/543_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/606_t.png" b/Algorithm/LeetCode/Tree/images/606_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/606_t.png" rename to Algorithm/LeetCode/Tree/images/606_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/617_s.png" b/Algorithm/LeetCode/Tree/images/617_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/617_s.png" rename to Algorithm/LeetCode/Tree/images/617_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/617_t.png" b/Algorithm/LeetCode/Tree/images/617_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/617_t.png" rename to Algorithm/LeetCode/Tree/images/617_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/637_s.png" b/Algorithm/LeetCode/Tree/images/637_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/637_s.png" rename to Algorithm/LeetCode/Tree/images/637_s.png diff --git a/Algorithm/LeetCode/Tree/images/637_s2.png b/Algorithm/LeetCode/Tree/images/637_s2.png new file mode 100644 index 00000000..64945c97 Binary files /dev/null and b/Algorithm/LeetCode/Tree/images/637_s2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/637_t.png" b/Algorithm/LeetCode/Tree/images/637_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/637_t.png" rename to Algorithm/LeetCode/Tree/images/637_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/654_s.png" b/Algorithm/LeetCode/Tree/images/654_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/654_s.png" rename to Algorithm/LeetCode/Tree/images/654_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/654_s2.png" b/Algorithm/LeetCode/Tree/images/654_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/654_s2.png" rename to Algorithm/LeetCode/Tree/images/654_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/654_t.png" b/Algorithm/LeetCode/Tree/images/654_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/654_t.png" rename to Algorithm/LeetCode/Tree/images/654_t.png diff --git a/Algorithm/LeetCode/Tree/images/655_s.png b/Algorithm/LeetCode/Tree/images/655_s.png new file mode 100644 index 00000000..aa0ee3bf Binary files /dev/null and b/Algorithm/LeetCode/Tree/images/655_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/655_t.png" b/Algorithm/LeetCode/Tree/images/655_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/655_t.png" rename to Algorithm/LeetCode/Tree/images/655_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/671_s.png" b/Algorithm/LeetCode/Tree/images/671_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/671_s.png" rename to Algorithm/LeetCode/Tree/images/671_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/671_t.png" b/Algorithm/LeetCode/Tree/images/671_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/671_t.png" rename to Algorithm/LeetCode/Tree/images/671_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/687_s.png" b/Algorithm/LeetCode/Tree/images/687_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/687_s.png" rename to Algorithm/LeetCode/Tree/images/687_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/687_t.png" b/Algorithm/LeetCode/Tree/images/687_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/687_t.png" rename to Algorithm/LeetCode/Tree/images/687_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/699_s.png" b/Algorithm/LeetCode/Tree/images/699_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/699_s.png" rename to Algorithm/LeetCode/Tree/images/699_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/699_t.png" b/Algorithm/LeetCode/Tree/images/699_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/699_t.png" rename to Algorithm/LeetCode/Tree/images/699_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/98_s.png" b/Algorithm/LeetCode/Tree/images/98_s.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/98_s.png" rename to Algorithm/LeetCode/Tree/images/98_s.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/98_s2.png" b/Algorithm/LeetCode/Tree/images/98_s2.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/98_s2.png" rename to Algorithm/LeetCode/Tree/images/98_s2.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/98_s3.png" b/Algorithm/LeetCode/Tree/images/98_s3.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/98_s3.png" rename to Algorithm/LeetCode/Tree/images/98_s3.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/98_s4.png" b/Algorithm/LeetCode/Tree/images/98_s4.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/98_s4.png" rename to Algorithm/LeetCode/Tree/images/98_s4.png diff --git "a/\345\210\267\351\242\230/LeetCode/Tree/images/98_t.png" b/Algorithm/LeetCode/Tree/images/98_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Tree/images/98_t.png" rename to Algorithm/LeetCode/Tree/images/98_t.png diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-11.Container With Most Water.md b/Algorithm/LeetCode/TwoPointer/LeetCode-11.Container With Most Water.md new file mode 100644 index 00000000..ec6d0752 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-11.Container With Most Water.md @@ -0,0 +1,56 @@ +# LeetCode - 11. Container With Most Water + +#### [题目链接](https://leetcode.com/problems/container-with-most-water/) + +> https://leetcode.com/problems/container-with-most-water/ + +#### 题目 + +![11_tt.png](images/11_tt.png) + +### 解析 + +暴力解法,没什么好说的。。 + +```java +class Solution { + public int maxArea(int[] height) { + int res = 0; + for (int i = 0; i < height.length; i++) { + for (int j = i + 1; j < height.length; j++) { + res = Math.max(res, Math.min(height[i], height[j]) * (j - i)); + } + } + return res; + } +} +``` +高效的解法是使用双指针。 + +使用双指针的原因: **两线段之间形成的区域的高总是会受到其中较短那条高的长度的限制。而且,两线段距离越远,得到的面积就越大**。 + +做法,**一个指针在开头,一个在末尾**。然后两个指针比较,并不断靠拢即可。 + +

+ +代码: + +```java +class Solution { + public int maxArea(int[] height) { + int L = 0, R = height.length - 1; + int max = 0; + while(L < R){ + // 底是 (R - L), 高只能取height[L]和height[R]中小的那一个 + max = Math.max(max, Math.min(height[L], height[R]) * (R - L)); + // 因为从两边往中间靠,所以只有当小的那一方变大,才有可能让结果变大 + if(height[L] < height[R]) + L++; + else + R--; + } + return max; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-125.Valid Palindrome.md b/Algorithm/LeetCode/TwoPointer/LeetCode-125.Valid Palindrome.md new file mode 100644 index 00000000..18f1ff32 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-125.Valid Palindrome.md @@ -0,0 +1,44 @@ +# LeetCode - 125. Valid Palindrome + +#### [题目链接](https://leetcode.com/problems/valid-palindrome/) + +> https://leetcode.com/problems/valid-palindrome/ + +#### 题目 + +![1554772036936](assets/1554772036936.png) + +## 解析 + +很简单的模拟题,可以用双指针解法吧。 + +* 先转成小写; +* 然后两个指针向中间靠拢,遇到没用的字符就跳过; + +图: + +![1554772480646](assets/1554772480646.png) + +代码: + +```java +class Solution { + public boolean isPalindrome(String s) { + char[] chs = s.toLowerCase().toCharArray(); // 都转成小写 + for (int l = 0, r = chs.length - 1; l <= r; ) { + if (!Character.isLetterOrDigit(chs[l])) // 不是字母或者数字 + l++; + else if (!Character.isLetterOrDigit(chs[r])) + r--; + else if (chs[l] != chs[r]) + return false; + else { // chs[l] == chs[r] + l++; + r--; + } + } + return true; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-15.3Sum.md b/Algorithm/LeetCode/TwoPointer/LeetCode-15.3Sum.md new file mode 100644 index 00000000..72c98e90 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-15.3Sum.md @@ -0,0 +1,94 @@ +# LeetCode - 15. 3Sum + +#### [题目链接](https://leetcode.com/problems/3sum/) + +> https://leetcode.com/problems/3sum/ + +#### 题目 + +![15_t.png](images/15_t.png) + +## 解析 + +两种做法。一种和**LeetCode - 1. Two Sum**一样。另一种是使用双指针。 + + + +第一种写法,和**LeetCode - 1. Two Sum**类似的,那个题目是找到两个数的和为`target`。 + +这里的做法是: + +* 枚举每个不重复的`a`(把`a`看做第一题的`target`),然后去寻找另外的两个数的和`b+c == -a`即可; +* 这个过程主要的麻烦在于去重,为此我们先对数组排序,然后`if(i != 0 && nums[i] == nums[i-1]) continue`对`a`去重; +* 然后每次找到一个结果之后,还需要对`b`去重,具体看代码; + +图(图是没有sort之前的,sort只是为了去重,和这个算法没有关系): + +

+代码: + +```java +class Solution { + + public List> threeSum(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); // 必须先排序 + for(int i = 0; i < nums.length; i++){ + if(i != 0 && nums[i] == nums[i-1]) continue; // 不处理重复的a + int a = nums[i]; + HashMap map = new HashMap<>(); + for(int j = i+1; j < nums.length; j++){ + int b = nums[j]; + int c = -a - b; + if(map.containsKey(c)) { + res.add(Arrays.asList(a, b, c)); + while(j + 1 < nums.length && nums[j] == nums[j+1]) j++; // 不处理重复的b + } + map.put(nums[j], j); + } + } + return res; + } +} +``` + + + +第二种做法是使用`Two Pointer`。 + +* 也是先要对数组排序,这个排序和上面的意义不同,这里排序既要为了去重,更重要的是为了我们的双指针算法; +* 对于每一个`a`,我们设定两个指针`L、R`,L是`a`的后面一个元素,`R`是数组最后的元素; +* 每次我们累加`a + nums[L] + nums[R] `,如果`==0`,就统计答案,并且`L++, R++`,这个过程还需要去除重复的`b、c`;如果`!=0`,就按照大小移动`L`或者`R`即可; + +图: + +

+代码: + +```java +class Solution { + + public List> threeSum(int[] nums) { + List> res = new ArrayList<>(); + Arrays.sort(nums); // 必须先排序 + for(int i = 0; i < nums.length ; i++){ + if(i != 0 && nums[i] == nums[i-1]) continue; // 不处理重复的a + int L = i + 1, R = nums.length - 1; + while(L < R){ + if(nums[i] + nums[L] + nums[R] == 0){ + res.add(Arrays.asList(nums[i], nums[L], nums[R])); + while(L < R && nums[L] == nums[L+1]) L++;//去重 + while(L < R && nums[R] == nums[R-1]) R--;//去重 + L++; R--; + }else if(nums[i] + nums[L] + nums[R] < 0){//增加b + L++; + }else { // > 0 减小c + R--; + } + } + } + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-16.3Sum Closest.md b/Algorithm/LeetCode/TwoPointer/LeetCode-16.3Sum Closest.md new file mode 100644 index 00000000..9d1d2a1a --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-16.3Sum Closest.md @@ -0,0 +1,46 @@ +# LeetCode - 16. 3Sum Closest + +#### [题目链接](https://leetcode.com/problems/3sum-closest/) + +> https://leetcode.com/problems/3sum-closest/ + +#### 题目 + +![1555050932710](assets/1555050932710.png) + +## 解析 + +和上一道的双指针解法差不多。区别是不要去重,而且找到了和`target`相等的直接`return`。 + +* 先排序,根据情况移动left 和right; +* 当确定好了第一个数字`nums[i]`后,就在剩下的数组中找两数之和,再加上第一个数字,再减去`target` 来得到`newdiff`,如果`newdiff`比之前的小,那么更新`diff` 。 +* 利用双指针特性, 如果 `nums[i] + nums[L] + nums[R]`比`target` 小的话,说明我们需要更大的sum,所以要让`L++`以便得到更大的sum。否则`R--`。 + +图: + +

+代码: + +```java +import java.util.Arrays; + +class Solution { + public int threeSumClosest(int[] nums, int target) { + if (nums == null || nums.length == 0) return 0; + Arrays.sort(nums); // 必须先排序 + int diff = nums[0] + nums[1] + nums[2] - target; + for (int i = 0; i < nums.length; i++) { + int L = i + 1, R = nums.length - 1; + while (L < R) { + int newDiff = nums[i] + nums[L] + nums[R] - target; + if (newDiff == 0) return target; + if (Math.abs(newDiff) < Math.abs(diff)) diff = newDiff; + if (newDiff < 0) L++; + else R--; + } + } + return target + diff; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-167.Two Sum II - Input array is sorted.md b/Algorithm/LeetCode/TwoPointer/LeetCode-167.Two Sum II - Input array is sorted.md new file mode 100644 index 00000000..eb637462 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-167.Two Sum II - Input array is sorted.md @@ -0,0 +1,62 @@ +## LeetCode - 167. Two Sum II - Input array is sorted +#### [题目链接](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) + +> https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/ + +#### 题目 +![在这里插入图片描述](images/167_t.png) +## 解析 +两种做法,一种二分,一种双指针: + + - 对于每一个`arr[i]`,在后面的有序数组中二分查找有没有可以匹配的,时间复杂度`n * logn`; + - 使用双指针,因为是有序的,所以可以通过比较大小决定哪个指针的移动,时间复杂度` n` ; + +图: + +

+ +代码: + +```java +class Solution { + //n * logn + public int[] twoSum(int[] numbers, int target) { + for (int i = 0; i < numbers.length; i++) { + // binary search + int L = i + 1, R = numbers.length - 1; + while (L <= R) { + int mid = L + (R - L) / 2; + if (numbers[mid] + numbers[i] == target) + return new int[]{i + 1, mid + 1}; + else if (numbers[mid] + numbers[i] < target) + L = mid + 1; + else + R = mid - 1; + } + + } + return null; + } +} +``` +双指针: 一个指针在开头位置,一个在结尾位置。不断往中间靠拢。 + +

+ +代码: + +```java +class Solution { + public int[] twoSum(int[] numbers, int target) { + for (int l = 0, r = numbers.length - 1; l < r; ) { + if (numbers[l] + numbers[r] == target) + return new int[]{l + 1, r + 1}; + else if (numbers[l] + numbers[r] < target) + l++; + else + r--; + } + return null; + } +} +``` \ No newline at end of file diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-18.4Sum.md b/Algorithm/LeetCode/TwoPointer/LeetCode-18.4Sum.md new file mode 100644 index 00000000..17190dd8 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-18.4Sum.md @@ -0,0 +1,56 @@ +# LeetCode - 18. 4Sum + +#### [题目链接](https://leetcode.com/problems/4sum/) + +> https://leetcode.com/problems/4sum/ + +#### 题目 + +![1555057726155](assets/1555057726155.png) + +## 解析 + +还是用双指针的方法。只不过前面还需要套一层循环`for (j..)`,时间复杂度是`O(N^3)`。 + +图: + +

+代码: + +```java +class Solution { + + // O(N ^ 3) + public List> fourSum(int[] nums, int target) { + List> res = new ArrayList<>(); + if (nums == null || nums.length < 4) return res; + Arrays.sort(nums); + for (int i = 0; i < nums.length - 3; i++) { + if (i != 0 && nums[i] == nums[i - 1]) continue; // 去重 + for (int j = i + 1; j < nums.length - 2; j++) { + if (j != i + 1 && nums[j] == nums[j - 1]) continue; // 去重 + int L = j + 1, R = nums.length - 1; + int another = target - nums[i] - nums[j]; + while (L < R) { + if (nums[L] + nums[R] == another) { + res.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R])); + // 也是去重 + while (L < R && nums[L] == nums[L + 1]) L++;// 去重 + while (L < R && nums[R] == nums[R - 1]) R--;// 去重 + L++; + R--; + } else if (nums[L] + nums[R] > another) { + R--; + } else { + L++; + } + } + } + } + return res; + } +} + +``` + +讨论区关于使用`map`可以降低到`O(N ^ 2)`,有兴趣可以[点这里](https://leetcode.com/problems/4sum/discuss/8565/lower-bound-n3)看一下。 \ No newline at end of file diff --git "a/Algorithm/LeetCode/TwoPointer/LeetCode-209.Minimum Size Subarray Sum(\346\273\221\345\212\250\347\252\227\345\217\243).md" "b/Algorithm/LeetCode/TwoPointer/LeetCode-209.Minimum Size Subarray Sum(\346\273\221\345\212\250\347\252\227\345\217\243).md" new file mode 100644 index 00000000..345b9029 --- /dev/null +++ "b/Algorithm/LeetCode/TwoPointer/LeetCode-209.Minimum Size Subarray Sum(\346\273\221\345\212\250\347\252\227\345\217\243).md" @@ -0,0 +1,203 @@ +## LeetCode - 209. Minimum Size Subarray Sum(滑动窗口) +* 暴力**O(N3)**,超时 +* 暴力优化**O(N2)** +* 二分**O(N*logN)** +* 滑动窗口**O(N)** + +*** +#### [题目链接](https://leetcode.com/problems/minimum-size-subarray-sum/description/) + +> https://leetcode.com/problems/minimum-size-subarray-sum/description/ + +#### 题目 +![在这里插入图片描述](images/209_t.png) +### 暴力O(N3),超时 +可以枚举两个边界`L`和`R`,然后计算`[L,R]`之间的和,然后判断是否`>=sum` ,并记录最小值即可。 +```java +class Solution { + // tle 超时 + public int minSubArrayLen(int s, int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int res = nums.length + 1; + for (int l = 0; l < nums.length; l++) { + for (int r = l; r < nums.length; r++) { + int sum = 0; + for (int k = l; k <= r; k++) + sum += nums[k]; + if (sum >= s) + res = Math.min(res, r - l + 1); + } + } + if (res == nums.length + 1) + return 0; + return res; + } +} +``` +### 暴力优化O(N2) +可以先计算出从`0 `到每个位置`i`的和保存在一个`sums`数组中,然后枚举边界的时候,就可以直接取`sums`数组中的值相减即可,但是要注意: +* **这里尽量不要用`sums[i]`表示`[0,i]`内的和**; +* **因为等下如果要计算`[L,R]`内的和,要使用`sums[R] - sums[L-1]`,这样的话对于`0`位置就不好处理,所以使用`sums[i]`表示`[0,i-1]`之间的和,这样方便一点**; + +代码: + + +```java +class Solution { + // O(n^2) + public int minSubArrayLen(int s, int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int res = nums.length + 1; + int[] sums = new int[nums.length + 1]; // sums[i]存放nums[0...i-1]的和 (最好不要sums[i]存放0...i的和,不好处理0位置) + sums[0] = 0; // 0~-1的和 + for (int i = 1; i <= nums.length; i++) + sums[i] = sums[i - 1] + nums[i - 1]; + + for (int l = 0; l < nums.length; l++) { + for (int r = l; r < nums.length; r++) { + if (sums[r + 1] - sums[l] >= s) // 使用sums[r+1] - sums[l] 快速获得nums[l...r]的和 + res = Math.min(res, r - l + 1); + } + } + if (res == nums.length + 1) + return 0; + return res; + } +} +``` +*** +### 二分O(N*logN) +还是上面的思路,不同的是: +* 我们可以利用二分查找求一个数组中`>=key`的位置,而我们的题目就是要求`>=s`的位置,所以对于左边界`L`,我们可以利用二分查找去查找第一个`>=sum[l]+s`的位置,这时就是我们要找的右边界`R`; +* **不过要注意: 我们每一个枚举的`L`,在`sums`数组查找到的`R`,其实是`sum[R+1]`位置代表的是`[0,R]`的和,所以要注意`res = Math.min(res,R-L)`,而不是`R-L+1`**; + +图: + +

+ +二分查找的几种变形请看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Algorithm/BinarySearch/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%9A%84%E6%80%BB%E7%BB%93(6%E7%A7%8D%E5%8F%98%E5%BD%A2).md)。 + +```java +class Solution { + //ologn + public int minSubArrayLen(int s, int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int res = nums.length + 1; + int[] sums = new int[nums.length + 1]; + sums[0] = 0; + for (int i = 1; i <= nums.length; i++) + sums[i] = sums[i - 1] + nums[i - 1]; + for (int l = 0; l < nums.length; l++) {//必须从0开始 比如 s = 6,nums = {1,2,3} + int r = firstLargeEqual(sums, sums[l] + s); + if (r != sums.length) + res = Math.min(res, r - l); //注意这里不是r-l+1 ,因为寻找到的r实际上是r+1 + } + if (res == nums.length + 1) + return 0; + return res; + } + + public int firstLargeEqual(int[] arr, int key) {// 寻找第一个>= key的,不存在就返回arr.length + int L = 0, R = arr.length - 1; + int mid; + while (L <= R) { + mid = L + (R - L) / 2; + if (arr[mid] >= key) + R = mid - 1; + else + L = mid + 1; + } + return L; + } +} +``` +### 滑动窗口O(N) +滑动窗口的思想很简单,一直维护窗口的数: +* 如果当前窗口内的和`sum < s`我们就往右边扩一个位置,并且维护窗口的和`sum`的值,**但是要考虑`R`已经到达边界的情况,此时我们可以`break`了,因为就算`L`再往右边,也没用,因为此时`sum < s`;** +* 否则我们的窗口就左边缩一个,并且继续维护`sum`; +* 然后我们要做的就是不断的记录窗口的长度` R - L + 1`的最小值; + +图: + +![1554859322815](assets/1554859322815.png) + +代码: + + +```java +class Solution { + //O(n) + public int minSubArrayLen(int s, int[] nums) { + int L = 0, R = -1; //一开始窗口内没有数 + int sum = 0; + int res = nums.length + 1; //不可能的答案 + while (R < nums.length) { + if (sum < s) { //这里写成sum <= s也可以 ,看下面的方法 + if (++R == nums.length) break; //已经扩到最后一个数,可以退出了,因为此时已经sum < s,所以L你也更加不需要往右边扩了 + sum += nums[R]; + } else // sum >= s + sum -= nums[L++]; + + if (sum >= s) + res = Math.min(res, R - L + 1); + } + if (res == nums.length + 1) + return 0; + return res; + } +} +``` +上面的`sum < s`也可以写成`sum <= s`,效果是一样的: + +```java +class Solution { + //O(n) + public int minSubArrayLen(int s, int[] nums) { + int L = 0, R = -1; //一开始窗口内没有数 + int sum = 0; + int res = nums.length + 1; //不可能的答案 + while (R < nums.length) { + if (sum <= s) { //这里sum <= s也可以,因为下面已经判断了sum >= s就计算 + if (++R == nums.length) break; + sum += nums[R]; + } else // sum > s + sum -= nums[L++]; + + if (sum >= s) + res = Math.min(res, R - L + 1); + } + if (res == nums.length + 1) + return 0; + return res; + } +} +``` +同时,也可以一开始窗口内有一个数,但是这样的话,要先维护`res`的值,然后再更新窗口: + +```java +class Solution { + public int minSubArrayLen(int s, int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int L = 0, R = 0; //一开始窗口有一个数 + int sum = nums[0]; + int res = nums.length + 1; + while (R < nums.length) { + if (sum >= s) //这个必须放到上面,因为此时窗口已经有了一个数了 + res = Math.min(res, R - L + 1); + if (sum < s) { + if (++R == nums.length) break; + sum += nums[R]; + } else // sum >= s + sum -= nums[L++]; + } + if (res == nums.length + 1) + return 0; + return res; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-26.Remove Duplicates from Sorted Array.md b/Algorithm/LeetCode/TwoPointer/LeetCode-26.Remove Duplicates from Sorted Array.md new file mode 100644 index 00000000..8382f974 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-26.Remove Duplicates from Sorted Array.md @@ -0,0 +1,36 @@ +# LeetCode - 26. Remove Duplicates from Sorted Array + +#### [题目链接](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) + +> https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ + +#### 题目 + +![1557823000431](assets/1557823000431.png) + +### 解析 + +比较简单的双指针问题。走在前面的指针负责判断当前元素是否重复(`nums[i] == nums[i-1]`),走在后面的指针记录每次需要将前面的指针的不重复的数放的位置。 + +![1557823315086](assets/1557823315086.png) + +代码: + +```java +class Solution { + + // 返回修改后的数组,并且要修改数组 + public int removeDuplicates(int[] nums) { + if (nums == null || nums.length == 0) return 0; + if (nums.length == 1) return 1; + int slow = 1; // 第一个指针first,第二个second + for (int fast = 1; fast < nums.length; fast++) { + if (nums[fast] != nums[fast - 1]) { + nums[slow++] = nums[fast]; + } + } + return slow; + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-27.Remove Element.md b/Algorithm/LeetCode/TwoPointer/LeetCode-27.Remove Element.md new file mode 100644 index 00000000..dc94fec0 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-27.Remove Element.md @@ -0,0 +1,29 @@ +# LeetCode - 27. Remove Element + +#### [题目链接](https://leetcode.com/problems/remove-element/) + +> https://leetcode.com/problems/remove-element/ + +#### 题目 + +![1557824207866](assets/1557824207866.png) + +### 解析 + +和上一题(`LeetCode-26`)基本差不多,思想是一样的(双指针),只不过注意一开始都要从索引`0`开始。具体可以参考上一题,过于简单就不画图了(上一题画图了)。 + +代码: + +```java +class Solution { + public int removeElement(int[] nums, int val) { + if(nums == null || nums.length == 0) return 0; + int slow = 0; // 注意初始不是1了 + for(int f = 0; f < nums.length; f++) if(nums[f] != val){ + nums[slow++] = nums[f]; + } + return slow; + } +} +``` + diff --git "a/Algorithm/LeetCode/TwoPointer/LeetCode-283.Move Zeroes(\347\247\273\345\212\250\351\233\266)(\347\256\200\345\215\225\351\242\230)(\344\270\211\347\247\215\345\206\231\346\263\225).md" "b/Algorithm/LeetCode/TwoPointer/LeetCode-283.Move Zeroes(\347\247\273\345\212\250\351\233\266)(\347\256\200\345\215\225\351\242\230)(\344\270\211\347\247\215\345\206\231\346\263\225).md" new file mode 100644 index 00000000..e4153f61 --- /dev/null +++ "b/Algorithm/LeetCode/TwoPointer/LeetCode-283.Move Zeroes(\347\247\273\345\212\250\351\233\266)(\347\256\200\345\215\225\351\242\230)(\344\270\211\347\247\215\345\206\231\346\263\225).md" @@ -0,0 +1,96 @@ +# LeetCode - 283. Move Zeroes(移动零)(简单题)(三种写法) + + - O(n)空间,O(n)时间 + - O(1)空间,O(n)时间 + - O(1)空间,O(n)时间 + 巧妙使用swap()优化 + +*** +#### [题目链接](https://leetcode.com/problems/move-zeroes/description/) + +> https://leetcode.com/problems/move-zeroes/description/ + +#### 题目 +![在这里插入图片描述](images/283_t.png) +#### O(n)空间,O(n)时间 +这个方法很简单,直接使用一个额外的数组来保存原数组非`0`的元素,最后再补上`0`即可。 + +```java +class Solution { + public void moveZeroes(int[] nums) { + Listlist = new ArrayList<>(); + + for(int i = 0; i < nums.length; i++){ + if(nums[i] != 0){ + list.add(nums[i]); + } + } + + for(int i = list.size(); i < nums.length; i++) list.add(0); + + for(int i = 0; i < list.size(); i++) nums[i] = list.get(i); + } +} +``` +*** +### O(1)空间,O(n)时间 +这个就稍微具有一点技巧性,可以说是使用了双指针吧,也就是在前面记录一个指针`k`,在我们另一个指针遍历 + +` i `的时候,只要当前的元素` != 0`,就将这个元素放到相应的`k`指针指向的位置,如果`i == k` ,我们也可以放到那里,不会出错。 + +图: + +

+ +代码: + +```java +class Solution { + public void moveZeroes(int[] nums) { + int k = 0; + for(int i = 0; i < nums.length; i++){ + if(nums[i] != 0){ + nums[k++] = nums[i]; + } + } + + for(; k < nums.length; k++) nums[k] = 0; + } +} +``` + +### O(1)空间,O(n)时间 + 巧妙使用swap()优化 +第三种方式也是使用两个指针,和前面更加优化的是: + + - 我们遍历到一个元素的时候,如果`nums[i] != 0`,就可以直接`i `位置的和` k `位置的交换; + - 这样我们最后甚至都不需要再把`0`填充进去,只需要一次遍历就可以,效率又提高一点。 + +图: + +

+ +代码: + +```java +class Solution { + // [k,i]为0 + public void moveZeroes(int[] nums) { + int k = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + if (i != k)//另一个小优化 + swap(nums, i, k); + k++; + } + } + } + + public void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } +} +``` + + + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-3.Longest Substring Without Repeating Characters.md b/Algorithm/LeetCode/TwoPointer/LeetCode-3.Longest Substring Without Repeating Characters.md new file mode 100644 index 00000000..1413a34a --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-3.Longest Substring Without Repeating Characters.md @@ -0,0 +1,134 @@ +# LeetCode - 3. Longest Substring Without Repeating Characters(滑动窗口) +#### [题目链接](https://leetcode.com/problems/longest-substring-without-repeating-characters/description/) + +> https://leetcode.com/problems/longest-substring-without-repeating-characters/description/ + +#### 题目 +![在这里插入图片描述](images/3_t.png) +## 暴力O(N3) +方法: +* 使用一个函数`isAllUnique`来判断某一段字符串是不是有重复的字符; +* 然后枚举左右边界`[L, R]`,一段一段的判断,时间复杂度O(N3); + +暴力会超时。 + +```java +class Solution { + + public int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) + return 0; + int res = 0; + for (int l = 0; l < s.length(); l++) { + for (int r = l; r < s.length(); r++) {//注意从l开始,可以是长度为1 + if (isAllUnique(s, l, r)) + res = Math.max(res, r - l + 1); + } + } + return res; + } + + //判断[L,R]之间的字符是不是都是不重复的 + public boolean isAllUnique(String s, int L, int R) { + Set set = new HashSet<>(); + for (int i = L; i <= R; i++) { + if (!set.contains(s.charAt(i))) + set.add(s.charAt(i)); + else + return false; + } + return true; + } +} +``` +## 滑动窗口 +和普通的滑动窗口一样,使用一个`freq`数组来保存字符出现的次数: +* 每次试探`++R`,要判断是否越界,然后判断如果`chs[R+1]`与前面的字符串段中没有重复的话,`R`就可以继续扩展,对应`chs[R+1]`上的频次要`++`; +* 如果有重复的话,左边界`L`就扩展,此时对应的频次要`--`; +* 每一个窗口都更新一下全局最大值; + +图: + +![3_s.png](images/3_s.png) + +代码: + +```java +class Solution { + public int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) return 0; + char[] chs = s.toCharArray(); + int[] freq = new int[256]; + int L = 0, R = -1; + int res = 0; + while (R < s.length()) { + if (R + 1 == s.length()) //一定要break, 一是防止越界,二是不然L不会break; R你都到str.length - 1,L你要再移动也不会更长了 + break; + //如果freq[chs[R + 1]] != 0,就要先一直移动L,直到freq[chs[R+1]]可以进入窗口,这个过程不会错过最优解 + if (freq[chs[R + 1]] == 0) + freq[chs[++R]]++; + else + freq[chs[L++]]--; + res = Math.max(res, R - L + 1); + } + return res; + } +} +``` +对于`R`边界的判断以及循环的终止,因为`R`如果到达了边界的话,此时`L`你再往右边扩展,此时的长度只会更小,所以上述代码也可以简写成下面的样子: + +```java +class Solution { + public int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) return 0; + char[] chs = s.toCharArray(); + int[] freq = new int[256]; + int L = 0, R = -1; + int res = 0; + while (R + 1 < s.length()) { // 直接写成这样就行 + if (freq[chs[R + 1]] == 0) + freq[chs[++R]]++; + else + freq[chs[L++]]--; + res = Math.max(res, R - L + 1); + } + return res; + } +} +``` +更加优化的方式是: + +* 使用一个`index[]`数组记录每一个` chs[i]`最后一次出现位置; +* 下次我的`L`要移动的时候,我直接移动到`index[chs[R+1]]`的位置,前面的`L~index[R]`都直接跳过了。 + +图: + +

+代码: + + +```java +// 优化的滑动窗口 +// 其中使用index[c]保存字符c上一次出现的位置, 用于在右边界发现重复字符时, 快速移动左边界 +// 使用这种方法, 时间复杂度依然为O(n), 但是只需要动r指针, 实际上对整个s只遍历了一次 +class Solution { + public int lengthOfLongestSubstring(String s) { + if (s == null || s.length() == 0) return 0; + char[] chs = s.toCharArray(); + int[] index = new int[256]; + int L = 0, R = -1, res = 0; + Arrays.fill(index, -1); + while (R + 1 < s.length()) { + R++; + if(index[chs[R]] != -1) + L = Math.max(L, index[chs[R]]+1); // 此时L直接移动到上一个R的前一个位置 + //注意更新 R的索引位置 + index[chs[R]] = R; + res = Math.max(res, R - L + 1); + } + return res; + } +} +``` + + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-344.Reverse String.md b/Algorithm/LeetCode/TwoPointer/LeetCode-344.Reverse String.md new file mode 100644 index 00000000..85ccc8d6 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-344.Reverse String.md @@ -0,0 +1,39 @@ +# LeetCode - 344. Reverse String + +#### [题目链接](https://leetcode.com/problems/reverse-string/) + +> https://leetcode.com/problems/reverse-string/ + +#### 题目 + +![1554944597829](assets/1554944597829.png) + +## 解析 + +巨简单题。。 + +```java +class Solution { + + public void reverseString(char[] s) { + for(int i = 0; i < s.length/2; i++){ + char c = s[i]; + s[i] = s[s.length - 1 - i]; + s[s.length - 1 - i] = c; + } + } +} +``` + +```java +class Solution { + public void reverseString(char[] s) { + for(int l = 0, r = s.length - 1; l < r; l++, r--){ + char c = s[l]; + s[l] = s[r]; + s[r] = c; + } + } +} +``` + diff --git a/Algorithm/LeetCode/TwoPointer/LeetCode-345.Reverse Vowels of a String.md b/Algorithm/LeetCode/TwoPointer/LeetCode-345.Reverse Vowels of a String.md new file mode 100644 index 00000000..b4acafb3 --- /dev/null +++ b/Algorithm/LeetCode/TwoPointer/LeetCode-345.Reverse Vowels of a String.md @@ -0,0 +1,41 @@ +# LeetCode - 345. Reverse Vowels of a String + +#### [题目链接](https://leetcode.com/problems/reverse-vowels-of-a-string/) + +> https://leetcode.com/problems/reverse-vowels-of-a-string/ + +#### 题目 + +![1554945695956](assets/1554945695956.png) + +## 解析 + +也比较简单,还是双指针,两边都是元音字母的时候才交换。 + +

+ +代码: + +```java +class Solution { + + public boolean isU(char c) { + return c == 'a' || c == 'o' || c == 'e' || c == 'u' || c == 'i' + || c == 'A' || c == 'O' || c == 'E' || c == 'U' || c == 'I'; + } + public String reverseVowels(String s) { + char[] chs = s.toCharArray(); + for (int l = 0, r = chs.length - 1; l < r; ) { + while (l < r && !isU(chs[l])) l++; + while (l < r && !isU(chs[r])) r--; + char t = chs[l]; + chs[l] = chs[r]; + chs[r] = t; + l++; + r--; + } + return String.valueOf(chs); + } +} +``` + diff --git "a/Algorithm/LeetCode/TwoPointer/LeetCode-\345\217\214\346\214\207\351\222\210\351\242\230\345\260\217\346\200\273\347\273\223-(LeetCode344\343\200\201LeetCode345\343\200\201LeetCode125\343\200\201LeetCode167\343\200\201LeetCode11)(\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/LeetCode/TwoPointer/LeetCode-\345\217\214\346\214\207\351\222\210\351\242\230\345\260\217\346\200\273\347\273\223-(LeetCode344\343\200\201LeetCode345\343\200\201LeetCode125\343\200\201LeetCode167\343\200\201LeetCode11)(\347\256\200\345\215\225\351\242\230).md" new file mode 100644 index 00000000..b0106521 --- /dev/null +++ "b/Algorithm/LeetCode/TwoPointer/LeetCode-\345\217\214\346\214\207\351\222\210\351\242\230\345\260\217\346\200\273\347\273\223-(LeetCode344\343\200\201LeetCode345\343\200\201LeetCode125\343\200\201LeetCode167\343\200\201LeetCode11)(\347\256\200\345\215\225\351\242\230).md" @@ -0,0 +1,262 @@ +## LeetCode - 双指针题小总结-(LeetCode344、LeetCode345、LeetCode125、LeetCode167、LeetCode11)(简单题) + + - [LeetCode - 344. Reverse String-反转字符串](#leetcode---344-reverse-string-反转字符串) + - [LeetCode - 345. Reverse Vowels of a String-反转字符串中的元音字母](#leetcode---345-reverse-vowels-of-a-string-反转字符串中的元音字母) + - [LeetCode - 125. Valid Palindrome-验证回文串](#leetcode---125-valid-palindrome-验证回文串) + - [LeetCode - 167. Two Sum II - Input array is sorted-两数之和 II - 输入有序数组](#leetcode---167-two-sum-ii---input-array-is-sorted-两数之和-ii---输入有序数组) + - [LeetCode - 11. Container With Most Water-盛最多水的容器](#leetcode---11-container-with-most-water-盛最多水的容器) + +*** +### LeetCode - 344. Reverse String-反转字符串 +#### [题目链接](https://leetcode.com/problems/reverse-string/description/) + +> https://leetcode.com/problems/reverse-string/description/ + +#### 题目 +![在这里插入图片描述](images/344_t.png) +#### 解析 +两种做法: + + - 可以使用下标的对应关系,调换`i`和`(len - 1 - i)`的位置; + - 也可以使用两个指针`l`、`r`,分别指向数组的开始和结尾,然后两个指针不停的交换对应的字符,并逐渐向中间移动; + +```java +class Solution { + public void reverseString(char[] s) { + for(int l = 0, r = s.length-1; l < r; l++, r--){ + char tc = s[l]; + s[l] = s[r]; + s[r] = tc; + } + } +} +``` + +```java +class Solution { + public void reverseString(char[] s) { + int n = s.length; + for(int i = 0; i < n/2; i++){ + char tc = s[i]; + s[i] = s[n - i - 1]; + s[n - i - 1] = tc; + } + } +} +``` + +*** +### LeetCode - 345. Reverse Vowels of a String-反转字符串中的元音字母 +#### [题目链接](https://leetcode.com/problems/reverse-vowels-of-a-string/description/) + +> https://leetcode.com/problems/reverse-vowels-of-a-string/description/ + +#### 题目 +![在这里插入图片描述](images/345_t.png) +#### 解析 +也是使用两个指针扫描,从两边到中间扫: + + - 只要其中一个不是元音字母,那个指针就往中间移动,继续下一次循环; + - 只有两个都是元音字母,才交换位置; + +```java +class Solution { + + public boolean isU(char c) { + return c == 'a' || c == 'o' || c == 'e' || c == 'u' || c == 'i' + || c == 'A' || c == 'O' || c == 'E' || c == 'U' || c == 'I'; + } + + public String reverseVowels(String s) { + char[] str = s.toCharArray(); + for (int l = 0, r = str.length - 1; l < r; ) { + if (!isU(str[l])) { + l++; + continue; + } + if (!isU(str[r])) { + r--; + continue; + } + if (isU(str[l]) && isU(str[r])) { + char t = str[l]; + str[l] = str[r]; + str[r] = t; + l++; + r--; + } + } + return String.valueOf(str); + } +} +``` +*** +### LeetCode - 125. Valid Palindrome-验证回文串 +#### [题目链接](https://leetcode.com/problems/valid-palindrome/description/) +> https://leetcode.com/problems/valid-palindrome/description/ + +#### 题目 + +![在这里插入图片描述](images/125_t.png) +#### 解析 +也是使用双指针,过程和上面的类似,不过要处理一下大小写和数字的问题; +```java +class Solution { + + public boolean isC(char c) { + return Character.isLetter(c); + } + + public boolean isD(char c) { + return Character.isDigit(c); + } + + public boolean ok(char lc, char rc) { + return (isD(lc) && isD(rc) && lc == rc) || + (isC(lc) && isC(rc) && Character.toLowerCase(lc) == Character.toLowerCase(rc)); + } + + + public boolean isPalindrome(String s) { + + boolean flag = true; + char[] str = s.toCharArray(); + for (int l = 0, r = str.length - 1; l <= r; ) { + if (!isC(str[l]) && !isD(str[l])) { + l++; + continue; + + } + if (!isC(str[r]) && !isD(str[r])) { + r--; + continue; + } + if (ok(str[l], str[r])) { + l++; + r--; + } else { + flag = false; + break; + } + } + return flag; + } +} +``` + +或者更加简单的写法: + +```java +class Solution { + + public boolean isLD(char c) { + return Character.isLetterOrDigit(c); + } + + public boolean isPalindrome(String s) { + s = s.toLowerCase(); + + char[] str = s.toCharArray(); + + for (int l = 0, r = str.length - 1; l <= r; ) { + if (!isLD(str[l])) + l++; + else if (!isLD(str[r])) + r--; + else if (str[l] != str[r]) + return false; + else { + l++; + r--; + } + } + return true; + } +} +``` +*** +### LeetCode - 167. Two Sum II - Input array is sorted-两数之和 II - 输入有序数组 +#### [题目链接](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/) + +> https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/ + +#### 题目 +![在这里插入图片描述](images/167_t.png) +#### 解析 +两种做法: + + - 对于每一个`arr[i]`,在后面的有序数组中二分查找有没有可以匹配的,时间复杂度`n * logn`; + - 使用双指针,因为是有序的,所以可以通过比较大小决定哪个指针的移动,时间复杂度` n` ; + +```java +class Solution { + //n * logn + public int[] twoSum(int[] numbers, int target) { + for (int i = 0; i < numbers.length; i++) { + // binary search + int L = i + 1, R = numbers.length - 1; + while (L <= R) { + int mid = L + (R - L) / 2; + if (numbers[mid] + numbers[i] == target) + return new int[]{i + 1, mid + 1}; + else if (numbers[mid] + numbers[i] < target) + L = mid + 1; + else + R = mid - 1; + } + + } + return null; + } +} +``` +双指针: + +```java +class Solution { + public int[] twoSum(int[] numbers, int target) { + for (int l = 0, r = numbers.length - 1; l < r; ) { + if (numbers[l] + numbers[r] == target) + return new int[]{l + 1, r + 1}; + else if (numbers[l] + numbers[r] < target) + l++; + else + r--; + } + return null; + } +} +``` +*** +### LeetCode - 11. Container With Most Water-盛最多水的容器 +#### [题目链接](https://leetcode.com/problems/container-with-most-water/description/) + +> https://leetcode.com/problems/container-with-most-water/description/ + +#### 题目 +![在这里插入图片描述](images/11_t.png) +#### 解析 +这种方法背后的思路在于,**两线段之间形成的区域总是会受到其中较短那条长度的限制。此外,两线段距离越远,得到的面积就越大**。 + +使用双指针: + + - 两个指针从两边开始出发,遍历的过程中使用一个变量`max`记录最大值; + - 因为`x`轴方向每两个之间的宽度是相同的,所以向中间移动的时候,要选择高度更小的那个向中间移动; + +```java +class Solution { + public int maxArea(int[] height) { + int max = 0; + for (int l = 0, r = height.length - 1; l < r; ) { + if (max < (r - l) * (Math.min(height[l], height[r]))) { + max = (r - l) * (Math.min(height[l], height[r])); + } + if (height[l] > height[r]) + r--; + else + l++; + } + return max; + } +} +``` diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554772036936.png b/Algorithm/LeetCode/TwoPointer/assets/1554772036936.png new file mode 100644 index 00000000..e72ba853 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554772036936.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554772480646.png b/Algorithm/LeetCode/TwoPointer/assets/1554772480646.png new file mode 100644 index 00000000..699d33b1 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554772480646.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554796953696.png b/Algorithm/LeetCode/TwoPointer/assets/1554796953696.png new file mode 100644 index 00000000..9d5f59b5 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554796953696.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554797073969.png b/Algorithm/LeetCode/TwoPointer/assets/1554797073969.png new file mode 100644 index 00000000..4f8d76db Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554797073969.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554858757605.png b/Algorithm/LeetCode/TwoPointer/assets/1554858757605.png new file mode 100644 index 00000000..f78d89ab Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554858757605.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554859322815.png b/Algorithm/LeetCode/TwoPointer/assets/1554859322815.png new file mode 100644 index 00000000..f05903b6 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554859322815.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554876914333.png b/Algorithm/LeetCode/TwoPointer/assets/1554876914333.png new file mode 100644 index 00000000..4f9c4a89 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554876914333.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554877108236.png b/Algorithm/LeetCode/TwoPointer/assets/1554877108236.png new file mode 100644 index 00000000..2536752e Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554877108236.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554944597829.png b/Algorithm/LeetCode/TwoPointer/assets/1554944597829.png new file mode 100644 index 00000000..055a7eec Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554944597829.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554945695956.png b/Algorithm/LeetCode/TwoPointer/assets/1554945695956.png new file mode 100644 index 00000000..ac769108 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554945695956.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1554946338715.png b/Algorithm/LeetCode/TwoPointer/assets/1554946338715.png new file mode 100644 index 00000000..1d36aa35 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1554946338715.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1555050932710.png b/Algorithm/LeetCode/TwoPointer/assets/1555050932710.png new file mode 100644 index 00000000..c49ef7ed Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1555050932710.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1555051471920.png b/Algorithm/LeetCode/TwoPointer/assets/1555051471920.png new file mode 100644 index 00000000..eca034e9 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1555051471920.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1555057726155.png b/Algorithm/LeetCode/TwoPointer/assets/1555057726155.png new file mode 100644 index 00000000..2fc8b4eb Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1555057726155.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1555063406368.png b/Algorithm/LeetCode/TwoPointer/assets/1555063406368.png new file mode 100644 index 00000000..0dd10aaa Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1555063406368.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1557823000431.png b/Algorithm/LeetCode/TwoPointer/assets/1557823000431.png new file mode 100644 index 00000000..22ce51c2 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1557823000431.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1557823315086.png b/Algorithm/LeetCode/TwoPointer/assets/1557823315086.png new file mode 100644 index 00000000..a82cf7da Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1557823315086.png differ diff --git a/Algorithm/LeetCode/TwoPointer/assets/1557824207866.png b/Algorithm/LeetCode/TwoPointer/assets/1557824207866.png new file mode 100644 index 00000000..ad4e8a0e Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/assets/1557824207866.png differ diff --git a/Algorithm/LeetCode/TwoPointer/images/11_s.png b/Algorithm/LeetCode/TwoPointer/images/11_s.png new file mode 100644 index 00000000..e3e0e821 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/11_s.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/11_t.png" b/Algorithm/LeetCode/TwoPointer/images/11_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/11_t.png" rename to Algorithm/LeetCode/TwoPointer/images/11_t.png diff --git a/Algorithm/LeetCode/TwoPointer/images/11_tt.png b/Algorithm/LeetCode/TwoPointer/images/11_tt.png new file mode 100644 index 00000000..0e957636 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/11_tt.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/125_t.png" b/Algorithm/LeetCode/TwoPointer/images/125_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/125_t.png" rename to Algorithm/LeetCode/TwoPointer/images/125_t.png diff --git a/Algorithm/LeetCode/TwoPointer/images/15_s.png b/Algorithm/LeetCode/TwoPointer/images/15_s.png new file mode 100644 index 00000000..66ed026b Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/15_s.png differ diff --git a/Algorithm/LeetCode/TwoPointer/images/15_s2.png b/Algorithm/LeetCode/TwoPointer/images/15_s2.png new file mode 100644 index 00000000..5d9aa167 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/15_s2.png differ diff --git a/Algorithm/LeetCode/TwoPointer/images/15_t.png b/Algorithm/LeetCode/TwoPointer/images/15_t.png new file mode 100644 index 00000000..c4faa022 Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/15_t.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/167_t.png" b/Algorithm/LeetCode/TwoPointer/images/167_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/167_t.png" rename to Algorithm/LeetCode/TwoPointer/images/167_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/209_t.png" b/Algorithm/LeetCode/TwoPointer/images/209_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/209_t.png" rename to Algorithm/LeetCode/TwoPointer/images/209_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/283_t.png" b/Algorithm/LeetCode/TwoPointer/images/283_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/283_t.png" rename to Algorithm/LeetCode/TwoPointer/images/283_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/344_t.png" b/Algorithm/LeetCode/TwoPointer/images/344_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/344_t.png" rename to Algorithm/LeetCode/TwoPointer/images/344_t.png diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/345_t.png" b/Algorithm/LeetCode/TwoPointer/images/345_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/345_t.png" rename to Algorithm/LeetCode/TwoPointer/images/345_t.png diff --git a/Algorithm/LeetCode/TwoPointer/images/3_s.png b/Algorithm/LeetCode/TwoPointer/images/3_s.png new file mode 100644 index 00000000..df1a429e Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/3_s.png differ diff --git a/Algorithm/LeetCode/TwoPointer/images/3_s_2.png b/Algorithm/LeetCode/TwoPointer/images/3_s_2.png new file mode 100644 index 00000000..8e34022b Binary files /dev/null and b/Algorithm/LeetCode/TwoPointer/images/3_s_2.png differ diff --git "a/\345\210\267\351\242\230/LeetCode/Two Pointer/images/3_t.png" b/Algorithm/LeetCode/TwoPointer/images/3_t.png similarity index 100% rename from "\345\210\267\351\242\230/LeetCode/Two Pointer/images/3_t.png" rename to Algorithm/LeetCode/TwoPointer/images/3_t.png diff --git a/Algorithm/LeetCode/assets/1554710794888.png b/Algorithm/LeetCode/assets/1554710794888.png new file mode 100644 index 00000000..2250f2ae Binary files /dev/null and b/Algorithm/LeetCode/assets/1554710794888.png differ diff --git "a/Algorithm/Other/AtCoder/AtCoder Beginner Contest 120 - D. Decayed(\345\271\266\346\237\245\351\233\206\345\244\247\345\260\217(\344\270\215\346\230\257rank)).md" "b/Algorithm/Other/AtCoder/AtCoder Beginner Contest 120 - D. Decayed(\345\271\266\346\237\245\351\233\206\345\244\247\345\260\217(\344\270\215\346\230\257rank)).md" new file mode 100644 index 00000000..ec0a442f --- /dev/null +++ "b/Algorithm/Other/AtCoder/AtCoder Beginner Contest 120 - D. Decayed(\345\271\266\346\237\245\351\233\206\345\244\247\345\260\217(\344\270\215\346\230\257rank)).md" @@ -0,0 +1,117 @@ +## AtCoder Beginner Contest 120 - D. Decayed(并查集大小(不是rank)) + +#### [题目链接](https://atcoder.jp/contests/abc120/tasks/abc120_d) + +> https://atcoder.jp/contests/abc120/tasks/abc120_d + +#### 题目 + +给你`n`个点、`m`条边,生成了一个无向图。 + +接下来,这`m`条边将会从`0 ~ m-1`的顺序逐渐的消失,问你每次消失一条边之后,这个图中有多少对点``是不能相互到达的。 + +![begin120_D_t.png](images/begin120_D_t.png) + +![begin120_D_t2.png](images/begin120_D_t2.png) + +#### 解析 + +这题肯定要想到并查集,如果不用并查集,时间复杂度会很高。 + +用并查集每次来寻找两个在不在同一个集合中。但是这个并查集是基于每一个集合会维护一个`size`,表示这个集合的`size`。 + +我们的思路是从`m-1 ~ 0`的方向做。 + +当我们去掉`m`条边之后,不能相互到达的数目肯定是`n * (n-1) / 2`; + +然后相当于每次从`m-1 ~ 0`不断的加上一条边,每次加上一条边我们就要合并边对应的两个顶点的集合。 + +然后`cur`要减去当前边的两个顶点的两个集合的数量之积。 + +具体自己推敲一下。注意最后一个就不要加入`res`了。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static int[] parent; + static int[] sz; // sz[i]表示以i为根的集合中元素个数 + + static int findRoot(int p) { + while (p != parent[p]) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + static void union(int a, int b) { + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + /** + * 根据两个元素所在树的元素个数不同判断合并方向, 将元素个数少的集合合并到元素个数多的集合上 + */ + if (sz[aRoot] < sz[bRoot]) { + parent[aRoot] = bRoot; + sz[bRoot] += sz[aRoot]; + sz[aRoot] = sz[bRoot];//可要可不要 + } else { + parent[bRoot] = aRoot; + sz[aRoot] += sz[bRoot]; + sz[bRoot] = sz[aRoot];//可要可不要 + } + } + + static int n, m; + + static void solve(Scanner in) { + n = in.nextInt(); + m = in.nextInt(); + parent = new int[n + 1]; + sz = new int[n + 1]; + /** 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合 */ + for (int i = 1; i <= n; i++) { + parent[i] = i; + sz[i] = 1; + } + int[] a = new int[m+1]; + int[] b = new int[m+1]; + for(int i = 0; i < m; i++){ + a[i] = in.nextInt(); + b[i] = in.nextInt(); + } + + long cur = ((long)n * (long)(n-1)) / 2L;//所有的边都去掉之后的不能相互到达的点的对数 + + long[] res = new long[m+1]; + int p = m; + res[p--] = cur; + for(int i = m - 1; i >= 0; i--){ + int aR = findRoot(a[i]); + int bR = findRoot(b[i]); + if(aR == bR ){ //如果 a[i]和b[i]已经在同一个集合了,则不需要-了 + res[p--] = cur; + continue; + } + cur -= (long)sz[aR] * (long)sz[bR]; + res[p--] = cur; + union(a[i], b[i]); + } + for(int i = 1; i <= m; i++) out.println(res[i]);//注意没有要最后一个res[0] + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + solve(in); + } +} +``` + diff --git a/Algorithm/Other/AtCoder/images/begin120_D_t.png b/Algorithm/Other/AtCoder/images/begin120_D_t.png new file mode 100644 index 00000000..ddc6693e Binary files /dev/null and b/Algorithm/Other/AtCoder/images/begin120_D_t.png differ diff --git a/Algorithm/Other/AtCoder/images/begin120_D_t2.png b/Algorithm/Other/AtCoder/images/begin120_D_t2.png new file mode 100644 index 00000000..558dab90 Binary files /dev/null and b/Algorithm/Other/AtCoder/images/begin120_D_t2.png differ diff --git a/Algorithm/Other/CompetitiveProgramming/Notes.md b/Algorithm/Other/CompetitiveProgramming/Notes.md new file mode 100644 index 00000000..62e6ecb4 --- /dev/null +++ b/Algorithm/Other/CompetitiveProgramming/Notes.md @@ -0,0 +1,121 @@ +时间复杂度: + +![1_2.png](images/1_2.png) + +### 1、模运算 + +性质 + +```c +(a + b) mod m = (a mod m + b mod m) mod m +(a − b) mod m = (a mod m − b mod m) mod m +(a · b) mod m = (a mod m · b mod m) mod m +``` + +Usually we want the remainder to always be between `0 ... m−1`. However, in C++ and other languages, `the remainder of a negative number is either zero or negative`. An easy way to make sure there are no negative remainders is to first calculate the remainder as usual and then add m if the result is negative: + +```cpp +x = x % m; +if (x < 0) x += m; +``` + +### 2、Generating Subsets + +`n = 3`的搜索图: + +![1_1.png](images/1_1.png) + +代码: + +```java +import java.util.ArrayList; + +public class Code_01_SubSet { + + static ArrayList subset; + static ArrayList> res; + static int n; + + static void search(int k) { + if (k == n+1) { // process subset + res.add(new ArrayList<>(subset)); + } else { // include k in the subset + subset.add(k); + search(k+1); + subset.remove(subset.size() - 1); // don’t include k in the subset + search(k+1); + } + } + + public static void main(String[] args){ + n = 4; + subset = new ArrayList<>(); + res = new ArrayList<>(); + search(1); + System.out.println(res); + } +} +``` + +### 3、Permutation + +```java +import java.util.ArrayList; + +public class Code_02_Permutation { + + static ArrayList tmp; + static ArrayList> res; + static int n; + static boolean[] vis; + + static void search(){ + if(tmp.size() == n){ + res.add(new ArrayList<>(tmp)); + }else { + for(int i = 1; i <= n; i++){ + if(vis[i]) continue; + tmp.add(i); + vis[i] = true; + search(); + tmp.remove(tmp.size() - 1); + vis[i] = false; + } + } + } + + public static void main(String[] args){ + n = 3; + vis = new boolean[n+1]; + tmp = new ArrayList<>(); + res = new ArrayList<>(); + search(); + System.out.println(res); + } +} + +``` + +### 4、Bit + +It is also possible to modify single bits of numbers using similar ideas. The formula `x | (1 << k) `sets the kth bit of x to one, the formula `x & ~(1 << k) `sets the kth bit of x to zero, and the formula `x ˆ (1 << k) `inverts the kth bit of x. Then, the formula `x & (x − 1)` sets the last one bit of x to zero, and the formula `x & −x` sets all the one bits to zero, except for the last one bit. The formula `x | (x − 1)` inverts all the bits after the last one bit. Finally, a positive number x is a power of two exactly when `x & (x − 1) = 0`. +One pitfall when using bit masks is that `1<> i) & 1) != 0) + if( ((1 << i) & mask) != 0) + System.out.print(i + " "); + System.out.println(); + } + } +} +``` + diff --git a/Algorithm/Other/CompetitiveProgramming/images/1_1.png b/Algorithm/Other/CompetitiveProgramming/images/1_1.png new file mode 100644 index 00000000..da32fedf Binary files /dev/null and b/Algorithm/Other/CompetitiveProgramming/images/1_1.png differ diff --git a/Algorithm/Other/CompetitiveProgramming/images/1_2.png b/Algorithm/Other/CompetitiveProgramming/images/1_2.png new file mode 100644 index 00000000..71852d0c Binary files /dev/null and b/Algorithm/Other/CompetitiveProgramming/images/1_2.png differ diff --git "a/Algorithm/Other/Hdu/DP/Hdu - 2602. Bone Collector(01\350\203\214\345\214\205\351\227\256\351\242\230).md" "b/Algorithm/Other/Hdu/DP/Hdu - 2602. Bone Collector(01\350\203\214\345\214\205\351\227\256\351\242\230).md" new file mode 100644 index 00000000..10a40a12 --- /dev/null +++ "b/Algorithm/Other/Hdu/DP/Hdu - 2602. Bone Collector(01\350\203\214\345\214\205\351\227\256\351\242\230).md" @@ -0,0 +1,179 @@ +## Hdu - 2602. Bone Collector(01背包问题) + - 递归写法 + - 二维dp + - 一维dp + +*** +#### [题目链接](http://acm.hdu.edu.cn/showproblem.php?pid=2602) + +> http://acm.hdu.edu.cn/showproblem.php?pid=2602 + +#### 题目 + +![](images/2602_t.png) + +### 递归写法 + +递归的思想就是: + + - **我要计算的是从第`0`号物体到`n-1`号物体的最大重量;** + - **记录一个当前判断到`i`号物体时,已经选择的物体的容量`curW`;** + - 如果当前剩余容量`C - curW < w[i]`,当前物品就不能选; + - s如果`i`越界的话就返回`0`; + - 否则我就递归的去求**选择了`i`号物品的最大值和不选择`i`号物品的最大值**中,我们要取的最大值; + +因为上面的时间复杂度为`O(2^n)`,我们需要使用一个二维数组来记录计算过的重复子问题。(也就是记忆化) + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int[] w; + static int[] v; + static int n, C; + static int[][] dp; + + static int rec(int pos, int curW) { + if (pos == n) + return 0; + if (dp[pos][curW] != -1) return dp[pos][curW]; + int res; + if (C - curW < w[pos]) // 这里是 C-curW + res = rec(pos + 1, curW); + else + res = Math.max(rec(pos + 1, curW), rec(pos + 1, curW + w[pos]) + v[pos]); + return dp[pos][curW] = res; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int T = in.nextInt(); + while (T-- > 0) { + n = in.nextInt(); + C = in.nextInt(); + w = new int[n]; + v = new int[n]; + dp = new int[n][C + 1]; // C+1不是C注意 + for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1); + for (int i = 0; i < n; i++) v[i] = in.nextInt(); + for (int i = 0; i < n; i++) w[i] = in.nextInt(); + System.out.println(rec(0, 0)); //这里是0了 + } + } +} +``` +另一种是从反向记录剩余体积的,从`curW`变成`remain`。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int[] w; + static int[] v; + static int n, C; + static int[][] dp; + + static int rec(int pos, int remain) { + if (pos == n) + return 0; // 没有物品了 + if (dp[pos][remain] != -1) return dp[pos][remain]; + int res; + if (remain < w[pos]) // 无法挑选这个物品 + res = rec(pos + 1, remain); + else //挑选和不挑选都尝试一下 + res = Math.max(rec(pos + 1, remain), rec(pos + 1, remain - w[pos]) + v[pos]); + return dp[pos][remain] = res; + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int T = in.nextInt(); + while (T-- > 0) { + n = in.nextInt(); + C = in.nextInt(); + w = new int[n]; + v = new int[n]; + dp = new int[n][C + 1]; // C+1不是C注意 + for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1); + for (int i = 0; i < n; i++) v[i] = in.nextInt(); + for (int i = 0; i < n; i++) w[i] = in.nextInt(); + System.out.println(rec(0, C)); + } + } +} +``` + +### 二维dp + +下面是通过从最后一个物品开始考虑,也可以从第一个物品开始考虑。 + +![这里写图片描述](images/2602_s.png) +![这里写图片描述](images/2602_s2.png) + +(输入就不处理了,和上面的一样) + +```java +public class Main { + + static int maxValue() { + int[][] dp = new int[n + 1][C + 1];//注意是n+1,那样我们就不要一开始考虑初始化了 +// for (int j = 0; j <= C; j++) dp[n][j] = 0; //auto initialize to 0 + for (int i = n - 1; i >= 0; i--) { + for (int j = C; j >= 0; j--) { + dp[i][j] = j + w[i] > C ? dp[i + 1][j] : + Math.max(dp[i + 1][j], dp[i + 1][j + w[i]] + v[i]); + } + } + return dp[0][0]; + } +} +``` +空间复杂度可以从`O(n*C)`优化到`O(2*C)`,因为每次都只使用了上下两行的内容: + +每次都只使用`0、1`这两个第一维。 + +```java +public class Main { + + static int maxValue2() { + int[][] dp = new int[2][C + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int j = C; j >= 0; j--) { + dp[i&1][j] = j + w[i] > C ? dp[(i + 1)&1][j] : + Math.max(dp[(i + 1)&1][j], dp[(i + 1)&1][j + w[i]] + v[i]); + } + } + return dp[0][0]; + } +} +``` + +### 一维dp + +也就是滚动优化,`dp`用来记录列。 +```java +public class Main { + static int maxValue() { + int[] dp = new int[C + 1]; + for (int i = n - 1; i >= 0; i--) { + for (int j = 0; j <= C; j++) { + dp[j] = j + w[i] > C ? dp[j] : Math.max(dp[j], dp[j + w[i]] + v[i]); + } + } + return dp[0]; + } +} +``` + + - 这一行代码`for (int j = 0; j <= C; j++)` 千万不能写成下面的样子(也就是不能和二维`dp`一样从`C --> 0`): + - 因为这样的话我们的需要的`dp[j + w[i]] `此时已经更新了,不是我们需要的了。 + +```java + for (int j = C; j >= 0 ; j--) //万万不可 +``` + +![这里写图片描述](images/2602_s3.png) diff --git "a/\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s.png" b/Algorithm/Other/Hdu/DP/images/2602_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s.png" rename to Algorithm/Other/Hdu/DP/images/2602_s.png diff --git "a/\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s2.png" b/Algorithm/Other/Hdu/DP/images/2602_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s2.png" rename to Algorithm/Other/Hdu/DP/images/2602_s2.png diff --git "a/\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s3.png" b/Algorithm/Other/Hdu/DP/images/2602_s3.png similarity index 100% rename from "\345\210\267\351\242\230/Other/Hdu/DP/images/2602_s3.png" rename to Algorithm/Other/Hdu/DP/images/2602_s3.png diff --git a/Algorithm/Other/Hdu/DP/images/2602_t.png b/Algorithm/Other/Hdu/DP/images/2602_t.png new file mode 100644 index 00000000..5bafeff3 Binary files /dev/null and b/Algorithm/Other/Hdu/DP/images/2602_t.png differ diff --git "a/Algorithm/Other/Interview/\344\272\254\344\270\234-\345\257\273\346\211\276\345\255\220\344\270\262.md" "b/Algorithm/Other/Interview/\344\272\254\344\270\234-\345\257\273\346\211\276\345\255\220\344\270\262.md" new file mode 100644 index 00000000..188eaac5 --- /dev/null +++ "b/Algorithm/Other/Interview/\344\272\254\344\270\234-\345\257\273\346\211\276\345\255\220\344\270\262.md" @@ -0,0 +1,93 @@ +# 京东-寻找子串 + +#### 题目 + +给出`m`个字符串`S1、S2、S3...Sm`和一个单独的字符串`T`,请在T中选出尽可能多的子串同时满足: + +* 1)、这些子串在`T`中互不相交; +* 这些子串都是`S1、S2、S3...Sm`中的某个串; + +问你最多能选出多少个子串。 + +#### 输入输出 + +给出m个字符串,接下来m行,每行一个串,最后一行输入一个串`T`。输出最多能选出多少个串。 + +```java +输入: +3 +aa +b +ac +bbaac +输出: +3 +``` + +### 解析 + +DP。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + int m = in.nextInt(); + String[] S = new String[m]; + for (int i = 0; i < m; i++) S[i] = in.next(); + String T = in.next(); + int[] dp = new int[T.length() + 1]; + int tLen = T.length(); + for (int i = 1; i < tLen + 1; i++) { + dp[i] = dp[i - 1]; + for (int j = 0; j < m; j++) { + if (i - S[j].length() >= 0) + if (T.substring(i - S[j].length(), i).equals(S[j])) + dp[i] = Math.max(dp[i], dp[i - S[j].length()] + 1); + } + } + System.out.println(dp[tLen]); + } + + /*--------------------------------------------------------------------------------------*/ + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} +``` + diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/LintCode - 861. K Empty Slots (BST _ \346\241\266).md" "b/Algorithm/Other/LintCode/Data Structure/Bucket/LintCode - 861. K Empty Slots (BST _ \346\241\266).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/LintCode - 861. K Empty Slots (BST _ \346\241\266).md" rename to "Algorithm/Other/LintCode/Data Structure/Bucket/LintCode - 861. K Empty Slots (BST _ \346\241\266).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/images/861_s.png" b/Algorithm/Other/LintCode/Data Structure/Bucket/images/861_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/images/861_s.png" rename to Algorithm/Other/LintCode/Data Structure/Bucket/images/861_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/images/861_t.png" b/Algorithm/Other/LintCode/Data Structure/Bucket/images/861_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Bucket/images/861_t.png" rename to Algorithm/Other/LintCode/Data Structure/Bucket/images/861_t.png diff --git a/Algorithm/Other/LintCode/Data Structure/Map/LintCode - 856. Sentence Similarity.md b/Algorithm/Other/LintCode/Data Structure/Map/LintCode - 856. Sentence Similarity.md new file mode 100644 index 00000000..3d8396d3 --- /dev/null +++ b/Algorithm/Other/LintCode/Data Structure/Map/LintCode - 856. Sentence Similarity.md @@ -0,0 +1,66 @@ +## LintCode - 856. Sentence Similarity + +#### [题目链接](https://www.lintcode.com/problem/sentence-similarity/description) + +> https://www.lintcode.com/problem/sentence-similarity/description + +#### 题目 + +![856_t.png](images/856_t.png) + +样例1: + +```java +输入: words1 = ["great","acting","skills"], words2 = ["fine","drama","talent"] and pairs = [["great","fine"],["drama","acting"],["skills","talent"]] +输出: true +解释: +"great"和"fine"相似 +"acting"和"drama"相似 +"skills"和"talent"相似 +``` + +样例2: + +```java +输入: words1 = ["fine","skills","acting"], words2 = ["fine","drama","talent"] and pairs = [["great","fine"],["drama","acting"],["skills","talent"]] +输出: false +解释: +"fine"和"fine"相同 +"skills"和"drama"不相似 +"acting"和"talent"不相似 +``` + +### 解析 + +需要注意的是,一个单词可能对应多个相似的单词,所以我们要用一个`Set`来存储第二维。 + +```java +import java.util.*; + +public class Solution { + + public boolean isSentenceSimilarity(String[] words1, String[] words2, List> pairs) { + if(words1.length != words2.length) return false; + HashMap> map = new HashMap<>(); + for(List pair : pairs){ + String s1 = pair.get(0); + String s2 = pair.get(1); +// if(!map.containsKey(s1)) +// map.put(s1, new HashSet<>()); +// if(!map.containsKey(s2)) +// map.put(s2, new HashSet<>()); + map.putIfAbsent(s1, new HashSet<>()); + map.putIfAbsent(s2, new HashSet<>()); + map.get(s1).add(s2); + map.get(s2).add(s1); + } + for(int i = 0; i < words1.length; i++){ + if(words1[i] == words2[i]) continue; + if(!map.containsKey(words1[i])) return false; + if(!map.get(words1[i]).contains(words2[i])) return false; + } + return true; + } +} +``` + diff --git a/Algorithm/Other/LintCode/Data Structure/Map/images/856_t.png b/Algorithm/Other/LintCode/Data Structure/Map/images/856_t.png new file mode 100644 index 00000000..8aefc455 Binary files /dev/null and b/Algorithm/Other/LintCode/Data Structure/Map/images/856_t.png differ diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" "b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" similarity index 89% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" rename to "Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" index 85a834f6..95650bae 100644 --- "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" +++ "b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 122. Largest Rectangle in Histogram(\347\233\264\346\226\271\345\233\276\346\234\200\345\244\247\347\237\251\345\275\242\350\246\206\347\233\226)(\345\215\225\350\260\203\346\240\210).md" @@ -6,20 +6,22 @@ #### 题目 ![这里写图片描述](images/122_t.png) -**给出 `height = [2,1,5,6,2,3]`,返回`10`。** +**给出 `height = [2,1,5,6,2,3]`,返回`10`**。 ### 解析 -主要是运用单调栈(单调栈的知识[**这篇博客**](https://blog.csdn.net/zxzxzx0119/article/details/81629626))来解决,注意这个栈是 从栈底到栈顶依次是从小到大的: +主要是运用单调栈(单调栈的知识[**这篇博客**](https://blog.csdn.net/zxzxzx0119/article/details/81629626))来解决,注意这个栈是 **从栈底到栈顶依次是从小到大的**: - 如果栈中的数比当前的数大(或者等于)就要处理栈顶的(记录左右两边的比它小的第一个数); - 然后如果遍历完之后,单独处理栈,此时所有元素右边都不存在比它小的; -看下图(上面的例子)栈的变化过程: +看下图(上面的例子)栈的变化过程: (**栈的左侧是索引,右侧是值,但是实际中栈只存索引**) ![在这里插入图片描述](images/122_s.png) +代码: + ```java public class Solution { /** diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" "b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" similarity index 87% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" rename to "Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" index ac10eec6..4ab23be9 100644 --- "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" +++ "b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/LintCode - 510. Maximal Rectangle(\346\234\200\345\244\247\347\237\251\345\275\242)(\345\215\225\350\260\203\346\240\210).md" @@ -11,8 +11,8 @@ ### 解析 这个题目就是[**直方图最大矩形覆盖**](https://blog.csdn.net/zxzxzx0119/article/details/81630814)的一个变式: -我们只需要求出以每一行作为底最大的矩形是多少,每一行都有一个`height`数组,把每个数组都调用上个题目的方法就可以求出,以每一行作为底(直方图最下面)时最大矩形面积,然后记录最大值即可。 -**关键就是每次更新`height`数组,`height`数组代表的是这一列上面有多少个连续的`1`** +我们只需要求出以**每一行作为底最大的矩形是多少**,每一行都有一个`height`数组,把每个数组都调用上个题目的方法就可以求出,以每一行作为底(直方图最下面)时最大矩形面积,然后记录最大值即可。 +**关键就是每次更新`height`数组,`height`数组代表的是这一列上面有多少个连续的**`1` 例如: @@ -24,7 +24,7 @@ map = 1 0 1 1 以第一行做切割后,`height = {1, 0, 1, 1}`,`height[j]`表示目前的底上(第`1`行),`j`位置往上(包括`j`位置)有多少连续的`1`; -以第`2`行做切割后,`height = {2, 1, 2, 2}`,注意到从第一行到第二行,`height`数组的更新是十分方便的,即`height[j] = map[i][j] == 0 ? 0 : height[j] + 1`; +以第`2`行做切割后,`height = {2, 1, 2, 2}`,注意到从第一行到第二行,`height`数组的更新是十分方便的,即`height[j] = map[i][j] == 0 ? 0 : height[j] + 1`;(这里有点dp的意思) 第`3`行做切割后,`height = {3, 2, 3, 0}`; diff --git a/Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/122_s.png b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/122_s.png new file mode 100644 index 00000000..54254f38 Binary files /dev/null and b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/122_s.png differ diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/images/122_t.png" b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/122_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/images/122_t.png" rename to Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/122_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/images/510_t.png" b/Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/510_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/MonotoneStack/images/510_t.png" rename to Algorithm/Other/LintCode/Data Structure/MonotoneStack/images/510_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/LintCode - 131. The Skyline Problem(\345\273\272\347\255\221\347\232\204\350\275\256\345\273\223)(\345\210\251\347\224\250\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\247\243\345\206\263).md" "b/Algorithm/Other/LintCode/Data Structure/Set/LintCode - 131. The Skyline Problem(\345\273\272\347\255\221\347\232\204\350\275\256\345\273\223)(\345\210\251\347\224\250\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\247\243\345\206\263).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/LintCode - 131. The Skyline Problem(\345\273\272\347\255\221\347\232\204\350\275\256\345\273\223)(\345\210\251\347\224\250\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\247\243\345\206\263).md" rename to "Algorithm/Other/LintCode/Data Structure/Set/LintCode - 131. The Skyline Problem(\345\273\272\347\255\221\347\232\204\350\275\256\345\273\223)(\345\210\251\347\224\250\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\247\243\345\206\263).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/images/131_s.png" b/Algorithm/Other/LintCode/Data Structure/Set/images/131_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/images/131_s.png" rename to Algorithm/Other/LintCode/Data Structure/Set/images/131_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/images/131_t.png" b/Algorithm/Other/LintCode/Data Structure/Set/images/131_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Set/images/131_t.png" rename to Algorithm/Other/LintCode/Data Structure/Set/images/131_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" similarity index 98% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" rename to "Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" index 0fc9a77a..a51d92a9 100644 --- "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" +++ "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 370. Convert Expression to Reverse Polish Notation(\350\275\254\346\215\242\344\270\272\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217)(\344\270\255\347\274\200\350\275\254\345\220\216\347\274\200\350\241\250\350\276\276\345\274\217).md" @@ -10,9 +10,9 @@ - 当前字符串是数字,直接添加到结果集; - 当前字符串是`"("`,直接入栈; - - **当前字符串是`")"`,从栈中一直弹出操作符,直到操作符为`")“`,并把这些操作符加入结果集( `")"`不加入 );** - - **当前字符串是`"+"`或者`"-"`,如果栈顶的操作符不是`"("`且栈顶操作符`>="+"`或者`"-"`,这些操作符都要出栈,因为`"+"`和`"-"`本来就是最低优先级的(或者可以从运算的从左到右计算来思考),所以只要栈不空或者栈顶` != "("`就出栈,最后,`"+"`或者`"-"`入栈;** - - **当前字符串是`" * "` 或者`"/" `,如果栈顶是`"*"`或者`"/"`,就要先出栈(也是`>=`的(进来这么久了,透透气)),然后入栈当前字符串(即`"*"`或者`"/"`);** + - **当前字符串是`")"`,从栈中一直弹出操作符,直到操作符为`")“`,并把这些操作符加入结果集( `")"`不加入 )**; + - **当前字符串是`"+"`或者`"-"`,如果栈顶的操作符不是`"("`且栈顶操作符`>="+"`或者`"-"`,这些操作符都要出栈,因为`"+"`和`"-"`本来就是最低优先级的(或者可以从运算的从左到右计算来思考),所以只要栈不空或者栈顶` != "("`就出栈,最后,`"+"`或者`"-"`入栈**; + - **当前字符串是`" * "` 或者`"/" `,如果栈顶是`"*"`或者`"/"`,就要先出栈(也是`>=`的(进来这么久了,透透气)),然后入栈当前字符串(即`"*"`或者`"/"`)**; ![这里写图片描述](images/370_s.png) diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 424. Evaluate Reverse Polish Notation(\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274).md" "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 424. Evaluate Reverse Polish Notation(\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 424. Evaluate Reverse Polish Notation(\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274).md" rename to "Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 424. Evaluate Reverse Polish Notation(\351\200\206\346\263\242\345\205\260\350\241\250\350\276\276\345\274\217\346\261\202\345\200\274).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 849. Basic Calculator III(\345\214\205\346\213\254+ - _ _ \345\222\214\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\345\231\250).md" "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 849. Basic Calculator III(\345\214\205\346\213\254+ - _ _ \345\222\214\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\345\231\250).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 849. Basic Calculator III(\345\214\205\346\213\254+ - _ _ \345\222\214\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\345\231\250).md" rename to "Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 849. Basic Calculator III(\345\214\205\346\213\254+ - _ _ \345\222\214\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\345\231\250).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 978. Basic Calculator(\345\217\252\346\234\211 + - \345\217\267\347\232\204\350\241\250\350\276\276\345\274\217\350\256\241\347\256\227).md" "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 978. Basic Calculator(\345\217\252\346\234\211 + - \345\217\267\347\232\204\350\241\250\350\276\276\345\274\217\350\256\241\347\256\227).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 978. Basic Calculator(\345\217\252\346\234\211 + - \345\217\267\347\232\204\350\241\250\350\276\276\345\274\217\350\256\241\347\256\227).md" rename to "Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 978. Basic Calculator(\345\217\252\346\234\211 + - \345\217\267\347\232\204\350\241\250\350\276\276\345\274\217\350\256\241\347\256\227).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 980. Basic Calculator II(\346\262\241\346\234\211\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\350\241\250\350\276\276\345\274\217).md" "b/Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 980. Basic Calculator II(\346\262\241\346\234\211\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\350\241\250\350\276\276\345\274\217).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/LintCode - 980. Basic Calculator II(\346\262\241\346\234\211\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\350\241\250\350\276\276\345\274\217).md" rename to "Algorithm/Other/LintCode/Data Structure/Stack/LintCode - 980. Basic Calculator II(\346\262\241\346\234\211\346\213\254\345\217\267\347\232\204\350\256\241\347\256\227\350\241\250\350\276\276\345\274\217).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/370_s.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/370_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/370_s.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/370_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/370_t.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/370_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/370_t.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/370_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/424_t.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/424_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/424_t.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/424_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/849_s.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/849_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/849_s.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/849_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/849_t.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/849_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/849_t.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/849_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/978_s.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/978_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/978_s.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/978_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/978_t.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/978_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/978_t.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/978_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/980_s.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/980_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/980_s.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/980_s.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/980_t.png" b/Algorithm/Other/LintCode/Data Structure/Stack/images/980_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Data Structure/Stack/images/980_t.png" rename to Algorithm/Other/LintCode/Data Structure/Stack/images/980_t.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/Search/LintCode - 862. Next Closest Time (\346\232\264\345\212\233 _ DFS).md" "b/Algorithm/Other/LintCode/Search/LintCode - 862. Next Closest Time (\346\232\264\345\212\233 _ DFS).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Search/LintCode - 862. Next Closest Time (\346\232\264\345\212\233 _ DFS).md" rename to "Algorithm/Other/LintCode/Search/LintCode - 862. Next Closest Time (\346\232\264\345\212\233 _ DFS).md" diff --git "a/\345\210\267\351\242\230/Other/LintCode/Search/images/862_t.png" b/Algorithm/Other/LintCode/Search/images/862_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/Search/images/862_t.png" rename to Algorithm/Other/LintCode/Search/images/862_t.png diff --git "a/Algorithm/Other/LintCode/TwoPointer/LintCode - 362. Sliding Window Maximum\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Algorithm/Other/LintCode/TwoPointer/LintCode - 362. Sliding Window Maximum\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" new file mode 100644 index 00000000..f317351d --- /dev/null +++ "b/Algorithm/Other/LintCode/TwoPointer/LintCode - 362. Sliding Window Maximum\346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" @@ -0,0 +1,82 @@ +## LintCode - 362. Sliding Window Maximum(滑动窗口的最大值)(单调队列(窗口滑动)) + + - 单调队列介绍 + - 使用单调队列解决此题 + +*** +#### [题目链接](https://www.lintcode.com/problem/sliding-window-maximum/description) + +> https://www.lintcode.com/problem/sliding-window-maximum/description + +#### 单调队列介绍 +单调队列一开始用来求出**在数组的某个区间范围内求出最大值**。具体的做法如下 + +![这里写图片描述](images/363_t.png) + +准备一个双端队列,队列遵守从左到右从大到小(不能相等)的规则,然后当窗口决定加入一个数,**则`R`往右移动一位**,`L`不动,**并且判断队列中的尾部的数是否比刚刚窗口加入的数小**,如果不是就加入,如果是就一直弹出队列尾部的数,直到比这个要加入的数大,就把这个数**的下标**加入双向链表,由于这里一开始队列为空直接加入。 + +![这里写图片描述](images/362_s.png) + + 再接下来判断`1`位置上的`4`和`2`位置的`1`,他们都比各自的队列尾部小,可以直接进入队列; + +![这里写图片描述](images/362_s3.png) + +再判断`3`位置上的`2`,由于比`1`大(也就是队列的尾部比这个数小),所以把队列尾部弹出一个,`1`弹出,由于`4`比`2`大,就可以放`2`了: + +![这里写图片描述](images/362_s4.png) + +再看窗口中减数的逻辑,当`L`向右移动的时候,检查队列中的头部**有没有过期,也就是还在不在窗口,如果不在,就弹出,如图,弹出`5`,`L`向右移动**: + +![这里写图片描述](images/362_s5.png) + +然后如果`R`再向右移动一位,来到`4`位置上的`6`,队列中的数都比`6`小,所以都弹出: + +![这里写图片描述](images/362_s6.png) + +继续往后,直到最后: + +![这里写图片描述](images/362_s7.png) + + - 上面就是单调队列的窗口加数和减数的操作规则; + - 要注意队列从左到右一直是降序的; + - 要注意减数的判断过期规则; + - 所以在任意的窗口时刻,**队列头部就是这个窗口的最大值**; + +*** +### 使用单调队列解决此题 +### 题意 + +给出一个可能包含重复的整数数组,和一个大小为` k` 的滑动窗口, 从左到右在数组中滑动这个窗口,找到数组中每个窗口内的最大值。要求`O(n)`时间,`O(k)`的额外空间。 + +![在这里插入图片描述](images/362_s8.png) + +### 解析 +这个和上面的唯一区别就是`L、R`是同时向右,每次都要动一格,所以多了一个判断过期的逻辑,`i-k == qmax.peekFirst()`这个,就是`L`向左移动的逻辑,此时要判断队头是否过期。其他的都是和上面一样。 + +![在这里插入图片描述](images/362_s9.png) + +代码: + +```java +public class Solution { + //单调双向队列(窗口内最大值) + public ArrayList maxSlidingWindow(int[] nums, int k) { + if (nums == null || k < 1 || nums.length < k) return null; + ArrayList res = new ArrayList<>(); + LinkedList qmax = new LinkedList<>();//保存的是下标 + for (int i = 0; i < nums.length; i++) { + while (!qmax.isEmpty() && nums[qmax.peekLast()] <= nums[i]) {//要队尾满足条件 + qmax.pollLast(); + } + qmax.addLast(i); + if (i - k == qmax.peekFirst()) { + qmax.pollFirst();//向左弹出 过期的数据s + } + if (i >= k - 1) { + res.add(nums[qmax.peekFirst()]); + } + } + return res; + } +} +``` diff --git a/Algorithm/Other/LintCode/TwoPointer/images/362_s.png b/Algorithm/Other/LintCode/TwoPointer/images/362_s.png new file mode 100644 index 00000000..6706e84c Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/362_s.png differ diff --git a/Algorithm/Other/LintCode/TwoPointer/images/362_s3.png b/Algorithm/Other/LintCode/TwoPointer/images/362_s3.png new file mode 100644 index 00000000..db123c37 Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/362_s3.png differ diff --git "a/\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s4.png" b/Algorithm/Other/LintCode/TwoPointer/images/362_s4.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s4.png" rename to Algorithm/Other/LintCode/TwoPointer/images/362_s4.png diff --git a/Algorithm/Other/LintCode/TwoPointer/images/362_s5.png b/Algorithm/Other/LintCode/TwoPointer/images/362_s5.png new file mode 100644 index 00000000..0c0bbd26 Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/362_s5.png differ diff --git a/Algorithm/Other/LintCode/TwoPointer/images/362_s6.png b/Algorithm/Other/LintCode/TwoPointer/images/362_s6.png new file mode 100644 index 00000000..002962b6 Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/362_s6.png differ diff --git a/Algorithm/Other/LintCode/TwoPointer/images/362_s7.png b/Algorithm/Other/LintCode/TwoPointer/images/362_s7.png new file mode 100644 index 00000000..4da86001 Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/362_s7.png differ diff --git "a/\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s8.png" b/Algorithm/Other/LintCode/TwoPointer/images/362_s8.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s8.png" rename to Algorithm/Other/LintCode/TwoPointer/images/362_s8.png diff --git "a/\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s9.png" b/Algorithm/Other/LintCode/TwoPointer/images/362_s9.png similarity index 100% rename from "\345\210\267\351\242\230/Other/LintCode/TwoPointer/images/362_s9.png" rename to Algorithm/Other/LintCode/TwoPointer/images/362_s9.png diff --git a/Algorithm/Other/LintCode/TwoPointer/images/363_t.png b/Algorithm/Other/LintCode/TwoPointer/images/363_t.png new file mode 100644 index 00000000..7023f89b Binary files /dev/null and b/Algorithm/Other/LintCode/TwoPointer/images/363_t.png differ diff --git "a/\345\210\267\351\242\230/Other/Other/Tree/images/tree1.png" b/Algorithm/Other/Other/images/tree1.png similarity index 100% rename from "\345\210\267\351\242\230/Other/Other/Tree/images/tree1.png" rename to Algorithm/Other/Other/images/tree1.png diff --git "a/\345\210\267\351\242\230/Other/Other/Tree/images/tree2.png" b/Algorithm/Other/Other/images/tree2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/Other/Tree/images/tree2.png" rename to Algorithm/Other/Other/images/tree2.png diff --git "a/Algorithm/Other/Other/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" "b/Algorithm/Other/Other/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" new file mode 100644 index 00000000..e77eba58 --- /dev/null +++ "b/Algorithm/Other/Other/\345\255\220\346\225\260\347\273\204\347\264\257\345\212\240\345\222\214\344\270\272aim(\345\260\217\344\272\216\347\255\211\344\272\216aim)\347\232\204\344\270\211\344\270\252\351\227\256\351\242\230.md" @@ -0,0 +1,162 @@ +# 子数组累加和为aim(小于等于aim)的三个问题 + + - 累加和` = aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); + - 累加和` = aim`的最长子数组的长度(**数组`+`**)(只有正数); + - 累加和` <= aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); + +*** +## 一、累加和` = aim`的最长子数组的长度(**数组可`+`,`-`,`0`**) +这个题目使用`HashMap`来存储前面出现过的累加和的下标,具体过程如下: + + - 使用变量`sum`表示从`0`位置开始一直加到i位置所有元素的累加和; + - `HashMap`中`key`表示从`arr`最左边开始累加过程中出现过的`sum`值,`value`表示的是`sum`值出现最早的位置;、 + - 假设当前元素为`arr[i]`,则`sum += arr[i]`,之前所有累加和为`sum` ,查看`map`中是否有`sum - aim`这个值,如果有,且对应`value`为`j`,那么就找到一个子数组累加和为`aim`的,且长度为 `i - j + 1`; + - 检查现在的`sum `是否在`map`中出现,如果不存在,说明此时是第一次出现的,把`(sum,i)`加入到`map`中; + - 继续遍历数组; + +很重要的一个地方就是一开始`map`中要存`(0,-1)`这个值,直观理解是一个数也没有的时候也可以累加出`0`。 +看下面例子: +`[1,2,3,3] , aim = 6`; +如果没有存`(0,-1)`,累加到下标为`2`的时候,`sum = 6` 此时,`sum - aim = 6 - 6 = 0`,但是没有`0`这个累加和,就会忽略; + +```java + /** O(n)时间 O(n)空间 */ + static int getMaxLength(int[] arr,int aim){ + if(arr == null || arr.length== 0 )return 0; + HashMapmap = new HashMap(); //表示key这个累加和最早出现在value位置 + map.put(0,-1); //这个很重要 + int sum = 0; + int res = 0; + for(int i = 0; i < arr.length; i++){ + sum += arr[i]; + if(map.containsKey(sum - aim)){ //如果之前出现了 + res = Math.max(res,i - map.get(sum - aim)); + } + if(!map.containsKey(sum)){ + map.put(sum,i); + } + } + return res; + } +``` +问题变式: 给定一个数组,求正数和负数个数相等最长子数组长度。 +解: 把正数变成`1`,负数变成`-1`即可。 + + +还有一个扩展问题: + +题目: +> 定义数组的异或和的概念: +> 数组中所有的数异或起来,得到的结果叫做数组的异或和,比如数组`{3, 2, 1}`的异或和是: `3 ^ 2 ^ 1 = 0 `。 +> 给定一个数组`arr`,你可以任意把`arr`分成很多不相容的子数组,你的目的是: 分出来的子数组中,异或和为`0`的子数组最多。 +> 请返回: 分出来的子数组中,异或和为`0`的子数组最多是多少? + +解析: 可以利用这个思想找到**最晚出现和`0~i`内异或和(假设为`xor`)同样异或和的更小的范围内最晚出现的位置,因为最后一个部分是异或和为`0`,且`xor^0 = xor`。 + +![这里写图片描述](https://img-blog.csdn.net/20180816092937744?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: + +```java + /** + * dp[i] = max(dp[i-1],dp[k] + 1) k表示的是i 如果是最优划分中的最后一个部分的最后一个数的话,k是那个部分的开始的地方的前一个 + * 从 0~i-1中异或还是 xor的最晚的位置 : + * @param arr + * @return + */ + static int maxEor(int[] arr){ + HashMapmap = new HashMap<>();//存放某个异或和最晚出现的位置 + int res = 0,xor = 0; + int[] dp = new int[arr.length]; + map.put(0,-1); + for(int i = 0; i < arr.length; i++){ + xor ^= arr[i]; + if(map.containsKey(xor)){// 找到上一个异或和为xor的最晚出现的位置   因为xor^0 = xor + int k = map.get(xor); //k + dp[i] = k == -1 ? 1 : dp[k] + 1; + } + if(i > 0){ + dp[i] = Math.max(dp[i],dp[i-1]); + } + map.put(xor,i); //每次都要put进去 + res = Math.max(dp[i],res); + } + return res; + } +``` + +*** +## 二、累加和` = aim`的最长子数组的长度(**数组`+`**)(只有正数); +这个和上面唯一的不同就是数组中只有正数,这里使用类似窗口移动的做法,给出两个指针,`L、R`表示窗口的左右边界 ,`sum`表示的是`arr[L,R]`之间的累加和,`L`,`R`一直往右动。 + + - 如果窗口内`sum < aim`,`R`就往右扩,并且`sum += arr[R]`; + - 如果窗口内`sum > aim`,`L` 就往右扩,并且`sum -= arr[L]`; + - 如果窗口内`sum = aim`, 就说明这个窗口内累加和为`sum` ,此时记录最大值即可; + +![这里写图片描述](https://img-blog.csdn.net/20180812095546595?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: + +```java + static int getMax(int[] arr,int aim){ + if(arr == null || arr.length == 0 || aim < 0)return 0; + int L = 0,R = 0; + int res = 0, sum = arr[0]; + while(R < arr.length){ + if(sum == aim){ + res = Math.max(res,R - L + 1); + sum -= arr[L++]; + }else if(sum < aim){//小于等于就往右边扩 + if(++R == arr.length) break; + sum += arr[R]; + }else { // 大于就往左边扩 sum > aim + sum -= arr[L++]; + } + } + return res; + } +``` +*** +## 三、累加和` <= aim`的最长子数组的长度(**数组可`+`,`-`,`0`**); +两个数组`sum`和`ends`,`sum[i]`表示的是以`arr[i]`开头(必须包含`arr[i]`)的所有子数组的最小累加和,对应的`ends[i]`表示的是取得这个最小累加和的右边界。 一开始先求出`sums`数组和`ends[]`数组。 + +![这里写图片描述](https://img-blog.csdn.net/20180812104757419?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +这个题目最精华的是左右边界不回退,就是说,如果从`0`位置扩到`T`区间,`T+1`区间不能扩了,此时不是回到`1`位置开始扩,而是舍弃`0`位置,看能不能由于舍弃`0`位置把`T+1`位置加进来: + +![这里写图片描述](https://img-blog.csdn.net/20180812105420955?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) + +代码: + +```java + static int getMaxLength2(int[] arr,int aim){ + if(arr == null || arr.length == 0)return 0; + int[] sums = new int[arr.length]; //以arr[i]开头所有子数组的最小累加和 + int[] ends = new int[arr.length]; //取得最小累加和的最右边界 + sums[arr.length-1] = arr[arr.length-1]; + ends[arr.length-1] = arr.length-1; + for(int i = arr.length - 2; i >= 0; i--){ //求出sums数组和ends数组 + if(sums[i+1] < 0){ + sums[i] = arr[i] + sums[i+1]; + ends[i] = ends[i+1]; + }else { + sums[i] = arr[i]; + ends[i] = i; + } + } + int sum = 0; //目前的累加和 sum -> R + int R = 0;//每一次扩到的右边界 + int res = 0; //答案 + for(int start = 0; start < arr.length; start++){//每一次开头 + while(R < arr.length && sum + sums[R] <= aim){//一整块一整块的扩 + sum += sums[R]; + R = ends[R] + 1; + } + sum -= R > start ? arr[start] : 0;//如果R>start,下面start要++了,窗口内减去arr[start] + res = Math.max(res,R - start);//窗口是start ~ R-1 ,所以是长度为R-start + R = Math.max(R,start + 1); //有没有可能扩不出去 + } + return res; + } +``` + diff --git "a/\345\210\267\351\242\230/Other/Other/Tree/\346\212\230\347\272\270\351\227\256\351\242\230.md" "b/Algorithm/Other/Other/\346\212\230\347\272\270\351\227\256\351\242\230.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/Other/Tree/\346\212\230\347\272\270\351\227\256\351\242\230.md" rename to "Algorithm/Other/Other/\346\212\230\347\272\270\351\227\256\351\242\230.md" diff --git "a/\345\210\267\351\242\230/Other/POJ/Bit/POJ - 1222. EXTENDED LIGHTS OUT(\347\206\204\347\201\257\351\227\256\351\242\230)(\344\272\214\350\277\233\345\210\266\346\236\232\344\270\276).md" "b/Algorithm/Other/POJ/Bit/POJ - 1222. EXTENDED LIGHTS OUT(\347\206\204\347\201\257\351\227\256\351\242\230)(\344\272\214\350\277\233\345\210\266\346\236\232\344\270\276).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Bit/POJ - 1222. EXTENDED LIGHTS OUT(\347\206\204\347\201\257\351\227\256\351\242\230)(\344\272\214\350\277\233\345\210\266\346\236\232\344\270\276).md" rename to "Algorithm/Other/POJ/Bit/POJ - 1222. EXTENDED LIGHTS OUT(\347\206\204\347\201\257\351\227\256\351\242\230)(\344\272\214\350\277\233\345\210\266\346\236\232\344\270\276).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/DP/POJ - 2342. Anniversary party(\345\221\230\345\267\245\347\232\204\346\264\273\350\267\203\345\272\246\351\227\256\351\242\230)(\345\244\232\345\217\211\346\240\221\344\273\245\345\217\212\346\240\221\345\275\242dp).md" "b/Algorithm/Other/POJ/DP/POJ - 2342. Anniversary party(\345\221\230\345\267\245\347\232\204\346\264\273\350\267\203\345\272\246\351\227\256\351\242\230)(\345\244\232\345\217\211\346\240\221\344\273\245\345\217\212\346\240\221\345\275\242dp).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/DP/POJ - 2342. Anniversary party(\345\221\230\345\267\245\347\232\204\346\264\273\350\267\203\345\272\246\351\227\256\351\242\230)(\345\244\232\345\217\211\346\240\221\344\273\245\345\217\212\346\240\221\345\275\242dp).md" rename to "Algorithm/Other/POJ/DP/POJ - 2342. Anniversary party(\345\221\230\345\267\245\347\232\204\346\264\273\350\267\203\345\272\246\351\227\256\351\242\230)(\345\244\232\345\217\211\346\240\221\344\273\245\345\217\212\346\240\221\345\275\242dp).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/DP/images/2342_s.png" b/Algorithm/Other/POJ/DP/images/2342_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/DP/images/2342_s.png" rename to Algorithm/Other/POJ/DP/images/2342_s.png diff --git "a/\345\210\267\351\242\230/Other/POJ/DP/images/2342_s2.png" b/Algorithm/Other/POJ/DP/images/2342_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/DP/images/2342_s2.png" rename to Algorithm/Other/POJ/DP/images/2342_s2.png diff --git "a/\345\210\267\351\242\230/Other/POJ/DP/images/2342_s3.png" b/Algorithm/Other/POJ/DP/images/2342_s3.png similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/DP/images/2342_s3.png" rename to Algorithm/Other/POJ/DP/images/2342_s3.png diff --git "a/\345\210\267\351\242\230/Other/POJ/Search/POJ - 3126 & 2251 & 1321 & 3278 (DFS BFS).md" b/Algorithm/Other/POJ/Search/POJ - 3126 & 2251 & 1321 & 3278 (DFS BFS).md similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Search/POJ - 3126 & 2251 & 1321 & 3278 (DFS BFS).md" rename to Algorithm/Other/POJ/Search/POJ - 3126 & 2251 & 1321 & 3278 (DFS BFS).md diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 1006. Biorhythms(\346\236\232\344\270\276\346\212\200\345\267\247).md" "b/Algorithm/Other/POJ/Simulation/POJ - 1006. Biorhythms(\346\236\232\344\270\276\346\212\200\345\267\247).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 1006. Biorhythms(\346\236\232\344\270\276\346\212\200\345\267\247).md" rename to "Algorithm/Other/POJ/Simulation/POJ - 1006. Biorhythms(\346\236\232\344\270\276\346\212\200\345\267\247).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 1013. Counterfeit Dollar(\346\236\232\344\270\276).md" "b/Algorithm/Other/POJ/Simulation/POJ - 1013. Counterfeit Dollar(\346\236\232\344\270\276).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 1013. Counterfeit Dollar(\346\236\232\344\270\276).md" rename to "Algorithm/Other/POJ/Simulation/POJ - 1013. Counterfeit Dollar(\346\236\232\344\270\276).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 2039. ToAndFro(\347\253\226\345\236\213\350\276\223\345\207\272).md" "b/Algorithm/Other/POJ/Simulation/POJ - 2039. ToAndFro(\347\253\226\345\236\213\350\276\223\345\207\272).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 2039. ToAndFro(\347\253\226\345\236\213\350\276\223\345\207\272).md" rename to "Algorithm/Other/POJ/Simulation/POJ - 2039. ToAndFro(\347\253\226\345\236\213\350\276\223\345\207\272).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 2136. VerticalHistogram(\347\273\237\350\256\241\345\255\227\346\257\215\344\270\252\346\225\260).md" "b/Algorithm/Other/POJ/Simulation/POJ - 2136. VerticalHistogram(\347\273\237\350\256\241\345\255\227\346\257\215\344\270\252\346\225\260).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/POJ - 2136. VerticalHistogram(\347\273\237\350\256\241\345\255\227\346\257\215\344\270\252\346\225\260).md" rename to "Algorithm/Other/POJ/Simulation/POJ - 2136. VerticalHistogram(\347\273\237\350\256\241\345\255\227\346\257\215\344\270\252\346\225\260).md" diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/images/2039_t.png" b/Algorithm/Other/POJ/Simulation/images/2039_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/images/2039_t.png" rename to Algorithm/Other/POJ/Simulation/images/2039_t.png diff --git "a/\345\210\267\351\242\230/Other/POJ/Simulation/images/2136_t.png" b/Algorithm/Other/POJ/Simulation/images/2136_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/POJ/Simulation/images/2136_t.png" rename to Algorithm/Other/POJ/Simulation/images/2136_t.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1146. Maximum Sum(\345\255\220\347\237\251\351\230\265\347\232\204\346\234\200\345\244\247\347\264\257\345\212\240\345\222\214).md" "b/Algorithm/Other/TimusOJ/DP/TimusOJ - 1146. Maximum Sum(\345\255\220\347\237\251\351\230\265\347\232\204\346\234\200\345\244\247\347\264\257\345\212\240\345\222\214).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1146. Maximum Sum(\345\255\220\347\237\251\351\230\265\347\232\204\346\234\200\345\244\247\347\264\257\345\212\240\345\222\214).md" rename to "Algorithm/Other/TimusOJ/DP/TimusOJ - 1146. Maximum Sum(\345\255\220\347\237\251\351\230\265\347\232\204\346\234\200\345\244\247\347\264\257\345\212\240\345\222\214).md" diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1225.Flags & 1119.Metr & 1009.K-based Numbers (DP\347\256\200\345\215\225\351\242\230).md" "b/Algorithm/Other/TimusOJ/DP/TimusOJ - 1225.Flags & 1119.Metr & 1009.K-based Numbers (DP\347\256\200\345\215\225\351\242\230).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1225.Flags & 1119.Metr & 1009.K-based Numbers (DP\347\256\200\345\215\225\351\242\230).md" rename to "Algorithm/Other/TimusOJ/DP/TimusOJ - 1225.Flags & 1119.Metr & 1009.K-based Numbers (DP\347\256\200\345\215\225\351\242\230).md" diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1353. Milliard Vasya's Function(DP).md" b/Algorithm/Other/TimusOJ/DP/TimusOJ - 1353. Milliard Vasya's Function(DP).md similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/TimusOJ - 1353. Milliard Vasya's Function(DP).md" rename to Algorithm/Other/TimusOJ/DP/TimusOJ - 1353. Milliard Vasya's Function(DP).md diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1009_t.png" b/Algorithm/Other/TimusOJ/DP/images/1009_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1009_t.png" rename to Algorithm/Other/TimusOJ/DP/images/1009_t.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1119_t.png" b/Algorithm/Other/TimusOJ/DP/images/1119_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1119_t.png" rename to Algorithm/Other/TimusOJ/DP/images/1119_t.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1146_s.png" b/Algorithm/Other/TimusOJ/DP/images/1146_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1146_s.png" rename to Algorithm/Other/TimusOJ/DP/images/1146_s.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1146_t.png" b/Algorithm/Other/TimusOJ/DP/images/1146_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1146_t.png" rename to Algorithm/Other/TimusOJ/DP/images/1146_t.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_s.png" b/Algorithm/Other/TimusOJ/DP/images/1225_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_s.png" rename to Algorithm/Other/TimusOJ/DP/images/1225_s.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_s2.png" b/Algorithm/Other/TimusOJ/DP/images/1225_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_s2.png" rename to Algorithm/Other/TimusOJ/DP/images/1225_s2.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_t.png" b/Algorithm/Other/TimusOJ/DP/images/1225_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1225_t.png" rename to Algorithm/Other/TimusOJ/DP/images/1225_t.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_s.png" b/Algorithm/Other/TimusOJ/DP/images/1353_s.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_s.png" rename to Algorithm/Other/TimusOJ/DP/images/1353_s.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_s2.png" b/Algorithm/Other/TimusOJ/DP/images/1353_s2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_s2.png" rename to Algorithm/Other/TimusOJ/DP/images/1353_s2.png diff --git "a/\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_t.png" b/Algorithm/Other/TimusOJ/DP/images/1353_t.png similarity index 100% rename from "\345\210\267\351\242\230/Other/TimusOJ/DP/images/1353_t.png" rename to Algorithm/Other/TimusOJ/DP/images/1353_t.png diff --git "a/Algorithm/Other/acwing/Algorithm/AcWing-100. IncDec\345\272\217\345\210\227(\345\267\256\345\210\206).md" "b/Algorithm/Other/acwing/Algorithm/AcWing-100. IncDec\345\272\217\345\210\227(\345\267\256\345\210\206).md" new file mode 100644 index 00000000..3f3265e3 --- /dev/null +++ "b/Algorithm/Other/acwing/Algorithm/AcWing-100. IncDec\345\272\217\345\210\227(\345\267\256\345\210\206).md" @@ -0,0 +1,104 @@ +# AcWing-100. IncDec序列 + +## [题目链接](https://www.acwing.com/problem/content/102/) + +> https://www.acwing.com/problem/content/102/ + +## 题目 + +给定一个长度为 nn 的数列 a1,a2,…,ana1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。 + +求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。 + +#### 输入格式 + +第一行输入正整数nn。 + +接下来nn行,每行输入一个整数,第i+1行的整数代表aiai。 + +#### 输出格式 + +第一行输出最少操作次数。 + +第二行输出最终能得到多少种结果。 + +#### 数据范围 + +0 0; i--) a[i] -= a[i-1]; //差分,从后往前遍历 + + long pos = 0, neg = 0; + for(int i = 1 ; i < n; i++){ + if(a[i] > 0) pos += a[i]; + else neg -= a[i]; + } + out.println(Math.min(pos, neg) + Math.abs(pos - neg)); + out.println(Math.abs(pos - neg) + 1); + out.close(); + + } + +} + +``` + diff --git "a/Algorithm/Other/acwing/Algorithm/AcWing-101. \346\234\200\351\253\230\347\232\204\347\211\233(\345\267\256\345\210\206).md" "b/Algorithm/Other/acwing/Algorithm/AcWing-101. \346\234\200\351\253\230\347\232\204\347\211\233(\345\267\256\345\210\206).md" new file mode 100644 index 00000000..15e5ab49 --- /dev/null +++ "b/Algorithm/Other/acwing/Algorithm/AcWing-101. \346\234\200\351\253\230\347\232\204\347\211\233(\345\267\256\345\210\206).md" @@ -0,0 +1,143 @@ +# AcWing-101. 最高的牛 + +## [题目链接](https://www.acwing.com/problem/content/103/) + +> https://www.acwing.com/problem/content/103/ + +## 题目 + +有 NN 头牛站成一行,被编队为1、2、3…N,每头牛的身高都为整数。 + +当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。 + +现在,我们只知道其中最高的牛是第 PP 头,它的身高是 HH ,剩余牛的身高未知。 + +但是,我们还知道这群牛之中存在着 MM 对关系,每对关系都指明了某两头牛 AA 和 BB 可以相互看见。 + +求每头牛的身高的最大可能值是多少。 + +#### 输入格式 + +第一行输入整数N,P,H,MN,P,H,M,数据用空格隔开。 + +接下来M行,每行输出两个整数 AA 和 BB ,代表牛 AA 和牛 BB 可以相互看见,数据用空格隔开。 + +#### 输出格式 + +一共输出 NN 行数据,每行输出一个整数。 + +第 ii 行输出的整数代表第 ii 头牛可能的最大身高。 + +#### 数据范围 + +1≤N≤100001≤N≤10000, +1≤H≤10000001≤H≤1000000, +1≤A,B≤100001≤A,B≤10000, +0≤M≤100000≤M≤10000 + +#### 输入样例: + +``` +9 3 5 5 +1 3 +5 3 +4 3 +3 7 +9 8 +``` + +#### 输出样例: + +``` +5 +4 +5 +3 +4 +4 +5 +5 +5 +``` + +##### 注意: + +- 此题中给出的关系对可能存在重复 + +## 解析 + +这道题目一个核心要点,就是如何处理这些特殊的关系,也就是两头牛互相看见。 + +其实题目中已经告诉我们如何处理,因为我们发现,题目中要求牛的身高最高,那么既然如此,我们完全可以将每一组关系(A,B),看作`[A+1,B−1]`这组牛身高只比`A,B`这两头牛矮1。 + +各位可以画一个图,来更好的理解这道题目。 + +![1567126568387](assets/1567126568387.png) + +因此我们可以可以利用区间处理小操作,也就是前缀和加差分。设一个数组D,`D[i]`为比最高牛矮多少,则`D[P]=0`,那么对于一组关系,我们可以这样操作,`D[A+1]–,D[B]++;`然后从左到右前缀和,就可以求出矮多少。具体可以看代码实现。 + +```java +import java.io.BufferedInputStream; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Objects; +import java.util.Scanner; + +public class Main { + + + static Scanner in = new Scanner(new BufferedInputStream(System.in)); + static PrintWriter out = new PrintWriter(System.out); + + static class Node{ + int a, b; + Node(int a, int b){ + this.a = a; + this.b = b; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Node node = (Node) o; + return a == node.a && + b == node.b; + } + + @Override + public int hashCode() { + return Objects.hash(a, b); + } + } + + public static void main(String[] args) { + + int n = in.nextInt(), p = in.nextInt(), h = in.nextInt(), m = in.nextInt(); + int[] height = new int[n+1]; + height[1] = h; // b数组,可以让所有的牛都是最高 + HashSet set = new HashSet<>(); + for(int i = 0; i < m; i ++){ + int a = in.nextInt(), b = in.nextInt(); + if(a > b){ + int t = a; + a = b; + b = t; + } + Node node = new Node(a, b); + if(!set.contains(node)){ + set.add(node); + height[a+1]--; + height[b]++; + } + } + for(int i = 1; i <= n; i++){ + height[i] += height[i-1]; + out.println(height[i]); + } + out.close(); + } +} + +``` + diff --git a/Algorithm/Other/acwing/Algorithm/assets/1567126568387.png b/Algorithm/Other/acwing/Algorithm/assets/1567126568387.png new file mode 100644 index 00000000..126544f4 Binary files /dev/null and b/Algorithm/Other/acwing/Algorithm/assets/1567126568387.png differ diff --git "a/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/Nowcoder - \345\217\221\345\245\226\351\207\221.md" "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/Nowcoder - \345\217\221\345\245\226\351\207\221.md" new file mode 100644 index 00000000..336028dc --- /dev/null +++ "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/Nowcoder - \345\217\221\345\245\226\351\207\221.md" @@ -0,0 +1,85 @@ +## Nowcoder - 发奖金 + +#### [题目链接](https://www.nowcoder.com/practice/acb888f7ccee4fc0aab208393d41a552?tpId=49&&tqId=29328&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking) + +> https://www.nowcoder.com/practice/acb888f7ccee4fc0aab208393d41a552?tpId=49&&tqId=29328&rp=1&ru=/activity/oj&qru=/ta/2016test/question-ranking + +#### 题目 + +![01_发奖金.png](images/01_发奖金.png) + +样例输入 + +```c +10 +20 +32 +12 +32 +45 +11 +21 +31 +41 +33 +``` + +样例输出: + +```c +20 +``` + +### 解析 + +从前往后和从后往前计算两个数组`r`和`r2`。最后累加结果的时候取大的那个。可以说是贪心吧。 + +![01_发奖金_s.png](images/01_发奖金_s.png) + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + while (in.hasNext()) { + int n = in.nextInt(); + int[] a = new int[n]; + for (int i = 0; i < n; i++) a[i] = in.nextInt(); + int[] r = new int[n];// 从前往后 + r[0] = 1; + for (int i = 1; i < n; i++) { + if (a[i] > a[i - 1]) + r[i] = r[i - 1] + 1; + else + r[i] = 1; + } + int[] r2 = new int[n]; + r2[n - 1] = 1;//从后往前 + for (int i = n - 2; i >= 0; i--) { + if (a[i] > a[i + 1]) + r2[i] = r2[i + 1] + 1; + else + r2[i] = 1; + } + long res = 0; + for (int i = 0; i < n; i++) res += Math.max(r[i], r2[i]); + out.println(res); + } + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // 不关闭就没有输出 + } +} + +``` + diff --git "a/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221.png" "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221.png" new file mode 100644 index 00000000..2f87d5a7 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221.png" differ diff --git "a/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221_s.png" "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221_s.png" new file mode 100644 index 00000000..7b8e4104 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2016\346\240\241\346\213\233\347\234\237\351\242\230/images/01_\345\217\221\345\245\226\351\207\221_s.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-01-\346\213\274\345\244\232\345\244\232 - \346\234\200\345\244\247\344\271\230\347\247\257.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-01-\346\213\274\345\244\232\345\244\232 - \346\234\200\345\244\247\344\271\230\347\247\257.md" new file mode 100644 index 00000000..5a9ceee9 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-01-\346\213\274\345\244\232\345\244\232 - \346\234\200\345\244\247\344\271\230\347\247\257.md" @@ -0,0 +1,53 @@ +# 拼多多 - 最大乘积 + +#### [题目链接](https://www.nowcoder.com/practice/5f29c72b1ae14d92b9c3fa03a037ac5f?tpId=90&tqId=30776&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/5f29c72b1ae14d92b9c3fa03a037ac5f?tpId=90&tqId=30776&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![pingduoduo_01.png](images/pingduoduo_01.png) + +#### 解析 + +记录最大的三个数`max1、max2、max3`和最小的两个数(`min1、min2`)即可。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + public static void main(String[] args){ + + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + + long max1 = Long.MIN_VALUE, max2 = Long.MIN_VALUE, max3 = Long.MIN_VALUE; + long min1 = Long.MAX_VALUE, min2 = Long.MAX_VALUE; + + for(int i = 0; i < n; i++){ + if(a[i] > max1){ + max3 = max2; + max2 = max1; + max1 = a[i]; + }else if(a[i] > max2){ + max3 = max2; + max2 = a[i]; + }else if(a[i] > max3){ + max3 = a[i]; + } + if(a[i] < min1){ + min2 = min1; + min1 = a[i]; + }else if(a[i] < min2) + min2 = a[i]; + } + long res = Math.max(max1 * max2 * max3, min1 * min2 * max1); + out.println(res); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-02-\346\213\274\345\244\232\345\244\232 - \345\244\247\346\225\264\346\225\260\347\233\270\344\271\230.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-02-\346\213\274\345\244\232\345\244\232 - \345\244\247\346\225\264\346\225\260\347\233\270\344\271\230.md" new file mode 100644 index 00000000..d2b41b2a --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-02-\346\213\274\345\244\232\345\244\232 - \345\244\247\346\225\264\346\225\260\347\233\270\344\271\230.md" @@ -0,0 +1,134 @@ +# 拼多多 - 大整数相乘 + +#### [题目链接](https://www.nowcoder.com/practice/0f0badf5f2204a6bb968b0955a82779e?tpId=90&tqId=30777&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/0f0badf5f2204a6bb968b0955a82779e?tpId=90&tqId=30777&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![pingduoduo_02.png](images/pingduoduo_02.png) + +#### 解析 + +经典的大数乘法。 + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static String bigMul(char[] s1, char[] s2){ + int n1 = s1.length, n2 = s2.length; + int[] a = new int[n1]; + int[] b = new int[n2]; + int[] c = new int[n1 + n2]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < n1; i++){ + for(int j = 0; j < n2; j++){ + c[i+j] += a[i] * b[j]; + } + } + for(int i = 0; i < n1 + n2 - 1; i++){ + if(c[i] >= 10){ + c[i+1] += c[i]/10; + c[i] %= 10; + } + } + int i; + for(i = n1 + n2 - 1; i >= 0; i--) if(c[i] != 0) break; + StringBuilder sb = new StringBuilder(); + for(; i >= 0; i--) sb.append( (char)(c[i] + '0')); + return sb.toString(); + } + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + char[] s1 = in.next().toCharArray(); + char[] s2 = in.next().toCharArray(); + out.println(bigMul(s1, s2)); + } +} +``` + +顺便加上一个加法的模板: + +`POJ2506`代码: + +思路是`F(n) = F(n - 1) + 2 * F(n - 2)`;但是要用大数处理。 + +```java +import java.io.*; +import java.util.*; + +public class Main{ + + static String bigAdd(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int maxL = Math.max(n1, n2); + int[] a = new int[maxL + 1];//注意a,b的数组大小都必须是maxL+1 + int[] b = new int[maxL + 1]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < maxL; i++){ + if(a[i] + b[i] >= 10){ + int tmp = a[i] + b[i];//注意一定要先抽取出来 + a[i] = tmp%10; + a[i+1] += tmp/10; + }else + a[i] += b[i]; + } + StringBuilder sb = new StringBuilder(); + if(a[maxL] != 0) sb.append((char)(a[maxL] + '0')); + for(int i = maxL-1; i >= 0; i--) sb.append((char)(a[i] + '0')); + return sb.toString(); + } + + static String bigMul(String str1, String str2){ + char[] s1 = str1.toCharArray(); + char[] s2 = str2.toCharArray(); + int n1 = s1.length, n2 = s2.length; + int[] a = new int[n1]; + int[] b = new int[n2]; + int[] c = new int[n1 + n2]; + for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0'; + for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0'; + for(int i = 0; i < n1; i++){ + for(int j = 0; j < n2; j++){ + c[i+j] += a[i] * b[j]; + } + } + for(int i = 0; i < n1 + n2 - 1; i++){ + if(c[i] >= 10){ + c[i+1] += c[i]/10; + c[i] %= 10; + } + } + int i; + for(i = n1 + n2 - 1; i >= 0; i--) if(c[i] != 0) break; + StringBuilder sb = new StringBuilder(); + for(; i >= 0; i--) sb.append( (char)(c[i] + '0')); + return sb.toString(); + } + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + String[] s = new String[255]; + s[0] = "1"; s[1] = "1"; + for(int i = 2; i < 255; i++){ + String mul = bigMul("2", s[i-2]); + s[i] = bigAdd(mul, s[i-1]); + } + while(in.hasNext()){ + int n = in.nextInt(); + out.println(s[n]); + } + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-03-\346\213\274\345\244\232\345\244\232 - \345\205\255\344\270\200\345\204\277\347\253\245\350\212\202.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-03-\346\213\274\345\244\232\345\244\232 - \345\205\255\344\270\200\345\204\277\347\253\245\350\212\202.md" new file mode 100644 index 00000000..60461fce --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-03-\346\213\274\345\244\232\345\244\232 - \345\205\255\344\270\200\345\204\277\347\253\245\350\212\202.md" @@ -0,0 +1,46 @@ +# 拼多多 - 六一儿童节 + +#### [题目链接](https://www.nowcoder.com/practice/d2dfc62bf1ba42679a0e358c57da9828?tpId=90&tqId=30778&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/d2dfc62bf1ba42679a0e358c57da9828?tpId=90&tqId=30778&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![pingduoduo_03.png](images/pingduoduo_03.png) + +#### 解析 + +先对两个数组排序,然后贪心即可。 + +```java +import java.io.*; +import java.util.*; + +public class Main{ + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + int n = in.nextInt(); + int[] h = new int[n]; + for(int i = 0; i < n; i++) h[i] = in.nextInt(); + int m = in.nextInt(); + int[] w = new int[m]; + for(int i = 0; i < m; i++) w[i] = in.nextInt(); + Arrays.sort(h); + Arrays.sort(w); + int res = 0; + for(int i = 0, j = 0; i < n && j < m; ){ + if(w[j] >= h[i]){ + res++; + i++; + j++; + }else { + j++; + } + } + out.println(res); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-04-\347\275\221\346\230\223 - \346\223\215\344\275\234\345\272\217\345\210\227.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-04-\347\275\221\346\230\223 - \346\223\215\344\275\234\345\272\217\345\210\227.md" new file mode 100644 index 00000000..f48efd30 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-04-\347\275\221\346\230\223 - \346\223\215\344\275\234\345\272\217\345\210\227.md" @@ -0,0 +1,85 @@ +# 网易 - 操作序列 + +#### [题目链接](https://www.nowcoder.com/practice/b53bda356a494154b6411d80380295f5?tpId=90&tqId=30783&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/b53bda356a494154b6411d80380295f5?tpId=90&tqId=30783&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![wangyin_1.png](images/wangyin_1.png) + +### 解析 + +一开始用两个双向链表模拟,感觉时间复杂度是在O(N),但是还是超时了。 + +正解是找规律。就是**从后往前隔一个打印一个,然后从前往后隔一个打印一个**。 + +一开始超时的代码(用两个双向链表画一下): + +```java +import java.io.*; +import java.util.*; + +public class Main { + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); + /**-------------write code-----------------**/ + int n = in.nextInt(); + LinkedList b1 = new LinkedList<>(); + LinkedList b2 = new LinkedList<>(); + boolean ok = true; + for (int i = 1; i <= n; i++) { + int x = in.nextInt(); + if (!ok) { + b1.addLast(x); + b2.addFirst(x); + } else { + b1.addFirst(x); + b2.addLast(x); + } + ok = !ok; + } + if (n % 2 == 1) for (int i = 0; i < n; i++) out.print(b1.get(i) + " "); + else for (int i = 0; i < n; i++) out.print(b2.get(i) + " "); + out.println(); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); //must close + } +} +``` + +找规律通过代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); + /**-------------write code-----------------**/ + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + for(int i = n-1; i >= 0; i-=2) out.print(a[i] +" "); + for(int i = (n%2 == 1) ? 1 : 0; i < n; i+=2) out.print(a[i] + " "); + out.println(); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); //must close + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-05-\347\210\261\345\245\207\350\211\272 - \347\272\242\345\222\214\347\273\277.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-05-\347\210\261\345\245\207\350\211\272 - \347\272\242\345\222\214\347\273\277.md" new file mode 100644 index 00000000..89e80c53 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-05-\347\210\261\345\245\207\350\211\272 - \347\272\242\345\222\214\347\273\277.md" @@ -0,0 +1,129 @@ +# 爱奇艺 - 红和绿 + +#### [题目链接](https://www.nowcoder.com/practice/56ab932abac44c8aabf0af75f598a0b4?tpId=90&tqId=30799&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/56ab932abac44c8aabf0af75f598a0b4?tpId=90&tqId=30799&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![aiqiyi_1.png](images/aiqiyi_1.png) + +#### 解析 + +暴力,暴力枚举每个位置左边的`G`的数目和右边的`R`的数目: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use +// FR in = new FR(is); + /**------------------------*/ + char[] s = in.next().toCharArray(); + int n = s.length; + int min = n; + for (int i = 0; i < n; i++) { + int cnt = 0; + for (int j = 0; j < i; j++) if (s[j] == 'G') cnt++; + for (int j = i + 1; j < n; j++) if (s[j] == 'R') cnt++; + min = Math.min(min, cnt); + } + out.print(min); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + } +} + +``` + +记录一个前缀的`G`出现多少次,然后就可以省去计算左边的`G`和右边的`R`的数目的时间。 + +时间复杂度`O(N)`。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + /**------------------------*/ + char[] s = in.next().toCharArray(); + int n = s.length; + int count = 0; + int[] c = new int[n + 1]; + for (int i = 0; i < n; i++) { + c[i] = count; + if(s[i] == 'G') count++; + } + int min = n; + for(int i = 0; i < n; i++){ + int leftG = c[i];//左边的G + int rightG = count - c[i] - (s[i] == 'G' ? 1 : 0 );//注意减去s[i] == G的情况 + int rightR = n - i - 1 - rightG;//右边的R + min = Math.min(min, leftG + rightR); + } + out.print(min); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + } +} + +``` + +更简单的做法: + +在当前位置为R时有可能两种情况: + +* 一种是把这个位置变成G; +* 另一种是把前面的G全部变成R; + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + /**------------------------*/ + char[] s = in.next().toCharArray(); + int n = s.length; + int min = 0; + int gCount = 0; + for(int i = 0; i < n; i++){ + if(s[i] == 'G') gCount++; + else min = Math.min(min+1, gCount); + } + out.println(min); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + } +} + +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-06-\345\244\264\346\235\241 - \347\224\250\346\210\267\345\226\234\345\245\275.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-06-\345\244\264\346\235\241 - \347\224\250\346\210\267\345\226\234\345\245\275.md" new file mode 100644 index 00000000..62ff1764 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-06-\345\244\264\346\235\241 - \347\224\250\346\210\267\345\226\234\345\245\275.md" @@ -0,0 +1,116 @@ +# 头条 - 用户喜好 + +#### [题目链接](https://www.nowcoder.com/practice/d25162386a3140cbbe6dc071e1eb6ed6?tpId=90&tqId=30818&tPage=3&rp=3&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/d25162386a3140cbbe6dc071e1eb6ed6?tpId=90&tqId=30818&tPage=3&rp=3&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![toutiao_2.png](images/toutiao_2.png) + +### 解析 + +先用`HashMap>`存储 ,然后每次查询在对应的`mp.get(k)`的list中二分查找,因为输入的时候每个list内部都会是有序的。 + +

+ +代码: + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + static int firstLargeEqual(ArrayList list, int key){ + int L = 0, R = list.size() - 1; + while(L <= R){ + int m = L + (R - L) / 2; + if(list.get(m) >= key){ + R = m - 1; + }else { + L = m + 1; + } + } + return L; + } + + static int lastSmallEqual(ArrayList list, int key){ + int L = 0, R = list.size() - 1; + while(L <= R){ + int m = L + (R - L) / 2; + if(list.get(m) <= key){ + L = m + 1; + }else { + R = m - 1; + } + } + return R; + } + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + + int n = in.nextInt(); + int[] a = new int[n]; + HashMap> mp = new HashMap<>(); + for(int i = 0; i < n; i++){ + a[i] = in.nextInt(); + ArrayList tmp = mp.getOrDefault(a[i], new ArrayList<>()); + tmp.add(i); + mp.put(a[i], tmp); + } + int q = in.nextInt(); + for(int i = 0; i < q; i++){ + int l = in.nextInt() - 1; + int r = in.nextInt() - 1; + int k = in.nextInt(); + if(mp.get(k) == null) { + out.println(0); + continue; + } + ArrayList pos = mp.get(k); + int lp = firstLargeEqual(pos, l); + int rp = lastSmallEqual(pos, r); + out.println(rp + 1 - lp); + } + out.close(); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // 不关闭就没有输出 + } + + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} + +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-07-\347\275\221\346\230\223-\347\226\257\347\213\202\351\230\237\345\210\227.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-07-\347\275\221\346\230\223-\347\226\257\347\213\202\351\230\237\345\210\227.md" new file mode 100644 index 00000000..e2e05e4f --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-07-\347\275\221\346\230\223-\347\226\257\347\213\202\351\230\237\345\210\227.md" @@ -0,0 +1,107 @@ +# 网易-疯狂队列 + +#### [题目链接](https://www.nowcoder.com/practice/d996665fbd5e41f89c8d280f84968ee1?tpId=90&tqId=30786&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/d996665fbd5e41f89c8d280f84968ee1?tpId=90&tqId=30786&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![wangyi_2.png](images/wangyi_2.png) + +### 解析 + +贪心。 + +先拿出最大的和最小的。 + +然后拿出最小的放到上一个最大的右边。拿出最大的放在上一个最小的左边。 + +。。。 + +如果是奇数个,最后还要判断一下放在哪里可以更大。 + +可以用双向链表来操作每次的边界。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { +// Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + FR in = new FR(is); + /**------------------------*/ + int n = in.nextInt(); + int[] a = new int[n]; + for(int i = 0; i < n; i++) a[i] = in.nextInt(); + Arrays.sort(a); + LinkedList list = new LinkedList<>(); + for(int i = 0; i < n; i++) list.add(a[i]); + if(list.size() == 1){ + out.println(list.get(0)); + return; + } + int res = 0; + int p1 = list.removeFirst(), p2 = list.removeLast(); + res += p2 - p1; + boolean flag = true; + while(list.size() > 1){ + if(flag) { + int fi = list.removeFirst(); + res += Math.abs(fi - p2); + p2 = fi; + int se = list.removeLast(); + res += Math.abs(se - p1); + p1 = se; + }else { + int fi = list.removeLast(); + res += Math.abs(fi - p2); + p2 = fi; + int se = list.removeFirst(); + res += Math.abs(se - p1); + p1 = se; + } + flag = !flag; + } + if(list.size() == 1) + res += Math.max(Math.abs(p1-list.get(0)), Math.abs(p2-list.get(0))); + out.println(res); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} + +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-08-\347\210\261\345\245\207\350\211\272-DNA\345\272\217\345\210\227.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-08-\347\210\261\345\245\207\350\211\272-DNA\345\272\217\345\210\227.md" new file mode 100644 index 00000000..cbd195f1 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-08-\347\210\261\345\245\207\350\211\272-DNA\345\272\217\345\210\227.md" @@ -0,0 +1,50 @@ +## 爱奇艺-DNA序列 + +#### [题目链接](https://www.nowcoder.com/practice/ab900f183e054c6d8769f2df977223b5?tpId=90&tqId=30789&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/ab900f183e054c6d8769f2df977223b5?tpId=90&tqId=30789&tPage=1&rp=1&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![aiqiyi_2.png](images/aiqiyi_2.png) + +### 解析 + +一开始理解错误题目意思,以为是`AA,CC,TT,GG`。。。 + +不存在的最短DNA序列是什么? + +例如,长度为2的序列包括:`(AA, AG, AC, AT, CA, CC, CG, CT ……..)`,要全部判断一遍才可以。并不是判断`(AA, CC, GG TT)`就可以了。 + +所以我们可以加入到一个set中,然后判断集合容量即可。 + +```java +import java.util.*; +import java.io.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + String s = in.next(); + int n = s.length(); + for(int len = 1; len <= n; len++){ + HashSet set = new HashSet<>(); + for(int i = 0; i < n-len; i++) set.add(s.substring(i, i + len)); + if(set.size() < Math.pow(4, len)){ + out.println(len); + return; + } + } + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-09-\347\210\261\345\245\207\350\211\272-\351\235\222\350\215\211\346\270\270\346\210\217.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-09-\347\210\261\345\245\207\350\211\272-\351\235\222\350\215\211\346\270\270\346\210\217.md" new file mode 100644 index 00000000..bc3558c3 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-09-\347\210\261\345\245\207\350\211\272-\351\235\222\350\215\211\346\270\270\346\210\217.md" @@ -0,0 +1,94 @@ +# 爱奇艺-青草游戏 + +#### [题目链接](https://www.nowcoder.com/practice/ed0334a5e88f4662bb69374b308862d8?tpId=90&tqId=30802&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/ed0334a5e88f4662bb69374b308862d8?tpId=90&tqId=30802&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![1554892019366](assets/1554892019366.png) + +## 解析 + +博弈dp,先暴力计算出前面几十项,然后发现规律。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int MAX = (int) 1e6; // 1e9 + static boolean[] dp = new boolean[MAX + 5]; + // 求log(4)(MAX)的计算结果 + // 利用换底公式:log(x)(y) =log(e)(x) / log(e)(y), + static int strategyN = (int) (Math.log(MAX) / Math.log(4)) + 1; + static int[] strategies = new int[strategyN]; + + static { + for (int i = 0; i < strategyN; i++) + strategies[i] = (int) Math.pow(4, i); + Arrays.fill(dp, false); + } + + static void test() { + for (int i = 1; i <= MAX; i++) { + for (int j = 0; j < strategyN; j++) { + if (strategies[j] <= i) + dp[i] |= !dp[i - strategies[j]]; + } + } + // 发现规律 + for (int i = 0; i < 40; i++) System.out.println(dp[i] ? "niu" : "yang"); + } + + static void solve(InputStream stream, PrintWriter out) { + FR in = new FR(stream); + +// test(); + + int T = in.nextInt(); + for(int t = 0; t < T; t++){ + int n = in.nextInt(); + out.println( (n%5 == 0 || n % 5 == 2) ? "yang" : "niu"); + } + } + + /** + * -------------------------------------------------------------------------------------- + **/ + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + int nextInt() { + return Integer.parseInt(next()); + } + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-10-\347\210\261\345\245\207\350\211\272-\345\271\270\350\277\220\345\255\220\345\272\217\345\210\227.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-10-\347\210\261\345\245\207\350\211\272-\345\271\270\350\277\220\345\255\220\345\272\217\345\210\227.md" new file mode 100644 index 00000000..65d8cfcd --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-10-\347\210\261\345\245\207\350\211\272-\345\271\270\350\277\220\345\255\220\345\272\217\345\210\227.md" @@ -0,0 +1,41 @@ +# 爱奇艺-幸运子序列 + +#### [题目链接](https://www.nowcoder.com/practice/872919272a33406a9c5ddc8b2f7532f4?tpId=90&tqId=30804&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/872919272a33406a9c5ddc8b2f7532f4?tpId=90&tqId=30804&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![1555421420336](assets/1555421420336.png) + +## 解析 + +单调栈解决。 + +找到每个数的左右两边比它大的数。记录最大值 + +代码: + +```java +import java.util.*; +import java.io.*; +public class Main{ + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + int n = in.nextInt(); + Stack stack = new Stack<>(); + int res = 0; + for(int i = 0; i < n; i++){ + int x = in.nextInt(); + while(!stack.isEmpty() && stack.peek() <= x) + res = Math.max(res, x ^ stack.pop()); + if(!stack.isEmpty()) res = Math.max(res, x ^ stack.peek()); + stack.push(x); + } + out.println(res); + out.close(); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-11-\347\210\261\345\245\207\350\211\272-\347\274\272\345\244\261\347\232\204\346\213\254\345\217\267.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-11-\347\210\261\345\245\207\350\211\272-\347\274\272\345\244\261\347\232\204\346\213\254\345\217\267.md" new file mode 100644 index 00000000..308bf872 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-11-\347\210\261\345\245\207\350\211\272-\347\274\272\345\244\261\347\232\204\346\213\254\345\217\267.md" @@ -0,0 +1,43 @@ +## 爱奇艺-缺失的括号 + +#### [题目链接](https://www.nowcoder.com/practice/de7d4a4b50f643669f331941afb1e728?tpId=90&tqId=30805&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/de7d4a4b50f643669f331941afb1e728?tpId=90&tqId=30805&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![1555590199584](assets/1555590199584.png) + +## 解析 + +统计左边的需要匹配的和右边的需要匹配的,累加即可。 + +代码: + +```java +import java.util.*; +import java.io.*; + +public class Main{ + + public static void main(String[] args){ + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintWriter out = new PrintWriter(System.out); + char[] s = in.next().toCharArray(); + Stack stack = new Stack<>(); + int right = 0; + for(int i = 0; i < s.length; i++){ + if(s[i] == '('){ + stack.push(s[i]); + }else { + if(stack.isEmpty()) right++; + else if(stack.peek() == '(') + stack.pop(); + } + } + out.println(stack.size() + right); + out.close(); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-\347\210\261\345\245\207\350\211\272-12-\346\234\200\345\220\216\344\270\200\344\275\215.md" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-\347\210\261\345\245\207\350\211\272-12-\346\234\200\345\220\216\344\270\200\344\275\215.md" new file mode 100644 index 00000000..e6f49a74 --- /dev/null +++ "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/XZ-\347\210\261\345\245\207\350\211\272-12-\346\234\200\345\220\216\344\270\200\344\275\215.md" @@ -0,0 +1,65 @@ +## 爱奇艺-最后一位 + +#### [题目链接](https://www.nowcoder.com/practice/fae8632cfc64433989720bc01e09f382?tpId=90&tqId=30806&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking) + +> https://www.nowcoder.com/practice/fae8632cfc64433989720bc01e09f382?tpId=90&tqId=30806&tPage=2&rp=2&ru=/ta/2018test&qru=/ta/2018test/question-ranking + +#### 题目 + +![1555593369074](assets/1555593369074.png) + +## 解析 + +二分即可。 + +最小从`0`开始,最大从`sum`开始。 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream stream, PrintWriter out) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + long sum = in.nextLong(); + long L = 0, R = sum; + while (L <= R) { + long mid = L + (R - L) / 2; + long midSum = getSum(mid); + if (midSum == sum) { + out.println(mid); + return; + } else if (midSum < sum) { + L = mid + 1; + } else if (midSum > sum) { + R = mid - 1; + } + } + out.println(-1); + } + + static long getSum(long n) { + long res = 0; + while (n > 0) { + res += n; + n /= 10; + } + return res; + } + + /*--------------------------------------------------------------------------------------*/ + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); + } +} + +``` + diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892017667.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892017667.png" new file mode 100644 index 00000000..e8a0bca6 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892017667.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892019366.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892019366.png" new file mode 100644 index 00000000..e8a0bca6 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1554892019366.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555421420336.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555421420336.png" new file mode 100644 index 00000000..4503220d Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555421420336.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555590199584.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555590199584.png" new file mode 100644 index 00000000..4069c517 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555590199584.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555593369074.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555593369074.png" new file mode 100644 index 00000000..56437fca Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/assets/1555593369074.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_1.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_1.png" new file mode 100644 index 00000000..da190d20 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_1.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_2.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_2.png" new file mode 100644 index 00000000..c84818d3 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/aiqiyi_2.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_01.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_01.png" new file mode 100644 index 00000000..3bd6a028 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_01.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_02.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_02.png" new file mode 100644 index 00000000..9be02993 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_02.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_03.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_03.png" new file mode 100644 index 00000000..ca128a0b Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/pingduoduo_03.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_1.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_1.png" new file mode 100644 index 00000000..8352f007 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_1.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_2.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_2.png" new file mode 100644 index 00000000..a6bc970f Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/toutiao_2.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyi_2.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyi_2.png" new file mode 100644 index 00000000..9409d3b3 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyi_2.png" differ diff --git "a/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyin_1.png" "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyin_1.png" new file mode 100644 index 00000000..c7411613 Binary files /dev/null and "b/Algorithm/Other/nowcoder/2018\346\240\241\346\213\233\347\234\237\351\242\230/images/wangyin_1.png" differ diff --git "a/Algorithm/Other/nowcoder/2019\346\240\241\346\213\233\347\234\237\351\242\230/\346\220\234\347\213\2272019\347\247\213\346\213\233 - \346\225\260\345\255\227\345\272\217\345\210\227.md" "b/Algorithm/Other/nowcoder/2019\346\240\241\346\213\233\347\234\237\351\242\230/\346\220\234\347\213\2272019\347\247\213\346\213\233 - \346\225\260\345\255\227\345\272\217\345\210\227.md" new file mode 100644 index 00000000..21fff36f --- /dev/null +++ "b/Algorithm/Other/nowcoder/2019\346\240\241\346\213\233\347\234\237\351\242\230/\346\220\234\347\213\2272019\347\247\213\346\213\233 - \346\225\260\345\255\227\345\272\217\345\210\227.md" @@ -0,0 +1,140 @@ +## 搜狗 - 数字序列 + +#### [题目链接](https://www.nowcoder.com/test/question/a1976d118e9d436cae1ce25752ffb357?pid=15245444&tid=22577649) + +> https://www.nowcoder.com/test/question/a1976d118e9d436cae1ce25752ffb357?pid=15245444&tid=22577649 + +#### 题目 + +一个由若干个取值范围在[1,2^31-1]的整数构成长度为N的数字序列,其中N<=5,000,000;求该数字序列上一段最小的连续区间的长度,要求该区间内正好包含了所有不同的数字,如果存在多个这样的区间,按照出现的顺序有序输出所有的区间起始和结束位置,序列的位置编号从1到N,其中最小的区间长度不会超过10,000。 + +输入描述: + +> 第一行:N +> +> 第2至N+1行:每行1个数 + +输出描述: + +> 第一行:最小的区间长度 区间个数X (以空格进行分隔) +> +> 第二行:X个区间的起始和结束位置,按照出现的顺序有序输出,多个区间之间以空格分隔,每个区间的输出格式如下所示:[start,end],最后以换行结尾 + +输入样例: + +```java +10 +1 +1 +3 +4 +6 +6 +5 +1 +3 +3 +``` + +输出样例: + +```java +6 3 +[2,7] [3,8] [4,9] +``` + +### 解析 + +使用队列存储a[i],使用两个指针`l、r`,注意只有当新的元素`a[i]`和最前面的对头相等的时候才有可能,因为不然就会更长,不然就不是最长的。 + +然后再下面判断每个区间的个数并记录最大值即可。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static void solve(InputStream is, PrintWriter out) { +// Scanner in = new Scanner(new BufferedInputStream(is)); // hasNext method use + FR in = new FR(is); + /**------------------------*/ + int n = in.nextInt(); + int[] a = new int[n]; + HashSet set = new HashSet<>(); + for (int i = 0; i < n; i++) { + a[i] = in.nextInt(); + set.add(a[i]); + } + HashMap map = new HashMap<>(); + Queue queue = new LinkedList<>(); + List res = new ArrayList<>(); + int maxLen = n, l = 1, r = 0; + for(int i = 0; i < n; i++){ + if(!queue.isEmpty() && queue.peek() == a[i]){ + queue.poll(); + queue.add(a[i]); + l++; + r++; + while(map.size() == set.size() && map.get(queue.peek()) > 1){ // 注意不是>=1,还要留一个 + map.put(queue.peek(), map.get(queue.peek())-1); + queue.poll(); + l++; + } + }else { + queue.add(a[i]); + map.put(a[i], map.getOrDefault(a[i], 0) + 1); + r++; + } + if(map.size() == set.size() && r - l + 1 <= maxLen){ + if(r - l + 1 < maxLen){ + maxLen = r - l + 1; + res.clear(); + } + res.add(l); + res.add(r); + } + } + out.println(maxLen + " " + res.size() / 2); + for(int i = 0; i < res.size(); i += 2) + out.print("[" + res.get(i) + "," + res.get(i+1) + "] "); + out.println(); + } + + public static void main(String[] args) { + OutputStream os = System.out; + InputStream is = System.in; + PrintWriter out = new PrintWriter(os); + solve(is, out); + out.close(); // must close + + } + + static class FR { + BufferedReader br; + StringTokenizer tk; + + FR(InputStream stream) { + br = new BufferedReader(new InputStreamReader(stream), 32768); + tk = null; + } + + String next() { + while (tk == null || !tk.hasMoreElements()) { + try { + tk = new StringTokenizer(br.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + } + return tk.nextToken(); + } + + int nextInt() { + return Integer.parseInt(next()); + } + } +} + +``` + diff --git "a/\345\210\267\351\242\230/Other/nowcoder/images/jd1.png" b/Algorithm/Other/nowcoder/images/jd1.png similarity index 100% rename from "\345\210\267\351\242\230/Other/nowcoder/images/jd1.png" rename to Algorithm/Other/nowcoder/images/jd1.png diff --git "a/\345\210\267\351\242\230/Other/nowcoder/images/jd2.png" b/Algorithm/Other/nowcoder/images/jd2.png similarity index 100% rename from "\345\210\267\351\242\230/Other/nowcoder/images/jd2.png" rename to Algorithm/Other/nowcoder/images/jd2.png diff --git a/Algorithm/Other/nowcoder/images/nowcoder_41_C_t.png b/Algorithm/Other/nowcoder/images/nowcoder_41_C_t.png new file mode 100644 index 00000000..ead7003b Binary files /dev/null and b/Algorithm/Other/nowcoder/images/nowcoder_41_C_t.png differ diff --git a/Algorithm/Other/nowcoder/images/nowcoder_41_C_t2.png b/Algorithm/Other/nowcoder/images/nowcoder_41_C_t2.png new file mode 100644 index 00000000..ad10bb23 Binary files /dev/null and b/Algorithm/Other/nowcoder/images/nowcoder_41_C_t2.png differ diff --git "a/\345\210\267\351\242\230/Other/nowcoder/\344\272\254\344\270\2342017\346\240\241\346\213\233\347\274\226\347\250\213\351\242\230 - \344\277\235\345\215\253\346\226\271\346\241\210(\345\261\261\345\263\260\345\257\271\346\225\260\351\207\217).md" "b/Algorithm/Other/nowcoder/\344\272\254\344\270\2342017\346\240\241\346\213\233\347\274\226\347\250\213\351\242\230 - \344\277\235\345\215\253\346\226\271\346\241\210(\345\261\261\345\263\260\345\257\271\346\225\260\351\207\217).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/nowcoder/\344\272\254\344\270\2342017\346\240\241\346\213\233\347\274\226\347\250\213\351\242\230 - \344\277\235\345\215\253\346\226\271\346\241\210(\345\261\261\345\263\260\345\257\271\346\225\260\351\207\217).md" rename to "Algorithm/Other/nowcoder/\344\272\254\344\270\2342017\346\240\241\346\213\233\347\274\226\347\250\213\351\242\230 - \344\277\235\345\215\253\346\226\271\346\241\210(\345\261\261\345\263\260\345\257\271\346\225\260\351\207\217).md" diff --git "a/Algorithm/Other/nowcoder/\346\235\202\351\241\271/Nowcoder - \346\224\276\350\213\271\346\236\234.md" "b/Algorithm/Other/nowcoder/\346\235\202\351\241\271/Nowcoder - \346\224\276\350\213\271\346\236\234.md" new file mode 100644 index 00000000..d898b4fe --- /dev/null +++ "b/Algorithm/Other/nowcoder/\346\235\202\351\241\271/Nowcoder - \346\224\276\350\213\271\346\236\234.md" @@ -0,0 +1,70 @@ +## NowCoder - 放苹果 + +#### [题目链接](https://www.nowcoder.com/practice/a2a1d0266629404fba582d416d84b6a0?tpId=61&&tqId=29533&rp=1&ru=/activity/oj&qru=/ta/pku-kaoyan/question-ranking) + +> https://www.nowcoder.com/practice/a2a1d0266629404fba582d416d84b6a0?tpId=61&&tqId=29533&rp=1&ru=/activity/oj&qru=/ta/pku-kaoyan/question-ranking + +#### 题目 + +![放苹果_t.png](images/放苹果_t.png) + +### 解析 + +递归思路: + +* 1)、递归出口:当只有一个盘子或者 含有 0 个 或 1 个苹果的时候只有一种方法; +* 2)、当盘子数 n 大于苹果数 m 时,则必有 n - m 个空盘子,所以只需求 m 个盘子放 m 个苹果时的方法数即可; +* 3)、当盘子数 n 小于等于 苹果数 m 时,总方法数 = 当含有一个空盘子时的方法数+不含空盘子时的方法数; + +> 原因:当在求只含有一个空盘子时的方法数时,已经包含了含有 `2 ~ n-1` 个空盘子 的情况。 +> +> 不含空盘子的计算:先将每个盘子装一个苹果,则问题变成了求 n 个盘子放 m - n个苹果的方法数了。 + + 代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static int[][] dp; + + static int dfs(int n, int m){ + if(n == 1 || m == 0 || m == 1) return 1; + if(dp[n][m] != -1) return dp[n][m]; + if(n > m) return dp[n][m] = dfs(m, m); + return dp[n][m] = dfs(n, m-n) + dfs(n-1, m); + } + + static int getDp(int n, int m){ + for(int i = 0; i <= n; i++) for(int j = 0; j <= m; j++) dp[i][j] = 1; + for(int i = 2; i <= n; i++){ + for(int j = 2; j <= m; j++){ + if(i > j) dp[i][j] = dp[j][j]; + else dp[i][j] = dp[i][j - i] + dp[i-1][j]; + } + } + return dp[n][m]; + } + + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(stream)); + while(in.hasNext()){ + int m = in.nextInt(); + int n = in.nextInt(); + dp = new int[n+1][m+1]; +// for(int i = 0; i <= n; i++) Arrays.fill(dp[i], -1); +// out.println(dfs(n, m)); + out.println(getDp(n, m)); + } + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/\346\235\202\351\241\271/images/\346\224\276\350\213\271\346\236\234_t.png" "b/Algorithm/Other/nowcoder/\346\235\202\351\241\271/images/\346\224\276\350\213\271\346\236\234_t.png" new file mode 100644 index 00000000..7085e402 Binary files /dev/null and "b/Algorithm/Other/nowcoder/\346\235\202\351\241\271/images/\346\224\276\350\213\271\346\236\234_t.png" differ diff --git "a/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - B. 666RPG.md" "b/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - B. 666RPG.md" new file mode 100644 index 00000000..576cd1d5 --- /dev/null +++ "b/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - B. 666RPG.md" @@ -0,0 +1,62 @@ +## 牛客练习赛41 - B. 666RPG + +#### [题目链接](https://ac.nowcoder.com/acm/contest/373/B) + +https://ac.nowcoder.com/acm/contest/373/B + +#### 题目 + +lililalala正在玩一种有 N N个回合的回合制RPG游戏,初始分数为0,第 i个回合lililalala有如下两种选择。 + +* A.将分数加上 `a[i]` ; +* B.将分数 × `-1`; + +lililalala同样也很讨厌野兽数 666,但是他很却喜欢数字 -666 。他想知道有多少种不同的方案使得 N个回合后分数变为 -666 且在任何一个回合之后分数都不为 666。 如果两种方案有任何一个回合选择不同,就认为这两种方案是不同的。 + +答案请对 `10^8+7` 取模。 + +#### 解析 + +DP。 + +用滚动数组`dp[2][2*N + 10]`。 + +由于负数不能访问索引,所以`+N`。 + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static PrintStream out = System.out; + + static final int MOD = (int) 1e8 + 7; + static final int N = (int) 2e5 + 10; + + // write code + static void solve(InputStream stream) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + int n = in.nextInt(); + int[][] dp = new int[2][2*N + 10]; + dp[0][N] = 1; + for (int i = 0; i < n; i++) { + int x = in.nextInt(); + for (int j = -N; j <= N; j++) + if (j != 666 && dp[i & 1][j + N] != 0) { + dp[(i+1)&1][j+x+N] = (dp[(i+1)&1][j+x+N] + dp[i&1][j+N]) % MOD;//+x + dp[(i+1)&1][-j+N] = (dp[(i+1)&1][-j+N] + dp[i&1][j+N]) % MOD;//-j + } + for (int j = -N; j <= N; j++) { + dp[i&1][j + N] = 0; + } + } + out.println(dp[n &1][-666 + N]); + } + + public static void main(String[] args) { + solve(System.in); + } +} +``` + diff --git "a/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - C. \346\212\223\346\215\225\347\233\227\347\252\203\347\212\257 (\345\271\266\346\237\245\351\233\206).md" "b/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - C. \346\212\223\346\215\225\347\233\227\347\252\203\347\212\257 (\345\271\266\346\237\245\351\233\206).md" new file mode 100644 index 00000000..bb178084 --- /dev/null +++ "b/Algorithm/Other/nowcoder/\347\211\233\345\256\242\347\273\203\344\271\240\350\265\23341 - C. \346\212\223\346\215\225\347\233\227\347\252\203\347\212\257 (\345\271\266\346\237\245\351\233\206).md" @@ -0,0 +1,83 @@ +## 牛客练习赛41 - C. 抓捕盗窃犯 (并查集) + +#### [题目链接](https://ac.nowcoder.com/acm/contest/373/C) + +> https://ac.nowcoder.com/acm/contest/373/C + +#### 题目 + +![nowcoder_41_C_t.png](images/nowcoder_41_C_t.png) + +![nowcoder_41_C_t2.png](images/nowcoder_41_C_t2.png) + +### 解析 + +代码: + +```java +import java.io.*; +import java.util.*; + +public class Main { + + static int[] parent; + static int[] rank; + static int n, m; + static long[] val;//注意val数组要用Long,后面会累加 + + static int findRoot(int p) { + while (parent[p] != p) { + parent[p] = parent[parent[p]]; + p = parent[p]; + } + return p; + } + + static void union(int a, int b) { + int aRoot = findRoot(a); + int bRoot = findRoot(b); + if (aRoot == bRoot) + return; + if (rank[aRoot] < rank[bRoot]) { + parent[aRoot] = bRoot; + } else if (rank[aRoot] > rank[bRoot]) { + parent[bRoot] = aRoot; + } else { + parent[aRoot] = bRoot; + rank[bRoot]++; + } + } + + public static void main(String[] args) { + Scanner in = new Scanner(new BufferedInputStream(System.in)); + PrintStream out = System.out; + n = in.nextInt(); + m = in.nextInt(); + parent = new int[n + 1]; + rank = new int[n + 1]; + val = new long[n + 1]; + for (int i = 1; i <= n; i++){ + val[i] = in.nextInt(); + parent[i] = i; + rank[i] = 1; + } + for (int i = 1; i <= n; i++) { + int pa = in.nextInt(); + union(i, pa); + } + for (int i = 1; i <= n; i++) { + int root = findRoot(i); + if (root != i) val[root] += val[i];//只求root != i的 + } + ArrayList ans = new ArrayList<>(); + for (int i = 1; i <= n; i++) + if (parent[i] == i)//注意只要parent[i] = i的 + ans.add(val[i]); + Collections.sort(ans, (o1, o2) -> o2 - o1 > 0 ? 1 : (o2 - o1 < 0 ? -1 : 0)); + long res = 0; + for (int i = 0; i < ans.size() && i < m; i++) res += ans.get(i); + System.out.println(res); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202832462.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202832462.png" new file mode 100644 index 00000000..ff1e1105 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202832462.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202889455.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202889455.png" new file mode 100644 index 00000000..509f45e9 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202889455.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202908028.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202908028.png" new file mode 100644 index 00000000..fb056010 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/assets/1555202908028.png" differ diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/01_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/01_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/01_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/01_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/02_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/02_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/02_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/02_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/04_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/04_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/04_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/04_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/05_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/05_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/05_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/05_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/05_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/05_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/05_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/05_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/06_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/06_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/06_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/06_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/07_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/07_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/07_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/07_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/08_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/08_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/08_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/08_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/09_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/09_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/09_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/09_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/10_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/10_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/10_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/10_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/10_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/10_t.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/10_t.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/10_t.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/11_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/11_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/11_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/11_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/13_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/13_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/13_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/13_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/13_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/13_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/13_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/13_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/14_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/14_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/14_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/14_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/16_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/16_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/16_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/16_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/17_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/17_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/17_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/17_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/17_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/17_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/17_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/17_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/18_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/18_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/18_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/18_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/18_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/18_t.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/18_t.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/18_t.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/19_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/19_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/19_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/19_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/19_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/19_t.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/19_t.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/19_t.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/20_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/20_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/20_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/20_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/20_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/20_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/20_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/20_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/21_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/21_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/21_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/21_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/21_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/21_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/21_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/21_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/23_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/23_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/23_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/23_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/24_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/24_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/24_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/24_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/25_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/25_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/25_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/25_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s3.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s3.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s3.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s3.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s4.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s4.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/26_s4.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/26_s4.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/27_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/27_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/27_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/27_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/29_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/29_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/29_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/29_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/29_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/29_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/29_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/29_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/30_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/30_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/30_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/30_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/33_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/33_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/33_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/33_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/35_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/35_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/35_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/35_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/35_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/35_t.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/35_t.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/35_t.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/36_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/36_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/36_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/36_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/36_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/36_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/36_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/36_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/37_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/37_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/37_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/37_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/38_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/38_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/38_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/38_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/39_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/39_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/39_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/39_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/39_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/39_s2.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/39_s2.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/39_s2.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/40_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/40_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/40_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/40_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/41_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/41_t.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/41_t.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/41_t.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/42_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/42_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/42_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/42_s.png" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/45_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/45_s.png" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/images/45_s.png" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/images/45_s.png" diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s.png" new file mode 100644 index 00000000..01acc3d3 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s2.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s2.png" new file mode 100644 index 00000000..d89d09d4 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s2.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s3.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s3.png" new file mode 100644 index 00000000..9bdff0fb Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s3.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s4.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s4.png" new file mode 100644 index 00000000..3839de69 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/46_s4.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/49_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/49_t.png" new file mode 100644 index 00000000..9d37e75d Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/49_t.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/50_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/50_s.png" new file mode 100644 index 00000000..335e1b39 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/50_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/51_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/51_s.png" new file mode 100644 index 00000000..6e4ef84f Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/51_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/52_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/52_s.png" new file mode 100644 index 00000000..04820fce Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/52_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/53_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/53_t.png" new file mode 100644 index 00000000..2fb72acf Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/53_t.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_s.png" new file mode 100644 index 00000000..e64ce442 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_t.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_t.png" new file mode 100644 index 00000000..f940e405 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/54_t.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/55_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/55_s.png" new file mode 100644 index 00000000..4160a4da Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/55_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/56_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/56_s.png" new file mode 100644 index 00000000..4f3a80d8 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/56_s.png" differ diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/images/58_s.png" "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/58_s.png" new file mode 100644 index 00000000..2c90ae50 Binary files /dev/null and "b/Algorithm/Other/\345\211\221\346\214\207Offer/images/58_s.png" differ diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 01 - \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 01 - \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 01 - \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 01 - \344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276.md" diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 02 - \346\233\277\346\215\242\347\251\272\346\240\274.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 02 - \346\233\277\346\215\242\347\251\272\346\240\274.md" new file mode 100644 index 00000000..840be32a --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 02 - \346\233\277\346\215\242\347\251\272\346\240\274.md" @@ -0,0 +1,46 @@ +## 剑指Offer - 02 - 替换空格 + +#### [题目链接](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +> 请实现一个函数,将一个字符串中的每个空格替换成`“%20”`。例如,当字符串为`We Are Happy.`则经过替换之后的字符串为`We%20Are%20Happy.`。 + +### 解析 + +思路 + + - 这个题目如果只是简单的插入的话,插入之后导致后面的元素的移动导致,需要O(n2)的复杂度; + - 这个的解决方法使用两个指针,可以达到`O(n)`复杂度; + - **首先计算出空格的个数,这样求的新的字符串的长度**; + - 然后使用两个指针,新的指针`second`指向新的字符串的末尾,老指针`first`指向原来字符串的末尾,每次检查字符串的末尾如果是空格的话,就添加`%20`进去,否则把原来的字符串复制到后面; + +如下图: + + ![这里写图片描述](images/02_s.png) + +代码: + +```java +public class Solution { + public String replaceSpace(StringBuffer str) { + int spaceNum = 0; + for(int i = 0; i < str.length(); i++) if(str.charAt(i) == ' ') spaceNum++; + int newLen = str.length() + 2*spaceNum; + int oriPos = str.length() - 1 , newPos = newLen-1; + str.setLength(newLen); + for(; oriPos >= 0; oriPos--){ + if(str.charAt(oriPos) == ' '){ + str.setCharAt(newPos--, '0'); + str.setCharAt(newPos--, '2'); + str.setCharAt(newPos--, '%'); + }else { + str.setCharAt(newPos--, str.charAt(oriPos)); + } + } + return str.toString(); + } +} +``` \ No newline at end of file diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 03 - \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 03 - \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 03 - \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 03 - \344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 04 - \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 04 - \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 04 - \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 04 - \351\207\215\345\273\272\344\272\214\345\217\211\346\240\221.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 05 - \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\344\270\200\344\270\252\351\230\237\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 05 - \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\344\270\200\344\270\252\351\230\237\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 05 - \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\344\270\200\344\270\252\351\230\237\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 05 - \347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\344\270\200\344\270\252\351\230\237\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 06 - \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 06 - \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 06 - \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 06 - \346\227\213\350\275\254\346\225\260\347\273\204\347\232\204\346\234\200\345\260\217\346\225\260\345\255\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 07 - \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 07 - \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 07 - \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 07 - \346\226\220\346\263\242\351\202\243\345\245\221\346\225\260\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 08 - \350\267\263\345\217\260\351\230\266.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 08 - \350\267\263\345\217\260\351\230\266.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 08 - \350\267\263\345\217\260\351\230\266.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 08 - \350\267\263\345\217\260\351\230\266.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 09 - \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 09 - \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 09 - \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 09 - \345\217\230\346\200\201\350\267\263\345\217\260\351\230\266.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 10 - \347\237\251\345\275\242\350\246\206\347\233\226.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 10 - \347\237\251\345\275\242\350\246\206\347\233\226.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 10 - \347\237\251\345\275\242\350\246\206\347\233\226.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 10 - \347\237\251\345\275\242\350\246\206\347\233\226.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 11 - \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 11 - \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 11 - \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 11 - \344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 12 - \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 12 - \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 12 - \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 12 - \346\225\260\345\200\274\347\232\204\346\225\264\346\225\260\346\254\241\346\226\271.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 13 - \350\260\203\345\242\236\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 13 - \350\260\203\345\242\236\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 13 - \350\260\203\345\242\236\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 13 - \350\260\203\345\242\236\346\225\260\347\273\204\351\241\272\345\272\217\344\275\277\345\245\207\346\225\260\344\275\215\344\272\216\345\201\266\346\225\260\345\211\215\351\235\242.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 14 - \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\347\273\223\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 14 - \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\347\273\223\347\202\271.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 14 - \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\347\273\223\347\202\271.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 14 - \351\223\276\350\241\250\344\270\255\345\200\222\346\225\260\347\254\254k\344\270\252\347\273\223\347\202\271.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 15 - \345\217\215\350\275\254\351\223\276\350\241\250.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 15 - \345\217\215\350\275\254\351\223\276\350\241\250.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 15 - \345\217\215\350\275\254\351\223\276\350\241\250.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 15 - \345\217\215\350\275\254\351\223\276\350\241\250.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 16 - \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 16 - \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 16 - \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 16 - \345\220\210\345\271\266\344\270\244\344\270\252\346\216\222\345\272\217\347\232\204\351\223\276\350\241\250.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 17 - \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 17 - \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 17 - \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 17 - \346\240\221\347\232\204\345\255\220\347\273\223\346\236\204.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 18 - \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 18 - \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 18 - \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 18 - \344\272\214\345\217\211\346\240\221\347\232\204\351\225\234\345\203\217.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 19 - \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 19 - \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 19 - \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 19 - \351\241\272\346\227\266\351\222\210\346\211\223\345\215\260\347\237\251\351\230\265.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 20 - \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 20 - \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 20 - \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 20 - \345\214\205\345\220\253min\345\207\275\346\225\260\347\232\204\346\240\210.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 21 - \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 21 - \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 21 - \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 21 - \346\240\210\347\232\204\345\216\213\345\205\245\343\200\201\345\274\271\345\207\272\345\272\217\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 22 - \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 22 - \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 22 - \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 22 - \344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 23 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 23 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 23 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 23 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 24 - \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 24 - \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 24 - \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 24 - \344\272\214\345\217\211\346\240\221\344\270\255\345\222\214\344\270\272\346\237\220\344\270\200\345\200\274\347\232\204\350\267\257\345\276\204.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 25 - \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 25 - \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 25 - \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 25 - \345\244\215\346\235\202\351\223\276\350\241\250\347\232\204\345\244\215\345\210\266.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" similarity index 99% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" index 4dcca5e0..7b3fef0e 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 26 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\344\270\216\345\217\214\345\220\221\351\223\276\350\241\250.md" @@ -54,7 +54,7 @@ public class Solution { } ``` -上面的写法也可以改成非递归的写法,如下: +上面的写法也可以改成递归的写法,如下: ```java public class Solution { diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 27 - \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 27 - \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 27 - \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 27 - \345\255\227\347\254\246\344\270\262\347\232\204\346\216\222\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" similarity index 98% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" index 7acf22c6..3a64d5c6 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 28 - \346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227.md" @@ -1,5 +1,8 @@ ## 剑指Offer - 28 - 数组中出现次数超过一半的数字 +* [解析](#解析) +* [LeetCode - 229. MajorityElementII](#leetcode---229-majorityelementii) + #### [题目链接](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) > https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&tqId=11181&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking @@ -169,6 +172,8 @@ public class Solution { #### 4、摩尔投票两个变形题 +#### LeetCode - 229. MajorityElementII + 第三种解法有两种变形题目,且都可以用摩尔投票问题解决: * 求数组中`>n/3`的次数的数(最多两个); @@ -183,7 +188,7 @@ public class Solution { * 按照投票的说法,大于`n/3`次数的解题方法是: 先选出两个候选人`candi1、candi2`,如果投`candi1`,则`candi1`的票数`count1++`,如果投`candi2`,则`candi2`的票数`count2++`; * 如果既不投`candi1`,也不投`candi2`,那么检查此时是否`candi1`和`candi2`候选人的票数是否已经为`0`,如果为`0`,则需要更换新的候选人;如果都不为`0`,则`candi1`和`candi2`的票数都要**减一**;当然最后也需要看看是否两个候选人的票数超过`nums.length / 3`; -LeetCode - 229. Majority Element II题解代码如下: +`LeetCode - 229. Majority Element II`题解代码如下: ```java class Solution { diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 29 - \346\234\200\345\260\217\347\232\204K\344\270\252\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 29 - \346\234\200\345\260\217\347\232\204K\344\270\252\346\225\260.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 29 - \346\234\200\345\260\217\347\232\204K\344\270\252\346\225\260.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 29 - \346\234\200\345\260\217\347\232\204K\344\270\252\346\225\260.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 30 - \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 30 - \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 30 - \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 30 - \350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\347\232\204\346\234\200\345\244\247\345\222\214.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" similarity index 78% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" index 4a859ce3..8676d6d4 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 31 - \346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\210\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260\357\274\211.md" @@ -10,35 +10,47 @@ ### 解析 -这题的解法非常多。很好的一道思维+拓展题。 +这题的解法非常多。很好的一道思维+拓展题。说一种个人认为比较好理解的方法,其他自己好好琢磨吧。 -#### 1、常规思路 +参考了这个博客: https://blog.csdn.net/yi_afly/article/details/52012593 -这种方法就是每个数都求一下。显然不是好方法。 +![1555202832462](assets/1555202832462.png) + +![1555202889455](assets/1555202889455.png) + +![1555202908028](assets/1555202908028.png) + +**再举一个栗子: 统1~5246中1的个数** : + +* 想到每一位和1的关系,拿5246,先是个位6,个位的变化范围是0~9,而这样的变化,会有524次,所以这里有524个1,又因为最后一次有个6,所以还要加一次,所以个位的1的个数是524+1 = 525; +* 再看十位,十位上的数字是4,所以同理,这个位数的上的1的个数应该是52 * 10,**注意这里不是52 * 1,因为,10位上的数后面10-20之间有10个1,且最后`4>1`,所以还要加上10,所以十位上的1的个数是`52 * 10+10 = 530`。这里要注意如果十位上的数字是1的话,就要看个位上的数是多少了,也就是10 ~ 20之间取多少个,这时候我们只要计算**`n%10+1`就行了。 +* 然后同理推高位,可以得到1~5246中1的个数是`(524 * 1+1)+(52 * 10+10)+(5 * 100+100) +( 0 * 1000+1000) = 2655`个。 + +代码: ```java public class Solution { - public int NumberOf1Between1AndN_Solution(int n) { + if (n <= 0) return 0; int res = 0; - for (int i = 1; i <= n; i++) - res += numbers(i); - return res; - } - - private int numbers(int n) { - int sum = 0; - while (n > 0) { - if (n % 10 == 1) - sum += 1; - n /= 10; + // base表示当前判断的位数、cur表示当前位、height表示高位 + int base = 1, cur, height = n; + while (height > 0) { + cur = height % 10; + height /= 10; + res += height * base; //先加上一开始的 + if (cur == 1) + res += (n % base) + 1; //==1 就要看前面的了 + else if (cur > 1) + res += base; //后面剩的,>1 还要+base + base *= 10; } - return sum; + return res; } } ``` -#### 2、 +#### 二、其他解法 ```java public class Solution { @@ -125,33 +137,6 @@ public class Solution { } ``` -#### 3、 - -```java -public class Solution { - public int NumberOf1Between1AndN_Solution(int n) { - if (n <= 0) - return 0; - int res = 0; - int base = 1, cur, height = n; // base表示当前判断的位数、cur表示当前位、height表示高位 - while (height > 0) { - cur = height % 10; - height /= 10; - res += height * base; //先加上一开始的 - if (cur == 1) { - res += (n % base) + 1; //==1 就要看前面的了 - } else if (cur > 1) { - res += base; //后面剩的,>1 还要+base - } - base *= 10; - } - return res; - } -} -``` - -#### 4、 - ```java public class Solution { @@ -179,8 +164,6 @@ public class Solution { } ``` -#### 5、 - ```java public class Solution { diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 32 - \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 32 - \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 32 - \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 32 - \346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" similarity index 99% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" index 8c954fcf..63ef5583 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 33 - \344\270\221\346\225\260.md" @@ -37,7 +37,7 @@ public class Solution { } ``` -O(N)的思路如下: +`O(N)`的思路如下: 为了保持丑数数组的顺序,可以维护三个队列`q2、q3、q5`,分别存放每次由上一个最小的没有用过的丑数乘以`2、3、5`得到的丑数: diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 34 - \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 34 - \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 34 - \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 34 - \347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 35 - \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 35 - \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 35 - \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 35 - \346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 36 - \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 36 - \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 36 - \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 36 - \344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 37 - \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 37 - \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 37 - \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 37 - \346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" similarity index 98% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" index 1fff00ed..75e93295 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 38 - \344\272\214\345\217\211\346\240\221\347\232\204\346\267\261\345\272\246.md" @@ -34,7 +34,7 @@ public class Solution { * 同时,我们在当前层的时候,可以得知下一层的节点的数量(通过`queue.size()`); * 然后在到了下一层的时候, 就判断统计的数量`count == nextLevelSize`,如果等于,就加一层`depth++`; -![](images/38_s.png) +![png](images/38_s.png) 代码: diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 39 - \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 39 - \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 39 - \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 39 - \345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 40 - \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 40 - \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 40 - \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 40 - \346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" similarity index 92% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" index 5a48ee71..bc9e2f27 100644 --- "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 41 - \345\222\214\344\270\272S\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227.md" @@ -16,10 +16,10 @@ 双指针思路: -* 两个指针 small 和 big 分别表示序列(窗口)的最小值和最大值。 -* 首先把 small 初始化为 1,big 初始化为 2。 -* 如果从 small 到 big 的序列的和大于 s,我们可以从序列中去掉较小的值,也就是增大 small 的值。 -* 如果从 small 到 big 的序列的和小于 s,我们可以增大big,让这个序列包含更多的数字。 +* 两个指针 `small `和 `big` 分别表示序列(窗口)的最小值和最大值。 +* 首先把 `small` 初始化为 1,`big` 初始化为 2。 +* 如果从` small `到 `big` 的序列的和大于 `s`,我们可以从序列中去掉较小的值,也就是增大 `small `的值。 +* 如果从 `small` 到 `big` 的序列的和小于 `s`,我们可以增大`big`,让这个序列包含更多的数字。 **因为这个序列至少要有两个数字,我们一直增加 `small` 到 `(1+s)/2` 为止**。 @@ -35,7 +35,7 @@ | 6 | 3 | 5 | 3, 4, 5 | 12 | 大于 | 增加small | | 7 | 4 | 5 | 4, 5 | 9 | 等于 | 打印序列 | -> 过程 : +> 过程: > > 先把 small 初始化为 1,big 初始化为2。此时介于 small 和 big 之间的序列是`{1, 2}`,序列的和为 3,小于9,记以我们下一步要让序列包含更多的数字。我们把 big 增加 1 变成 3,此时序列为`{1,2,, 3}`。由于序列的和是 6,仍然小于 9,我们接下来再增加big 变成4,介于 small 和 big 之间的序列也随之变成`{1, 2, 3, 4}`。由于序列的和 10 大于 9,我们要删去去序列中的一些数字,于是我们增加 small 变成2,此时得到的序列是`{2, 3, 4}`,序列的和正好是 9。我们找到了第一个和为 9 的连续序列,把它打印出来。接下来我们再增加 big,重复前面的过程, 可以找到第二个和为 9 的连续序列`{4, 5}`。 diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 42 - \345\222\214\344\270\272S\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227(\346\216\222\345\272\217\346\225\260\347\273\204).md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 42 - \345\222\214\344\270\272S\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227(\346\216\222\345\272\217\346\225\260\347\273\204).md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 42 - \345\222\214\344\270\272S\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227(\346\216\222\345\272\217\346\225\260\347\273\204).md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 42 - \345\222\214\344\270\272S\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227(\346\216\222\345\272\217\346\225\260\347\273\204).md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 43 - \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 43 - \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 43 - \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 43 - \345\267\246\346\227\213\350\275\254\345\255\227\347\254\246\344\270\262.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 44 - \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 44 - \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 44 - \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 44 - \347\277\273\350\275\254\345\215\225\350\257\215\351\241\272\345\272\217\345\210\227.md" diff --git "a/\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 45 - \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 45 - \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" similarity index 100% rename from "\345\210\267\351\242\230/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 45 - \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" rename to "Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 45 - \346\211\221\345\205\213\347\211\214\351\241\272\345\255\220.md" diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 46 - \345\255\251\345\255\220\344\273\254\347\232\204\346\270\270\346\210\217(\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260)(\347\272\246\347\221\237\345\244\253\347\216\257).md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 46 - \345\255\251\345\255\220\344\273\254\347\232\204\346\270\270\346\210\217(\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260)(\347\272\246\347\221\237\345\244\253\347\216\257).md" new file mode 100644 index 00000000..d6b1c348 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 46 - \345\255\251\345\255\220\344\273\254\347\232\204\346\270\270\346\210\217(\345\234\206\345\234\210\344\270\255\346\234\200\345\220\216\345\211\251\344\270\213\347\232\204\346\225\260)(\347\272\246\347\221\237\345\244\253\347\216\257).md" @@ -0,0 +1,176 @@ +## 孩子们的游戏(圆圈中最后剩下的数)(约瑟夫环) + +#### [题目链接](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +### 解析 + +提供几种做法。 + +#### 1、解法一 + +常规解法,使用链表模拟。 + +* 在环形链表中遍历每一个节点,不断转圈,不断让每个节点报数; +* 当报数达到`m`互,就删除当前报数的点; +* 删除节点后,继续连接整个环形链表; +* 继续报数,继续删除,知道链表节点数为`1`; + +看个例子。 + +![](images/46_s3.png) + +代码: + +```java +public class Solution { + + class Node { + int val; + Node next; + + Node(int v) { + val = v; + } + } + + public int LastRemaining_Solution(int n, int m) { + if (m == 0 || n == 0) + return -1; + // 构造环形链表 + Node head = new Node(0); + Node pre = head; + for (int i = 1; i < n; i++) { + Node cur = new Node(i); + pre.next = cur; + pre = cur; + } + pre.next = head; // 环形 + Node tail = pre; + int cnt = 0; + while (head != tail) { + if (++cnt == m) { + tail.next = head.next; // del head + cnt = 0; + head = tail.next; + } else { + tail = tail.next; // del tail + head = tail.next; + } + } + return head.val; + } +} + +``` + +#### 2、解法二 + +利用取摸。看个例子就懂了。 + +`n = 5, m = 4`。 + + + +![](images/46_s4.png) + +代码: + +```java +import java.util.ArrayList; +public class Solution { + public int LastRemaining_Solution(int n, int m) { + if(n == 0 || m == 0) + return -1; + ArrayList list = new ArrayList<>(); + for(int i = 0; i < n; i++) list.add(i); + int pos = -1; + while(list.size() > 1){ + pos = (pos + m) % list.size(); + list.remove(pos); + pos--; + } + return list.get(0); + } +} + +``` + +#### 3、解法三 + +这个解法我也没看的很懂。。。。 + +函数`F(n, m)`关于`n`个数字的序列中最后剩下的数字 与 函数`F(n-1, m)`关于`n-1`个数字的序列中最后一个数字存在一定的关系。 + +要得到`n`个数字的序列中最后剩下的数字,只需要得到`n-1`个数字的序列中最后一个数字即可。 + +先给出递归公式: + +`F(n, m) = [F(n-1, m) + m] % n`。 + +当 `n == 1`时 `F(n, m) = 0`。 + +下面给出一些简单的推理。 + +* 定义`F(n, m)`表示每次在`n`个数字`(0, 1, ... n-1)`中,每次删除第`m`个数字最后剩下的数字。在这个`n`个数字中,**第一个被删除的数字 = `(m - 1)%n`;**(记为`k`) + + 看个例子: + + ![](images/46_s.png) + +* 那么删除 `k` 之后剩下的 `n-1` 个数字为 `(0, 1,.…, k-1,k+1,,.…, n-1)`,并且下一次删除从`k+1`开始计数,从而形参`(k+1, k+2, ... n-1, 0, 1, ... k-1)`。 + +* 上述序列最后剩下的数字也是关于`n`和 `m`的函数,由于这个序列的规律和前面最初的序列不一样,记为`F'(n-1, m)`; + +* 最初序列最后剩下的数字`F(n, m)`一定是删除一个数字之后的序列最后剩下的数字,即`F(n, m) = F'(n-1, m)`; + +* 把上面的序列和新的索引做一个映射如下: + +![](images/46_s2.png) + +* 我们把映射定义为 `p`,则 `p(x)=(x-k-1)%n`。它表示如果映射前的数字是`x`,那么映射后的数字是`(x-k-1)%n`。该映射的着映射是 **p-1(x)=(x + k + 1)%n**。 +* 由于映射之后的序列和最初的序列具有同样的形式,即都是从 0 开始的连续序列,因此仍然可以用函数`F`来表示,记为 `F(n-1 m)`。根据我们的映射规则, 映射之前的序列中最后剩下的数字**F'(n-1, m) = p-1[F(n-1, m)] = [F(n-1, m) + k + 1] % n**; +* 把`k = (m-1)%n`带入得到`F(n, m) = F'(n-1, m) = [F(n-1, m) + m] %n`。 + +代码: + +递归: + +```java +public class Solution { + + public int LastRemaining_Solution(int n, int m) { + if (m == 0 || n == 0) + return -1; + return news(n, m); + } + + public int news(int n, int m) { + if (n == 1) // 当链表中只有一个元素的时候就返回编号1 + return 0; + int upper = news(n - 1, m); // 从 n-1中移除后最后剩下的 + return (upper + m) % n; // 回到 n 层 对应的编号是啥 + } +} + +``` + +非递归: + +```java +public class Solution { + + public int LastRemaining_Solution(int n, int m) { + if (m == 0 || n == 0) + return -1; + int last = 0; + for(int i = 2; i <= n; i++) last = (last + m) % i; + return last; + } +} + +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 47 - \346\261\2021+2+3+...+n.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 47 - \346\261\2021+2+3+...+n.md" new file mode 100644 index 00000000..e21fa532 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 47 - \346\261\2021+2+3+...+n.md" @@ -0,0 +1,27 @@ +## 剑指Offer - 47 - 求1+2+3+...+n + +#### [题目链接](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) +> https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +> 求`1+2+3+...+n`,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 + +### 解析 + +思路: **利用逻辑与的短路特性实现递归终止**。 + +- 当`n == 0`时,`n > 0 && (res += Sum_Solution(n-1)) > 0`只执行前面的判断,为`false`,然后直接返回`0`; +- 当`n > 0`时,递归`res += Sum_Solution(n-1)`,实现递归计算; + +代码: + +```java +public class Solution { + public int Sum_Solution(int n) { + int res = n; + boolean b = n > 0 && (res += Sum_Solution(n-1)) > 0; + return res; + } +} +``` diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 48 - \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 48 - \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" new file mode 100644 index 00000000..a43ecccf --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 48 - \344\270\215\347\224\250\345\212\240\345\207\217\344\271\230\351\231\244\345\201\232\345\212\240\346\263\225.md" @@ -0,0 +1,209 @@ +## 剑指Offer - 48 - 不用加减乘除做加法 + +#### [题目链接](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + + +> https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&tqId=11201&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking +#### 题目 + +> 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。 + +### 解析 + +如果在不考虑进位的情况下,`a ^ b`就是正确结果。因为`0 + 0 = 0(0 ^ 0)`,`1 + 0 = 1(1 ^ 0)`,`0 + 1 = 1(0 ^ 1)`,`1 + 1 = 0 (1 ^ 1)`。 + +例如: + +```java +a : 001010101 +b : 000101111 + 001111010 +``` + +在只考虑进位的情况下,也就是只考虑`a + b`中进位产生的值的情况下,`(a & b) << 1`就是结果。因为第`i`位的结果只有可能是`i - 1`位都为`1`的情况下才会产生进位。 + +例如: + +```java +a : 001010101 +b : 000101111 + 000001010 +``` + +把完全不考虑进位和考虑进位的两种情况相加,就是最终的结果。也就是说一直重复这样的过程,直到最后的进位为`0`则说明完成了相加。 + +例如: + +```java +1、一开始的值: +a : 001010101 +b : 000101111 + +2、上面两个异或和&<<1的值: +^ : 001111010 +&<<1 : 000001010 + +3、上面两个异或和&<<1的值: +^ : 001110000 +&<<1 : 000010100 + +4、上面两个异或和&<<1的值: +^ : 001100100 +&<<1 : 000100000 + +5、上面两个异或和&<<1的值: +^ : 001000100 +&<<1 : 001000000 + +6、上面两个异或和&<<1的值: +^ : 000000100 +&<<1 : 010000000 + +7、上面两个异或和&<<1的值: +^ : 010000100 +&<<1 : 000000000 (num2 == 0) +``` + +代码: + +```java +public class Solution { + public int Add(int num1, int num2) { + int sum = num1, carry = 0;//一开始sum = num1的原因是如果num2 == 0,后面我直接返回sum,而不是num1 + while(num2 != 0){ + sum = num1 ^ num2; + carry = (num1 & num2) << 1; + num1 = sum; + num2 = carry; + } + return sum; + } +} +``` + +也可以写成这样: + +```java +public class Solution { + public int Add(int num1, int num2) { + int sum = 0, carry = 0; + while(num2 != 0){ + sum = num1 ^ num2; + carry = (num1 & num2) << 1; + num1 = sum; + num2 = carry; + } + return num1; + } +} +``` + +*** + +另外,也可以用位运算实现**减法**,因为`a - b = a +(-b)`,而在二进制的表示中,得到一个数的相反数,就是一个数取反然后`+1`(补码)即可。 + +取反`+1`的代码看`negNum()`方法,而`Minus()`方法实现的是两数的减法运算。 + +```java +public class Solution { + + public int Add(int num1, int num2) { + int sum = num1, carry = 0;//一开始sum = num1的原因是如果num2 == 0,后面我直接返回sum,而不是num1 + while(num2 != 0){ + sum = num1 ^ num2; + carry = (num1 & num2) << 1; + num1 = sum; + num2 = carry; + } + return sum; + } + + // a + (-b) + public int Minus(int num1, int num2){ + return Add(num1, negNum(num2)); + } + + private int negNum(int n) { + return Add(~n, 1); // 取反+1 + } + + public static void main(String[] args){ + System.out.println(new Solution().Minus(1000, 100)); + } +} +``` + +*** + +用位运算也可以实现乘法。做法如下: + +**a * b = a * 20 * b 0 + a * 21 * b1 + ... + a * 2i * b i + ... + a * 231 * b31** 。 + +其中`bi`为`0`或者`1`代表整数`b`的二进制数表达中第`i`位的值。 + +下面就计算过程举一个例子: + +`a = 22 = 000010110,b = 13 = 000001101,res = 0`: + +```java +1、一开始的值 +a : 000010110 +b : 000001101 +res: 000000000 +2、b的最左侧为1,所以res = res + a,同时b右移一位, a左移一位 +a : 000101100 +b : 000000110 +res: 000010110 +3、b的最左侧为0,所以res不变, 同时b右移一位, a左移一位 +a : 001011000 +b : 000000011 +res: 000010110 +4、b的最左侧为1,所以res = res + a,同时b右移一位, a左移一位 +a : 010110000 +b : 000000001 +res: 001101110 +5、b的最左侧为1,所以res = res + a,同时b右移一位, a左移一位 +a : 101100000 +b : 000000000 +res: 100011110 +``` + +此时`b == 0`,过程停止,返回`res = 100011110`,即`286`。 + +不管`a、b`是正、负、0,以上过程都是对的。 + +代码: + +```java +public class Solution { + + public int Add(int num1, int num2) { + int sum = num1, carry = 0;//一开始sum = num1的原因是如果num2 == 0,后面我直接返回sum,而不是num1 + while(num2 != 0){ + sum = num1 ^ num2; + carry = (num1 & num2) << 1; + num1 = sum; + num2 = carry; + } + return sum; + } + + public int Multi(int a, int b){ + int res = 0; + while(b != 0){ + if( (b & 1) != 0) + res = Add(res, a); + a <<= 1; + b >>>= 1; // 无符号右移 + } + return res; + } + + public static void main(String[] args){ + System.out.println(new Solution().Multi(1000, 100)); + } +} +``` + + + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 49 - \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 49 - \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" new file mode 100644 index 00000000..ffebc751 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 49 - \346\212\212\345\255\227\347\254\246\344\270\262\350\275\254\346\215\242\346\210\220\346\225\264\346\225\260.md" @@ -0,0 +1,43 @@ +## 把字符串转换成整数 + +#### [题目链接](https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/1277c681251b4372bdef344468e4f26e?tpId=13&tqId=11202&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +![pic](images/49_t.png) + +> 注意可以包含**字母和符号**。 + +### 解析 + +比较简单的模拟题。但是也要注意一些情况: + +* 前面的空字符要去掉; +* 然后就是第一个如果是符号要判断; +* 还有一个就是判断溢出,这个问题可能有点麻烦,方式挺多,但是很多都有小问题; + +代码: + +```java +public class Solution { + + public int StrToInt(String str) { + if (str == null || str.trim().equals("")) + return 0; + char[] chs = str.trim().toCharArray();//去除前面的空字符' ' + int res = 0; + for (int i = (chs[0] == '-' || chs[0] == '+') ? 1 : 0; i < str.length(); i++) { + if(chs[i] < '0' || chs[i] > '9') return 0; // < 48 || > 57 + int num = chs[i] - '0'; // chs[i] - 48 + int sum = res * 10 + num; + if((sum - num)/10 != res) // 如果 sum超出范围了,这个表达式就回不到原来的res + return 0; + res = sum; + } + return chs[0] == '-' ? -res : res; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 50 - \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 50 - \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" new file mode 100644 index 00000000..9b3084fa --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 50 - \346\225\260\347\273\204\344\270\255\351\207\215\345\244\215\347\232\204\346\225\260\345\255\227.md" @@ -0,0 +1,90 @@ +## 剑指Offer - 50 - 数组中重复的数字 + +#### [题目链接](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +> **在一个长度为n的数组里的所有数字都在0到`n-1`的范围内**。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组`{2,3,1,0,2,5,3}`,那么对应的输出是第一个重复的数字`2`。 + +### 解析 + +给出三种解法,后面两种O(1)空间实现。 + +#### 解法一 + +直接用一个数组保存那个数之前有没有出现过即可。因为数在`0~n-1`之间,所以数组只需要开`n`即可。 + +```java +public class Solution { + public boolean duplicate(int numbers[], int length, int[] duplication) { + if(numbers == null || length == 0) return false; + boolean[] used = new boolean[length]; + for(int num : numbers){ + if(used[num]){ + duplication[0] = num; + return true; + } + used[num] = true; + } + return false; + } +} +``` + +#### 解法二 + +思路: + +* 充分利用数只出现在`0 ~ n-1`之间,所以我们每次将`arr[ abs(arr[i])] `标记成它的相反数; +* 下次,如果再发现一个`arr[i]`,且`arr[ abs(arr[i])] < 0`,说明之前已经标记过了,所以可以返回`arr[i]`; + +先给出代码: + +```java +public class Solution { + public boolean duplicate(int numbers[], int length, int[] duplication) { + if(numbers == null || length == 0) return false; + for(int i = 0; i < length; i++){ + int idx = Math.abs(numbers[i]); + if(numbers[idx] >= 0) + numbers[idx] = -numbers[idx]; + else { + duplication[0] = idx; + return true; + } + } + return false; + } +} +``` + +看个例子: `arr = {1, 2, 3, 4, 4, 6, 6}` + +![50_s.png](images/50_s.png) + +#### 解法三 + +这种解法和上面那种解法其实本质差不多。 + +这里是将`arr[ arr[i] ] = arr[arr[i]] + length`。然后一开始的时候就需要还原(`-=length`),然后判断。 + +```java +public class Solution { + public boolean duplicate(int numbers[], int length, int[] duplication) { + if(numbers == null || length == 0) return false; + for(int i = 0; i < length; i++){ + int idx = numbers[i]; + if(idx >= length) idx -= length; + if(numbers[idx] >= length) { + duplication[0] = idx; + return true; + } + numbers[idx] = numbers[idx] + length; + } + return false; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 51 - \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 51 - \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" new file mode 100644 index 00000000..f5e8cb9e --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 51 - \346\236\204\345\273\272\344\271\230\347\247\257\346\225\260\347\273\204.md" @@ -0,0 +1,43 @@ +## 剑指Offer - 51 - 构建乘积数组 + +#### [题目链接](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +> 给定一个数组`A[0,1,...,n-1]`,请构建一个数组`B[0,1,...,n-1]`,其中B中的元素`B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]`。**不能使用除法**。 + +### 解析 + +显然O(N^2)的方法不是好方法,好的方法是**分别从两边开始乘**。 + +* 一开始从左往右累乘到`B[i]`,但是不要包括`A[i]` (也就是`A[0 ~ i-1]`); +* 第二次从后往前累乘到`B[i]`,也不要包括`A[i]`(也就是`A[i+1 ~ n-1]`); + +看个例子: + +![51_s.png](images/51_s.png) + +代码: + +```java +public class Solution { + public int[] multiply(int[] A) { + int n = A.length; + int[] B = new int[n]; + int mul = 1; + for (int i = 0; i < n; i++) { + B[i] = mul;//先 = + mul *= A[i]; + } + mul = 1; + for (int i = n - 1; i >= 0; i--) { + B[i] *= mul;//先 * + mul *= A[i]; + } + return B; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 52 - \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 52 - \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" new file mode 100644 index 00000000..b49ea43a --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 52 - \346\255\243\345\210\231\350\241\250\350\276\276\345\274\217\345\214\271\351\205\215.md" @@ -0,0 +1,111 @@ +## 剑指Offer - 52 - 正则表达式匹配 + +#### [题目链接](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +> 请实现一个函数用来匹配包括`'.'`和`'*'`的正则表达式。模式中的字符`'.'`表示任意一个字符,而`'*'`表示它**前面的字符**可以出现任意次(**包含0次**)。 +> +> 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串`"aaa"`与模式`"a.a"`和`"ab*ac*a"`匹配,但是与`"aa.a"`和`"ab*a"`均不匹配。 + +### 解析 + +这种题目一般都会用动态规划。 + +递归的思路如下图,大致分三种情况 + +我们的`sl`和`pl`分别表示当前判断的两个串的长度。而对应的索引是`s[sl - 1]`和`p[pl-1]`。 + +* `s[sl - 1] = p[pl-1]`; +* `p[pl - 1] = '.'`; +* `p[pl - 1] = '*'`;这种情况具体又可以分几种情况,具体看下图。 + +![52_s.png](images/52_s.png) + +最后还要注意边界,当`s == "" && p == ""`的时候返回`true`,当`p=="" & s!= ""`的时候返回`false`。 + +当`s == "" && p != ""`的时候就要注意,如果`p[i-1] == '*'`则`dp[0][i] = dp[0][i-2]`,因为可以用`*`可以消去前一个字符。 + +虽然第三种情况,可以合起来考虑,代码会更简洁一些,但是这里个人认为还是写的清楚一点较好。 + +递归版本: + +```java +public class Solution { + + private int[][] dp; + + public boolean match(char[] str, char[] pattern) { + dp = new int[str.length + 1][pattern.length + 1]; + return isMatch(str, pattern, str.length, pattern.length); + } + + private boolean isMatch(char[] s, char[] p, int ls, int lp) { + if (ls == 0 && lp == 0) + return true; + if (dp[ls][lp] != 0) + return dp[ls][lp] == 1; + if (lp == 0) + return false; + boolean res = false; + if (ls == 0) { + res = lp >= 2 && p[lp - 1] == '*' && isMatch(s, p, ls, lp - 2); + } else { + if (p[lp - 1] == '.' || p[lp - 1] == s[ls - 1]) { + res = isMatch(s, p, ls - 1, lp - 1); + } else if (p[lp - 1] == '*') { + if (p[lp - 2] == '.') + res = isMatch(s, p, ls - 1, lp - 1) + || isMatch(s, p, ls - 1, lp) + || isMatch(s, p, ls, lp - 2); + else if (s[ls - 1] == p[lp - 2]) { + res = isMatch(s, p, ls - 1, lp - 2) //这里和上面不同,不是ls-1, lp-1, + || isMatch(s, p, ls - 1, lp) + || isMatch(s, p, ls, lp - 2); + } else + res = isMatch(s, p, ls, lp - 2); + } + } + dp[ls][lp] = res ? 1 : -1; + return res; + } +} +``` + +递推版本: + +```java +public class Solution { + public boolean match(char[] str, char[] pattern) { + return isMatch(str, pattern); + } + + private boolean isMatch(char[] s, char[] p) { + int sn = s.length; + int pn = p.length; + boolean[][] dp = new boolean[sn + 1][pn + 1]; + dp[0][0] = true; + for (int i = 1; i <= pn; i++) + if (p[i - 1] == '*') // 第一个不会是 * + dp[0][i] = dp[0][i - 2]; + + for (int i = 1; i <= sn; i++) { + for (int j = 1; j <= pn; j++) + if (s[i - 1] == p[j - 1] || p[j - 1] == '.') + dp[i][j] = dp[i - 1][j - 1]; + else if (p[j - 1] == '*') { + if (p[j - 2] == '.') { + dp[i][j] = dp[i - 1][j - 1] || dp[i - 1][j] || dp[i][j - 2]; + } else if (s[i - 1] == p[j - 2]) { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - 2] || dp[i][j - 2]; + } else + dp[i][j] = dp[i][j - 2]; + } + } + return dp[sn][pn]; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 53 - \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 53 - \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" new file mode 100644 index 00000000..775738b2 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 53 - \350\241\250\347\244\272\346\225\260\345\200\274\347\232\204\345\255\227\347\254\246\344\270\262.md" @@ -0,0 +1,69 @@ +## 剑指Offer - 53 - 表示数值的字符串 + +#### [题目链接](https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/6f8c901d091949a5837e24bb82a731f2?tpId=13&tqId=11206&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +![53_t.png](images/53_t.png) + + +### 解析 + +模拟题,比较好的方式是用三个`bool`变量`sign、dot、E`记录前面是否已经出现过`+/-、.、E/e`,然后就去考虑一些情况: + +* 首先最后一个字符不能是`E、e、.、+、e`; +* 当`str[i] == E/e`时,先判断之前有没有出现过`E/e`,且`E`前面只能是数字; +* 当`str[i] == +/-`时,如果是前面以及出现了`+/-`,则这个`+/-`只能在`E/e`之后。如果第一次出现`+/-`,则必须出现在开头或者在`E/e`之后; +* 当`str[i] == '.'`时,判断`.`只能出现一次,且`.`不能出现在`E`之后; + +代码: + +```java +import java.math.BigDecimal; + +class Solution { + + public boolean isNumeric(char[] str) { + if (str == null || str.length == 0) + return false; + int n = str.length; + // 最后一个不能为这些 + if (str[n - 1] == 'E' || str[n - 1] == 'e' || str[n - 1] == '.' || str[n - 1] == '+' || str[n - 1] == '-') + return false; + boolean sign = false, dot = false, E = false; // 是否出现 +/- 、. 、E/e + for (int i = 0; i < str.length; i++) { + if (str[i] == 'e' || str[i] == 'E') { + if (E) return false; // 只能出现一个E + if (i == str.length - 1) return false; // E后面一定要有东西 + if (i > 0 && (str[i - 1] == '+' || str[i - 1] == '-' || str[i - 1] == '.')) return false; //E 前面是数字 + E = true; + } else if (str[i] == '-' || str[i] == '+') { + // 第二次出现+- 必须在e之后 + if (sign && str[i - 1] != 'e' && str[i - 1] != 'E') return false; // 第二个符号必须在E的后面 + // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后 + if (!sign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false; + sign = true; + } else if (str[i] == '.') { // dot + if (E || dot) return false; // E后面不能有小数点, 小数点不能出现两次 例如: 12e+4.3 + dot = true; + } else if (str[i] < '0' || str[i] > '9') { + return false; + } + } + return true; + } + + public static void main(String[] args) { + System.out.println(new Solution().isNumeric(new char[]{'-', '.', 'E', '5'})); // false + System.out.println(new Solution().isNumeric(new char[]{'-', '+'})); // false + System.out.println(new Solution().isNumeric(new char[]{'-', '.', 'E', '+'})); // false + + // 测试 科学计数法 + BigDecimal bd = new BigDecimal("-3.40256010353E11"); + System.out.println(bd.toPlainString()); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 54 - \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 54 - \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" new file mode 100644 index 00000000..efb96e48 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 54 - \345\255\227\347\254\246\346\265\201\344\270\255\347\254\254\344\270\200\344\270\252\344\270\215\351\207\215\345\244\215\347\232\204\345\255\227\347\254\246.md" @@ -0,0 +1,107 @@ +## 剑指Offer - 54 - 字符流中第一个不重复的字符 + +#### [题目链接](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +![54_t.png](images/54_t.png) + +### 解析 + +解法一,O(N)。 + +最容易想到的是: 先用一个容器或者字符串记录`Insert`的字符,并且使用一个哈希表`count`数组统计出现次数。 + +然后查询的时候,就遍历容器,第一个`c[str[i]] == 1`的就是答案。 + +但是这种方法需要遍历容器一遍,也就是时间复杂度是`Insert`的长度`O(N)`。 + +```java +public class Solution { + + private StringBuilder sb = new StringBuilder(); + + private int[] c = new int[256]; + + public void Insert(char ch) { + sb.append(ch); + c[ch]++; + } + + public char FirstAppearingOnce() { + for(int i = 0; i < sb.length(); i++) if(c[sb.charAt(i)] == 1) return sb.charAt(i); + return '#'; + } +} +``` + +O(256)。 + +第二种优化的方法是使用一个队列,来记录`c[ch] == 1`的,然后每次查询的时候,从对头取,直到取到`c[q.peek()] == 1`的,就是我们要的,**因为队列的先进先出,所以对头的一定是我们之前最早加入的**。 + +这种方法将时间复杂度从`O(N)`降低到`O(256)`。 + +例如,加入`googl`的过程。 + +![54_s.png](images/54_s.png) + +代码: + +```java +import java.util.*; + +public class Solution { + + private int[] c; + private Queue q; + + public Solution() { + c = new int[256]; + q = new LinkedList<>(); + } + + public void Insert(char ch) { + if (++c[ch] == 1) q.add(ch); // 将出现一次的入队 + } + + public char FirstAppearingOnce() { + while (!q.isEmpty() && c[q.peek()] != 1) q.poll(); + if (q.isEmpty()) return '#'; // 不能将这个放在上面,可能会空指针异常 + return q.peek(); + } +} +``` + +还一种使用特殊标记`c`数组的方法。这种方法的时间复杂度也是`O(256)`。 + +方法也差不多,用一个`pos`变量记录加入的顺序,用`c[ch] = -1`表示超过了`2`次。 + +后面查找函数,就遍历`ch(0~ 256)`,然后找到一个最小的索引(最先加入的)即可。 + +```java +public class Solution { + + private int[] c = new int[256]; + private int pos = 1; // 从1开始 + + public void Insert(char ch) { + if(c[ch] == 0) + c[ch] = pos++; + else + c[ch] = -1; // 超过一次的直接标记为-1 + } + + public char FirstAppearingOnce() { + int minIndex = Integer.MAX_VALUE; + char res = '#'; + for(int i = 0; i < 256; i++) if(c[i] != 0 && c[i] != -1 && c[i] < minIndex){ + minIndex = c[i]; + res = (char)i; + } + return res; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 55 - \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 55 - \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" new file mode 100644 index 00000000..797664ff --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 55 - \351\223\276\350\241\250\344\270\255\347\216\257\347\232\204\345\205\245\345\217\243\347\273\223\347\202\271.md" @@ -0,0 +1,47 @@ +## 剑指Offer - 55 - 链表中环的入口结点 + +#### [题目链接](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出`null`。 + +### 解析 + +这题可以用HashSet来做,但是那样空间复杂度为`O(N)`,比较好的经典的算法是双指针(数学)方法。 + +* 两个指针`fast、slow`,`fast`一次走两步,`slow`一次走一步; +* 如果有环,他们一定会在环内相遇; +* 当他们相遇之后,让`fast`回到起点,`slow`不动; +* 然后两个指针一起走,都是走一步,当他们走到一起的时候,他们的交点就是入环点; + +简单证明: + +![55_s.png](images/55_s.png) + +代码: + +```java +public class Solution { + public ListNode EntryNodeOfLoop(ListNode pHead) { + if(pHead == null) return null; + ListNode fast = pHead, slow = pHead; + while(slow.next != null && fast.next.next != null){ + slow = slow.next; + fast = fast.next.next; + if(slow == fast){ + fast = pHead; + while(slow != fast){ + slow = slow.next; + fast = fast.next; + } + return fast; + } + } + return null; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 56 - \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\350\212\202\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 56 - \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\350\212\202\347\202\271.md" new file mode 100644 index 00000000..17ad9004 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 56 - \345\210\240\351\231\244\351\223\276\350\241\250\344\270\255\351\207\215\345\244\215\347\232\204\350\212\202\347\202\271.md" @@ -0,0 +1,69 @@ +## 剑指Offer - 56 - 删除链表中重复的节点 + +#### [题目链接](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表`1->2->3->3->4->4->5` 处理后为 `1->2->5`。 + +### 解析 + +这题主要是练习链表的操作。 + +非递归: + +用三个变量`pre、cur、next`操作即可。注意用`dummyHead`操作会方便一些。 + +![56_s.png](images/56_s.png) + +代码: + +```java +public class Solution { + + public ListNode deleteDuplication(ListNode pHead){ + if(pHead == null) return null; + if(pHead.next == null) return pHead; + ListNode dummyHead = new ListNode(Integer.MAX_VALUE); + dummyHead.next = pHead; + ListNode pre = dummyHead, cur = pHead, next; + while(cur != null){ + next = cur.next; + if(next == null) break; + if(cur.val == next.val){ + while(next != null && cur.val == next.val)//重复的一截 + next = next.next; + pre.next = next;// 减掉中间重复的 + cur = next; + }else { + pre = pre.next; + cur = cur.next; + } + } + return dummyHead.next; + } +} +``` + +递归写法: 原理一样。 + +```java +public class Solution { + + public ListNode deleteDuplication(ListNode pHead) { + if (pHead == null || pHead.next == null) return pHead; + ListNode cur = pHead, next = pHead.next; + if (cur.val != next.val) { + cur.next = deleteDuplication(next); + return cur; + } else { + while (next != null && cur.val == next.val) + next = next.next; + return deleteDuplication(next); + } + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 57 - \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 57 - \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" new file mode 100644 index 00000000..f3385b18 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 57 - \344\272\214\345\217\211\346\240\221\347\232\204\344\270\213\344\270\200\344\270\252\347\273\223\347\202\271.md" @@ -0,0 +1,50 @@ +## 剑指Offer - 57 - 二叉树的下一个结点 + +#### [题目链接](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 + +```java +public class TreeLinkNode { + int val; + TreeLinkNode left = null; + TreeLinkNode right = null; + TreeLinkNode next = null; //觉得改成parent更好一点 + + TreeLinkNode(int val) { + this.val = val; + } +} +``` + +### 解析 + +这个题目我在另一篇博客[**在一颗二叉树中寻找一个结点的后继结点或者前驱节点**](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Tree/%E5%9C%A8%E4%B8%80%E9%A2%97%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%AF%BB%E6%89%BE%E4%B8%80%E4%B8%AA%E7%BB%93%E7%82%B9%E7%9A%84%E5%90%8E%E7%BB%A7%E7%BB%93%E7%82%B9(%E5%89%8D%E9%A9%B1%E7%BB%93%E7%82%B9).md)已经详细介绍。 + +分三种情况。不赘述。 + +```java +public class Solution { + + // next 最好写成 parent + public TreeLinkNode GetNext(TreeLinkNode pNode) { + if (pNode == null) return null; + if (pNode.right != null) return getMostLeft(pNode.right); // 答案是: 右孩子的最左节点 + if (pNode.next != null && pNode.next.left != null && pNode.next.left == pNode) // 答案是: 父亲 + return pNode.next; + while (pNode.next != null && pNode.next.right != null && pNode.next.right == pNode) //答案是不断的往上找 + pNode = pNode.next; + return pNode.next; + } + //获得node的最左下角节点 + public TreeLinkNode getMostLeft(TreeLinkNode node) { + while (node.left != null) node = node.left; + return node; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 58 - \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 58 - \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 00000000..68d11990 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 58 - \345\257\271\347\247\260\347\232\204\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,98 @@ +## 剑指Offer - 58 - 对称的二叉树 + +#### [题目链接](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +请实现一个函数,用来判断一颗二叉树是不是对称的。注意,**如果一个二叉树同此二叉树的镜像是同样的**,定义其为对称的。 + +### 解析 + +递归思路。 + +* 首先根节点,只要`pRoot.left`和`pRoot.right`对称即可; + +* 左右节点的**值相等**且对称子树`left.left 和 right.right对称` ,且`left.rigth和right.left也对称`。 + +
>

+ +递归: + +```java +public class Solution { + + boolean isSymmetrical(TreeNode pRoot){ + return pRoot == null ? true : mirror(pRoot.left, pRoot.right); + } + + boolean mirror(TreeNode left, TreeNode right) { + if(left == null && right == null) return true; + if(left == null || right == null) return false; + return left.val == right.val + && mirror(left.left, right.right) + && mirror(left.right, right.left); + } +} +``` + +非递归: + +层次遍历即可,注意队列中要成对成对的取。 + +```java +import java.util.LinkedList; +import java.util.Queue; + +public class Solution { + + boolean isSymmetrical(TreeNode pRoot) { + if (pRoot == null) return true; + Queue queue = new LinkedList<>(); + queue.add(pRoot.left); + queue.add(pRoot.right); + while (!queue.isEmpty()) { + TreeNode right = queue.poll(); + TreeNode left = queue.poll(); + if (left == null && right == null) continue; + if (left == null || right == null) return false; + if (left.val != right.val) return false; + //成对插入 + queue.add(left.left); queue.add(right.right); + queue.add(left.right); queue.add(right.left); + } + return true; + } +} +``` + +栈也可以: + +```java +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class Solution { + + boolean isSymmetrical(TreeNode pRoot) { + if (pRoot == null) return true; + Stack s = new Stack<>(); + s.push(pRoot.left); + s.push(pRoot.right); + while (!s.isEmpty()) { + TreeNode right = s.pop(); + TreeNode left = s.pop(); + if (left == null && right == null) continue; + if (left == null || right == null) return false; + if (left.val != right.val) return false; + //成对插入 + s.push(left.left); s.push(right.right); + s.push(left.right); s.push(right.left); + } + return true; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 59 - \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 59 - \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 00000000..676a98c8 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 59 - \346\214\211\344\271\213\345\255\227\345\275\242\351\241\272\345\272\217\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,78 @@ +## 剑指Offer - 59 - 按之字形顺序打印二叉树 + +#### [题目链接](https://www.nowcoder.com/practice/91b69814117f4e8097390d107d2efbe0?tpId=13&tqId=11212&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +#### 题目 + +请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 + +### 解析 + +这题是[剑指Offer - 60 - 把二叉树打印成多行](剑指Offer - 60 - 把二叉树打印成多行.md)的加强版,可以先做那一题。 + +另外在[LeetCode - 103](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20103.%20Binary%20Tree%20Zigzag%20Level%20Order%20Traversal.md)我也写过。可以看那一篇。 + +然后只需要将偶数层的翻转一下即可。 + +非递归: + +```java +import java.util.*; +public class Solution { + + public ArrayList> Print(TreeNode pRoot) { + ArrayList> res = new ArrayList<>(); + if(pRoot == null) + return res; + Queue queue = new LinkedList<>(); + queue.add(pRoot); + boolean ok = false; + ArrayList list = new ArrayList<>(); + while(!queue.isEmpty()){ + int n = queue.size(); + ArrayList tmp = new ArrayList<>(); + for(int i = 0; i < n; i++){ + TreeNode cur = queue.poll(); + tmp.add(cur.val); + if(cur.left != null) queue.add(cur.left); + if(cur.right != null) queue.add(cur.right); + } + if(ok) Collections.reverse(tmp); + ok = !ok; + res.add(tmp); + } + return res; + } +} +``` + +递归: + +```java +import java.util.*; +public class Solution { + + ArrayList> res; + + public ArrayList> Print(TreeNode pRoot) { + res = new ArrayList<>(); + rec(pRoot, 0); + for(int i = 0; i < res.size(); i++) if( i % 2 == 1) Collections.reverse(res.get(i)); + return res; + } + + private void rec(TreeNode node, int level){ + if(node == null) return; + if(level >= res.size()){ + ArrayList tmp = new ArrayList<>(); + tmp.add(node.val); + res.add(tmp); + }else { + res.get(level).add(node.val); + } + rec(node.left, level+1); + rec(node.right, level+1); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 60 - \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 60 - \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" new file mode 100644 index 00000000..0b95ddbc --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 60 - \346\212\212\344\272\214\345\217\211\346\240\221\346\211\223\345\215\260\346\210\220\345\244\232\350\241\214.md" @@ -0,0 +1,71 @@ +## 剑指Offer - 60 - 把二叉树打印成多行 + +#### [题目链接](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 + +### 解析 + +[**LeetCode637**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20637.%20Average%20of%20Levels%20in%20Binary%20Tree(%E6%B1%82%E6%A0%91%E7%9A%84%E6%AF%8F%E4%B8%80%E5%B1%82%E7%9A%84%E5%B9%B3%E5%9D%87%E5%80%BC).md)已经做过,而且稍微加强了一点。具体可以看那个题目解析。提供递归和非递归写法。 + +非递归,一次处理一层。 + +```java +import java.util.*; + +public class Solution { + ArrayList> Print(TreeNode pRoot) { + ArrayList> res = new ArrayList<>(); + if(pRoot == null) return res; + Queue queue = new LinkedList<>(); + queue.add(pRoot); + while(!queue.isEmpty()){ + int n = queue.size(); + ArrayList tmp = new ArrayList<>(); + for(int i = 0; i < n; i++){ + TreeNode cur = queue.poll(); + tmp.add(cur.val); + if(cur.left != null) queue.add(cur.left); + if(cur.right != null) queue.add(cur.right); + } + res.add(new ArrayList<>(tmp)); + } + return res; + } +} +``` + +递归写法,可以前序,中序和后序(中序和后序要先建出所有中间的ArrayList)。 + +```java +import java.util.*; + +public class Solution { + + ArrayList> res; + + ArrayList> Print(TreeNode pRoot) { + res = new ArrayList<>(); + rec(pRoot, 0); + return res; + } + + private void rec(TreeNode node, int level) { + if (node == null) return; + if (res.size() <= level) {//新建一个 + ArrayList tmp = new ArrayList<>(); + tmp.add(node.val); + res.add(tmp); + } else {//已经建立过 + res.get(level).add(node.val); + } + rec(node.left, level + 1); + rec(node.right, level + 1); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 61 - \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 61 - \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 00000000..0da17925 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 61 - \345\272\217\345\210\227\345\214\226\344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,119 @@ +## 剑指Offer - 61 - 序列化二叉树 + +#### [题目链接](https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&tqId=11214&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +请实现两个函数,分别用来序列化和反序列化二叉树 + +### 解析 + +这个题目在[**LeetCode - 297**](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Tree/LeetCode%20-%20297.%20Serialize%20and%20Deserialize%20Binary%20Tree(%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96).md)也写过,直接上代码,解释可以看那篇。 + +前序序列化。 + +```java +public class Solution { + String Serialize(TreeNode root) { + StringBuilder sb = new StringBuilder(); + serHelper(root, sb); + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + private void serHelper(TreeNode root, StringBuilder sb) { + if(root == null) { + sb.append("null,"); + return; + } + sb.append(root.val + ","); + serHelper(root.left, sb); + serHelper(root.right, sb); + } + + TreeNode Deserialize(String str) { + if(str == null || str.length() == 0) return null; + String[] data = str.split(","); + int[] idx = new int[1]; + return desHelper(data, idx); + } + + private TreeNode desHelper(String[] arr, int[] idx){ + if(idx[0] >= arr.length) return null; + String val = arr[idx[0]]; + if("null".equals(val)){ + return null; + } + TreeNode root = new TreeNode(Integer.parseInt(val)); + idx[0]++; + root.left = desHelper(arr, idx); + idx[0]++; + root.right = desHelper(arr, idx); + return root; + } +} +``` + +层序序列化。 + +```java +import java.util.*; +public class Solution { + String Serialize(TreeNode root) { + StringBuilder sb = serHelper(root); + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); + } + + public StringBuilder serHelper(TreeNode root) { + StringBuilder res = new StringBuilder(); + if (root == null) { + res.append("null,"); + return res; + } + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + while (!queue.isEmpty()) { + top = queue.poll(); + if (top != null) { + res.append(top.val + ","); + queue.add(top.left); + queue.add(top.right); + } else { + res.append("null,"); + } + } + return res; + } + + TreeNode Deserialize(String str) { + if (str == null || str.length() == 0) return null; + String[] arr = str.split(","); + int idx = 0; + TreeNode root = recon(arr[idx++]); + if (root == null) return root; + + Queue queue = new LinkedList<>(); + queue.add(root); + TreeNode top = null; + while (!queue.isEmpty()) { + top = queue.poll(); + top.left = recon(arr[idx++]); + top.right = recon(arr[idx++]); + if (null != top.left) + queue.add(top.left); + if (null != top.right) + queue.add(top.right); + } + return root; + } + + private TreeNode recon(String str) { + return str.equals("null") ? null : new TreeNode(Integer.valueOf(str)); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 62 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\344\270\252\347\273\223\347\202\271.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 62 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\344\270\252\347\273\223\347\202\271.md" new file mode 100644 index 00000000..9ed1dc94 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 62 - \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\347\254\254k\344\270\252\347\273\223\347\202\271.md" @@ -0,0 +1,63 @@ +## 剑指Offer - 62 - 二叉搜索树的第k个结点 + +#### [题目链接](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。 + +### 解析 + +这题目也不难,二叉搜索树中序遍历是升序的,可以中序遍历然后计数即可。 + +非递归中序不懂的可以看[这篇博客](https://github.com/ZXZxin/ZXBlog/blob/master/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%AE%97%E6%B3%95/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%90%84%E7%A7%8D%E6%93%8D%E4%BD%9C(%E9%80%92%E5%BD%92%E5%92%8C%E9%9D%9E%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86,%E6%A0%91%E6%B7%B1%E5%BA%A6,%E7%BB%93%E7%82%B9%E4%B8%AA%E6%95%B0%E7%AD%89%E7%AD%89).md#1%E9%80%92%E5%BD%92%E4%B8%AD%E5%BA%8F)。 + +```java +import java.util.*; + +public class Solution { + TreeNode KthNode(TreeNode pRoot, int k){ + Stack stack = new Stack<>(); + TreeNode p = pRoot; + int cnt = 0; + while(!stack.isEmpty() || p != null){ + while(p != null){ + stack.push(p); + p = p.left; + } + p = stack.pop(); + cnt++; + if(k == cnt) + return p; + p = p.right; + } + return null; + } +} +``` + +递归可能稍微有点难以理解。 + +要注意的是, 先走到最左边,最下面如果没有到达k,就直接返回null,即可,只有在`k == cnt`的时候,才会返回找到的节点。 + +```java +import java.util.*; + +public class Solution { + int cnt; + + TreeNode KthNode(TreeNode pRoot, int k) { + return in(pRoot, k); + } + + private TreeNode in(TreeNode node, int k) { + if (node == null) return null; + TreeNode L = in(node.left, k); + if (L != null) return L;//之前已经找到了 + return ++cnt == k ? node : in(node.right, k); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 63 - \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 63 - \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" new file mode 100644 index 00000000..5721265d --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 63 - \346\225\260\346\215\256\346\265\201\344\270\255\347\232\204\344\270\255\344\275\215\346\225\260.md" @@ -0,0 +1,51 @@ +## 剑指Offer - 63 - 数据流中的中位数 + +#### [题目链接](https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/9be0172896bd43948f8a32fb954e1be1?tpId=13&tqId=11216&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。 + +### 解析 + +也在[LeetCode - 295](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/LeetCode/Data%20Structure/Trie/LeetCode%20-%20676.%20Implement%20Magic%20Dictionary(%E5%AD%97%E5%85%B8%E6%A0%91)%20%26%20295.%20Find%20Median%20from%20Data%20Stream(%E5%A0%86).md#leetcode-295-find-median-from-data-stream)做过。具体可以看那篇博客,利用一个最大堆和一个最小堆即可。 + +代码: + +```java +import java.util.PriorityQueue; + +public class Solution { + + //堆顶最小,但是存的是最大的 n/2个元素 + private PriorityQueue minHeap = new PriorityQueue<>(); + //堆顶最大,但是存的是最小的 n/2个元素 + private PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); + + public void Insert(Integer num) { + if(maxHeap.isEmpty() || num <= maxHeap.peek()){ + maxHeap.add(num); + }else{ + minHeap.add(num); + } + if(minHeap.size() - maxHeap.size() > 1) + maxHeap.add(minHeap.poll()); + else if(maxHeap.size() - minHeap.size() > 1){ + minHeap.add(maxHeap.poll()); + } + } + + public Double GetMedian() { + if(minHeap.size() > maxHeap.size()) + return 1.0 * minHeap.peek(); + else if(maxHeap.size() > minHeap.size()) + return 1.0 * maxHeap.peek(); + else + return 1.0 * (minHeap.peek() + maxHeap.peek())/2; + } + +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 64 - \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 64 - \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" new file mode 100644 index 00000000..bdcad1f9 --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 64 - \346\273\221\345\212\250\347\252\227\345\217\243\347\232\204\346\234\200\345\244\247\345\200\274.md" @@ -0,0 +1,36 @@ +## 剑指Offer - 64 - 滑动窗口的最大值 + +#### [题目链接](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组`{2,3,4,2,6,2,5,1}`及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为`{4,4,6,6,6,5}`; 针对数组`{2,3,4,2,6,2,5,1}`的滑动窗口有以下6个:` {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}`。 + +### 解析 + +也做过。。。 + +在[LintCode - 362](https://github.com/ZXZxin/ZXBlog/blob/master/%E5%88%B7%E9%A2%98/Other/LintCode/TwoPointer/LintCode%20-%20362.%20Sliding%20Window%20Maximum%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E6%9C%80%E5%A4%A7%E5%80%BC.md)。具体看那篇博客,详细介绍了单调队列的使用。 + +```java +import java.util.*; + +public class Solution { + public ArrayList maxInWindows(int [] num, int size){ + ArrayList res = new ArrayList<>(); + if(num == null || size < 1 || num.length < size) return res; + LinkedList qmax = new LinkedList<>(); + for(int i = 0; i < num.length; i++){ + while(!qmax.isEmpty() && num[qmax.peekLast()] < num[i]) + qmax.pollLast(); + qmax.addLast(i); + if(i - size == qmax.peekFirst()) qmax.pollFirst(); + if(i >= size-1) res.add(num[qmax.peekFirst()]); + } + return res; + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 65 - \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 65 - \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" new file mode 100644 index 00000000..4978819d --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 65 - \347\237\251\351\230\265\344\270\255\347\232\204\350\267\257\345\276\204.md" @@ -0,0 +1,61 @@ +## 剑指Offer - 65 - 矩阵中的路径 + +#### [题目链接](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 `a b c e s f c s a d e e` 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 + +### 解析 + +比较简单的dfs。 + +注意边界判断`if (cur == str.length-1 && matrix[x * c + y] == str[cur]) return true;`。不要判断`cur == str.length`。。 + +```java +public class Solution { + + final int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + private boolean vis[]; + private int r, c; + + private boolean dfs(char[] matrix, char[] str, int cur, int x, int y) { + if (cur == str.length-1 && matrix[x * c + y] == str[cur]) return true; + if (vis[x * c + y] || matrix[x * c + y] != str[cur]) return false; + vis[x * c + y] = true; + for (int i = 0; i < 4; i++) { + int nx = x + dir[i][0]; + int ny = y + dir[i][1]; + if (nx >= 0 && nx < r && ny >= 0 && ny < c && !vis[nx * c + ny] && + (dfs(matrix, str, cur + 1, nx, ny))) return true; + } + vis[x * c + y] = false; + return false; + } + + public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { + r = rows; + c = cols; + vis = new boolean[r * c]; + for (int i = 0; i < matrix.length; i++) { + int x = i / c; + int y = i % c; + if (dfs(matrix, str, 0, x, y)) return true; + } + return false; + } + + public static void main(String[] args) { +// char[] matrix = {'a', 'b' ,'c' ,'e' ,'s' ,'f','c' ,'s','a' ,'d','e','e'}; + char[] matrix = {'A','A','A','A','A','A','A','A','A','A','A','A'}; +// char[] str = {'b','c','c','e','d'}; +// char[] str = {'a', 'b','c','d'}; + char[] str = {'A','A','A','A','A','A','A','A','A','A','A','A','A'}; + System.out.println(new Solution().hasPath(matrix, 3, 4, str)); + } +} +``` + diff --git "a/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 66 - \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 66 - \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" new file mode 100644 index 00000000..45f5ab5d --- /dev/null +++ "b/Algorithm/Other/\345\211\221\346\214\207Offer/\345\211\221\346\214\207Offer - 66 - \346\234\272\345\231\250\344\272\272\347\232\204\350\277\220\345\212\250\350\214\203\345\233\264.md" @@ -0,0 +1,72 @@ +## 剑指Offer - 66 - 机器人的运动范围 + +#### [题目链接](https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/6e5207314b5241fb83f2329e89fdecc8?tpId=13&tqId=11219&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking + +#### 题目 + +地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,**但是不能进入行坐标和列坐标的数位之和大于k的格子**。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子? + +### 解析 + +也是比较简单经典的BFS。 + +不过要注意`threshold < 0` ( k < 0)的情况,也就是不能包含`(0,0)`。 + +```java +import java.util.*; + +public class Solution { + + class State{ + int x,y; + + public State(int x, int y) { + this.x = x; + this.y = y; + } + } + + final int[][] dir = {{-1, 0},{0, 1},{1, 0},{0, -1}}; + + //不能进入行坐标和列坐标的 数位之和 大于threshold的格子 + public int movingCount(int threshold, int rows, int cols){ + Queuequeue = new LinkedList<>(); + boolean[][] vis = new boolean[rows][cols]; + queue.add(new State(0, 0)); + vis[0][0] = true; + int res = threshold < 0 ? 0 : 1; //注意threshold < 0的时候连(0,0)都不能加入 + while(!queue.isEmpty()){ + State cur = queue.poll(); + for(int i = 0; i < 4; i++){ + int nx = cur.x + dir[i][0]; + int ny = cur.y + dir[i][1]; + int d1 = digitSum(nx); + int d2 = digitSum(ny); + if(d1 + d2 > threshold) continue; + if(nx >= 0 && nx < rows && ny >= 0 && ny < cols && !vis[nx][ny]){ + res++; + vis[nx][ny] = true; + queue.add(new State(nx, ny)); + } + } + } + return res; + } + + private int digitSum(int n){ + int cnt = 0; + while(n > 0){ + cnt += n % 10; + n /= 10; + } + return cnt; + } + + public static void main(String[] args){ + System.out.println(new Solution().movingCount(5,10,10)); + } +} +``` + diff --git a/Algorithm/images/acm_1.png b/Algorithm/images/acm_1.png new file mode 100644 index 00000000..767439cb Binary files /dev/null and b/Algorithm/images/acm_1.png differ diff --git a/Algorithm/images/acm_2.png b/Algorithm/images/acm_2.png new file mode 100644 index 00000000..38e562a0 Binary files /dev/null and b/Algorithm/images/acm_2.png differ diff --git a/Algorithm/images/acm_3.png b/Algorithm/images/acm_3.png new file mode 100644 index 00000000..dedbf893 Binary files /dev/null and b/Algorithm/images/acm_3.png differ diff --git a/Algorithm/images/acm_4.png b/Algorithm/images/acm_4.png new file mode 100644 index 00000000..8fa0897a Binary files /dev/null and b/Algorithm/images/acm_4.png differ diff --git a/Algorithm/images/acm_5.png b/Algorithm/images/acm_5.png new file mode 100644 index 00000000..fe15a824 Binary files /dev/null and b/Algorithm/images/acm_5.png differ diff --git a/Algorithm/images/acm_6.png b/Algorithm/images/acm_6.png new file mode 100644 index 00000000..422c8da5 Binary files /dev/null and b/Algorithm/images/acm_6.png differ diff --git a/Basics/NetWork/1_NetworkProtocol.md b/Basics/NetWork/1_NetworkProtocol.md new file mode 100644 index 00000000..2fd11cbd --- /dev/null +++ b/Basics/NetWork/1_NetworkProtocol.md @@ -0,0 +1,228 @@ +# 计网总结(一)一计算机网络和协议 + +* [1、互联网概述](#1互联网概述) +* [2、互联网组成](#2互联网组成) +* [3、计算机网络的性能指标](#3计算机网络的性能指标) +* [4、OSI参考模型以及TCP/IP模型](#4osi参考模型以及tcpip模型) +* [5、通信过程、数据传输、网络设备](#5通信过程数据传输网络设备) +* [6、小结](#6小结) +*** +## 1、互联网概述 + +* 计算机网络: 由若干节点和连接这些节点的链路组成,网络中的节点可以是计算机、集线器、交换机、或路由器等; +* 网络之间可以通过**路由器**相互连接,这就构成了一个更大范围的计算机网路,这样的网路称为**互连网**。 +* 因特网(互联网): 全球最大的特定互连网; + +注意以下两个意思相差很大的名词 `internet` 和 `Internet` [RFC 1208]: + +* 以小写字母 i 开始的 `internet` (互连网) 是一个通用名词,它泛指由多个计算机网络互连而成的计算机网络。在这些网络之间的通信协议〈即通信规则) 可以任意选择,不一定非要使用`TCP/IP` 协议; + +* 以大写字母 I 开始的 `Internet` (互联网或因特网) 则是一个专用名词,它指当前全球最大的、开放的、由众多网络相互连接而成的特定互连网,它采用 `TCP/IP` 协议族作为通信的规则。 + +* 可见,任意把几个计算机网络互连起来(不管采用什么协议),并能够相互通信,这样构成的是一个互连网(`internet`,而不是互联网(`Internet`)。 + + +![在这里插入图片描述](images/1_1.png) + +* 路由器(Router),是连接因特网中各局域网、广域网的设备,它会根据信道的情况自动选择和设定路由,以最佳路径,按前后顺序发送信号。 +* 路由器是互联网络的枢纽,"交通警察"。**路由和交换机之间的主要区别就是交换机发生在OSI参考模型第二层(数据链路层),而路由发生在第三层,即网络层**。 +* 路由器(`Router`)又称网关设备(`Gateway`)是用于连接多个逻辑上分开的网络,所谓逻辑网络是代表一个单独的网络或者一个子网。当数据从一个子网传输到另一个子网时,可通过路由器的路由功能来完成。因此,路由器具有**判断网络地址和选择IP路径**的功能。 +* **交换机是将不同IP地址的电脑连在一起,共享一根网线;路由器是将同一个IP给不同的电脑使用,就像一条大路分成很多条小路。一句话,路由器是接外网的,交换机是接内网的** + +![在这里插入图片描述](images/1_2.png) + +*** +## 2、互联网组成 + +### 2.1、基本组成 + +基本组成: + +* ①边缘部分: 由所有连接在互联网上的主机组成,这部分是用户直接使用的,用来进行通信(传送数据,音频或视频)和资源共享; +* ②核心部分: 由大量网络 和连接这些网络的**路由器** 组成,这部分是为边缘部分提供服务的(提供连通性和交换); + +![在这里插入图片描述](images/1_3.png) + +边缘部分的主机间的通信方式: + +![在这里插入图片描述](images/1_4.png) + +**(1)客户程序** + +* 被用户调用后运行,在通信时主动向远地服务器发起通信(请求服务)。因此,客户程序必须知道服务器程序的地址。 + +* 不需要特殊的硬件和很复杂的操作系统。 + +**(2)服务器程序** + +* 是一种专门用来提供某种服务的程序,可同时处理多个远地或本地客户的请求。 +* 系统启动后即自动调用并一直不断地运行着,被动地等待并接受来自各地的客户的通信请求。因此,服务器程序不需要知道客户程序的地址。 + +**客户与服务器的通信关系建立后,通信可以是双向的,客户和服务器都可发送和接收数据** + + +### 2.2、数据交换方式 + +#### 2.2.1、电路交换 + +

+#### 2.2.2、分组交换 + +

+#### 2.2.3、三种交换方式的比较 + +* 电路交换: 整个报文的比特流连续地从源点直达终点,好像在一个管道中传送。 +* 报文交换: 整个报文先传送到相邻结点,全部存储下来后查找转发表,转发到下一个结点。 + +* 分组交换 : 单个分组(这只是整个报文的一部分) 传送到相邻结点,存储下来后查找转发表,转发到下一个结点。 + +

+选择: + +* 报文交换和分组交换都采用存储转发。 +* 传送数据量大,且传送时间远大于呼叫时选择电路交换。电路交换传输时延最小。 +* 从信道利用率看,报文交换和分组交换优于电路交换,其中分组交换时延更小。 + +*** +## 3、计算机网络的性能指标 + +### 3.1、速率 + +* 网络技术中的速率是指每秒钟传输的比特数量,称为数据率或比特率,速率的单位是`bit/s`,或`b/s`(比特每秒); +* 速率较高时,就可以使用kb/s,Mb/s,Gb/s,Tb/s,人们现在所说的10M网速,其实是10Mb/s; +* 360等可以显示网速的软件,测试你电脑的那个网速,这里单位是B/秒,大写的B是字节(byte),8bit = 1byte,也就是说如果测速为3.82MB/s,则下载速率为3.82 * 8Mb/s。 + + +### 3.2、带宽 + +* 带宽用来表示网络通信线路传输数据的能力(数字信道所能传送的最高数据率),即最高速率; +* 比如说家里使用ADSL拨号,有4M带宽、8M带宽,这里说的带宽就是你访问Internet的最高带宽,你家里的带宽由电信运营商控制; + +### 3.3、吞吐量 + +* 吞吐量表示在单位时间内通过某个网络或接口的数据量,包括全部上传和下载的流量; +* 吞吐量受网络带宽或网络额定速率的限制,计算机的网卡如果连接交换机,网卡就可以工作在全双工模式,即能够同时接收和发送数据; + +![在这里插入图片描述](images/1_10.png) + +### 3.4、时延 + +* 时延(delay)是指数据(一个数据包或bit)从网络的一段传送到另一端所需要的时间,是一个很重要的性能指标; +* 时延包括: 发送时延、传播时延、处理时延、排队时延;(数据在网络中经历的的总时延就是这四种时延的累加和); + + +#### 3.4.1、发送时延 +![在这里插入图片描述](images/1_11.png) + +#### 3.4.2、传播时延 +传播时延是电磁波在信道中传播一定的距离要花费的时间; + +![在这里插入图片描述](images/1_12.png) + +#### 3.4.3、排队时延和处理时延 +![在这里插入图片描述](images/1_13.png) + +#### 3.4.4、时延带宽积 +![在这里插入图片描述](images/1_14.png) +### 3.5、往返时间 + +表示从发送端发送数据开始,到发送端接收到来自接收端的确认(发送端收到确认立即发送确认),总共经历的时间; + +### 3.6、利用率 + +![在这里插入图片描述](images/1_15.png) + +*** + +## 4、OSI参考模型以及TCP/IP模型 + +分层的方法可以是7层、5层、4层。 + +![在这里插入图片描述](images/1_18.png) + +七层中各层的作用一览: + +![1_31.png](images/1_31.png) + +具体: + +* 应用层:提供用户接口,特制能够发起网络通信的应用程序,比如客户端程序,QQ,浏览器等,服务器程序有Web服务器,邮件服务器,流媒体服务器等。 +* 表示层:使用何种编码方式。比如要传输的数据使用ASCI编码,Unicode编码还是二进制文件,是否要加密和压缩。发送端和接收端程序必须使用相同的编码方式,才能正确显示,否则就产生乱码。 +* 会话层: 通信的应用程序之间建立、维护和释放面向用户的连接。通信的应用程序之间立会话,需要传输层建立1个或多个连接。 +* 传输层: 负责在通信的两个计算机之间建立连接,实现可靠的或不可靠的数据通信, 能够发现发送端和接收端的丢包重传,访量控制。 +* 网路层: 路由器查看数据包目标IP地址,根据路由表为数据包选择路径。路由表中的条目可以人工添加静态路由) 也可以动态生成(动态路由) 。 +* 数据链路层: 不同的网络类型,发送数据的机制不同,数据链路层就是将数据包封装成能够在不同网络传输的帧。能够进行差错检查,但不纠错,检测出错误去掉该帧。 +* 物理层: 该层规定了网络设备接口标准、电压标准。尽可能的通过频分复用、时分复用技术在通信和链路上更快的传输数据。 + +七层模型各层作用: + +

+*** +## 5、通信过程、数据传输、网络设备 + +### 5.1、通信过程 +![在这里插入图片描述](images/1_20.png) + +> **注意: MAC地址由48位二进制数组成,在Windows操作系统命令提示符下, 输入"ipconfig / all"能够看到计算机网卡的MAC地址,物理地址.... : C8-60-00-2E-6E-EB,这里显示的是十六进制表示的MAC地址,使用MA和MB代替MAC地址是为了简化说明。** + +相关解释 + +![](images/1_21.png) + +![](images/1_22.png) + +![](images/1_23.png) + +> 为什么计算机通信需要物理地址和IP地址?,物理地址决定了数据帧下一跳给谁,而 IP地址决定了数据包最终给谁。如果全球的计算机都使用集线器或交换机连接,就可以只使用 MAC 地址进行通信了。 + +![](images/1_24.png) + +通过本图也可以看出: + +* **目标MAC地址决定了数据帧下一跳由哪个设备接收**; +* **目标IP地址决定了数据包最终到达那个计算机**; +* **不同的网络数据链路层使用不同的协议,帧格式也不相同,路由器在不同网络转发数据包,需要将数据包重新封装**; + +**通信过程上面四层是端到端的,下面三层是点到点的**。 + +![1_29.png](images/1_29.png) + +### 5.2、数据封装和解封(数据传输) + +下面看几张类似的图,来理解这个过程: + +![在这里插入图片描述](images/1_25.png) + +![1_30.png](images/1_30.png) + +**网络的传输过程(从客户端和服务器的角度来看)** + +![在这里插入图片描述](images/1_5.png) + +### 5.3、网络设备 +现实中各个网络设备的样子: + +

+## 6、小结 + +* 计算机网络〈可简称为网络) 把许多计算机连接在一起,而互连网则把许多网络连接在一起,是网络的网络。 + +* 以小写字母`i `开始的 `internet` 〈互连网) 是通用名词,它泛指由多个计算机网络互连而成的网络。在这些网络之间的通信协议〈即通信规则) 可以是任意的。 + +* 以大写字母 `I `开始的 Internet (互联网) 是专用名词,它指当前全球最大的、开放的、由众多网络相互连接而成的特定互连网,并采用 TCP/P 协议族作为通信规则,且其前身是美国的 ARPANET。Internet 的推荐译名是“因特网” 但很少被使用。 + +* 互联网现在采用存储转发的分组交换技术,以及三层 ISP 结构。 + +* 互联网按工作方式可划分为**边缘部分与核心部分**。**主机在网络的边缘部分,其作用是进行信息处理。路由器在网络的核心部分,其作用是按存储转发方式进行分组交换。** + +* 计算机通信是计算机中的进程〈即运行着的程序) 之间的通信。**计算机网络采用的通信方式是客户-服务器方式和对等连接方式 (P2P 方式)。** + +* 客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务 提供方。 + +* 按作用范围的不同,计算机网络分为广域网 WAN、城域网 MAN、局域网 LAN 和个人区域网 PAN。 + +* 计算机网络最常用的性能指标是: **速率、带宽、吞吐量、时延〈发送时延、传播时延、处理时延、排队时延)、时延带宽积、往返时间和信道(或网络)利用率。** + +* 网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层及其协议的集合,称为网络的体系结构。 + +* 五层协议的体系结构由应用层、运输层、网络层〈或网际层)、数据链路层和物理层组成。**运输层最重要的协议是 `TCP` 和 `UDP` 协议,而网络层最重要的协议是 `IP`协议。** diff --git a/Basics/NetWork/2_PhysicalLayer.md b/Basics/NetWork/2_PhysicalLayer.md new file mode 100644 index 00000000..aa1d3bc8 --- /dev/null +++ b/Basics/NetWork/2_PhysicalLayer.md @@ -0,0 +1,174 @@ +# 计网总结(二)一物理层 + +* [1、物理层基本概念](#1物理层基本概念) +* [2、数据通信基础](#2数据通信基础) +* [3、物理层下的传输媒体](#3物理层下的传输媒体) +* [4、物理层设备](#4物理层设备) + +知识总览: + +![2_8.png](images/2_8.png) + +## 1、物理层基本概念 + +简单定义以及涉及相关知识: +* 定义: 为传输数据所需要的物理链路创建、维持、拆除,而提供具有机械的,电子的,功能的和规范的特性。简单的说,**物理层确保原始的数据可在各种物理媒体上传输**,即传输数据比特流。 +* 物理层设计的相关知识点: 数字信号、双绞线 、同轴电缆 、 光纤 、 时分多路 、波分复用 、 编码方式 、模拟信号、 频分多路复用、码分复用技术、 全双工 、半双工 、单工通信; + +四大特性: + +* **机械特性**: 指明接口所用接线器的形状和尺寸、引脚数目和排列、固定和锁定装置等。 +* **电器特性**: 指明在接口电缆的各条线上出现的电压的范围; +* **功能特性**: 指明某条线上出现的某一电平的电压的意义; +* **过程特性**: 定义了在信号线上进行二进制比特流传输的一组操作过程,包括各信号线的工作顺序和时序,使得比特流传输得以完成; + + +## 2、数据通信基础 +### 2.1、数据通信模型 + +分别看局域网通信模型和广域网通信模型: + +![在这里插入图片描述](images/2_1.png) + + +> 不过现在很多用户已经通过**光纤**接入`Internet`了,这就需要将计算机网卡的数字信号通过**光电转换设备**转换成光信号进行长距离传输,在接收端通过光电转换设备转换成数字信号。 + +### 2.2、数据通信的一些术语 + + +![在这里插入图片描述](images/2_2.png) + + +### 2.3、模拟信号和数字信号 +* 模拟信号: 代表消息的参数的取值是**离散**的,在一定的范围内可以有无限多个不同的取值; +* 数字信号: 代表消息的参数的取值是**连续**的;在数字信号中通常用码元(时间间隔相同的符号)来表示一个二进制数字; +* 码元: 在使用时间域(或简称为时域)的波形表示数字信号时,代表不同离散数值的基本波形。(可以表示一、二、三、四位二进制数); +* 模拟信号和数字信号之间的转换; + +看下图: + +![在这里插入图片描述](images/2_3.png) + + +### 2.4、信道 + +信道: 一般用来表示向某一个方向传送信息的媒体。 + +按照信号传送方向与时间的关系,数据通信分为三种类型: + +* 单向通信(单工通信): 只能有一个方向的通信而没有反方向的交互。 +* 双向交替通信(半双工通信) : 通信的双方都可以发送信息,但不能双方同时发送(
当然也就不能同时接收)。 +* 双向同时通信(全双工通信) : 通信的双方可以同时发送和接收信息。 + + +![在这里插入图片描述](images/2_4.png) + +### 2.5、调制 +调制分为两大类: +* 基带调制:仅对基带信号的波形进行变换,使它能够与信道特性相适应。变换后的信号仍然是基带信号。把这种过程称为编码 (`coding`)。 +* 带通调制:使用载波 (`carrier`)进行调制,把基带信号的频率范围搬移到较高的频段,并转换为模拟信号,这样就能够更好地在模拟信道中传输(即仅在一段频率范围内能够通过信道) 。 +* 带通信号:经过载波调制后的信号。 + + +常用编码方式: + +* 不归零制:正电平代表 `1`,负电平代表 `0`。(效率最高,但是如果发送端发送连续的`0`或者`1`,接收端不容易判断码元的边界); +* 归零制:正脉冲代表 `1`,负脉冲代表 `0`。 +* 曼彻斯特编码:位周期中心的向上跳变代表 `0`,位周期中心的向下跳变代表 `1`。但也可反过来定义。(`1`比特需要`2`码元) +* 差分曼彻斯特编码:在每一位的中心处始终都有跳变。位开始边界有跳变代表 `0`,而位开始边界没有跳变代表 `1`。 + +基本带通调制方法: + +* 基带信号往往包含有较多的低频成分,甚至有直流成分,而许多信道并不能传输这种低频分量或直流分量。为了解决这一问题,就必须对基带信号进行调制 (`modulation`)。 +* 最基本的二元制调制方法有以下几种:① 调幅(AM)。②调频(FM)。③调相(PM) 。 + +![在这里插入图片描述](images/2_5.png) + +* 从信号波形中可以看出,曼彻斯特 (Manchester) 编码和差分曼彻斯特编码产生的信号频率比不归零制高。 +* 从自同步能力来看,不归零制不能从信号波形本身中提取信号时钟频率(这叫作没有自同步能力),而曼彻斯特编码和差分曼彻斯特编码具有自同步能力。 + +> 不是码元越多越好。若每一个码元可表示的比特数越多,则在接收端进行解调时要正确识别每一种状态就越困难,出错率增加。 + +### 2.6、信道极限容量 +基本概念: + +* 任何实际的信道都不是理想的,在传输信号时会产生各种失真以及带来多种干扰。 +* 影响信道上的数字信息传输速率的因素有两个: **码元的传输速度和每个码元承载的比特信息量**,码元传输的速率越高,或信号传输的距离越远,或传输媒体质量越差,在信道的输出端的波形的失真就越严重。 + +从概念上讲,限制码元在信道上的传输速率的因素有以下两个: +* 信道能够通过的频率范围 +* 信噪比 + +①信道能够通过的频率范围 +* 具体的信道所能通过的频率范围总是有限的。信号中的许多高频分量往往不能通过信道。 +* 1924年,奈奎斯特 (Nyquist) 就推导出了著名的奈氏准则。**他给出了在假定的理想条件下,为了避免码间串扰,码元的传输速率的上限值**。 +* 在任何信道中,码元传输的速率是有上限的,否则就会出现码间串扰的问题,使接收端对码元的判决(即识别)成为不可能。 +* **如果信道的频带越宽,也就是能够通过的信号高频分量越多,那么就可以用更高的速率传送码元而不出现码间串扰**。 (可以通过信道的传输速度,计算码元的最高传输速率) + +②信噪比(香农公式) + +* 噪声存在于所有的电子设备和通信信道中。噪声是随机产生的,它的瞬时值有时会很大。因此噪声会使接收端对码元的判决产生错误; +* 但噪声的影响是相对的。如果信号相对较强,那么噪声的影响就相对较小; +* 信噪比就是信号的平均功率和噪声的平均功率之比。常记为 `S/N`,并用分贝 (`dB`) 作为度量单位。即: +信噪比(`dB`) = **10 log10(S/N)** (`dB`) 。例如,当 S/N = 10 时,信噪比为 10 `dB`,而当 S/N = 1000时,信噪比为 30 `dB`。 例如,当 S/N = 10 时,信噪比为 10 dB,而当 S/N = 1000时,信噪比为 30 dB; +* 1984年,香农 (Shannon) 用信息论的理论推导出了带宽受限且有高斯白噪声干扰的信道的极限、无差错的信息传输速率(香农公式)。 +* 信道的极限信息传输速率 C 可表达为:`C = W log2(1+S/N)` (`bit/s`) ;其中,`W` 为信道的带宽(以 Hz 为单位);`S` 为信道内所传信号的平均功率;`N` 为信道内部的高斯噪声功率。 + +香农公式表明: +* 信道的带宽或信道中的信噪比越大,则信息的极限传输速率就越高。 +* 只要信息传输速率低于信道的极限信息传输速率,就一定可以找到某种办法来实现无差错的传输。 +* 若信道带宽 W 或信噪比 S/N 没有上限(当然实际信道不可能是这样的),则信道的极限信息传输速率 C 也就没有上限。 +* 实际信道上能够达到的信息传输速率要比香农的极限传输速率低不少。 +* 对于频带宽度已确定的信道,如果信噪比不能再提高了,并且码元传输速率也达到了上限值,那么还有办法提高信息的传输速率。这就是:**用编码的方法让每一个码元携带更多比特的信息量**。 + + +## 3、物理层下的传输媒体 + +* 传输媒体也称为传输介质或传输媒介,它就是**数据传输系统中在发送器和接收器之间的物理通路**。 +* 传输媒体可分为两大类,即导引型传输媒体和非导引型传输媒体。 + * 在导引型传输媒体中,**电磁波被导引沿着固体媒体(铜线或光纤)传播**。 + * 非导引型传输媒体就是指自由空间。**在非导引型传输媒体中,电磁波的传输常称为无线传输**。 + +### 3.1、导引型传输媒体 +#### 3.1.1、双绞线(网线) +* 最常用的传输媒体; +* 模拟传输和数字传输都可以使用双绞线,其通信距离一般为几到十几公里。 屏蔽双绞线 `STP` 无屏蔽双绞线 `UTP`; + +![在这里插入图片描述](images/2_6.png) +#### 3.1.2、同轴电缆 +* 同轴电缆具有很好的抗干扰特性,被广泛用于传输较高速率的数据。 +* 同轴电缆的带宽取决于电缆的质量。 +50 同轴电缆 —— LAN / 数字传输常用 +75 同轴电缆 —— 有线电视 / 模拟传输常用 +#### 3.1.3、光缆 +* 光纤是光纤通信的传输媒体。 +* 由于可见光的频率非常高,约为 `108` MHz 的量级,因此一个光纤通信系统的传输带宽远远大于目前其他各种传输媒体的带宽。 + +多模光纤和单模光纤: + +* 多模光纤 : 可以存在多条不同角度入射的光线在一条光纤中传输。这种光纤就称为多模光纤(适合近距离)。 +* 单模光纤 : 若光纤的直径减小到只有一个光的波长,则光纤就像一根波导那样,它可使光线一直向前传播,而不会产生多次反射。这样的光纤称为单模光纤。 +### 3.2、非导引型传输媒体 +* 将自由空间称为“非导引型传输媒体”。 +* 无线传输所使用的频段很广。 +* 短波通信(即高频通信)主要是靠电离层的反射,但短波信道的通信质量较差,传输速率低。 +* 微波在空间主要是直线传播。 +* 传统微波通信有两种方式:①地面微波接力通信;②卫星通信 ; + +![在这里插入图片描述](images/2_7.png) + + +## 4、物理层设备 + +两个主要设备: + +* 中继器; +* 集线器; + +中继器: + +![2_9.png](images/2_9.png) + +集线器: + +![2_10.png](images/2_10.png) \ No newline at end of file diff --git a/Basics/NetWork/3_DataLinkLayer.md b/Basics/NetWork/3_DataLinkLayer.md new file mode 100644 index 00000000..e3546353 --- /dev/null +++ b/Basics/NetWork/3_DataLinkLayer.md @@ -0,0 +1,365 @@ +# 计网总结(三)一数据链路层 +* [1、概括](#1概括) +* [2、链路层的功能](#2链路层的功能) + * [2.1、封装成帧和透明传输](#21封装成帧和透明传输) + * [2.2、差错检验](#22差错检验) + * [2.3、流量控制](#23流量控制) +* [3、介质访问控制](#3介质访问控制) + * [3.1、静态划分信道-信道划分介质访问控制-信道复用技术](#31静态划分信道-信道划分介质访问控制-信道复用技术) + * [3.2、动态划分信道](#32动态划分信道) +* [4、局域网和广域网体系结构](#4局域网和广域网体系结构) + * [4.1、局域网](#41局域网) + * [4.2、以太网(属于局域网)](#42以太网属于局域网) + * [4.3、广域网和PPP协议](#43广域网和ppp协议) + * [4.4、链路层设备](#44链路层设备) +*** +## 1、概括 +七层模型中所处位置以及涉及知识概括 + +![在这里插入图片描述](images/3_1.png) + +数据链路层使用的信道主要有以下两种类型: +* **点对点信道**。这种信道使用一对一的点对点通信方式。 +* **广播信道**。这种信道使用一对多的广播通信方式,因此过程比较复杂。广播信道上连接的主机很多,因此必须使用专用的共享信道协议来协调这些主机的数据发送。 +> 结点:主机、路由器; +> +> 帧:链路层的协议数据单元、封装网络层数据报; + +**区分链路与数据链路** + +* 链路(`link`): 是一条点到点的物理线路段,中间没有任何其他的交换结点。一条链路只是一条通路的一个组成部分。 +* 数据链路(`data link`) : 除了物理线路外,还必须有通信协议来控制这些数据的传输(**逻辑链路**)。若把实现这些协议的硬件和软件加到链路上,就构成了数据链路。现最常用的方法是使用适配器(即网卡)来实现这些协议的硬件和软件。 + +## 2、链路层的功能 + +三个基本功能: **封装成帧、透明传输、差错检测** + +数据链路层在物理层提供服务的基础上向网络层提供服务,其最基本的服务是将源自网络层来的数据**可靠地传输到** +**相邻节点的目标机网络**层。 + +其主要作用是**加强物理层传输原始比特流的功能**,将物理层提供的可能出错的物理连接改造成为**逻辑上无差错的数据链路**,使之对网络层表现为一条无差错的链路。 + +* 功能一 : 为网络层提供服务。无确认无连接服务,有确认无连接服务,有确认面向连接服务; +* 功能二 : 链路管理,即连接的建立、维持、释放(用于面向连接的服务) ; +* 功能三 : 组帧 (封装成帧); +* 功能四 : 流量控制; +* 功能五 : 差错控制(帧错/位错); + +### 2.1、封装成帧和透明传输 + + +**点到点信道的数据链路层的协议数据单元 一 帧** + +![在这里插入图片描述](images/3_2.png) + +![在这里插入图片描述](images/3_3.png) + +封装成帧: + +* 封装成帧(framing)就是**在一段数据的前后分别添加首部和尾部**,然后就构成了一个帧。接收端在收到物理层上交的比特流后,就能根据首部和尾部的标记,从收到的比特流中识别帧的开始和结束。 +* 首部和尾部包含许多的控制信息,他们的重要作用就是进行帧定界; + +帧同步: 接收方应当能从接收到的二进制比特流中区分出帧的起始和终止。 + +透明传输: + +- 指不管所传数据是什么样的比特组合,都应当能在链路上传送; +- 因此,链路层就看不见有什么妨碍数据传输的东西; + +**组帧的四种方法: 1.字符计数法,2.字符(节) 填充法,3.零比特填充法,4.违规编码法**。 + +![3_4.png](images/3_4.png) + +这里简单介绍一下字符计数法和字符填充法: + +字符计数法: + +![3_5.png](images/3_5.png) + +字符填充法: + +

+> 帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入**转义字符**。如果数据部分出现转义字符,那么就在转义字符前面再加个转义字符。在接收端进行处理之后可以还原出原始数据。**这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在**。 + +### 2.2、差错检验 + +概括来说,传输中的差错都是由于噪声引起的。 + +* 全局性: 由于线路本身电气特性所产生的随机噪声(热噪声),是信道固有的,随机存在的。解决办法: 提高信噪比来减少或避免干扰。( 对传感器下手) +* 局部性: 外界特定的短暂原因所造成的冲击噪声,**是产生差错的主要原因**。解决办法: 通常利用编码技术来解决。 + +局部性的又可以分为: + +

+> 如果通信质量好,且用有线传输链路,一般会是**无确认无连接服务**。 +> +> 如果通信质量差,采用无线传输链路,则会采用**有确认面向连接服务**。 + +几种处理的方式 + +

+检错编码: + +![3_9.png](images/3_9.png) + +### 2.3、流量控制 + +较高的发送速度和较低的接收能力的不匹配,会造成传输出错,因此流量控制也是数据链路层的一项重要工作。 + +数据链路层的流量控制和传输层的流量控制不同:**数据链路层的流量控制是点对点的,而传输层的流量控制是端到端的 **: + +* 数据链路层流量控制手段: **接收方收不下就不回复确认**; +* 传输层流量控制手段: **接收端给发送端一个窗口公告**。 + +注意可靠传输和流量控制都和滑动窗口有关: + +![3_11.png](images/3_11.png) + +## 3、介质访问控制 + +数据传输时使用的两种链路: + +* **点对点信道**: 一对一通信。因为不会发生碰撞,因此也比较简单,使用 PPP 协议进行控制。 +* **广播信道**: 一对多通信,一个节点发送的数据能够被广播信道上所有的节点接收到。所有的节点都在同一个广播信道上发送数据,因此需要有专门的控制方法进行协调,避免发生冲突(冲突也叫碰撞)。主要有两种控制方法进行协调,一个是使用信道复用技术,一是使用 CSMA/CD 协议。 + +介质访问控制就是解决避免广播信道产生冲突的: + +采取一定的措施,使得两个节点之间的通信不会发生互相干扰的情况。 例如一堆对讲机,不能同时有两个人同时的讲话。 + +主要的措施: + +![3_24.png](images/3_24.png) + +### 3.1、静态划分信道-信道划分介质访问控制-信道复用技术 + +信道划分介质访问控制: 将使用介质的每个设备与来自同一信道上的其他设备的通信隔离开,把时域和 +频域资源合理地分配给网络上的设备。 + +![3_25.png](images/3_25.png) + +#### 3.1.1、频分多路FDM + +![3_26.png](images/3_26.png) + +#### 3.1.2、时分多路TDM + +![3_27.png](images/3_27.png) + +使用频分复用和时分复用进行通信,在通信的过程中主机会一直占用一部分信道资源。但是由于计算机数据的突发性质,通信过程没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。更好的方法是统计时分复用。 + +**统计时分复用** + +![3_28.png](images/3_28.png) + +#### 3.1.3、波分多路WDM + +波分多路复用就是**光的频分多路复用**,在一根光纤中传输多种不同波长(频率) 的光信号,由于波长(频率) +不同,所以各路光信号互不干扰,最后再用波长分解复用器将各路波长分解出来。 + +#### 3.1.4、码分多路CDM + +码分多址(CDMA) 是码分复用的一种方式。 + +1个比特分为多个码片/芯片 (chip) ,每一个站点被指定一个唯一的m位的蕊片序列。 +发送1时站点发送芯片序列,发送0时发送芯片序列反码(通常把0写成-1) 。 + +### 3.2、动态划分信道 + +#### 3.2.1、随机访问介质访问控制(随机访问MAC协议) + +##### 3.2.1.1、ALOHA协议(不听就说,想说就说) + +分为两种: 纯ALOHA协议和时隙ALOHA协议。 + +纯ALOHA协议: 不按时间槽发送,随机重发。想发就发。 + +时隙ALOHA协议: 把时间分成若干个相同的时间片,所有用户在时间开始时刻同步接入网络信道,若发生冲突,则必须等到下一个时间片开始时刻再发送。 + +纯ALOHA协议比时隙ALOHA协议吞吐量更低,效率更低。 + +##### 3.2.1.2、CSMA协议(先听再说) + +协议思想: **发送帧之前,监听信道**。发送数据之前要检测一下总线上是否有其他计算机在发送数据。 + +具体又可以分为三种: + +![3_29.png](images/3_29.png) + +具体: + +* 1)、1-坚持CSMA: 空闲则直接传输,不必等待;忙则一直坚挺,直到空闲马上传输;(可能冲突) +* 2)、非坚持CSMA: 空闲则直接传输,不必等待;忙则等待一个随机的时间之后再进行监听; +* 3)、p-坚持: 空闲则以`p`概率直接传输,以`1-p`概率等待到下一个时间槽再传输;忙则等待一个随机的时间之后再进行监听; + +![3_30.png](images/3_30.png) + +##### 3.2.1.3、CSMA/CD协议(重点) + +英文名:`Carrier sense multiple access with collision detection`。(CSMA/CD) + +主要用于**总线式以太网**。 + +CSMA/CD协议思想: **先监听再发送,边监听边发送**。 + +CSMA/CD 表示载波监听多点接入 / 碰撞检测。 + +* **载波监听(CS)** :每个主机都必须不停地监听信道。在发送前,如果监听到信道正在使用,就必须等待。 + +- **多点接入(MA)** :说明这是总线型网络,许多主机以多点的方式连接到总线上。 +- **碰撞检测(CD)** :在发送中,如果监听到信道已有其它主机正在发送数据,就表示发生了碰撞(冲突检测)。虽然每个主机在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞(所以适用在半双工网络)。 + +传播时延对载波监听的影响: + +![3_31.png](images/3_31.png) + +记端到端的传播时延为 `τ`,最先发送的站点最多经过 `2τ` 就可以知道是否发生了碰撞,称 `2τ` 为 **争用期** 。**只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞**。 + +当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定。从离散的整数集合 `{0, 1, 2, 4 .., (2^k-1)}` 中随机取出一个数,记作 `r`,然后取 `r` 倍的争用期作为重传等待时间。 + +帧的传播时延至少要两倍于信号在总线中的传播时延。 + +即 **帧长(bit) / 数据传输速率 >= 2τ**。 + +##### 3.2.1.4、CSMA/CA协议 + +英文名: `Carrier sense multiple access with collision avoidance`。(后面是碰撞**避免**)。 + +主要用于**无线网**,可以全面的检测碰撞。 + +过程: + +1)、发送数据前,先检测信道是否空闲-空闲则发出`RTS`(`request to send`) ,`RTS`包括发射端的地址、接收端的地址、下一份数据将持续发送的时间等信息。 信道忙则等待。 + +2)、接收端收到`RTS`后,将响应`CTS` ( `clear to send`) 。 + +3)、发送端收到`CTS`后,开始发送数据帧,同时**预约信道:** 发送方告知其他站点自己要传多久数据)。 + +4)、接收端收到数据帧后,将用`CRC`(循环冗余)来检验数据是否正确,正确则响应ACK帧-发送方收到ACK就可以进行下一个数据帧的发送,若没有则一直重传至规定重发次数为止(采用**二进制指数退避算法**来确定随机的推迟时间) 。 + + +#### 3.2.2、轮询访问介质访问控制 + +主要有两个: **轮询协议和令牌传递协议**。 + +**轮询协议**: 主节点轮流“邀请”从属节点发送数据。 + +**令牌传递协议**: 在所有主机之间,有一个特殊格式的令牌(MAC控制帧,不含任何信息)来控制信道的使用,确保同一时刻只有一个节点独占信道。 + +## 4、局域网和广域网体系结构 + +### 4.1、局域网 + +英文: Local Area Network,简称LAN,**指在某一区域由多态计算机互联组成的计算机组,使用广播信道**。 + +决定局域网的主要因素: **网络拓扑、传输介质和介质访问控制方法**。 + +局域网拓扑结构有: **星型拓扑、总线型拓扑、环形拓扑、树型拓扑**。 + +传输介质: + +* 有线局域网 常用介质: 双绞线、同轴电缆、光纤 +* 无线局域网 常用介质: 电磁波 + +局域网分类 + +* 1)、以太网,应用最广泛,符合`IEE802.3`系列标准。逻辑拓扑是总线型,物理拓扑是星型。 +* 2)、令牌环网: 不用了。 +* 3)、FDDI网: 双环拓扑。 +* 4)、ATM网。 +* 5)、无线局域网(WLAN): 采用`IEE802.11`标准 + +> MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标识网络适配器(**网卡**)。 +> +> 一台主机拥有多少个**网络适配器**就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。 + +### 4.2、以太网(属于局域网) + +**以太网是一种星型拓扑结构局域网**。 + +早期使用**集线器**进行连接,集线器是一种物理层设备, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,**并将其能量强度放大**,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到两个不同接口的帧,那么就发生了碰撞()。 + +目前以太网使用**交换机替代了集线器**,交换机是一种链路层设备,它不会发生碰撞,**能根据 MAC 地址进行存储转发**。 + +以太网帧格式(**图可以看最文章上面那张图的下方**): + +- **类型** :标记上层使用的协议; +- **数据** :长度在 46-1500 之间,如果太小则需要填充; +- **FCS** :帧检验序列,使用的是 CRC 检验方法; + +![3_33.png](images/3_33.png) + +以太网提供**无连接、不可靠**的服务。 + +无连接: 发送方和接收方之间无握手过程。 + +不可靠: 不对发送方的数据帧编号,接收方不向发送方进行确认,帧错直接丢弃,差错纠正由高层负责。 + +### 4.3、广域网和PPP协议 + +广域网 (`WAN`,Wide Area Network) ,通常跨接很大的物理范围。 + +广域网的通信子网主要使用**分组交换技术**。因特网 (Internet)是世界范围内最大的广域网。 + +PPP(Point-to-Point Protocol)协议是目前使用最广泛的数据链路层协议,**是在广域网使用的协议,只支持全双工链路**。 + +特点: + +* 简单: 对于链路层的帧,无需纠错,无需序号,无需流量控制。 +* 封装成帧: 加上帧定界符 +* 透明传输: 与帧定界符一样比特组合的数据应该如何处理: 异步线路用字节填充,同步线路用比特填充。 +* 多种网络层协议: 封装的IP数据报可以采用多种协议。 +* 多种类型链路”串行/并行,同步/异步,电/光… +* 差错检测: 错就丢弃 +* 检测连接状态 : 链路是否正常工作。 +* 最大传送单元: **数据部分最大长度MTU**。 +* 网络层地址协商: 知道通信双方的网络层地址 + +> (1)PPP具有动态分配IP地址的能力,允许在连接时刻协商IP地址; +> +> (2)PPP支持多种网络协议,比如TCP/IP、NetBEUI、NWLINK等; +> +> (3)PPP**具有错误检测能力,但不具备纠错能力,所以ppp是不可靠传输协议**; + +组成部分: + +(1)、高级数据链路控制协议 + +高级数据链路控制协议是将 卫 数据报封装到串行链路的方法。PPP 既支持异步链路 (无奇偶校验的 8 比特数据),也支持面向比特的同步链路。IP 数据报在 PPP 帧中就是其信息部分,这个信息部分的长度受最大传送单元 MTU 的限制。 + +(2)、链路控制协议 + +链路控制协议(Link Control Protocol,LCP) 用来建立、配置和测试数据链路连接,通信的双方可协商一些选项(进行**身份验证**)。 + +(3)、网络控制协议 + +网络控制协议 NCP (Network Control Protocol) 中的每一个协议支持不同的网络层协议, 如IP、IPv6、DECnet,以及 AppleTalk 等。 + +![3_34.png](images/3_34.png) + +PPP 的帧格式: + +- F 字段为**帧的定界符** +- FCS 字段是使用 CRC 的检验序列 +- 信息部分的长度不超过 1500 + ![3_35.png](images/3_35.png) + +### 4.4、链路层设备 + +物理层设备: 中继系统,即转发器(repeater)或集线器(hub)。 + +数据链路层: 即交换机(switch)或网桥(交换机前身)。 + +**网桥根据MAC帧的目的地址对帧进行转发和过滤**。当网桥收到一个帧时,并不向所有接口转发此帧,而是 +**先检查此帧的目的MAC地址,然后再确定将该帧转发到哪一个接口**,或者是把它丢弃 (即过滤) ,这就是比集线器好的地方。 + +![3_36.png](images/3_36.png)  + +集线器既不能分割冲突域也不能分割广播域,它就像一根接口比较多的网线一样。 + +![3_37.png](images/3_37.png) + +一个小例题: + +![3_38.png](images/3_38.png) + +关于冲突域和广播域更多可以看[**这篇博客**](https://blog.csdn.net/gui951753/article/details/79402528)。 \ No newline at end of file diff --git a/Basics/NetWork/4_NetworkLayer.md b/Basics/NetWork/4_NetworkLayer.md new file mode 100644 index 00000000..3fc4ea27 --- /dev/null +++ b/Basics/NetWork/4_NetworkLayer.md @@ -0,0 +1,772 @@ +# 计网总结(四)一网络层 + + +* [1、数据交换方式](#1数据交换方式) +* [2、IP数据报](#2ip数据报) +* [2.1、IP数据报格式](#21ip数据报格式) + * [2.2、IP数据报分片](#22ip数据报分片) +* [3、IP地址](#3ip地址) +* [3.1、IP地址概念](#31ip地址概念) + * [3.2、子网掩码](#32子网掩码) + * [3.3、IP地址分类](#33ip地址分类) + * [3.4、子网划分](#34子网划分) + * [3.5、变长子网划分](#35变长子网划分) + * [3.6、超网合并网段](#36超网合并网段) +* [4、重要协议](#4重要协议) +* [4.1、ARP协议(地址解析协议)](#41arp协议地址解析协议) + * [4.2、DCHP(动态主机设置协议,传输层协议)](#42dchp动态主机设置协议传输层协议) + * [4.3、ICMP(网际控制报文协议)](#43icmp网际控制报文协议) + * [4.4、IGMP](#44igmp) +* [5、路由算法和路由选择协议](#5路由算法和路由选择协议) +* [5.1、路由器结构](#51路由器结构) + * [5.2、路由转发分组流程](#52路由转发分组流程) + * [5.3、路由算法概括](#53路由算法概括) + * [5.4、RIP(距离向量)](#54rip距离向量) + * [5.5、OSPF(开放式最短路径优先)](#55ospf开放式最短路径优先) + * [5.6、BGP(边界网关)](#66bgp边界网关) +* [6、其他](#6其他) + + * [6.1、数据包传输过程以及简单的静态路由配置实验](#61数据包传输过程以及简单的静态路由配置实验) + * [6.2、IPV6](#62ipv6) + +先上一张总结图: + +![第四章总结脑图.png](images/第四章总结脑图.png) + +## 1、数据交换方式 + +第一章提到数据交换方式分为电路交换、报文交换、分组交换。 + +其中在网络层。分组交换又可以分为**数据报方式和虚电路方式**。 + +其中,**数据报方式为网络层提供无连接服务**。**虚电路方式为网路层提供连接服务**。 + +> 无连接服务: 不事先为分组的传输确定传输路径,每个分组独立确定传输路径,不同分组传输路径可能不同。 +> +> 连接服务: 首先为分组的**传输确定传输路径**(建立连接) ,然后沿该路径传输系列分组,系列分组传输路径相同,传输结束后拆除连接。 + +几种传输单元在各层的位置: + +![4_1.png](images/4_1.png) + +下面看数据包方式和虚电路交换方式: + +![4_2.png](images/4_2.png) + +对比: + +![4_3.png](images/4_3.png) + +## 2、IP数据报 + +### 2.1、IP数据报格式 + +![4_6.png](images/4_6.png) + +一个 IP 数据报由首部和数据两部分组成。首部的前一部分是固定长度,共` 20 `字节,是所有 IP 数据报必须具有的。在首部的固定部分的后面是一些可选字段,其长度是可变的。 + +* 版本——占 4 位,指 IP 协议的版本。目前的 IP 协议版本号为 4 (即 IPv4); + +* 首部长度——占 4 位,可表示的最大数值是 15 个单位(一个单位为 4 字节),因此 IP 的首部长度的最大值是 60 字节; + +* 总长度——占 16 位,指首部和数据之和的长度,单位为字节,因此数据报的最大长度为 65535 字节。总长度必须不超过最大传送单元 MTU(数据链路层规定); + +* 标识(identification) ——占 16 位,它是一个计数器,用来产生 IP 数据报的标识。; + +* 标志(flag) ——占 3 位,目前只有前两位有意义。标志字段的最低位是 MF (More Fragment)。MF = 1 表示后面“还有分片”。MF = 0 表示最后一个分片。标志字段中间的一位是 DF (Don't Fragment) 。只有当 DF = 0 时才允许分片; + +* 片偏移——占13 位,指出:较长的分组在分片后某片在原分组中的相对位置。片偏移以 8 个字节为偏移单位; + +* 生存时间——占8 位,记为 TTL (Time To Live),指示数据报在网络中可通过的路由器数的最大值; + +* 协议——占8 位,指出此数据报携带的数据使用何种协议,以便目的主机的 IP 层将数据部分上交给那个处理过程; + +* 首部检验和——占16 位,只检验数据报的首部,不检验数据部分。这里不采用 CRC 检验码而采用简单的计算方法。 + +![在这里插入图片描述](images/4_14.png) +### 2.2、IP数据报分片 + +当IP数据报传送到链路层的时候,如果IP数据报长度超过MTU,就需要分片。 + +![4_16.png](images/4_16.png) + +具体的分片过程: + +![4_7.png](images/4_7.png) + +> 中间位 DF(Don't Fragment) +> +> * DF = 1 ,禁止分片; +> * DF = 0,允许分片; +> +> 最低位MF(More Fragment) +> +> * MF = 1,后面"还有分片"; +> * MF = 0,代表最后一片/没分片; + +## 3、IP地址 + +### 3.1、IP地址概念 + +* `IP` 地址就是给每个连接在互联网上的主机(或路由器)分配一个在全世界范围是唯一的 `32` 位的标识符,用来定位网络中的计算机和网络设备; +* IP 地址用 `32` 位二进制来表示, 也就是`32` 比特, 换算成字节, 就 是 `4` 个字节。例如一个采用二进制形式的 IP 地址是`10101100 00010000 00011110 00111000 `, 这么长的地址, 处理起来太费劲。于是这些位被分割为`4` 个部分, 每一部分` 8 `位二进制, 中间使用符号 `.`分开 ,上面的 `IP` 地址可以表示为`172.16.30.56`。`IP` 地址的这种表示法叫做“点分十进制表示法”; +* 计算机的 `IP` 地址由两部分组成, **一部分为网络标识, 一部分为主机标识**;同 一网段的计算机网络部分相同。路由器连接不同网段,负责不同网段之间的数据转发,交换机连接的则是同一网段的计算机;见下图: +![在这里插入图片描述](images/4_9.png) + +> 计算机在和其他计算机通信之前, 首先要判断目标 `IP` 地址和自己的`IP` 地址是否在一个网段, 这决定了数据链路层的目标 `MAC` 地址是目标计算机的还是路由器接口的。 + +**IP地址和硬件地址的区别** + +![在这里插入图片描述](images/4_8.png) + +### 3.2、子网掩码 + +* 子网掩码 ( `Subnet Mask`) 又叫网络掩码、地址掩码,它是一种用来指明一个`IP`地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。**子网掩码只有一个作用,就是将某个`IP` 地址划分成网络地址和主机地址两部分**。 +* 例如: 计算机的 `IP` 地址是 `131.107.41.6`, 子网掩码是 `255.255.0.0`, 计算机所在网段是`131.107.0.0`。该计算机和远程计算机通信 ,目标 `IP` 地址只要前面两部分是 `131.107`就认为和 该计算机在同一个网段; + +通过`IP`和子网掩码计算网段: + +方法: IP 地址和子网掩码做**与运算**: + +![在这里插入图片描述](images/4_10.png) + +有关二进制的特殊转换和特征(和划分子网有关系): +|二进制|十进制|备注| +|-|-|-| +|1000 0000 |128|| +|1100 0000|192| 1000 0000+ 10 0000 也就是 128 + 64=192| +|1110 0000|224| 1000 0000 + 100 0000 + 10 0000 也就是 128 + 64 + 32=224| +|1111 0000|240| 128 + 64 + 32 + 16 = 240| +|1111 1000|248| 128 + 64 + 32 + 16 + 8 = 248| +|1111 1100|252| 128 + 64 + 32 + 16 + 8 + 4 = 252| +|1111 1110|254| 128 + 64 + 32+ 16 + 8 + 4 + 2=254| +|1111 1111|255| 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1=255| + +![在这里插入图片描述](images/4_11.png) + +一个规律,**如果要你写出十进制转换成二进制后,后n位二进制是多少**: + +> * 能够被 `2` 整除的数, 写成二进制形式, 后一位是 `0`。如果余数是` 1 `, 则最后一位是 `1`。 +> * 能够被 `4 `整除的数, 写成二进制形式, 后两位是 `00`。如果余数是`2` , 那就把 `2 `写成二进制,后两位是`10 `。 +> * 能够被 `8` 整除的数, 写成二进制形式, 最后三位是 `000 `。如果余 `5` , 就把`5 `写成二进制, 后三位是 `101`。 +> * 能够被 `16` 整除的数, 写成二进制形式, 最后四位是 `0000` 。如果余 `6` , 就把`6 `写成二进制, 最后四位是 `0110` 。 + + +我们可以找出规律, 如果让你写出一个十进制数转换成二进制数后面的 `n` 位二进制数 ,你可以将该数除以 2n, 将余数写成 `n` 位二进制即可。 + +举例: 写出十进制数 242 转换成二进制数后的最后 4 位: +24 是 `16` , `242` 除以 `16 `, 余 `2` , 将余数写成 4 位二进制,就是 `0010` 。 + +### 3.3、IP地址分类 + +如图所示: + +* A类地址: **网络地址最高位是 `0` 的地址为 A 类地址**。网络 ID 全 `0` 不能用,`127` 作为保留网段,因此 A 类地址的第 1 部分取值范围为 `1~126`;A 类网络默认子网掩码为`255.0.0.0`。主机ID 由第 2 部分、第 3 部分和第 4 部分组成,每部分的取值范围为 `0~255`,共 256 种取值,由排列组合知道,一个 A 类网络主机数量是 `256 * 256 * 256=166777216`,这里还需减去` 2`,主机 ID 全 0 的地址为网络地址,而主机ID 全 `1` 的地址为广播地址,如果你给主机 ID 全 `1` 的地址发送数据包,计算机产生一个数据链路层广播帧,**发送到本网段全部计算机**; +* B类地址: **网络地址最高位是`10`的地址为B类地址**。IP地址第一部分取值范围为`128 ~ 191`。B类网络默认子网掩码为 `255.255.0.0`。主机 ID 由第 3 部分和第 4 部分组成,每个 B 类网络可以容纳的最大主机数量为 `256X256-2=65023` ; +* C类地址: **网络地址最高位是`110`的地址为C类地址**。IP地址第一部分取值范围为`192 ~ 223`。子网掩码是`255.255.255.0`,主机ID由第4部分组成,每个C类网络可以容纳的最大主机数量为`256 - 2 = 254`; + +![在这里插入图片描述](images/4_12.png) + + 数轴表示:![在这里插入图片描述](images/4_13.png) + +* 一个`A`类网络的主机数量是`256`*`256`*`256`个,这里还需要减去`2`,因为主机`ID`全`0`的地址为网络地址,而主机`ID`全`1`的地址为广播地址,如果给主机`ID`全1的`IP`地址发送数据包,这个电脑将产生一个数据链路层广播帧,发送到本网段全部计算机; +* 在电脑中,我们只需要写出自己的`IP`地址,按下`TAB`键,就能自动将子网掩码补全,这就说明,可以按照前`8`位来推断是哪一类地址,然后推断子网掩码。子网掩码可以来划分网络部分(`net-id`)和主机部分(`host-id`); +* 在同一个局域网上的主机或路由器的IP 地址中的网络号(`net-id`)必须是一样的。 +* 路由器总是具有两个或两个以上的 IP 地址。路由器的每一个接口(`fastethernet`或者`serial`)都有一个不同网络号的 IP 地址。 + + +**保留的IP地址** +有些 IP 地址被保留用于某些特殊目的, 网络管理员不能将这些地址分配给计算机。 +* 主机 `ID` 全为 0 的地址: 特指某个网段, 比如 `192.168. 10.0 255.255.255.0`, 指 `192.168.10.0`网段(**网段也叫网络地址**); +* 主机`ID` 全为 1 的地址: 特指该网段的全部主机 ,如果你的计算机发送数据包使用主机 ID 全是 1 的 IP 地址, 数据链路层地址用广播地址 `FF-FF-FF-FF-FF-FF`。同一网段计算机名称解析就需要发送名称解析的广播包。比如你的计算机 lP 地址是` 192.168.10.10` , 子网掩码是 `255.255.255.0 `, 它要发送一个广播包, 如目标 IP 地址是 `192.168.10.255` , 帧的目标MAC 地址是 `FF-FF-FF-FF-FF-FF`,该网段中全部计算机都能收到; +* `127.0.0.1`: 是回送地址, 指本机地址, 一般用作测试使用。回送地址 ( `127.x.x.x` ) 即本机回送地址 ( `Loopback Address` ) , 指主机 IP 堆栈内部的 IP 地址, 主要用于网络软件测试以及本地机进程间通信, 无论什么程序, 一旦使用回送地址发送数据, 协议软件立即返回, 不进行任何网络传输。任何计算机都可以用该地址访问自己的共享资源或网站,如果 ping该地址能够通, 说明你的计算机的 `TCP/IP `协议栈工作正常, 即便你的计算机没有网卡,ping `127.0.0.1` 还是能够通的; +* `169. 254.0.0`: `169.254.0.0 ~ 169.254.255.255` 实际上是自动私有 IP 地址。 如果计算机无法获取 IP 地址, 对于Windows 2000 以后的操作系统, 则在无法获取 IP 地址时自动配置成 `" IP地址: 169.254.x.x "," 子网掩码:255.255.0.0“`,这样可以使所有获取不到IP地址的计算机能够通信; + + +**私网地址和公网地址** + +私网 IP 地址可以被用于私有网络 ,在 lnternet 上没有这些 IP 地址, Internet 上的路由器也没有到私有网络的路由表。**我们在 Internet 上不能访问这些私网地址, 从这一点来说使用私网地址的计算机更加安全 ,也有效地节省了宝贵的公网IP 地址**。使用私网地址的计算机可以通过 **NAT** ( Network Address Translation , 网络地址转换)技术访问Internet。 + +下面列出保留的私有IP 地址。 + +* A 类: `10.0.0.0 [255.0.0.0]`, 保留了一个 A 类网络。 +* B 类: `172.16.0.0 [255.255.0.0] ~ 172.31.0.0 [255.255.0.0]`, 保留了16个B类网络。 +* C 类: `192.168.0.0 [255.255.255.0] ~ 192.168.255.0 [255.255.255.0]`, 保留了 256 个C类网络。 + +关于**网络地址转换NAT** + +由于**路由器对目的地址是私有IP地址的数据包一律不转发**。所以需要使用`NAT`来使得私有地址访问`Internet`。 + +在专用网链接到因特网的路由器上安装了NAT软件,安装了NAT软件的路由器叫NAT路由器。 + +![4_18.png](images/4_18.png) + +IP地址分类总结(**下面的表格重要**): + +![4_36.png](images/4_36.png) + +![4_47.png](images/4_47.png) + +### 3.4、子网划分 + +子网划分就是**将一个网段等分成多个网段,也就是等分成多个子网**。 + +任务: + +* 确定子网掩码的长度; +* 确定子网中第一个可用的IP地址和最后一个可用的IP地址; + +在划分子网后,有些人将原来的网络号(地址)和主机号(地址)变成了三部分,即**网络号、子网号、主机号**: + +![4_37.png](images/4_37.png) + +![4_38.png](images/4_38.png) + +> 例题1: +> +> 已知IP地址是`141.14.72.24`,子网掩码是`255.255.192.0`,求网络地址。如果子网掩码是`255.255.224.0`,求网络地址。 +> +> (1)、答: 网络地址就是直接将子网掩码和IP地址逐位相与,这里只需要将`72和192`相与: +> +> ```c +> 01001000 +> 11000000 +> -------- +> 01000000 = 64 +> ``` +> +> 所以: 网络地址是 `141.14.64.0`。**这里子网号占2位,剩下的主机号占6 + 8 = 14位**。 +> +> (2)、同理: +> +> ```c +> 01001000 +> 11100000 +> -------- +> 01100000 = 64 +> ``` +> +> 可见网络地址还是`141.14.64.0`。**这里子网号占3位,剩下的主机号占5 + 8 = 13位**。 +> +> **这个例子说明同样的IP地址可以和不同的子网掩码相与得出不同的网络地址**。 +> +> 例题2: +> +> 某主机的IP地址为`180.80.77.55`,子网掩码为`255.255.252.0`。若该主机向其所在子网发送广播分组,则目的地址可以是: (**D**) +> +> A、 `180.80.76.0` B、`180.80.76.255` C、`180.80.77.255` D、`180.80.79.255` +> +> 首先发现子网掩码是划分了子网的,因为第三位是`252`,对应二进制是`11111100`,则可以知道IP地址的**网络号是16位、子网号是6位、主机号是2+8=10位**。 +> +> 将`77`写成二进制`01001101`,由于子网号是`6`位,所以我们取掉前`6`位 即`01001100`即`76`,但由于是广播地址,所以后面`+11`,即`01001111 11111111`,也就是`79.255`,即D。 + +划分后的路由器分组转发算法(**下面(五)路由算法那里也有讲**) + +* 1、提取目的IP地址; +* 2、是否直接交付; +* 3、特定主机路由; +* 4、检测路由表中有无路径; +* 5、默认路由0.0.0.0; +* 6、丢弃,报告转发分组出错; + +#### 3.4.1、无分类编址CIDR + +![4_39.png](images/4_39.png) + +> IP地址后面跟`/24`表示掩码位是24位,即子网掩码是255.255.255.0的IP地址,其主机位最多有254个。   +> 子网掩码通常有以下2种格式的表示方法: +> * 1、通过与IP地址格式相同的点分十进制表示。如:`255.0.0.0` 或 `255.255.255.128`等。 +> * 2、在IP地址后加上`"/"`符号以及`1-32`的数字,其中`1-32`的数字表示子网掩码中**网络标识位**的长度   如:`192.168.1.1/24` 的子网掩码也可以表示为`255.255.255.0`。 + +下面举几个划分的例子。 + +#### 3.4.2、C类地址等分成两个子网 + +下面以一个 C 类网络划分为两个子网为例,讲解子网划分的过程。 + +如图 所示,某公司有两个部门,每个部门 100 台计算机,通过路由器连接 Internet。给这200台电脑分配一个 C 类网络 `192.168.0.0`,该网段的子网掩码为 `255.255.255.0`,连接局域网的路由器接口使用该网段的第一个可用的IP地址 `192.168.0.1`。 + +![4_19.png](images/4_19.png) + +为了安全考虑,打算将这两个部门的计算机分为两个网段,中间使用路由器隔开。计算机数量没有增加,还是 200 台,因此一个 C 类网络的IP地址是足够用的。现在将 `192.168.0.0 [255.255.255.0]`这个 C 类网络划分成两个子网。 + +如图所示,将IP地址的第 4 部分写成二进制形式,子网掩码使用两种方式表示: 二进制和十进制。**子网掩码往右移一位,这样C类地址主机ID 第1位就成为网络位, 该位为 0 是 A子网,该位为1是B子网**。 + +![4_20.png](images/4_20.png) + +如图所示,IP 地址的第 4 部分,其值在 `0~127` 之间的,第 1 位均为 0;其值在 `128~255`之间的,第 1 位均为 1。分成A、B 两个子网,以 128 为界。现在的子网掩码中的1变成了 25 个,写成十进制就是 255.255.255.128。子网掩码向后移动了 1 位,就划分出 2 个子网。**A和B 两个子网的子网掩码都为255.255.255.128** 。 + +A子网可用的地址范围为 `192.168.0.1~192.168.0.126`,IP 地址 192.168.0.0 由于主机位全为 0,不能分配给计算机使用,如图 所示,`192.168.0.127` 由于主机位全为 1,也不能分配计算机。 + +![4_21.png](images/4_21.png) + +B 子网可用的地址范围为`192.168.0.129~192.168.0.254`,IP 地址 `192.168.0.128` 由于主机位全为0,不能分配给计算机使用,IP 地址 `192.168.0.255` 由于主机位全为 1,也不能分配给计算机。划分成两个子网后网络规划如图所示。 + +![4_22.png](images/4_22.png) + +#### 3.4.3、C类地址等分成四个子网 + +假如公司有 4 个部门,每个部门有 50 台计算机,现在使用 `192.168.0.0/24` 这个 C 类网络。从安全考虑,打算将每个部门的计算机放置到独立的网段,这就要求将 `192.168.0.0 [255.255.255.0]` 这个 C 类网络划分为4个子网,那么如何划分成4 个子网呢? + +如图 所示, 将 `192.168.0.0 [255.255.255.0]` 网段的地址的第 4 部分写成二进制,要想分成4个子网,需要将子网掩码**往右移动两位**,这样第 1 位和第 2 位就变为网络位。**就可以分成4个子网,第 1 位和第2 位为00是A子网,01是B子网,10是C子网,11是D子网**。 + +![4_23.png](images/4_23.png) + +A、B、C、D 子网的子网掩码都为 `255.255.255.192`。 + +* A子网可用的开始地址和结束地址为 `192.168.0.1~192.168.0.62`; +* B子网可用的开始地址和结束地址为 `192.168.0.65~192.168.0.126`; +* C子网可用的开始地址和结束地址为 `192.168.0.129~192.168.0.190`; +* D子网可用的开始地址和结束地址为 `192.168.0.193~192.168.0.254`; + +注意: 如图所示,**每个子网的最后一个地址都是本子网的广播地址**,不能分配给计算机使用,如A子网的63、B子网的 127、C子网的191和D子网的 255。 + +![4_24.png](images/4_24.png) + +#### 3.4.4、C类地址等分成八个子网 + +如果想把一个C类网络等分成 8 个子网,如图所示,子网掩码需要往右移 3 位,才能划分出 8个子网,**第 1 位、第 2 位和第 3 位都变成网络位**。 + +![4_25.png](images/4_25.png) + +每个子网的子网掩码都一样,为` 255.255.255.224`。 + +* A 子网可用的开始地址和结束地址为`192.168.0.1 ~ 192.168.0.30`; +* B 子网可用的开始地址和结束地址为 `192.168.0.33 ~ 192.168.0.62`; + +* C 子网可用的开始地址和结束地址为 `192.168.0.65 ~ 192.168.0.94`; + +* D 子网可用的开始地址和结束地址为 `192.168.0.97 ~ 192.168.0.126`; + +* E 子网可用的开始地址和结束地址为 `192.168.0.129 ~ 192.168.0.158`; + +* F 子网可用的开始地址和结束地址为 `192.168.0.161 ~ 192.168.0.190`; + +* G 子网可用的开始地址和结束地址为 `192.168.0.193 ~ 192.168.0.222`; + +* H 子网可用的开始地址和结束地址为 `192.168.0.225 ~ 192.168.0.254`; + +注意: 每个子网能用的主机 IP地址,都要去掉主机位全 0 和主机位全 1 的地址。 如上图所示,31、63、95、127、159、191、223、255 都是相应子网的广播地址。 + +每个子网是原来的了`1/2 * 1/2 * 1/2 `,即3个`1/2`, 子网掩码往右移 3 位。 + +总结: 如果一个子网地址是原来网段的`(1/2)^n` ,子网掩码就在原网段的基础上后移`n`位。 + +#### 3.4.5、B类地址划分子网 + +将`131.107.0.0 [255.255.0.0]`等分成2个子网。子网掩码往右移动1位,就能等分成两个子网。 + +![4_28.png](images/4_28.png) + +这两个子网的子网掩码都是 `255.255.128.0`。 + +先确定 A 子网第一个可用地址和最后一个可用地址,按照下图将主机部分写成二进制,**主机位不能全是 0,也不能全是 1**,然后再根据二进制写出第一个可用地址和最后一个可用地址。同理B也是。 + +![4_29.png](images/4_29.png) + +#### 3.4.6、A类地址划分子网 + +和 C 类地址和 B 类地址划分子网的规律一样,A 类地址子网掩码往右移动 1 位,也能划分出两个子网。只是写出每个网段第一个和最后一个可用的地址时,需要谨慎。 + +下面以 A 类网络 `42.0.0.0 [255.0.0.0]` 等分成4 个子网为例,写出各个子网的第一个和最后一个可用的IP地址。如图所示,划分出 4 个子网,子网掩码需要右移 2 位。每个子网的子网掩码为`255.192.0.0`。 + +![4_26.png](images/4_26.png) + +参照上图,可以很容易地写出这些子网能够使用的第一个IP地址和最后一个IP地址。 + +* A子网可用的第一个地址为 `42.0.0.1`,最后一个可用的地址为`42.63.255.254`; +* B 子网可用的第一个地址为` 42.64.0.1`,最后一个可用的地址为 `42.127.255.254`; +* C 子网可用的第一个地址为 `42.128.0.1`,最后一个可用的地址为 `42.191.255.254`; +* D 子网可用的第一个地址为 `42.192.0.1`,最后一个可用的地址为 `42.255.255.254`; + +具体如图: + +![4_27.png](images/4_27.png) + +### 3.5、变长子网划分 + +如图所示,有一个 C 类网络 `192.168.0.0 [255.255.255.0]`,需要将该网络划分成5个网段以满足以下网络需求,该网络中有 3 个交换机,**分别连接 20 台电脑、50 台电脑和 100 台电脑**,路由器之间的连接接口也需要地址,这两个地址也是一个网段,这样网络中一共有 5 个网段。 + +如图所示,将 `192.168.0.0 [255.255.255.0]` 的主机位从 0~255 画一条数轴。 + +从 `128~255` 的地址空间给 100 台电脑的网段比较合适, 该子网的地址范围是原来网络的, 子网掩码往后移 1位,写成十进制形式就是 `255.255.255.128`。第一个能用的地址是`192.168.0.129`,最后一个能用的地址是 `192.168.0.254`。 +64~128 之间的地址空间给 50 台电脑的网段比较合适,该子网的地址范围是原来的`1/2 * 1/2`,子网掩码往后移 2 位,写成十进制就是 `255.255.255.192`。第一个能用的地址是 `192.168.0.65`,最后一个能用的地址是`192.168.0.126`。 + +32~64之间的地址空间给 20 台电脑的网段比较合适, 该子网的地址范围是原来的`1/2 * 1/2 * 1/2` , +子网掩码往后移 3 位,写成十进制就是 `255.255.255.224`。第一个能用的地址是 `192.168.0.33`,最后 +一个能用的地址是 `192.168.0.62`。 + +![4_30.png](images/4_30.png) + +当然我们也可以使用以下的子网划分方案,100 台电脑的网段可以使用 `0~128` 之间的子网,50 台电脑的网段可以使用 `128~192` 之间的子网, 20 台电脑的网段可以使用 `192~224` 之间的子网,如图所示。 + +![4_31.png](images/4_31.png) + +总结规律: **如果一个子网地址块是原来网段的`(1/2) ^ n`,子网掩码就在原网段的基础上后移n位,不等长子网,子网掩码也不同**。 + +### 3.6、超网合并网段 + +前面讲的子网划分是将一个网络的主机位当作网络位, 来划分出多个子网。我们也可以将多个网段合并成一个大的网段,合并后的网段称为超网,下面就来讲解合并网段的方法。 + +如图所示,某企业有一个网段,该网段有 200 台计算机,使用 `192.168.0.0 [255.255.255.0]`网段,后来计算机数量增加到 400 台。 + +![4_32.png](images/4_32.png) + +在该网络中添加交换机,可以扩展网络的规模,一个C类 IP 地址不够用,再添加一个 C 类地址 `192.168.1.0 [255.255.255.0]`。这些计算机物理上在一个网段,但是IP地址没在一个网段,即逻辑上不在一个网段。 + +如果想让这些计算机之间能够通信,可以在路由器的接口添加这两个 C 类网络的地址作为这两个网段的网关。 + +在这种情况下,A 计算机到 B 计算机进行通信,必须通过路由器转发,这样两个子网才能够通信,本来这些计算机物理上在一个网段,还需要路由器转发,可见效率不高。 + +有没有更好的办法?,可以让这两个 C 类网段的计算机认为在一个网段,这就需要将`192.168.0.0/24` 和 `192.168.1.0/24` 两个 C 类网络合并。如图所示,将这两个网段的 IP地址第 3 部分和第 4 部分写成二进制,可以看到将子网掩码往左移动1位,两个网段的网络部分就一样了,两个网段就在一个网段了。 + +![4_33.png](images/4_33.png) + +合并后的网段为 `192.168.0.0/23`, 子网掩码写成十进制 `255.255.254.0`,可用地址为`192.168.0.1 ~ 192.168.1.254`,网络中计算机的 IP 地址和路由器接口的地址配置,如图所示。 + +![4_34.png](images/4_34.png) + +![4_35.png](images/4_35.png) + +有关超网合并的题目: (有一个**最长匹配算法**) + +![4_40.png](images/4_40.png) + +> 附加题目: +> +> 某网络的IP地址空间为`192.168.5.0/24`,采用定长子网划分,子网掩码为`255.255.255.248`,则该网络中的最大子网个数、每个子网内的最大可分配地址个数分别是( **B** ) 。 +> A. 32,8 B. 32,6 C. 8,32 D. 8,30 +> +> 答:由于 248 的二进制 `11111000`,子网号占`5`位,所以最大可以分配子网的个数`2 ^ 5 = 32`(注意CIDR中不要去掉全0和全1)。 +> +> 而主机位只剩下`3`位,就是`2 ^ 3 - 2 = 6`(这里要减去全0和全1)。 + +## 4、重要协议 + +网络层的四个主要的协议: `ARP`、`IP`、`ICMP`、`IGMP`。 + +![在这里插入图片描述](images/4_4.png) + +### 4.1、ARP协议(地址解析协议) + +ARP是为IP服务的。`Address Resolution Protocol`。 + +![在这里插入图片描述](images/4_15.png) + +由于在实际网络的链路上传送数据帧时,最终必须使用MAC地址。 + +ARP协议: **完成主机或路由器IP地址到MAC地址的映射。 解决下一跳走哪的问题**。 + +ARP协议使用过程: + +**检查ARP高速缓存,有对应表项则写入MAC帧,没有则用目的MAC地址为`FF-FF-FF-FF-FF-FF`的帧封装并广播ARP请求分组,同一局域网中所有主机都能收到该请求。目的主机收到请求后就会向源主机单播一个ARP响应分组,源主机收到后将此映射写入ARP缓存 (10-20min更新一次)** 。ARP协议是自动进行的。 + +先看**源主机和目的主机在同一个网络内的情况**: + +比如1号主机要和3号主机进行通信: + +![4_41.png](images/4_41.png) + +再看**源主机和目的主机不在同一个网络内的情况**: + +则1号主机会先判断一下,即将自己IP和目的IP相与一下,判断在不在同一个网段。 + +则需要先查询默认网关的MAC地址,即需要先跳到`MAC6`,在路由器需要封装,然后从路由器到目的主机再进行ARP转发请求: + +这个过程要进行三次APR请求:①第一次是PC1到路由器1; ②第二次是路由器1到路由器2(因为这里我画的是点到点的,但是实际上第一个路由器可能连着多个路由器);③ 第三次是路由器2到目的主机PC5。 + +![4_42.png](images/4_42.png) + +给个图,**注意如果R1和R2中间是点到点的连接**(中间没有别的路由器),则不需要ARP协议解析MAC地址。 + +![4_17.png](images/4_17.png) + +> ARP协议4种典型情况: +> +> * 1、主机A发给本网络上的主机B: 用ARP找到主机B的硬件地址; +> * 2、主机A发给另一网络上的主机B: 用ARP找到本网络上一个路由器(网关) 的硬件地址, +> * 3、路由器发给本网络的主机A: 用ARP找到主机A的硬件地址; +> * 4、路由器发给另一网络的主机B: 用ARP找到本网络上的一个路由器的硬件地址。 + +### 4.2、DCHP(动态主机设置协议,传输层协议) + +`Dynamic Host Configuration Protocol`。 + +静态主机配置: 例如机房的电脑的配置,你左边同学的配置的IP和你的IP一般是相邻的。 + +动态主机配置: 例如我们大学教师上课,是需要经常移动的,到了某个教室,会选择动态分配一个暂用IP。 + +![4_43.png](images/4_43.png) + +### 4.3、ICMP(网际控制报文协议) + +`Internet Control Message Protocol`。 + + ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会(桥梁作用)。它封装在 IP 数据报中,但是不属于高层协议。主要有两种: **ICMP差错报文、ICMP询问报文**。 + +![4_44.png](images/4_44.png) + +#### 4.3.1、ICMP差错报文 + +主要有四种: + +* 1)、终点不可达: 当路由器或主机不能交付数据报时就向源点发送终点不可达报文。 +* 2)、时间超过: 当路由器收到生存时间`TTL=0`的数据报时,除丢弃该数据报外,还要向源点发送时间超过报文。当终点在预先规定的时间内不能收到一个数据报的全部数据报片时,就把已收到的数据报片都丢弃,并向源点发送时间超过报文。 +* 3)、参数问题: 当路由器或目的主机收到的数据报的首部中有的字段的值不正确时,就丢弃该数据报,并向源点发送参数问题报文。 +* 4)、路由器把改变路由报文发送给主机,让主机知道下次应将数据报发送给另外的路由器(可通过更好的路由) + +![4_45.png](images/4_45.png) + +#### 4.3.2、ICMP询问报文 + +也有两种: + +* 1)、**回送请求和回答报文** : 主机或路由器向特定目的主机发出的询问,收到此报文的主机必须给源主机或路由 + 器发送ICMP回送回答报文。**测试目的站是否可达以及了解其相关状态**; +* 2)、**时间戳请求和回答报文** : 请某个主机或路由器回答当前的日期和时间。**用来进行时钟同步和测量时间**。 + +一表总结差错报文和询问报文: + +![4_46.png](images/4_46.png) + + + +#### 4.3.3、ICMP应用 + +ICMP有两个应用命令: `PING`和`Traceroute`。 + +**PING** + +测试两个主机之间的连通性,**使用了ICMP回送请求和回答报文**。 + +Ping 的原理是通过向目的主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文。Ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。 + +**Traceroute** + +跟踪一个分组从源点到终点的路径,**使用了ICMP时间超过差错报告报文**。 + +a)、源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,当 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文; +b)、源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文; +c)、不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文; +d)、最后源主机就知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间; + +### 4.4、IGMP + +#### 4.4.1、IP数据报的三种传输方式 + +分为**单播、广播、组播(多播)**。 + +* 单播:用于发送数据包到单个目的地,且每发送一份单播报文都使用一个单播IP地址作为目的地址。是一种**点对点**传输方式。 +* 广播: 广播是指发送数据包到同一广播域或子网内的所有设备的一种数据传输方式。是一种**点对多点**传输方式。 +* 组播(多播): 当网络中的某些用户需要特定数据时,**组播数据发送者仅发送一次数据**,借助**组播路由协议**为组播数据包建立**组播分发树**,被传递的数据到达距离用户端尽可能近的节点后才开始复制和分发,是一种**点对多点**传输方式。 + +![4_56.png](images/4_56.png) + +#### 4.4.2、IP组播地址 + +IP组播地址让源设备能够将分组发送给一组设备。**属于多播组的设备将被分配一个组播组IP地址**(一群共同需求主机的相同标识) 。 + +组播地址范围为`224.0.0.0一239.255.255.255`(D类地址) ,一个D类地址表示一个组播组。**只能用作分组的目标地址。源地址总是为单播地址** 。 + +* 1)、组播数据报也是“尽最大努力交付”,不提供可靠交付,应用于UDP; +* 2)、对组播数据报不产生ICMP差错报文; +* 3)、并非所有D类地址都可以作为组播地址; + +#### 4.4.3、IGMP + +IGMP 实现如下双向的功能: + +* (1)、主机通过 IGMP 通知路由器**希望接收或离开某个特定组播组**的信息。 +* (2)、路由器通过 IGMP 周期性地查询局域网内的组播组成员是否处于活动状态,实现所连网 + +段组成员关系的收集与维护。 + +IGMP工作的两个阶段: + +* 1)、某主机要加入组播组时,该主机向组播组的组播地址发送一个IGMP报文,声明自己要称为该组的成员。 + +本地组播路由器收到IGMP报文后,要利用**组播路由选择协议**把这组成员关系发给因特网上的其他组播路由器。 + +* 2)、本地组播路由器周期性探询本地局域网上的主机,以便知道这些主机是否还是组播组的成员。只要有一个主机对某个组响应,那么组播路由器就认为这个组是活跃的,如果经过几次探询后没有一个主机响应,组播路由器就认为本网络上的没有此组播组的主机,因此就不再把这组的成员关系发给其他的组播路由器。 + +ICMP和IGMP都使用**IP数据报**传递报文。 + +![4_57.png](images/4_57.png) + +> 组播路由协议: +> +> * 目的: 找出以源主机为根节点的组播转发树; +> * 对不同的多播组对应于不同的多播转发树,同一个多播组,对不同的源点也会有不同的多播转发树。 + +## 5、路由算法和路由选择协议 + +### 5.1、路由器结构 + +路由器从功能上可以划分为:**路由选择和分组转发**。 + +分组转发结构由三个部分组成:交换结构(`Switch fabric`)、一组输入端口、一组输出端口。 + +![4_48.png](images/4_48.png) + +### 5.2、路由转发分组流程 + +流程: + +- 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。 +- 若 N 就是与此路由器直接相连的某个网络地址,则进行**直接交付**; +- 若路由表中有目的地址为 D 的**特定主机路由**,则把数据报传送给表中**所指明**的下一跳路由器; +- 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所**指明的下一跳路由器;** +- 若路由表中有一个**默认路由**,则把数据报传送给路由表中所指明的默认路由器; +- 报告转发分组出错。 + +### 5.3、路由算法概括 + +静态路由算法和动态路由算法: + +![4_49.png](images/4_49.png) + +分层次的路由选择协议: 分成自治系统内(`RIP`和`OSPF`)的和自治系统外(`BGP`)的。 + +> 由于因特网规模很大,许多单位不想让外界知道自己的路由选择协议,但还是想连入因特网。于是有了自治系统`AS`。 +> +> 自治系统AS: 在单一的技术管理下的一组路由器,而这些路由器使用一种AS内部的路由选择协议和共同的度 +> 量以确定分组在该AS内的路由,同时还使用一种AS之间的路由协议以确定在AS之间的路由。一个As内的所有网络都属于一个行政单位来管辖,一个自治系统的所有路由器在本自治系统内都必须连通。 + +![4_50.png](images/4_50.png) + +### 5.4、RIP(距离向量) + +`Routing Information Protocol`,路由信息协议。**RIP其实是应用层的协议**。 + +RIP是一种分布式的基于**距离向量**的路由选择协议,是因特网的协议标准,最大优点是简单。 + +RIP协议要求网络中每一个路由器都维护**从它自己到其他每一个目的网络的唯一最佳距离记录** (即一组距离) 。 + +距离 : 通常为“跳数”,**即从源端口到目的端口所经过的路由器个数,经过一个路由器跳数+1。特别的,从一路** +**由器到直接连接的网络距离为1。RIP允许一条路由最多只能包含15个路由器,因此距离为16表示网络不可达**。所以RIP只适用于小型网络。 + +看个路由表例子: + +![4_51.png](images/4_51.png) + +相关问题: **RIP协议和谁交换? 多久交换一次? 交换什么?** + +* 仅和**相邻路由器**交换信息; +* **路由器交换的信息是自己的路由表**; +* **每30秒交换一次路由信息**,然后路由器根据新信息更新路由表。若超过180s没收到邻居路由器的通告,则判定邻居没了,并更新自己路由表; + +**距离向量算法(重要)**: + +![4_52.png](images/4_52.png) + +例题: + +![4_53.png](images/4_53.png) + +RIP协议报文格式: + +![4_54.png](images/4_54.png) + +### 5.5、OSPF(开放式最短路径优先) + +`Open Shortest Path First`。开放式最短路径优先算法。 + +RIP协议当网络出现故障时,要经过比较长的时间才能将此消息传送到所有路由器。且只使用小型网络。而OSPF就是为了克服RIP协议的缺点的。 + +开放表示 OSPF 不受某一家厂商控制,而是公开发表的;最短路径优先表示使用了 Dijkstra 提出的最短路径算法 SPF。 + +OSPF 具有以下特点: + +- 向本自治系统中的**所有路由器发送信息**,这种方法是**洪泛法**(即路由器通过输出端口向所有相邻的路由器发送信息)。 +- 发送的信息**就是与相邻路由器的链路状态**,链路状态包括与哪些路由器相连以及链路的度量,度量用费用、距离、时延、带宽等来表示。 +- **只有当链路状态发生变化时,路由器才会发送信息**。 + +所有路由器都具有全网的拓扑结构图,并且是一致的。相比于 RIP,OSPF 的更新过程**收敛的很快**。 + +### 5.6、BGP(边界网关) + +边界网关协议。`Border Gateway Protocol`。 + +* 和谁交换? : 与其他AS的邻站BGP发言人交换信息; +* 交换什么? : **交换的网络可达性的信息**,即要到达某个网络所要经过的一些列AS; +* 多久交换? : 发生变化时更新有变化的部分; + +**BGP 只能寻找一条比较好的路由,而不是最佳路由**。 + +**每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息**。 + +**BGP的邻站是交换整个的BGP路由表,但以后只需要在发生变化时更新有变化的部分**。 + +![4_55.png](images/4_55.png) + +## 6、其他 + +### 6.1、数据包传输过程以及简单的静态路由配置实验 + +![在这里插入图片描述](images/4_5.png) + +发送过程: +* 应用程序准备要传输的文件; +* 传输层: 将文件分段 并编号; +* 网络层: 添加目标`IP`地址和源`IP`地址,路由器根据路由表来选择路径(出口); +* 数据链路层 两种情况: 先使用自己的子网掩码,判断自己在哪个网段,然后判断目标地址在哪个网段, 如果是同一个网段 `arp`协议广播解析目标IP地址的`MAC` ;这一层的交换机接收到数字信号,看`MAC`地址,决定发送到下一个哪个交换机,即数据转发或存储转发。 (交换机看不到`ip`地址,只能看`MAC`地址); +* 物理层负责转换成比特流,进行数字信号的传输(注意这一层的集线器只是负责传输比特流); + +> 数据包的目标 `IP` 地址决定了数据包最终到达哪一个计算机, 而目标 `MAC`地址决定了该数据包下一跳由哪个设备接收。 + + +附上路由器`Router0`和`Router1`的配置代码 +`Router0`配置: + +```c +Router>en +Router#conf ter +Router(config)#int f0/0 +Router(config-if)#no shut +Router(config-if)#ip address 10.0.0.1 255.0.0.0 +Router(config-if)#exit +Router(config)#interface serial 2/0 +Router(config-if)#no shutdown +Router(config-if)#clock rate 64000 +Router(config-if)#ip address 11.0.0.1 255.0.0.0 +Router(config-if)#end +Router#show ip route +Router#conf ter +Router(config)#ip route 12.0.0.0 255.0.0.0 11.0.0.2 +Router#show ip route +``` +`Router1`路由器配置: + +```c +Router>en +Router#conf ter +Router(config)#interface fastethernet 0/0 +Router(config-if)#ip address 12.0.0.1 255.0.0.0 +Router(config-if)#no shut +Router(config-if)#exit +Router(config)#interface serial 2/0 +Router(config-if)#ip address 11.0.0.2 255.0.0.0 +Router(config-if)#no shutdown +Router(config)#exit +Router#show ip route +Router#conf ter +Router(config)#ip route 10.0.0.0 255.0.0.0 11.0.0.1 +Router(config)#end +Router#show ip route +Router#show running-config +``` + +### 6.2、IPV6 + +解决IPV4不够用的问题。 + +注意点: + +* **IPv6将地址从32位(4B) 扩大到128位(16B)** ,更大的地址空间; +* IPv6将IPv4的校验和字段彻底移除,以减少每跳的处理时间; +* IPv6将IPv4的可选字段移出首部,变成了**扩展首部**,成为灵活的首部格式,路由器通常不对扩展首部进行检查,大大提高了路由器的处理效率。 +* IPv6支持即播即用(即自动配置) ,**不需要DHCP协议**。 +* **IPv6首部长度必须是8B的整数倍,IPv4首部是48B的整数倍**。 +* IPv6只能在主机处分片,IPv4可以在路由器和主机处分片; + +![4_58.png](images/4_58.png) \ No newline at end of file diff --git a/Basics/NetWork/5_TransportLayer.md b/Basics/NetWork/5_TransportLayer.md new file mode 100644 index 00000000..f72aad68 --- /dev/null +++ b/Basics/NetWork/5_TransportLayer.md @@ -0,0 +1,597 @@ +# 计网总结(五)一运输层 + +* [1、概述](#1概述) +* [2、UDP](#2udp) +* [3、TCP连接管理](#3tcp连接管理) +* [4、TCP可靠传输和流量控制](#4tcp可靠传输和流量控制) +* [5、TCP拥塞控制](#5tcp拥塞控制) + +## 1、概述 +网络层是为主机之间提供逻辑通信,而运输层为应用进程之间提供**端到端的逻辑通信**。 + +即: **为相互通信的应用进程提供了逻辑通信**。 + +### 1.1、基本功能 + +* 传输层是**只有主机才有的层次**; +* **为相互通信的应用进程提供了逻辑通信**; +* 为应用层提供**通信服务**,使用网络层的服务; +* 复用和分用; +* 传输层对收到的报文进行差错检测; + +

+**复用: 应用层所有的应用进程都可以通过传输层再传输到网路层**。 + +**分用: 传输层从网路层收到数据后交付指明的应用进程**。 + +### 1.2、传输层协议和应用层协议之间的关系 + +应用层协议很多,传输层就两个协议,如何使用传输层两个协议标识应用层协议呢? 通常传输层协议加一个**端口号**来标识一个应用层协议,如下图所示,展示了传输层协议和应用层协议之间的关系。 + +端口用一个 16 位端口号进行标志。 + +是逻辑端口/软件端口: 是传输层的SAP,标识主机中的应用进程。只有本地意义。 + +> 由此可见,两个计算机中的进程要互相通信,不仅必须知道对方的 IP 地址(为了找到对方的计算机),而且还要知道对方的端口号(为了找到对方计算机中的应用进程)。 + +端口号以及常用的熟知端口: + + +

+> (1)、服务器端使用的端口号 +> * 熟知端口,数值一般为 0~1023(包括)。 +> * 登记端口号,数值为 1024~49151,为没有熟知端口号的应用程序使用的。使用这个范围的端口号必须在 IANA 登记,以防止重复。 +> (2)、客户端使用的端口号 +> * 又称为短暂端口号,数值为 49152~65535,留给客户进程选择暂时使用。 +> * 当服务器进程收到客户进程的报文时,就知道了客户进程所使用的动态端口号。通信结束后,这个端口号可供其他客户进程以后使用。 + +> 套接字: **网络中采用发送方和接收方的套接字组合来识别端点,套接字唯一标识了网络中的一个主机和它上面的一个进程**。即套接字Socket = (主机IP地址,端口号) + +整体过程: + +![images/5_4.png](images/5_4.png) + +> 网络层到传输层,用数据包中的**协议号**来标识是TCP还是UDP协议。 +> +> `TCP : 6, UDP : 17`。 + +下图展示了A、B、C计算机访问服务器数据包的过程。 + +客户端使用IP地址定位服务器,**使用目标端口,定位服务**。 + +![5_5.png](images/5_5.png) + +TCP和UDP相当于网络中的两扇大门,门上开的洞就相当于开发TCP和UDP的端口。如果想让服务器更加安全,就把能够通往应用层的TCP和UDP的两扇大门关闭,在大门上只开放必要的端口。 + +![5_6.png](images/5_6.png) + +上面讲的是设置服务器的防火墙只开放必要的端口,以加强服务器的网络安全。也可以在路由器上设置访问控制列表`ACL`来实现网络防火墙的功能,控制内网访问Internet的流量。 + +![5_7.png](images/5_7.png) + +## 2、UDP + +### 2.1、UDP协议的特点 + +**无连接的用户数据报协议**。 + +用户数据报协议 (UDP) 只在IP地址的数据报服务之上增加了很少一点功能,就是**复用和分用的功能以及差错检测**的功能,复用和分用,就是使用端口标识不同的应用层协议。 + +* 传送数据之前不需要建立连接,收到UDP报文后也不需要给出任何确认; +* 使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表(这里面有许多参数),通信的两端不用保持连接,因此节省系统资源; + + 一个数据包就能完成数据通信、不分段、不需要建立会话、不需要流量控制、不可靠传输。 + +简言之: 不可靠、无连接、时延小、适用于小文件。 + +接收方 UDP 对 IP 层交上来的 UDP 用户数据报,在去除首部后就原封不动地交付上层的应用进程,**一次交付一个完整的报文**。 + +### 2.2、UDP首部格式 + +用抓包工具捕获的域名解析的数据包,域名解析使用DNS协议,在传输层使用UDP协议。如下图: + +![5_8.png](images/5_8.png) + +UDP 用户数据报有两个字段: 数据字段和首部字段。如图所示,首部字段很简单,只有8 个字节,由 4 个字段组成,每个字段的长度都是两个字节,各字段含义如下: + +* 1)、 源端口。源端口号。在需要对方回信时选用。不需要时可用全 0。 +* 2)、**目的端口。目的端口号。在终点交付报文时必须要使用到**。 +* 3)、长度。UDP 用户数据报的长度,其最小值是8(仅有首部)。 +* 4)、检验和。检测 UDP 用户数据报在传输中是否有错。有错就入弃。 + +![5_9.png](images/5_9.png) + +UDP 用户数据报首部中检验和的计算方法有些特殊。在计算检验和时,要在 UDP 用户数据报之前增加 12 个字节的**伪首部**。所谓“伪首部” 是因为这种伪首部并不是 UDP 用户数据报真正的首部。 + +**UDP校验**: + +在发送端: + +* 1)、填上伪首部; +* 2)、全0填充检验和字段; +* 3)、全0填充数据部分(UDP数据报要看成许多4B的字串接起来); +* 4)、伪首部+首部+数据部分采用二进制及码求和; +* 5)、把和求反码填入检验和字段; +* 6)、去掉伪首部,发送; + +在接收端: +* 1)、填上伪首部; +* 2)、伪首部+首部+数据部分采用二进制反码求和; +* 3)、结果全为1则无差错,否则丢弃数据报/交给应用层附上出差错的警告(ICMP); + +![5_10.png](images/5_10.png) + +## 3、TCP + +**面向连接的传输控制协议**。 + +传送数据之前必须建立连接,数据传送结束后要释放连接。适用于大文件。 + +**不提供广播或多播服务**。 + +由于TCP要提供可靠的面向连接的传输服务,因此不可避免增加了许多开销:确认、流量控制、计时器及连接 +管理等。 + +### 3.1、TCP协议的特点 + +* 1)、TCP是面向连接 (**虚连接**) 的传输层协议;(类似打call) +* 2)、每一条TCP连接只能有两个端点,每一条TCP连接**只能是点对点的**; +* 3)、TCP提供可靠交付的服务,无差错、不丢失、不重复、按序到达;(可靠有序,不丢不重) +* 4)、TCP提供**全双工通信**。 + * "**发送缓存**" : 准备发送的数据 & 已发送但尚未收到确认的数据; + * "**接收缓存**" : 按序到达但尚未被接受应用程序读取的数据 &不按序到达的数据; +* 5)、TCP面向字节流。TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。流: 流入到进程或从进程流出的字节序列。 +* 6)、TCP 不保证接收方应用程序所收到的数据块和发送方应用程序所发出的数据块具有对应大小的关系;但接收方应用程序收到的字节流必须和发送方应用程序发出的字节流完全一样; +* 7)、TCP 对应用进程一次把多长的报文发送到TCP 的缓存中是不关心的。 **TCP 根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节**(UDP 发送的报文长度是应用进程给出的)。 + +### 3.2、TCP连接 + +TCP 连接的端点不是主机,不是主机的IP 地址,不是应用进程,也不是运输层的协议端口。TCP 连接的端点叫做**套接字 (socket) 或插口**。 + **端口号拼接到 (contatenated with) IP 地址即构成了套接字**。 + +套接字 `socket = (IP地址: 端口号)`。 + +**每一条 TCP 连接唯一地被通信两端的两个端点(即两个套接字)所确定**。 + +TCP 连接` ::= {socket1, socket2} = { (IP1: port1),(IP2: port2) } ` + +同一个 IP 地址可以有多个不同的 TCP 连接。 + +同一个端口号也可以出现在多个不同的 TCP 连接中。 + +### 3.3、TCP首部格式 + +首部格式: + +

+相关字段解释(对于理解TCP很重要) + +* 序号: 在一个TCP连接中传送的字节流中的每一个字节都按顺序编号,本字段表示本报文段所发送数据的第一个字节的序号,例如发送报文段`1-2-3`,则序号是`1`。 +* 确认号: **期望收到对方下一个报文段的第一个数据字节的序号**。若确认号为N,则证明到序号N-1为止的所有数据都已正确收到。例如 B 正确收到 A 发送来的一个报文段,序号为 301,携带的数据长度为 100 字节,因此 B 期望下一个报文段的序号为 401,B 发送给 A 的确认报文段中确认号就为 401; +* 数据偏移(**首部长度**) : TCP报文段的数据起始处距离TCP报文段的起始处有多远,以4B位单位,即1个数值是4B。 +* 6个控制位。 + * **紧急位URG** : URG=1时,标明此报文段中有紧急数据,是高优先级的数据,应尽快传送,不用在缓存里排队,配合**紧急指针字段**使用; + * **确认位ACK** : ACK=1时确认号有效,在连接建立后所有传送的报文段都必须把ACK置为1; + * **推送位PSH** : PSH=1时,**接收方尽快交付接收应用进程,不再等到缓存填满再向上交付**; + * **复位RST** : RST=1时,表明TCP连接中出现严重差错,必须释放连接,然后再**重新建立**传输链接; + * **同步位SYN** : SYN=1时,表明是一个连接请求/连接接受报文; + * **终止位FIN** : FIN=1时,表明此报文段发送方数据已发完,**要求释放连接**。 +* **窗口** :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。 +* 检验和: 占 2 字节。检验和字段检验的范围包括首部和数据这两部分。在计算检验和时,要在 TCP 报文段的前面加上 12 字节的伪首部。 +* 紧急指针: URG = 1时才有意义,指出本报文段紧急数据的字节数; +* 选项: 最大报文段长度MSS、窗口扩大、时间戳、时间确认....; + +> MSS (Maximum Segment Size)是 TCP 报文段中的数据字段的最大长度。**数据字段加上 TCP 首部才等于整个的 TCP 报文段**。所以,MSS是 `TCP 报文段长度 - TCP 首部长度`; + +捕获的TCP报文段: + +![5_12.png](images/5_12.png) + +### 3.4、TCP和UDP的区别 + +* 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。 +* 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。 +* 3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)。 +* 4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。 +* 5、TCP首部开销20字节;UDP的首部开销小,只有8个字节。 +* 6、TCP的逻辑通信信道是全双工的可靠信道;UDP则是不可靠信道。 + +## 4、TCP连接管理 + +### 4.1、TCP的连接建立 + +#### 4.1.1、三次握手 + +TCP连接传输三个阶段: + +* 连接建立; +* 数据传送; +* 连接释放; + +TCP连接的建立采用客户端服务器方式,主动发起连接建立的应用进程叫做客户端(A),而被动等待连接建立的应用进程叫服务器(B)。 + +假设运行在一台主机(客户)上的一个进程想与另一台主机(服务器)上的一个进程建立一条连接,客户应用进程首先通知客户TCP,他想建立一个与服务器上某个进程之间的连接,客户中的TCP会用以下步骤与服务器中的TCP建立一条TCP连接: + +

+步骤: + +* 1)、A 向 B 发送连接请求报文段,SYN=1,ACK=0,选择一个初始的序号 seq = x; +* 2)、B 收到连接请求报文段,,如果同意建立连接,服务器为该TCP连接分配缓存和变量,向 A 发送连接确认报文段,SYN=1,ACK=1,确认号`ack`为 `x+1`,同时也选择一个初始的序号 seq = y (随机); +* 3)、A 收到 B 的连接确认报文段后,为该TCP连接分配缓存和变量,同时还要向 B 发出确认,ACK = 1,确认号为 ack = y+1,序号为 `seq = x+1`(接上一开始的`x`);(SYN = 0,因为只有在连接请求和连接请求接受的时候才会置为1); +* 4)、最后,B收到A确认报文,状态变为ESTABLISHED(连接建立),双方就可以进行双向通信了; + +实战: + +(1)、第一次握手 + +

+(2)、第二次握手 + +

+(3)、第三次握手 + +

+#### 4.1.2、为什么TCP连接需要三次握手,两次不可以吗,为什么? + +主要是 : **为了防止已失效的连接请求报文段突然又传送到了服务端,占用服务器资源** 。(A是客户端,B是服务器) + +现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到B。本来这是一个已失效的报文段,但是B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。 + +由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。B的许多资源就这样白白浪费了。 + +采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下,A不会向B的确认发出确认。B由于收不到确认,就知道A并没有要求建立连接。 + +#### 4.1.3、SYN攻击 + +SYN洪泛攻击发生在OSI第四层,这种方式利用TCP协议的特性,就是三次握手。**攻击者发送TCP SYN,SYN是TCP三次握手中的第一个数据包,而当服务器返回ACK后,该攻击者就不对其进行再确认,那这个TCP连接就处于挂起状态,也就是所谓的半连接状态**,服务器收不到再确认的话,还会重复发送ACK给攻击者。这样更加会浪费服务器的资源。攻击者就对服务器发送非常大量的这种TCP连接,由于每一个都没法完成三次握手,所以在服务器上,这些TCP连接会因为挂起状态而消耗CPU和内存,最后服务器可能死机,就无法为正常用户提供服务了。 + +### 4.2、TCP的连接释放 + +#### 4.2.1、四次挥手 + +过程: + +![5_17.png](images/5_17.png) + +数据传输结束后,通信的双方都可释放连接。现在 A 的应用进程先向其 TCP **发出连接释放报文段**,并停止再发送数据,主动关闭 TCP连接。 + +- A 把连接释放报文段首部的 FIN = 1,其序号 seq = u,等待 B 的确认。 +- B 发出确认(会送一个确认报文段),ACK = 1,确认号 ack = u+1,而这个报文段自己的序号 seq = v(随机)。(TCP 服务器进程通知高层应用进程) +- 从 A 到 B 这个方向的连接就释放了,TCP 连接处于**半关闭状态**。A 不能向 B 发送数据;**B 若发送数据,A 仍要接收**。 +- 当 B 不再需要连接时,发送连接释放请求报文段,FIN=1,ACK =1,seq = w(随机),确认号ack = u + 1(为什么确认号是一样的,因为在这段时间内,A没有发送数据) (这时B进入`LAST-ACK`(最后确认阶段))。 +- A 收到后发出确认,ACK =1, seq = u + 1(因为FIN要消耗一个序号,所以从u+1开始),进入 TIME-WAIT 状态,等待 2 MSL(2*2 = 4 mins)时间后释放连接。 +- B 收到 A 的确认后释放连接。 + +#### 4.2.2、四次挥手的原因 + +因为TCP有个半关闭状态,假设A、B要释放连接,那么A发送一个释放连接报文给B,B收到后发送确认,**这个时候A不发数据,但是B如果发数据A还是要接受,这叫半关闭**。然后B还要发给A连接释放报文,然后A发确认,所以是4次。 + +客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。**这个状态是为了让服务器端发送还未传送完毕的数据**,传送完毕之后,服务器会发送 FIN 连接释放报文。 + +在tcp连接握手时为何ACK是和SYN一起发送,这里ACK却没有和FIN一起发送呢。原因是因为tcp是**全双工模式**,**接收到FIN时意味将没有数据再发来,但是还是可以继续发送数据。** + +#### 4.2.3、为什么A在TIME-WAIT状态必须等待2MSL的时间内? + +MSL是`Maximum Segment Lifetime`英文的缩写,中文可以译为 “报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。2MSL = 2*2mins = 4mins +客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由: + +* **确保最后一个确认报文段能够到达B**。如果 B 没收到 A 发送来的确认报文段,那么就会重新发送连接释放请求报文段,接着 A 重传一次确认,重新启动 2MSL 计时器。A 等待一段时间就是为了处理这种情况的发生。 +* 等待一段时间是为了让本连接持续时间内所产生的所有报文段都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文段。 + +### 4.3、三次握手和四次挥手涉及的状态 + +三次握手 + +- CLOSED:初始状态。 +- LISTEN:服务器处于监听状态。 +- SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。 +- SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。 +- ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后进入此状态。 + +四次挥手: + +- FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。 +- CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状态。 +- FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器的ACK包,但并没有立即接收到服务端的FIN包,进入FIN_WAIT_2状态。 +- LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。 +- TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的确认,在此之后的2MSL时间称为TIME_WAIT状态。 + +## 5、TCP可靠传输和流量控制 + +**所谓流量控制就是让发送发送速率不要过快,让接收方来得及接收。利用滑动窗口机制就可以实施流量控制**。 + +原理这就是运用TCP报文段中的窗口大小字段来控制,发送方的发送窗口不可以大于接收方发回的窗口大小。考虑一种特殊的情况,就是接收方若没有缓存足够使用,就会发送零窗口大小的报文,此时发送放将发送窗口设置为0,停止发送数据。 + +之后接收方有足够的缓存,发送了非零窗口大小的报文,但是这个报文在中途丢失的,那么发送方的发送窗口就一直为零导致死锁。解决这个问题,**TCP为每一个连接设置一个持续计时器**(persistence timer)。 + +只要TCP的一方收到对方的零窗口通知,就启动该计时器,周期性的发送一个零窗口探测报文段。对方就在确认这个报文的时候给出现在的窗口大小 + +(**注意:TCP规定,即使设置为零窗口,也必须接收以下几种报文段:零窗口探测报文段、确认报文段和携带紧急数据的报文段**)。 + +针对可靠传输和流量控制有三种协议: 停止等待协议、后退N帧协议、选择重传协议。 + +![3_10.png](images/3_10.png) + +### 5.1、停止等待协议 + +停止等待协议也可以在传输层讨论。 + +停止等待协议是**为了实现流量控制**。 + +简言之: **就是每发送一个分组就停止发送,等待对方确认,在收到确认后再发送下一个分组**。 + +主要分为: **无差错情况和有差错情况**。 + +#### 5.1.1、无差错的情况 + +![3_12.png](images/3_12.png) + +这里应注意以下三点。 + +* 第一,A 在发送完一个分组后,必须**暂时保留已发送的分组的副本**(发生超时重传时使用)。只有在收到相应的确认后才能清除暂时保留的分组副本。 +* 第二,**分组和确认分组都必须进行编号**。这样才能明确是哪一个发送出去的分组收到了确认,而哪一个分组还没有收到确认。 +* 第三,超时计时器设置的重传时间应当比数据分组传输的平均往返时间(RTT)更长一些。 + +> ACK (Acknowledgement)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。 +> +> 在TCP/IP协议中,如果接收方成功的接收到数据,那么会回复一个ACK数据。通常ACK信号有自己固定的格式,长度大小,由接收方回复给发送方。 + +#### 5.1.2、有差错的情况 + +又可以分为两种: + +* 数据帧丢失或者检验到帧出错; + +* ACK丢失; +* ACK迟到; + +数据帧丢失的情况: + +![3_13.png](images/3_13.png) + +ACK丢失和ACK迟到: + +![3_14.png](images/3_14.png) + +#### 5.1.3、缺点 + +信道利用率太低: + +* 大部分时间都在路上; +* 即RTT(传输往返时延)太长; + +![3_15.png](images/3_15.png) + + +针对上面停止等待协议的缺点,于是就有了在传输数据时的流水线发送数据,也就引出了两种协议: + +* 后退N帧协议; +* 选择重传协议; + +解决停等协议的流水线解决方案: + +![3_16.png](images/3_16.png) + +### 5.2、后退N帧协议(ARQ) + +用图来看一下步骤: + +

+

+在这个过程中,发送方可以分成几个部分: + +* 发送完被确认的; +* 已经发送但等待确认的; +* 还能发送的; +* 还不能发送的; + +#### 5.2.1、GBN发送方必须做的三件事 + +1)、上层的调用 + +上层要发送数据时,发送方先检查发送窗口是否已满,如果未满,则产生一个帧并将其发送, 如果窗口已满, +发送方只需将数据返回给上层,暗示上层窗口已满。上层等一会再发送。 (实际实现中,发送方可以缓存这 +些数据,窗口不满时再发送帧) 。 + +2)、收到了一个ACK (重要) + +GBN协议中,对n号帧的确认采用**累积确认**的方式,**标明接收方已经收到n号帧和它之前的全部帧**。 + +3)、超时事件 + +**协议的名字为后退N帧/回退N帧**,来源于出现丢失和时延过长帧时发送方的行为。就像在停等协议中一样, +定时器将再次用于恢复数据帧或确认帧的丢失。如果出现超时,发送方重传所有已发送但未被确认的帧。 + +#### 5.2.2、GBN接收方必须要做的事 + +1)、如果正确收到n号帧,并且按序,那么接收方为n帧发送一个ACK,并将该帧中的数据部分交付给上层。 + +2)、**其余情况都丢弃帧,并为最近按序接收的帧重新发送ACK。接收方无需缓存任何东西,只需要维护一个信息: expectedseqnum** (下一个按序接收的帧序、我期待的下一个的帧的序号,如果一直没来我就一直丢弃你的不符合的帧) 。 + +> 注意发送窗口不能无限大,范围是`1 ~ 2^n-1`,`n`是代表用`n`个比特对帧编号。 + +#### 5.2.3、GBN总结和注意事项 + +![3_19.png](images/3_19.png) + +几个重点: + +* 累积确认( 偶尔捎带确认); +* **接收方只按顺序接收帧,不按序无情丢弃**; +* 确认序列号最大的、按序到达的帧; +* **发送窗口最大为 2n - 1,接收窗口大小为1**; + +![3_20.png](images/3_20.png) + +### 5.3、选择重传协议 + +后退N帧协议的缺点 : 有些帧发送的好好的(由于`exceptedSeqNum`不对),为什么让我重传呢? + +解决办法: + +设置单个确认,同时加大接收窗口,设置接收缓存,缓存乱序到达的帧。 + +基本方法: + +![3_21.png](images/3_21.png) + +#### 5.3.1、SR发送方必须做的三件事 + +1)、从上层收到数据后,**SR发送方检查下一个可用于该帧的序号,如果序号位于发送窗口内,则发送数据帧**;否 +则就像GBN一样,要么将数据缓存,要么返回给上层之后再传输; + +2)、如果收到ACK,加入该帧序号在窗口内,则SR发送方将那个被确认的帧标记为已接收。如果该帧序号是窗口 +的下界(最左边第一个窗口对应的序号) ,**则窗口向前移动到具有最小序号的未确认帧处**。如果窗口移动了 +并且有序号在窗口内的未发送帧,则发送这些帧。 + +3)、每个帧都有自己的定时器,一个超时时间发生之后只重传一个帧。 + +#### 5.3.2、SR接收方必须要做的事 + +1)、**SR接收方将确认一个正确接收的帧而不管其是否按序**。**失序的帧将被缓存**,并返回给发送方一个该帧的确认帧【收谁确认谁】,直到所有帧(**即序号更小的帧)**皆被收到为止,这时才可以将一批帧按序交付给上层,然后向前移动滑动口上。 + +2)、**如果收到了小于窗口下界的帧,就返回一个ACK,否则就忽略该帧**。 + +#### 5.3.3、SR总结和注意事项 + +![3_22.png](images/3_22.png) + +SR重点: + +* 对数据帧逐一确认,收一个确认一个(先来着不拒); +* 只重传出错帧; +* 接收方有**缓存**; +* W发送方 = W接收方 = 2 n-1; + +![3_23.png](images/3_23.png) + +以字节为单位的滑动窗口技术详解(总的流程图): + +![滑动窗口.png](images/滑动窗口.png) + +**窗口是缓存的一部分,用来暂时存放字节流**。发送方和接收方各有一个窗口,**接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小**。 + +发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。**如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离**,直到左部第一个字节不是已发送并且已确认的状态; + +接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。 + +接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {1, 2, 4, 5},其中 {1, 2} 按序到达,而 {4, 5} 就不是,因此只对字节 {1, 2} 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。 + +> 发送缓存用来暂时存放: +> +> * 发送应用程序传送给发送方 TCP 准备发送的数据; +> * TCP 已发送出但尚未收到确认的数据。 +> +> 接收缓存用来暂时存放: +> +> * 按序到达的、但尚未被接收应用程序读取的数据; +> * **不按序到达的数据**。 + +### 5.4、流量控制 + +![流量控制.png](images/流量控制.png) + +> rwnd : Receive Window,即接收窗口。注意TCP的窗口单位是字节,不是报文段。 + +这个过程主机B进行了三次流量控制,第一次把窗口减小到`rwnd = 300`,第二次又减到`rwnd = 100`,最后减到`rwnd = 0`,即不允许发送方再发送数据了。 + +> 可能发生死锁的问题: +> +> B 向 A 发送了零窗口的报文段后不久,B 的接收缓存又有了一些存储空间。于是 B 向 A 发送了 rwnd = +> 400 的报文段。 +> 但这个报文段在传送过程中丢失了。A 一直等待收到B 发送的非零窗口的通知,而 B 也一直等待 A 发送的 +> 数据。 +> 如果没有其他措施,这种互相等待的死锁局面将一直延续下去。 +> 为了解决这个问题,TCP 为每一个连接设有一个**持续计时器** (persistence timer)。 + +## 6、TCP拥塞控制 + +### 6.1、拥塞控制原理 + +拥塞控制原理 + +- 在某段时间,若**对网络中某资源的需求超过了该资源所能提供的可用部分**,网络的性能就要变坏——产生拥塞(congestion)。 +- 出现资源拥塞的条件:**对资源需求的总和 > 可用资源**; +- 若网络中有许多资源同时产生拥塞,网络的性能就要明显变坏,整个网络的吞吐量将随输入负荷的增大而下降。 + +拥塞控制: 防止过多的数据注入到网络中。 + +注意流量控制和拥塞控制的本质区别: **流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度**。 + +

+> rwnd : Receive Window,即接收窗口。接收方根据接受缓存设置的值,并告知给发送方,反映接收方容量。 +> +> cwnd: Congestion Window,即拥塞窗口。发送方根据自己估算的网络拥塞程度而设置的窗口值,反映网络当前容量。 +> +> 接收窗口: 是接收方设置的。拥塞窗口: 是发送方设置的 +> +> 拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。 + +

+> 提供的负载,就是往网络上放的数据的量。 + +TCP 主要通过四种算法来进行拥塞控制:**慢开始、拥塞避免、快重传、快恢复**。 + +虽然 TCP 的窗口基于字节,但是这里按照窗口的大小单位为报文段来讨论。 + +### 6.2、慢开始和拥塞避免 + +慢开始的原理: **由小到大逐渐增大拥塞窗口数值**。每次可以按照收到的确认的个数来判断成功的概率。逐步增大发送方的拥塞窗口 cwnd,可以使分组注入到网络的速率更加合理。发送方每收到一个对新报文段的确认(重传的不算在内)就使 cwnd 加 1。 + +

+过程: + +

+发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ... + +注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。 + +设置一个慢开始阈值 (门限)ssthresh,当 `cwnd >= ssthresh` 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 + +如果出现了超时(最大拥塞窗口),则令 `ssthresh = cwnd/2`,然后重新执行慢开始。 + +> 慢开始门限 ssthresh 的用法如下: +> +> * 当 cwnd < ssthresh 时,使用慢开始算法。 +> * 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。 +> * 当cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞避免算法。 + +### 6.3、快重传和快恢复 + +思想: 发送方只要一连收到**三个重复确认**,就知道接收方确实没有收到报文段,因而应当立即进行重传(即“快重传”),这样就不会出现超时,发送方也不就会误认为出现了网络拥塞。 + +不难看出,快重传并非取消重传计时器,而是在某些情况下可更早地重传丢失的报文段。 + +

+在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。 + +在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。 + +在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令` ssthresh = cwnd/2 ,cwnd = ssthresh`,注意到此时直接进入拥塞避免。 + +慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。 + +> 注意: +> +> 发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个,即应按以下公式确定: +> +> 发送窗口的上限值 = `min {rwnd, cwnd}` +> 当 rwnd < cwnd 时,是接收方的接收能力限制发送窗口的最大值。 +> 当 cwnd < rwnd 时,则是网络的拥塞限制发送窗口的最大值。 + +### 6.4、流量控制和拥塞控制区分 + +- 拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。 +- 流量控制往往指在给定的发送端和接收端之间的**点对点**通信量的控制。 +- 流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。 +- 流量控制属于通信双方协商;拥塞控制涉及通信链路**全局**。 +- 流量控制需要通信双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身决定,发送窗大小由接收方响应的TCP报文段中窗口值确定;拥塞控制的拥塞窗口大小变化由**试探性**发送一定数据量数据探查网络状况后而自适应调整。 + +### 6.5、超时重传、RTO、RTT + +* 超时重传:发送端发送报文后若长时间未收到确认的报文则需要重发该报文。可能有以下几种情况: + * 发送的数据没能到达接收端,所以对方没有响应。 + * 接收端接收到数据,但是ACK报文在返回过程中丢失。 + * 接收端拒绝或丢弃数据。 +* RTO(`Retransmission TimeOut`):**从上一次发送数据,因为长期没有收到ACK响应,到下一次重发之间的时间**。就是重传间隔。通常每次重传RTO是前一次重传间隔的两倍,计量单位通常是RTT。例:1RTT,2RTT,4RTT,8RTT......重传次数到达上限之后停止重传。 +* RTT (`Round Trip Time`):**数据从发送到接收到对方响应之间的时间间隔**,即数据报在网络中一个往返用时。大小不稳定。 \ No newline at end of file diff --git a/Basics/NetWork/6_ApplicationLayer.md b/Basics/NetWork/6_ApplicationLayer.md new file mode 100644 index 00000000..6804eb98 --- /dev/null +++ b/Basics/NetWork/6_ApplicationLayer.md @@ -0,0 +1,40 @@ +# 计网总结(六)一应用层 + +* DNS +* FTP +* 电子邮件 + +## 1、DNS + +DNS提供了主机名和 IP 地址之间相互转换的服务。域名具有层次结构,从上到下依次为:根域名、顶级域名、二级域名...。 + +

+![5_24.png](images/5_24.png) + +> DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: +> - 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。 +> - 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 + +## 2、FTP + +FTP是基于**客户/服务器**(C/S) 的协议。 + +用户通过一个客户机程序连接至在远程计算机上运行的服务器程序。依照 FTP 协议提供服务,进行文件传送的计算机就是 FTP 服务器。 + +连接FTP服务器,遵循FTP协议与服务器传送文件的电脑就是FTP客户端。 + +FTP 使用 TCP 进行连接,它需要两个连接来传送一个文件: + +- 控制连接:**服务器打开端口号 21 等待客户端的连接**,客户端主动建立连接后,使用这个连接将客户端的命令传送给服务器,并传回服务器的应答。 +- 数据连接:用来传送一个文件数据。 + +![6_1.png](images/6_1.png) + +## 3、电子邮件 + +一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件协议。 + +邮件协议包含发送协议和读取协议,发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。 + +![6_2.png](images/6_2.png) + diff --git a/Basics/NetWork/Http.md b/Basics/NetWork/Http.md new file mode 100644 index 00000000..d744f6a9 --- /dev/null +++ b/Basics/NetWork/Http.md @@ -0,0 +1,155 @@ +# Http协议 + +* [1、Web和网络基础](#1web和网络基础) + +## 1、Web和网络基础 + +### 1.1、使用HTTP协议访问Web + +* 客户端: 通过发送请求获取服务器资源的 Web 浏览器等,都可称为客户端( client )。 +* Web 使用一种名为 HTTP ( `HyperText Transfer Protocol` ,超文本传输协议)的协议作为规范,完成从客户端到服务器端等一系列运作流程。而协议是指规则的约定。Web是建立在Http协议上通信的; + +![](images/http/1_客户端.png) + +### 1.2、TCP/IP + +TCP/IP 协议族按层次分别分为以下 4 层:应用层、传输层、网络层和数据链路层; + +* 应用层: TCP/IP 协议族内预存了各类通用的应用服务。比如, FTP ( File Transfer Protocol ,文件传输协议)和 DNS ( Domain Name System ,域名系统)服务就是其中两类。**HTTP 协议也处于该层**。 +* 传输层: 传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输。在传输层有两个性质不同的协议: TCP ( Transmission Control Protocol ,传输控制协议)和 UDP ( User Data Protocol ,用户数据报协议); +* 网络层: 网络层用来处理在网络上流动的数据包。数据包是网络传输的最小数据单位。该层规定了通过怎样的路径(所谓的传输路线)到达对方计算机,并把数据包传送给对方。 +* 数据链路层: 用来处理连接网络的硬件部分。 + +通信传输流: + +![](images/http/2_.png) + +传输过程: + +利用 TCP/IP 协议族进行网络通信时,会通过分层顺序与对方进行通信。 + +**发送端从应用层往下走,接收端则往应用层往上走**。 + +我们用 HTTP 举例来说明,首先作为发送端的客户端在应用层( HTTP 协议)发出一个想看某个 Web 页面的HTTP 请求。 + +接着,为了传输方便,**在传输层( TCP 协议)把从应用层处收到的数据( HTTP 请求报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层**。 + +**在网络层( IP 协议),增加作为通信目的地的 MAC 地址后转发给链路层**。这样一来,发往网络的通信请求就准备齐全了。 + +接收端的服务器在链路层接收到数据,按序往上层发送,一直到应用层。当传输到应用层,才能算真正接收到由客户端发送过来的 HTTP请求。 + +![](images/http/3_.png) + +发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层时会把对应的首部消去。 +这种把数据信息包装起来的做法称为**封装( encapsulate )**。 + +### 1.3、IP、TCP和DNS + +#### 1.3.1、IP(负责传输) + +按层次分, **IP ( Internet Protocol )网际协议位于网络层**。几乎所有使用网络的系统都会用到 IP 协议。 + +可能有人会把 “IP” 和 “IP 地址 ” 搞混, “IP” 其实是一种协议的名称。 + +* IP 协议的作用是把**各种数据包传送给对方**。而要保证确实传送到对方那里,则需要满足各类条件。其中两个重要的条件是 **IP 地址和 MAC地址**( Media Access Control Address )。 +* **IP 地址指明了节点被分配到的地址, MAC 地址是指网卡所属的固定地址**。 IP 地址可以和 MAC 地址进行配对。 IP 地址可变换,但 MAC地址基本上不会更改。 + +使用 ARP 协议凭借 MAC 地址进行通信 + +* IP 间的通信依赖 MAC 地址。在网络上,通信的双方在同一局域网( LAN )内的情况是很少的,通常是经过多台计算机和网络设备中转才能连接到对方。而在进行中转时,**会利用下一站中转设备的 MAC地址来搜索下一个中转目标。这时,会采用 ARP 协议( Address Resolution Protocol )**。 +* ARP 是一种用以解析地址的协议,**根据通信方的 IP 地址就可以反查出对应的 MAC 地址**。没有人能够全面掌握互联网中的传输状况,在到达通信目标前的中转过程中,那些计算机和路由器等网络设备只能获悉很粗略的传输路线。这种机制称为路由选择( routing )。 + +> 路由选择:有点像快递公司的送货过程。想要寄快递的人,只要将自己的货物送到集散中心,就可以知道快递公司是否肯收件发货,该快递公司的集散中心检查货物的送达地址,明确下站该送往哪个区域的集散中心。接着,那个区域的集散中心自会判断是否能送到对方的家中。 + +![](images/http/4_.png) + +#### 1.3.2、TCP协议(确保可靠性) + +按层次分, TCP 位于传输层,提供可靠的字节流服务。 + +* 所谓的字节流服务( Byte Stream Service )是指,为了方便传输,**将大块数据分割成以报文段( segment )为单位的数据包**进行管理; +* 而可靠的传输服务是指,能够把数据准确可靠地传给对方。一言以蔽之,TCP 协议为了更容易传送大数据才把数据分割,而且 TCP 协议能够确认数据最终是否送达到对方。确保数据能到达目标; + +为了准确无误地将数据送达目标处, TCP 协议采用了**三次握手( three-way handshaking )策略**。 + +* 用 TCP 协议把数据包送出去后, TCP不会对传送后的情况置之不理,它一定会向对方确认是否成功送达。 +* 握手过程中使用了 TCP 的标志( flag ) —— SYN ( synchronize ) 和ACK ( acknowledgement )。发送端首先发送一个带 **SYN** 标志的数据包给对方。接收端收到后,回传一个带有 **SYN/ACK** 标志的数据包以示传达确认信息。最后,发送端再回传一个带 **ACK** 标志的数据包,代表 “ 握手 ” 结束。 + +若在握手过程中某个阶段莫名中断, TCP 协议会再次以相同的顺序发送相同的数据包。 + +![](images/http/5_.png) + +#### 1.3.3、DNS(负责域名解析) + +DNS ( Domain Name System )服务是和 HTTP 协议一样位于应用层的协议。它提供**域名到 IP 地址之间**的解析服务。 + +用户通常使用**主机名或域名**来访问对方的计算机,而不是直接通过 IP地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表示形式来指定计算机名更符合人类的记忆习惯。 + +为了解决上述的问题, DNS 服务应运而生。 DNS 协议提供**通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务**。 + +![](images/http/6_DNS.png) + +### 1.4、各种协议与Http协议的关系 + +

+

+### 1.5、URI和URL + +与 URI (统一资源标识符)相比,我们更熟悉 URL ( UniformResource Locator ,统一资源定位符)。 + +URL 正是使用 Web 浏览器等访问 Web 页面时需要输入的网页地址。 + +#### 1.5.1、URI(统一资源标识符) + +URI 是 Uniform Resource Identifier 的缩写。 + +> Uniform +> +> 规定统一的格式可方便处理多种不同类型的资源,而不用根据上下文环境来识别资源指定的访问方式。另外,加入新增的协议方案(如http: 或 ftp: )也更容易。 +> +> Resource +> +> 资源的定义是 “ 可标识的任何东西 ” 。除了文档文件、图像或服务(例如当天的天气预报)等能够区别于其他类型的,全都可作为资源。另外,资源不仅可以是单一的,也可以是多数的集合体。 +> +> Identifier +> +> 表示可标识的对象。也称为标识符。 + + **URI 就是由某个协议方案表示的资源的定位标识符**。协议方案是指访问资源所使用的协议类型名称。采用 HTTP 协议时,协议方案就是 http 。除此之外,还有 `ftp 、mailto 、 telnet 、 file` 等。标准的 URI 协议方案有 30 种左右。 + +**URI 用字符串标识某一互联网资源,而 URL 表示资源的地点(互联网上所处的位置)。可见 URL 是 URI 的子集**。 +" RFC3986:统一资源标识符( URI )通用语法 " 中列举了几种 URI 例子,如下所示。 + +```c +ftp://ftp.is.co.za/rfc/rfc1808.txt +http://www.ietf.org/rfc/rfc2396.txt +ldap://[2001:db8::7]/c=GB?objectClass?one +mailto:John.Doe@example.com +news:comp.infosystems.www.servers.unix +tel:+1-816-555-1212 +telnet://192.0.2.16:80/ +urn:oasis:names:specification:docbook:dtd:xml:4.1.2 +``` + +#### 1.5.2、URI格式 + +表示指定的 URI ,要使用涵盖全部必要信息的绝对 URI 、绝对 URL 以及相对 URL 。相对 URL ,是指从浏览器中基本 URI 处指定的 URL ,形如` /image/logo.gif` 。 + +绝对 URI 的格式如下: + +![](images/http/9_.png) + +使用 `http: `或` https: `等协议方案名获取访问资源时要指定协议类型。不区分字母大小写,最后附一个冒号(` :` )。也可使用 data: 或 javascript: 这类指定数据或脚本程序的方案名。 + +* 登录信息(认证): 指定用户名和密码作为从服务器端获取资源时必要的登录信息(身份认证)。此项是可选项。 +* 服务器地址 : **使用绝对 URI 必须指定待访问的服务器地址**。地址可以是类似`baidu.com` 这种 DNS 可解析的名称,或是 192.168.1.1 这类 IPv4 地址名,还可以是 `[0:0:0:0:0:0:0:1]` 这样用方括号括起来的 IPv6 地址名。 +* 服务器端口号 : **指定服务器连接的网络端口号**。此项也是可选项,若用户省略则自动使用默认端口号。 +* 带层次的文件路径 : 指定服务器上的文件路径来定位特指的资源。这与 UNIX 系统的文件目录结构相似。 +* 查询字符串 : 针对已指定的文件路径内的资源,可以使用查询字符串传入任意参数。此项可选。 +* 片段标识符 : 使用片段标识符通常可标记出已获取资源中的子资源(文档内的某个位置)。但在 RFC 中并没有明确规定其使用方法。该项也为可选项。 + +## 2、简单的HTTP协议 + +### 2.1、Http是不保存状态的协议 + +HTTP 是一种不保存状态,即无状态( stateless )协议。 HTTP 协议自身不对请求和响应之间的通信状态进行保存。也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理。 \ No newline at end of file diff --git a/Basics/NetWork/Other.md b/Basics/NetWork/Other.md new file mode 100644 index 00000000..5cb2d495 --- /dev/null +++ b/Basics/NetWork/Other.md @@ -0,0 +1,399 @@ +# 常见问题 + +## 一、TCP、UDP协议的区别 + +**UDP 在传送数据之前不需要先建立连接**,远地主机在收到 UDP 报文后,**不需要给出任何确认**。 + +虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等。 + +TCP 提供面向连接的服务。**在传送数据之前必须先建立连接,数据传送结束后要释放连接**。 + +TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的运输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有**确认、窗口、重传、拥塞控制机制**,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。 + +这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 + +![xin_1.png](images/xin_1.png) + +## 二、从输入网址到获得页面的网络请求过程 + +简单版: + +* **查询DNS(域名解析),获取域名对应的IP地址**; +* **浏览器获得域名对应的IP地址后,浏览器与服务器建立tcp连接(三次握手)**; +* **TCP/IP连接建立起来后,浏览器就可以向服务器发送HTTP请求了**。**(请求和传输数据)** +* **服务器接受到这个请求后,根据路径参数,经过后端的一些处理生成html页面代码返回给浏览器**;(处理请求并返回HTTP报文) +* **浏览器拿到完整的html页面代码开始解析和渲染,如果遇到引用的外部css、图片等静态资源,他们同样也是一个个htpp请求,重复上面的步骤**。 +* **浏览器根据拿到的资源对页面进行渲染,最终把一个完整的页面呈现给用户**。 + +复杂版: + +- 查询 DNS + + - 浏览器搜索**自身的DNS缓存**,操作系统的DNS缓存,本地host文件查询 + - 如果 **DNS 服务器和我们的主机在同一个子网内**,系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询 + - 如果 DNS 服务器和我们的主机在不同的子网,系统会按照下面的 ARP 过程对默认网关进行查询 + +- **浏览器获得域名对应的IP地址后,发起HTTP三次握手**。 + +- **TCP/IP连接建立起来后,浏览器就可以向服务器发送HTTP请求了**。 + +- TLS 握手 + + - 客户端发送一个 `ClientHello` 消息到服务器端,消息中同时包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和压缩算法。 + - 服务器端向客户端返回一个 `ServerHello` 消息,消息中包含了服务器端的TLS版本,服务器所选择的加密和压缩算法,以及数字证书认证机构(Certificate Authority,缩写 CA)签发的服务器公开证书,证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程,直到协商生成一个新的对称密钥 + - 客户端根据自己的信任CA列表,验证服务器端的证书是否可信。如果认为可信,客户端会生成一串伪随机数,使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥 + - 服务器端使用自己的私钥解密上面提到的随机数,然后使用这串随机数生成自己的对称主密钥 + - 客户端发送一个 `Finished` 消息给服务器端,使用对称密钥加密这次通讯的一个散列值 + - 服务器端生成自己的 hash 值,然后解密客户端发送来的信息,检查这两个值是否对应。如果对应,就向客户端发送一个 `Finished` 消息,也使用协商好的对称密钥加密 + - 从现在开始,接下来整个 TLS 会话都使用对称秘钥进行加密,传输应用层(HTTP)内容 + +- HTTP 服务器请求处理 + + HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。 + + - HTTPD 接收请求 + + - - 服务器把请求拆分为以下几个参数: + + HTTP 请求方法(`GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, 或者 `TRACE`)。直接在地址栏中输入 URL 这种情况下,使用的是 GET 方法域名:google.com请求路径/页面:/ (我们没有请求google.com下的指定的页面,因此 / 是默认的路径) + + - 服务器验证其上已经配置了 google.com 的虚拟主机 + + - 服务器验证 google.com 接受 GET 方法 + + - 服务器验证该用户可以使用 GET 方法(根据 IP 地址,身份信息等) + + - 如果服务器安装了 URL 重写模块(例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite),服务器会尝试匹配重写规则,如果匹配上的话,服务器会按照规则重写这个请求 + + - 服务器根据请求信息获取相应的响应内容,这种情况下由于访问路径是 "/" ,会访问首页文件(你可以重写这个规则,但是这个是最常用的)。 + + - 服务器会使用指定的处理程序分析处理这个文件,假如 Google 使用 PHP,服务器会使用 PHP 解析 index 文件,并捕获输出,把 PHP 的输出结果返回给请求者 + +- 服务器接受到这个请求,根据路径参数,经过后端的一些处理生成HTML页面代码返回给浏览器 + +- 浏览器拿到完整的HTML页面代码开始解析和渲染,如果遇到引用的外部[js](http://lib.csdn.net/base/javascript),CSS,图片等静态资源,它们同样也是一个个的HTTP请求,都需要经过上面的步骤 + +- 浏览器根据拿到的资源对页面进行渲染,最终把一个完整的页面呈现给用户 + +## 三、GET和POST区别 + +- GET 被强制服务器支持 +- 浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据 +- **GET请求发送数据更小** +- **GET请求是不安全的** +- GET请求是幂等的 + - 幂等的意味着对同一URL的多个请求应该返回同样的结果 +- **POST请求不能被缓存** +- **POST请求相对GET请求是「安全」的** + - 这里安全的含义仅仅是指是非修改信息 +- GET用于信息获取,而且是安全的和幂等的 + - 所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。 +- **POST是用于修改服务器上的资源的请求** +- **发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠** +- **GET 参数通过 URL 传递,并且长度有限制,而 POST 放在 request body 并且长度没有限制**。并且,正因为这个原因, GET 比 POST 更不安全,因为参数暴露在 URL 中。 +- GET 产生一个 TCP 数据包,而 POST 产生两个 TCP 数据包。 + +**引申:说完原理性的问题,我们从表面上来看看GET和POST的区别:** + +- **GET是从服务器上获取数据,POST是向服务器传送数据**。 GET和 POST只是一种传递数据的方式,GET也可以把数据传到服务器,他们的本质都是发送请求和接收结果。只是组织格式和数据量上面有差别,http协议里面有介绍 +- GET是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。POST是通过HTTP POST机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。 **因为GET设计成传输小数据,而且最好是不修改服务器的数据,所以浏览器一般都在地址栏里面可以看到,但POST一般都用来传递大数据,或比较隐私的数据,所以在地址栏看不到**,能不能看到不是协议规定,是浏览器规定的。 +- 对于GET方式,服务器端用Request.QueryString获取变量的值,对于POST方式,服务器端用Request.Form获取提交的数据。 没明白,怎么获得变量和你的服务器有关,和GET或POST无关,服务器都对这些请求做了封装 +- GET传送的数据量较小,不能大于2KB。POST传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。 POST基本没有限制,我想大家都上传过文件,都是用POST方式的。只不过要修改form里面的那个type参数 +- GET安全性非常低,POST安全性较高。 如果没有加密,他们安全级别都是一样的,随便一个监听器都可以把所有的数据监听到。 + +## 四、HTTPS和HTTP区别 + +- http是HTTP协议运行在TCP之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。 +- https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上。**所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密**。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份。 +- https协议需要到ca申请证书,一般免费证书很少,需要交费。 +- **http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议** +- **http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443**。 +- http的连接很简单,是无状态的 +- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全。 + +## 五、如果已经建立了连接,但是客户端突然出现故障了怎么办? + +TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。 + +**服务器每收到一次客户端的请求后都会重新复位这个计时器**,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个**探测报文段**,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。 + +## 六、Cookie和Session + +cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式 + +session机制。session机制是一种服务器端的机制,服务器使用一种类似于散列表(`ConcurrentHashMap`)的结构来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为session id),如果已包含则说明以前已经为此客户端创建过session,服务器就按照session id把这个session检索出来使用。如果客户端请求不包含session id,则为此客户端创建一个session并且生成一个与此session相关联的session id,session id的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,**这个session id将被在本次响应中返回给客户端保存**。**保存这个session id的方式可以采用cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器**。一般这个cookie的名字都是类似于SEEESIONID。但cookie可以被人为的禁止,则必须有其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。 + +![1557016047435](assets/1557016047435.png) + +**cookie 和session 的区别:** + +**1、cookie数据存放在客户的浏览器上,session数据放在服务器上。** + +**2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗** + 考虑到安全应当使用session。 + +**3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能** + 考虑到减轻服务器性能方面,应当使用COOKIE。 + +**4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。** + +**5、所以个人建议:** + 将登陆信息等重要信息存放为SESSION + **其他信息如果需要保留,可以放在COOKIE中** + + + + + +## 七、HTTP1.0和 HTTP1.1的区别 + +HTTP1.0默认短连接,可以长连接,但是需要设置header connection:keep_Alive +HTTP1.1默认长连接 + +## 八、 HTTP1.0, HTTP1.1和 HTTP2.0的区别 + +HTTP2.0支持二进制传输数据,更加安全快捷,而 HTTP1.0, HTTP1.1支持文本 +HTTP2.0实现多路复用,更加快捷 +HTTP2.0压缩header +HTTP2.0支持服务“主动”给客户端缓存发送数据 + +## 九、什么是长连接、短连接? + +在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。 + +而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码: + +```java +Connection:keep-alive +``` + +在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。 + +HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。 + +## 十、Servlet 的生存周期 + +• Servlet 接口定义了 5 个方法,其中前三个方法与 Servlet 生命周期相关: +○ -void init(ServletConfig config) throws ServletException +○ void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException +○ void destory() +○ java.lang.String getServletInfo() +○ ServletConfig getServletConfig() +• Web 容器加载 Servlet 并将其实例化后, Servlet 生命周期开始,容器运行其 init() 方法进行 Servlet 的初始化;请求到达时调用 +Servlet 的 service() 方法, service() 方法会根据需要调用与请求对应的 doGet 或 doPost 等方法;当服务器关闭或项目被卸载时 +服务器会将 Servlet 实例销毁,此时会调用 Servlet 的 destroy() 方法。 + +## 十一、 Jsp 和 Servlet 的区别 + +• Servlet 是一个特殊的 Java 程序,它运行于服务器的 JVM 中,能够依靠服务器的支持向浏览器提供显示内容。 JSP 本质上是 +Servlet 的一种简易形式, JSP 会被服务器处理成一个类似于 Servlet 的 Java 程序,可以简化页面内容的生成。 Servlet 和 JSP 最 +主要的不同点在于, Servlet 的应用逻辑是在 Java 文件中,并且完全从表示层中的 HTML 分离开来。而 JSP 的情况是 Java 和 +HTML 可以组合成一个扩展名为 . +jsp 的文件。有人说, Servlet 就是在 Java 中写 HTML ,而 JSP 就是在 HTML 中写 Java 代码,当 +然这个说法是很片面且不够准确的。 JSP 侧重于视图, Servlet 更侧重于控制逻辑,在 MVC 架构模式中, JSP 适合充当视图 +( view )而 Servlet 适合充当控制器 + +## 十二、保存会话状态,有哪些方式、区别如何 + +• 由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记, +为用户分配唯一的 ID ,下一次用户在请求中包含此 ID ,服务器据此判断到底是哪一个用户。 +○ ① URL 重写:在 URL 中添加用户会话的信息作为请求的参数,或者将唯一的会话 ID 添加到 URL 结尾以标识一个会话。 +○ ② 设置表单隐藏域:将和会话跟踪相关的字段添加到隐式表单域中,这些信息不会在浏览器中显示但是提交表单时会提 +交给服务器。 +这两种方式很难处理跨越多个页面的信息传递,因为如果每次都要修改 URL 或在页面中添加隐式表单域来存储用户会话相关 +信息,事情将变得非常麻烦。 +○ ③ ** 补充: **HTML5 中可以使用 Web Storage 技术通过 JavaScript 来保存数据,例如可以使用 localStorage 和 +sessionStorage 来保存用户会话的信息,也能够实现会话跟踪。 + +## 十三、HTTP状态 + +服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。 + +| 状态码 | 类别 | 原因短语 | +| ------ | -------------------------------- | -------------------------- | +| 1XX | Informational(信息性状态码) | 接收的请求正在处理 | +| 2XX | Success(成功状态码) | 请求正常处理完毕 | +| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 | +| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 | +| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 | + +### (1)1XX 信息 + +- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。 + +### (2)2XX 成功 + +- **200 OK** +- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 +- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。 + +### (3)3XX 重定向 + +- **301 Moved Permanently** :永久性重定向 +- **302 Found** :临时性重定向 +- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。 +- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 +- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。 +- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。 + +### (4)4XX 客户端错误 + +- **400 Bad Request** :请求报文中存在语法错误。 +- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 +- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。 +- **404 Not Found** + +### (5)5XX 服务器错误 + +- **500 Internal Server Error** :服务器正在执行请求时发生错误。 +- **503 Service Unavailable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 + +## 十四、 HTTP方法 + +客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。 + +### (1)GET + +> 获取资源 + +当前网络请求中,绝大部分使用的是 GET 方法。 + +### (2)HEAD + +> 获取报文首部 + +和 GET 方法一样,但是不返回报文实体主体部分。 + +主要用于确认 URL 的有效性以及资源更新的日期时间等。 + +### (3)POST + +> 传输实体主体 + +POST 主要用来传输数据,而 GET 主要用来获取资源。 + +更多 POST 与 GET 的比较请见第八章。 + +### (4)PUT + +> 上传文件 + +由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。 + +``` +PUT /new.html HTTP/1.1 +Host: example.com +Content-type: text/html +Content-length: 16 + +

New File

+``` + +### (5)PATCH + +> 对资源进行部分修改 + +PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。 + +``` +PATCH /file.txt HTTP/1.1 +Host: www.example.com +Content-Type: application/example +If-Match: "e0023aa4e" +Content-Length: 100 + +[description of changes] +``` + +### (6)DELETE + +> 删除文件 + +与 PUT 功能相反,并且同样不带验证机制。 + +``` +DELETE /file.html HTTP/1.1 +``` + +### (7)OPTIONS + +> 查询支持的方法 + +查询指定的 URL 能够支持的方法。 + +会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。 + +### (8)CONNECT + +> 要求在与代理服务器通信时建立隧道 + +使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。 + +## 十五、udp如何实现可靠性传输? + +​ UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。 + +​ 传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。 + +​ 实现确认机制、重传机制、窗口确认机制。 + +​ 如果你不利用[Linux](http://lib.csdn.net/base/linux)协议栈以及上层socket机制,自己通过抓包和发包的方式去实现可靠性传输,那么必须实现如下功能: + +​ 发送:包的分片、包确认、包的重发 + +​ 接收:包的调序、包的序号确认 + +​ 目前有如下开源程序利用udp实现了可靠的数据传输。分别为RUDP、RTP、UDT。 + +### RUDP + +RUDP 提供一组数据服务质量增强机制,如拥塞控制的改进、重发机制及淡化服务器算法等,从而在包丢失和网络拥塞的情况下, RTP 客户机(实时位置)面前呈现的就是一个高质量的 RTP 流。在不干扰协议的实时特性的同时,可靠 UDP 的拥塞控制机制允许 TCP 方式下的流控制行为。 + +### RTP + +​ 实时传输协议(RTP)为数据提供了具有实时特征的端对端传送服务,如在组播或单播网络服务下的交互式视频音频或模拟数据。应用程序通常在 UDP 上运行 RTP 以便使用其多路结点和校验服务;这两种协议都提供了传输层协议的功能。但是 RTP 可以与其它适合的底层网络或传输协议一起使用。如果底层网络提供组播方式,那么 RTP 可以使用该组播表传输数据到多个目的地。 + +RTP 本身并没有提供按时发送机制或其它服务质量(QoS)保证,它依赖于底层服务去实现这一过程。 RTP 并不保证传送或防止无序传送,也不确定底层网络的可靠性。 RTP 实行有序传送, RTP 中的序列号允许接收方重组发送方的包序列,同时序列号也能用于决定适当的包位置,例如:在视频解码中,就不需要顺序解码。 + +### UDT + +​ 基于UDP的数据传输协议(UDP-basedData Transfer Protocol,简称UDT)是一种互联网数据传输协议。UDT的主要目的是支持高速广域网上的海量数据传输,而互联网上的标准数据传输协议TCP在高带宽长距离网络上性能很差。顾名思义,UDT建于UDP之上,并引入新的拥塞控制和数据可靠性控制机制。UDT是面向连接的双向的应用层协议。它同时支持可靠的数据流传输和部分可靠的数据报传输。由于UDT完全在UDP上实现,它也可以应用在除了高速数据传输之外的其它应用领域,例如点到点技术(P2P),防火墙穿透,多媒体数据传输等等。 + +​ 因项目中的需要,现在详细分析一下UDT是如何通过udp实现数据的可靠传输。通过阅读源码的方式。 + + + + + +## 十六、短连接与长连接 + +通俗来讲,浏览器和服务器每进行一次通信,就建立一次连接,任务结束就中断连接,即短连接。 + +相反地,假如通信结束(如完成了某个HTML文件的信息获取)后保持连接则为长连接。 + +在HTTP/1.0中,默认使用短连接。从HTTP/1.1起,默认使用长连接,这样做的优点是显而易见的,一个网页的加载可能需要HTML文件和多个CSS或者JS,假如每获取一个静态文件都建立一次连接,那么就太浪费时间了,而在保持连接的情况下,继续GET即可。 + +对于频繁请求资源的客户来说,较适用长连接。但连接数最好进行限制,防止建立太多连接拖累服务端。一般浏览器对一个网站的连接是有限制的几个,所以网站会将资源部署在多个域名上以实现浏览器同时请求。 + +有人常说HTTP的短连接和长连接如何如何,但是HTTP只是一个应用层协议,又是无状态的,最终实质性的保持连接还是得靠传输层,即TCP。 + +举个例子,NginX作为代理的一种常见配置方式是在NginX与客户端之间的连接使用长连接,NginX与后端服务器之间的连接使用短连接。 + + + +长连接是为了复用。那既然长连接是指的TCP连接,也就是说复用的是TCP连接。那这就很好解释了,也就是说,**长连接情况下,多个HTTP请求可以复用同一个TCP连接,这就节省了很多TCP连接建立和断开的消耗**。 + +比如你请求了博客园的一个网页,这个网页里肯定还包含了CSS、JS等等一系列资源,如果你是短连接(也就是每次都要重新建立TCP连接)的话,那你每打开一个网页,基本要建立几个甚至几十个TCP连接,这浪费了多少资源就不用LZ去说了吧。 + +但如果是长连接的话,**那么这么多次HTTP请求(这些请求包括请求网页内容,CSS文件,JS文件,图片等等),其实使用的都是一个TCP连接,很显然是可以节省很多消耗的**。 + +## 十七、keep-alive + +我们使用浏览器的开发者工具查看网络请求和响应信息时经常在HTTP请求头部看到Connection: keep-alive,一般的浏览器都会带着个头去请求数据,假如有特殊需求可以用Connection: close断开。HTTP头部的Connection也不一定就被客户端或服务端老老实实地遵循,毕竟各有各的考虑,尤其是在HTTP/1.0这还只是个实验性的功能,而在HTTP/1.1默认长连接于是没有对长连接做特殊的规定。 + +**长连接也不能无限期地长,服务端有可能在头部放Keep-Alive,其中timeout等于一个值来规定保持连接的秒数,还可以用max来规定多少次请求后断开。如果没有说明怎么断开,主动发起四次握手也可以实现连接的断开**。 + +现在有一个问题就是HTTP的keep-alive与TCP的keep-alive到底是什么关系。其实这是两种不同的机制,可以认为没有什么关系。 + +* HTTP在头部的Connection中声明keep-alive可以告诉对方要长连接不立即断开。 +* 但是TCP的keep-alive则是一种检查对方是否仍旧和自己保持着连接的机制以避免自作多情半开放的连接。假如发出一个探测段,成功收到响应,这证明连接正常保持;假如发出一个探测段一段时间后,一个响应都没收到,对方可能已挂断、机器异常或网络异常;假如对方收到探测段但重置,说明原来的连接已经因为某些原因挂断,目前是因为未进行三次握手新建立连接而被挂断。 \ No newline at end of file diff --git a/Basics/NetWork/assets/1557016047435.png b/Basics/NetWork/assets/1557016047435.png new file mode 100644 index 00000000..f00553ad Binary files /dev/null and b/Basics/NetWork/assets/1557016047435.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_1.png" b/Basics/NetWork/images/1_1.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_1.png" rename to Basics/NetWork/images/1_1.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_10.png" b/Basics/NetWork/images/1_10.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_10.png" rename to Basics/NetWork/images/1_10.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_11.png" b/Basics/NetWork/images/1_11.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_11.png" rename to Basics/NetWork/images/1_11.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_12.png" b/Basics/NetWork/images/1_12.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_12.png" rename to Basics/NetWork/images/1_12.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_13.png" b/Basics/NetWork/images/1_13.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_13.png" rename to Basics/NetWork/images/1_13.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_14.png" b/Basics/NetWork/images/1_14.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_14.png" rename to Basics/NetWork/images/1_14.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_15.png" b/Basics/NetWork/images/1_15.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_15.png" rename to Basics/NetWork/images/1_15.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_16.png" b/Basics/NetWork/images/1_16.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_16.png" rename to Basics/NetWork/images/1_16.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_17.png" b/Basics/NetWork/images/1_17.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_17.png" rename to Basics/NetWork/images/1_17.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_18.png" b/Basics/NetWork/images/1_18.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_18.png" rename to Basics/NetWork/images/1_18.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_19.png" b/Basics/NetWork/images/1_19.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_19.png" rename to Basics/NetWork/images/1_19.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_2.png" b/Basics/NetWork/images/1_2.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_2.png" rename to Basics/NetWork/images/1_2.png diff --git a/Basics/NetWork/images/1_20.png b/Basics/NetWork/images/1_20.png new file mode 100644 index 00000000..29b5467c Binary files /dev/null and b/Basics/NetWork/images/1_20.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_21.png" b/Basics/NetWork/images/1_21.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_21.png" rename to Basics/NetWork/images/1_21.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_22.png" b/Basics/NetWork/images/1_22.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_22.png" rename to Basics/NetWork/images/1_22.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_23.png" b/Basics/NetWork/images/1_23.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_23.png" rename to Basics/NetWork/images/1_23.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_24.png" b/Basics/NetWork/images/1_24.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_24.png" rename to Basics/NetWork/images/1_24.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_25.png" b/Basics/NetWork/images/1_25.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_25.png" rename to Basics/NetWork/images/1_25.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_26.png" b/Basics/NetWork/images/1_26.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_26.png" rename to Basics/NetWork/images/1_26.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_27.png" b/Basics/NetWork/images/1_27.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_27.png" rename to Basics/NetWork/images/1_27.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_28.png" b/Basics/NetWork/images/1_28.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_28.png" rename to Basics/NetWork/images/1_28.png diff --git a/Basics/NetWork/images/1_29.png b/Basics/NetWork/images/1_29.png new file mode 100644 index 00000000..3da5c9a2 Binary files /dev/null and b/Basics/NetWork/images/1_29.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_3.png" b/Basics/NetWork/images/1_3.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_3.png" rename to Basics/NetWork/images/1_3.png diff --git a/Basics/NetWork/images/1_30.png b/Basics/NetWork/images/1_30.png new file mode 100644 index 00000000..a924a47c Binary files /dev/null and b/Basics/NetWork/images/1_30.png differ diff --git a/Basics/NetWork/images/1_31.png b/Basics/NetWork/images/1_31.png new file mode 100644 index 00000000..7311fa30 Binary files /dev/null and b/Basics/NetWork/images/1_31.png differ diff --git a/Basics/NetWork/images/1_32.png b/Basics/NetWork/images/1_32.png new file mode 100644 index 00000000..8f7b7908 Binary files /dev/null and b/Basics/NetWork/images/1_32.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_4.png" b/Basics/NetWork/images/1_4.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_4.png" rename to Basics/NetWork/images/1_4.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_5.png" b/Basics/NetWork/images/1_5.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_5.png" rename to Basics/NetWork/images/1_5.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_6.png" b/Basics/NetWork/images/1_6.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_6.png" rename to Basics/NetWork/images/1_6.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_7.png" b/Basics/NetWork/images/1_7.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_7.png" rename to Basics/NetWork/images/1_7.png diff --git a/Basics/NetWork/images/1_8.png b/Basics/NetWork/images/1_8.png new file mode 100644 index 00000000..17b8e6b5 Binary files /dev/null and b/Basics/NetWork/images/1_8.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_9.png" b/Basics/NetWork/images/1_9.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/1_9.png" rename to Basics/NetWork/images/1_9.png diff --git a/Basics/NetWork/images/2_1.png b/Basics/NetWork/images/2_1.png new file mode 100644 index 00000000..c0ebf7bb Binary files /dev/null and b/Basics/NetWork/images/2_1.png differ diff --git a/Basics/NetWork/images/2_10.png b/Basics/NetWork/images/2_10.png new file mode 100644 index 00000000..f761bae8 Binary files /dev/null and b/Basics/NetWork/images/2_10.png differ diff --git a/Basics/NetWork/images/2_2.png b/Basics/NetWork/images/2_2.png new file mode 100644 index 00000000..212574c9 Binary files /dev/null and b/Basics/NetWork/images/2_2.png differ diff --git a/Basics/NetWork/images/2_3.png b/Basics/NetWork/images/2_3.png new file mode 100644 index 00000000..29d1d499 Binary files /dev/null and b/Basics/NetWork/images/2_3.png differ diff --git a/Basics/NetWork/images/2_4.png b/Basics/NetWork/images/2_4.png new file mode 100644 index 00000000..03868807 Binary files /dev/null and b/Basics/NetWork/images/2_4.png differ diff --git a/Basics/NetWork/images/2_5.png b/Basics/NetWork/images/2_5.png new file mode 100644 index 00000000..a1b7fbc9 Binary files /dev/null and b/Basics/NetWork/images/2_5.png differ diff --git a/Basics/NetWork/images/2_6.png b/Basics/NetWork/images/2_6.png new file mode 100644 index 00000000..8de65d97 Binary files /dev/null and b/Basics/NetWork/images/2_6.png differ diff --git a/Basics/NetWork/images/2_7.png b/Basics/NetWork/images/2_7.png new file mode 100644 index 00000000..6eabc900 Binary files /dev/null and b/Basics/NetWork/images/2_7.png differ diff --git a/Basics/NetWork/images/2_8.png b/Basics/NetWork/images/2_8.png new file mode 100644 index 00000000..744028f9 Binary files /dev/null and b/Basics/NetWork/images/2_8.png differ diff --git a/Basics/NetWork/images/2_9.png b/Basics/NetWork/images/2_9.png new file mode 100644 index 00000000..c40135aa Binary files /dev/null and b/Basics/NetWork/images/2_9.png differ diff --git a/Basics/NetWork/images/3_1.png b/Basics/NetWork/images/3_1.png new file mode 100644 index 00000000..1589bc7a Binary files /dev/null and b/Basics/NetWork/images/3_1.png differ diff --git a/Basics/NetWork/images/3_10.png b/Basics/NetWork/images/3_10.png new file mode 100644 index 00000000..345093e1 Binary files /dev/null and b/Basics/NetWork/images/3_10.png differ diff --git a/Basics/NetWork/images/3_11.png b/Basics/NetWork/images/3_11.png new file mode 100644 index 00000000..b834136d Binary files /dev/null and b/Basics/NetWork/images/3_11.png differ diff --git a/Basics/NetWork/images/3_12.png b/Basics/NetWork/images/3_12.png new file mode 100644 index 00000000..5687ae61 Binary files /dev/null and b/Basics/NetWork/images/3_12.png differ diff --git a/Basics/NetWork/images/3_13.png b/Basics/NetWork/images/3_13.png new file mode 100644 index 00000000..2245958a Binary files /dev/null and b/Basics/NetWork/images/3_13.png differ diff --git a/Basics/NetWork/images/3_14.png b/Basics/NetWork/images/3_14.png new file mode 100644 index 00000000..616d3cf1 Binary files /dev/null and b/Basics/NetWork/images/3_14.png differ diff --git a/Basics/NetWork/images/3_15.png b/Basics/NetWork/images/3_15.png new file mode 100644 index 00000000..a3f6e956 Binary files /dev/null and b/Basics/NetWork/images/3_15.png differ diff --git a/Basics/NetWork/images/3_16.png b/Basics/NetWork/images/3_16.png new file mode 100644 index 00000000..7639f5d5 Binary files /dev/null and b/Basics/NetWork/images/3_16.png differ diff --git a/Basics/NetWork/images/3_17.png b/Basics/NetWork/images/3_17.png new file mode 100644 index 00000000..7e139531 Binary files /dev/null and b/Basics/NetWork/images/3_17.png differ diff --git a/Basics/NetWork/images/3_18.png b/Basics/NetWork/images/3_18.png new file mode 100644 index 00000000..b8cea0d8 Binary files /dev/null and b/Basics/NetWork/images/3_18.png differ diff --git a/Basics/NetWork/images/3_19.png b/Basics/NetWork/images/3_19.png new file mode 100644 index 00000000..327457ae Binary files /dev/null and b/Basics/NetWork/images/3_19.png differ diff --git a/Basics/NetWork/images/3_2.png b/Basics/NetWork/images/3_2.png new file mode 100644 index 00000000..11a63054 Binary files /dev/null and b/Basics/NetWork/images/3_2.png differ diff --git a/Basics/NetWork/images/3_20.png b/Basics/NetWork/images/3_20.png new file mode 100644 index 00000000..d8f1ca19 Binary files /dev/null and b/Basics/NetWork/images/3_20.png differ diff --git a/Basics/NetWork/images/3_21.png b/Basics/NetWork/images/3_21.png new file mode 100644 index 00000000..470903b6 Binary files /dev/null and b/Basics/NetWork/images/3_21.png differ diff --git a/Basics/NetWork/images/3_22.png b/Basics/NetWork/images/3_22.png new file mode 100644 index 00000000..fbe288db Binary files /dev/null and b/Basics/NetWork/images/3_22.png differ diff --git a/Basics/NetWork/images/3_23.png b/Basics/NetWork/images/3_23.png new file mode 100644 index 00000000..b4975f88 Binary files /dev/null and b/Basics/NetWork/images/3_23.png differ diff --git a/Basics/NetWork/images/3_24.png b/Basics/NetWork/images/3_24.png new file mode 100644 index 00000000..134eb5da Binary files /dev/null and b/Basics/NetWork/images/3_24.png differ diff --git a/Basics/NetWork/images/3_25.png b/Basics/NetWork/images/3_25.png new file mode 100644 index 00000000..e730acac Binary files /dev/null and b/Basics/NetWork/images/3_25.png differ diff --git a/Basics/NetWork/images/3_26.png b/Basics/NetWork/images/3_26.png new file mode 100644 index 00000000..e516b7fb Binary files /dev/null and b/Basics/NetWork/images/3_26.png differ diff --git a/Basics/NetWork/images/3_27.png b/Basics/NetWork/images/3_27.png new file mode 100644 index 00000000..ebc7e67d Binary files /dev/null and b/Basics/NetWork/images/3_27.png differ diff --git a/Basics/NetWork/images/3_28.png b/Basics/NetWork/images/3_28.png new file mode 100644 index 00000000..ede8dd74 Binary files /dev/null and b/Basics/NetWork/images/3_28.png differ diff --git a/Basics/NetWork/images/3_29.png b/Basics/NetWork/images/3_29.png new file mode 100644 index 00000000..dbabb8f1 Binary files /dev/null and b/Basics/NetWork/images/3_29.png differ diff --git a/Basics/NetWork/images/3_3.png b/Basics/NetWork/images/3_3.png new file mode 100644 index 00000000..05baddbf Binary files /dev/null and b/Basics/NetWork/images/3_3.png differ diff --git a/Basics/NetWork/images/3_30.png b/Basics/NetWork/images/3_30.png new file mode 100644 index 00000000..cef59294 Binary files /dev/null and b/Basics/NetWork/images/3_30.png differ diff --git a/Basics/NetWork/images/3_31.png b/Basics/NetWork/images/3_31.png new file mode 100644 index 00000000..eb1387c5 Binary files /dev/null and b/Basics/NetWork/images/3_31.png differ diff --git a/Basics/NetWork/images/3_32.png b/Basics/NetWork/images/3_32.png new file mode 100644 index 00000000..ac98e1d8 Binary files /dev/null and b/Basics/NetWork/images/3_32.png differ diff --git a/Basics/NetWork/images/3_33.png b/Basics/NetWork/images/3_33.png new file mode 100644 index 00000000..60640fb0 Binary files /dev/null and b/Basics/NetWork/images/3_33.png differ diff --git a/Basics/NetWork/images/3_34.png b/Basics/NetWork/images/3_34.png new file mode 100644 index 00000000..203ede19 Binary files /dev/null and b/Basics/NetWork/images/3_34.png differ diff --git a/Basics/NetWork/images/3_35.png b/Basics/NetWork/images/3_35.png new file mode 100644 index 00000000..1c915538 Binary files /dev/null and b/Basics/NetWork/images/3_35.png differ diff --git a/Basics/NetWork/images/3_36.png b/Basics/NetWork/images/3_36.png new file mode 100644 index 00000000..f094fdd3 Binary files /dev/null and b/Basics/NetWork/images/3_36.png differ diff --git a/Basics/NetWork/images/3_37.png b/Basics/NetWork/images/3_37.png new file mode 100644 index 00000000..dbed8b3f Binary files /dev/null and b/Basics/NetWork/images/3_37.png differ diff --git a/Basics/NetWork/images/3_38.png b/Basics/NetWork/images/3_38.png new file mode 100644 index 00000000..55c099d0 Binary files /dev/null and b/Basics/NetWork/images/3_38.png differ diff --git a/Basics/NetWork/images/3_4.png b/Basics/NetWork/images/3_4.png new file mode 100644 index 00000000..fea4a649 Binary files /dev/null and b/Basics/NetWork/images/3_4.png differ diff --git a/Basics/NetWork/images/3_5.png b/Basics/NetWork/images/3_5.png new file mode 100644 index 00000000..fff80739 Binary files /dev/null and b/Basics/NetWork/images/3_5.png differ diff --git a/Basics/NetWork/images/3_6.png b/Basics/NetWork/images/3_6.png new file mode 100644 index 00000000..360b6444 Binary files /dev/null and b/Basics/NetWork/images/3_6.png differ diff --git a/Basics/NetWork/images/3_7.png b/Basics/NetWork/images/3_7.png new file mode 100644 index 00000000..f58f6a44 Binary files /dev/null and b/Basics/NetWork/images/3_7.png differ diff --git a/Basics/NetWork/images/3_8.png b/Basics/NetWork/images/3_8.png new file mode 100644 index 00000000..1809778b Binary files /dev/null and b/Basics/NetWork/images/3_8.png differ diff --git a/Basics/NetWork/images/3_9.png b/Basics/NetWork/images/3_9.png new file mode 100644 index 00000000..989eb095 Binary files /dev/null and b/Basics/NetWork/images/3_9.png differ diff --git a/Basics/NetWork/images/4_1.png b/Basics/NetWork/images/4_1.png new file mode 100644 index 00000000..80d9a8c7 Binary files /dev/null and b/Basics/NetWork/images/4_1.png differ diff --git a/Basics/NetWork/images/4_10.png b/Basics/NetWork/images/4_10.png new file mode 100644 index 00000000..b8de5d18 Binary files /dev/null and b/Basics/NetWork/images/4_10.png differ diff --git a/Basics/NetWork/images/4_11.png b/Basics/NetWork/images/4_11.png new file mode 100644 index 00000000..0e7a573c Binary files /dev/null and b/Basics/NetWork/images/4_11.png differ diff --git a/Basics/NetWork/images/4_12.png b/Basics/NetWork/images/4_12.png new file mode 100644 index 00000000..0bae5fe2 Binary files /dev/null and b/Basics/NetWork/images/4_12.png differ diff --git a/Basics/NetWork/images/4_13.png b/Basics/NetWork/images/4_13.png new file mode 100644 index 00000000..6b9918f3 Binary files /dev/null and b/Basics/NetWork/images/4_13.png differ diff --git a/Basics/NetWork/images/4_14.png b/Basics/NetWork/images/4_14.png new file mode 100644 index 00000000..56f31a35 Binary files /dev/null and b/Basics/NetWork/images/4_14.png differ diff --git a/Basics/NetWork/images/4_15.png b/Basics/NetWork/images/4_15.png new file mode 100644 index 00000000..394b3db9 Binary files /dev/null and b/Basics/NetWork/images/4_15.png differ diff --git a/Basics/NetWork/images/4_16.png b/Basics/NetWork/images/4_16.png new file mode 100644 index 00000000..564e248a Binary files /dev/null and b/Basics/NetWork/images/4_16.png differ diff --git a/Basics/NetWork/images/4_17.png b/Basics/NetWork/images/4_17.png new file mode 100644 index 00000000..b8d13de6 Binary files /dev/null and b/Basics/NetWork/images/4_17.png differ diff --git a/Basics/NetWork/images/4_18.png b/Basics/NetWork/images/4_18.png new file mode 100644 index 00000000..0eb703be Binary files /dev/null and b/Basics/NetWork/images/4_18.png differ diff --git a/Basics/NetWork/images/4_19.png b/Basics/NetWork/images/4_19.png new file mode 100644 index 00000000..5b8a936c Binary files /dev/null and b/Basics/NetWork/images/4_19.png differ diff --git a/Basics/NetWork/images/4_2.png b/Basics/NetWork/images/4_2.png new file mode 100644 index 00000000..5d5a98b7 Binary files /dev/null and b/Basics/NetWork/images/4_2.png differ diff --git a/Basics/NetWork/images/4_20.png b/Basics/NetWork/images/4_20.png new file mode 100644 index 00000000..b3860953 Binary files /dev/null and b/Basics/NetWork/images/4_20.png differ diff --git a/Basics/NetWork/images/4_21.png b/Basics/NetWork/images/4_21.png new file mode 100644 index 00000000..f3dafe00 Binary files /dev/null and b/Basics/NetWork/images/4_21.png differ diff --git a/Basics/NetWork/images/4_22.png b/Basics/NetWork/images/4_22.png new file mode 100644 index 00000000..05ff2b9c Binary files /dev/null and b/Basics/NetWork/images/4_22.png differ diff --git a/Basics/NetWork/images/4_23.png b/Basics/NetWork/images/4_23.png new file mode 100644 index 00000000..01171357 Binary files /dev/null and b/Basics/NetWork/images/4_23.png differ diff --git a/Basics/NetWork/images/4_24.png b/Basics/NetWork/images/4_24.png new file mode 100644 index 00000000..5ce48569 Binary files /dev/null and b/Basics/NetWork/images/4_24.png differ diff --git a/Basics/NetWork/images/4_25.png b/Basics/NetWork/images/4_25.png new file mode 100644 index 00000000..1b08b04c Binary files /dev/null and b/Basics/NetWork/images/4_25.png differ diff --git a/Basics/NetWork/images/4_26.png b/Basics/NetWork/images/4_26.png new file mode 100644 index 00000000..beedfb13 Binary files /dev/null and b/Basics/NetWork/images/4_26.png differ diff --git a/Basics/NetWork/images/4_27.png b/Basics/NetWork/images/4_27.png new file mode 100644 index 00000000..becb46c3 Binary files /dev/null and b/Basics/NetWork/images/4_27.png differ diff --git a/Basics/NetWork/images/4_28.png b/Basics/NetWork/images/4_28.png new file mode 100644 index 00000000..7d64c35f Binary files /dev/null and b/Basics/NetWork/images/4_28.png differ diff --git a/Basics/NetWork/images/4_29.png b/Basics/NetWork/images/4_29.png new file mode 100644 index 00000000..f7866526 Binary files /dev/null and b/Basics/NetWork/images/4_29.png differ diff --git a/Basics/NetWork/images/4_3.png b/Basics/NetWork/images/4_3.png new file mode 100644 index 00000000..f3468200 Binary files /dev/null and b/Basics/NetWork/images/4_3.png differ diff --git a/Basics/NetWork/images/4_30.png b/Basics/NetWork/images/4_30.png new file mode 100644 index 00000000..b165abfa Binary files /dev/null and b/Basics/NetWork/images/4_30.png differ diff --git a/Basics/NetWork/images/4_31.png b/Basics/NetWork/images/4_31.png new file mode 100644 index 00000000..15922e32 Binary files /dev/null and b/Basics/NetWork/images/4_31.png differ diff --git a/Basics/NetWork/images/4_32.png b/Basics/NetWork/images/4_32.png new file mode 100644 index 00000000..46778182 Binary files /dev/null and b/Basics/NetWork/images/4_32.png differ diff --git a/Basics/NetWork/images/4_33.png b/Basics/NetWork/images/4_33.png new file mode 100644 index 00000000..cbab71f5 Binary files /dev/null and b/Basics/NetWork/images/4_33.png differ diff --git a/Basics/NetWork/images/4_34.png b/Basics/NetWork/images/4_34.png new file mode 100644 index 00000000..a476d40f Binary files /dev/null and b/Basics/NetWork/images/4_34.png differ diff --git a/Basics/NetWork/images/4_35.png b/Basics/NetWork/images/4_35.png new file mode 100644 index 00000000..7e8f0fda Binary files /dev/null and b/Basics/NetWork/images/4_35.png differ diff --git a/Basics/NetWork/images/4_36.png b/Basics/NetWork/images/4_36.png new file mode 100644 index 00000000..4f8924a6 Binary files /dev/null and b/Basics/NetWork/images/4_36.png differ diff --git a/Basics/NetWork/images/4_37.png b/Basics/NetWork/images/4_37.png new file mode 100644 index 00000000..82d4801f Binary files /dev/null and b/Basics/NetWork/images/4_37.png differ diff --git a/Basics/NetWork/images/4_38.png b/Basics/NetWork/images/4_38.png new file mode 100644 index 00000000..83e968ba Binary files /dev/null and b/Basics/NetWork/images/4_38.png differ diff --git a/Basics/NetWork/images/4_39.png b/Basics/NetWork/images/4_39.png new file mode 100644 index 00000000..f90abb6f Binary files /dev/null and b/Basics/NetWork/images/4_39.png differ diff --git a/Basics/NetWork/images/4_4.png b/Basics/NetWork/images/4_4.png new file mode 100644 index 00000000..3c3ece70 Binary files /dev/null and b/Basics/NetWork/images/4_4.png differ diff --git a/Basics/NetWork/images/4_40.png b/Basics/NetWork/images/4_40.png new file mode 100644 index 00000000..c36b7a2a Binary files /dev/null and b/Basics/NetWork/images/4_40.png differ diff --git a/Basics/NetWork/images/4_41.png b/Basics/NetWork/images/4_41.png new file mode 100644 index 00000000..27d51796 Binary files /dev/null and b/Basics/NetWork/images/4_41.png differ diff --git a/Basics/NetWork/images/4_42.png b/Basics/NetWork/images/4_42.png new file mode 100644 index 00000000..8b1517c1 Binary files /dev/null and b/Basics/NetWork/images/4_42.png differ diff --git a/Basics/NetWork/images/4_43.png b/Basics/NetWork/images/4_43.png new file mode 100644 index 00000000..9a3bdfb6 Binary files /dev/null and b/Basics/NetWork/images/4_43.png differ diff --git a/Basics/NetWork/images/4_44.png b/Basics/NetWork/images/4_44.png new file mode 100644 index 00000000..3d4aab03 Binary files /dev/null and b/Basics/NetWork/images/4_44.png differ diff --git a/Basics/NetWork/images/4_45.png b/Basics/NetWork/images/4_45.png new file mode 100644 index 00000000..a025da3a Binary files /dev/null and b/Basics/NetWork/images/4_45.png differ diff --git a/Basics/NetWork/images/4_46.png b/Basics/NetWork/images/4_46.png new file mode 100644 index 00000000..846cb32e Binary files /dev/null and b/Basics/NetWork/images/4_46.png differ diff --git a/Basics/NetWork/images/4_47.png b/Basics/NetWork/images/4_47.png new file mode 100644 index 00000000..185c3949 Binary files /dev/null and b/Basics/NetWork/images/4_47.png differ diff --git a/Basics/NetWork/images/4_48.png b/Basics/NetWork/images/4_48.png new file mode 100644 index 00000000..e5402347 Binary files /dev/null and b/Basics/NetWork/images/4_48.png differ diff --git a/Basics/NetWork/images/4_49.png b/Basics/NetWork/images/4_49.png new file mode 100644 index 00000000..d38c8aa5 Binary files /dev/null and b/Basics/NetWork/images/4_49.png differ diff --git a/Basics/NetWork/images/4_5.png b/Basics/NetWork/images/4_5.png new file mode 100644 index 00000000..b6b3da36 Binary files /dev/null and b/Basics/NetWork/images/4_5.png differ diff --git a/Basics/NetWork/images/4_50.png b/Basics/NetWork/images/4_50.png new file mode 100644 index 00000000..ec33f56c Binary files /dev/null and b/Basics/NetWork/images/4_50.png differ diff --git a/Basics/NetWork/images/4_51.png b/Basics/NetWork/images/4_51.png new file mode 100644 index 00000000..7b2c2aa6 Binary files /dev/null and b/Basics/NetWork/images/4_51.png differ diff --git a/Basics/NetWork/images/4_52.png b/Basics/NetWork/images/4_52.png new file mode 100644 index 00000000..d5b5f10f Binary files /dev/null and b/Basics/NetWork/images/4_52.png differ diff --git a/Basics/NetWork/images/4_53.png b/Basics/NetWork/images/4_53.png new file mode 100644 index 00000000..0b8f4c95 Binary files /dev/null and b/Basics/NetWork/images/4_53.png differ diff --git a/Basics/NetWork/images/4_54.png b/Basics/NetWork/images/4_54.png new file mode 100644 index 00000000..579482e5 Binary files /dev/null and b/Basics/NetWork/images/4_54.png differ diff --git a/Basics/NetWork/images/4_55.png b/Basics/NetWork/images/4_55.png new file mode 100644 index 00000000..f77356ef Binary files /dev/null and b/Basics/NetWork/images/4_55.png differ diff --git a/Basics/NetWork/images/4_56.png b/Basics/NetWork/images/4_56.png new file mode 100644 index 00000000..2357cca3 Binary files /dev/null and b/Basics/NetWork/images/4_56.png differ diff --git a/Basics/NetWork/images/4_57.png b/Basics/NetWork/images/4_57.png new file mode 100644 index 00000000..a5630e12 Binary files /dev/null and b/Basics/NetWork/images/4_57.png differ diff --git a/Basics/NetWork/images/4_58.png b/Basics/NetWork/images/4_58.png new file mode 100644 index 00000000..a1b560aa Binary files /dev/null and b/Basics/NetWork/images/4_58.png differ diff --git a/Basics/NetWork/images/4_59.png b/Basics/NetWork/images/4_59.png new file mode 100644 index 00000000..e345fa01 Binary files /dev/null and b/Basics/NetWork/images/4_59.png differ diff --git a/Basics/NetWork/images/4_6.png b/Basics/NetWork/images/4_6.png new file mode 100644 index 00000000..0ae0a10c Binary files /dev/null and b/Basics/NetWork/images/4_6.png differ diff --git a/Basics/NetWork/images/4_7.png b/Basics/NetWork/images/4_7.png new file mode 100644 index 00000000..5f9aefe2 Binary files /dev/null and b/Basics/NetWork/images/4_7.png differ diff --git a/Basics/NetWork/images/4_8.png b/Basics/NetWork/images/4_8.png new file mode 100644 index 00000000..d6db7a6b Binary files /dev/null and b/Basics/NetWork/images/4_8.png differ diff --git a/Basics/NetWork/images/4_9.png b/Basics/NetWork/images/4_9.png new file mode 100644 index 00000000..c693cb19 Binary files /dev/null and b/Basics/NetWork/images/4_9.png differ diff --git a/Basics/NetWork/images/5_10.png b/Basics/NetWork/images/5_10.png new file mode 100644 index 00000000..656369a6 Binary files /dev/null and b/Basics/NetWork/images/5_10.png differ diff --git a/Basics/NetWork/images/5_11.png b/Basics/NetWork/images/5_11.png new file mode 100644 index 00000000..7bd688dd Binary files /dev/null and b/Basics/NetWork/images/5_11.png differ diff --git a/Basics/NetWork/images/5_12.png b/Basics/NetWork/images/5_12.png new file mode 100644 index 00000000..1a57c73a Binary files /dev/null and b/Basics/NetWork/images/5_12.png differ diff --git a/Basics/NetWork/images/5_13.png b/Basics/NetWork/images/5_13.png new file mode 100644 index 00000000..05048246 Binary files /dev/null and b/Basics/NetWork/images/5_13.png differ diff --git a/Basics/NetWork/images/5_14.png b/Basics/NetWork/images/5_14.png new file mode 100644 index 00000000..5a2d599a Binary files /dev/null and b/Basics/NetWork/images/5_14.png differ diff --git a/Basics/NetWork/images/5_15.png b/Basics/NetWork/images/5_15.png new file mode 100644 index 00000000..c052503b Binary files /dev/null and b/Basics/NetWork/images/5_15.png differ diff --git a/Basics/NetWork/images/5_16.png b/Basics/NetWork/images/5_16.png new file mode 100644 index 00000000..b40222fd Binary files /dev/null and b/Basics/NetWork/images/5_16.png differ diff --git a/Basics/NetWork/images/5_17.png b/Basics/NetWork/images/5_17.png new file mode 100644 index 00000000..e524ca94 Binary files /dev/null and b/Basics/NetWork/images/5_17.png differ diff --git a/Basics/NetWork/images/5_18.png b/Basics/NetWork/images/5_18.png new file mode 100644 index 00000000..28c23aad Binary files /dev/null and b/Basics/NetWork/images/5_18.png differ diff --git a/Basics/NetWork/images/5_19.png b/Basics/NetWork/images/5_19.png new file mode 100644 index 00000000..f2ea7220 Binary files /dev/null and b/Basics/NetWork/images/5_19.png differ diff --git a/Basics/NetWork/images/5_2.png b/Basics/NetWork/images/5_2.png new file mode 100644 index 00000000..9bf21bf2 Binary files /dev/null and b/Basics/NetWork/images/5_2.png differ diff --git a/Basics/NetWork/images/5_20.png b/Basics/NetWork/images/5_20.png new file mode 100644 index 00000000..f1023abc Binary files /dev/null and b/Basics/NetWork/images/5_20.png differ diff --git a/Basics/NetWork/images/5_21.png b/Basics/NetWork/images/5_21.png new file mode 100644 index 00000000..9d53d8c1 Binary files /dev/null and b/Basics/NetWork/images/5_21.png differ diff --git a/Basics/NetWork/images/5_22.png b/Basics/NetWork/images/5_22.png new file mode 100644 index 00000000..28498f59 Binary files /dev/null and b/Basics/NetWork/images/5_22.png differ diff --git a/Basics/NetWork/images/5_23.png b/Basics/NetWork/images/5_23.png new file mode 100644 index 00000000..354efc58 Binary files /dev/null and b/Basics/NetWork/images/5_23.png differ diff --git a/Basics/NetWork/images/5_24.png b/Basics/NetWork/images/5_24.png new file mode 100644 index 00000000..60ca95ed Binary files /dev/null and b/Basics/NetWork/images/5_24.png differ diff --git a/Basics/NetWork/images/5_3.png b/Basics/NetWork/images/5_3.png new file mode 100644 index 00000000..277584c5 Binary files /dev/null and b/Basics/NetWork/images/5_3.png differ diff --git a/Basics/NetWork/images/5_4.png b/Basics/NetWork/images/5_4.png new file mode 100644 index 00000000..4b1b6a41 Binary files /dev/null and b/Basics/NetWork/images/5_4.png differ diff --git a/Basics/NetWork/images/5_5.png b/Basics/NetWork/images/5_5.png new file mode 100644 index 00000000..ae94ff1e Binary files /dev/null and b/Basics/NetWork/images/5_5.png differ diff --git a/Basics/NetWork/images/5_6.png b/Basics/NetWork/images/5_6.png new file mode 100644 index 00000000..6eced267 Binary files /dev/null and b/Basics/NetWork/images/5_6.png differ diff --git a/Basics/NetWork/images/5_7.png b/Basics/NetWork/images/5_7.png new file mode 100644 index 00000000..6a3a9673 Binary files /dev/null and b/Basics/NetWork/images/5_7.png differ diff --git a/Basics/NetWork/images/5_8.png b/Basics/NetWork/images/5_8.png new file mode 100644 index 00000000..777cf735 Binary files /dev/null and b/Basics/NetWork/images/5_8.png differ diff --git a/Basics/NetWork/images/5_9.png b/Basics/NetWork/images/5_9.png new file mode 100644 index 00000000..d1c3436d Binary files /dev/null and b/Basics/NetWork/images/5_9.png differ diff --git a/Basics/NetWork/images/6_1.png b/Basics/NetWork/images/6_1.png new file mode 100644 index 00000000..b72db161 Binary files /dev/null and b/Basics/NetWork/images/6_1.png differ diff --git a/Basics/NetWork/images/6_2.png b/Basics/NetWork/images/6_2.png new file mode 100644 index 00000000..583485d5 Binary files /dev/null and b/Basics/NetWork/images/6_2.png differ diff --git a/Basics/NetWork/images/http/10_add.png b/Basics/NetWork/images/http/10_add.png new file mode 100644 index 00000000..42ae2dd1 Binary files /dev/null and b/Basics/NetWork/images/http/10_add.png differ diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/1_\345\256\242\346\210\267\347\253\257.png" "b/Basics/NetWork/images/http/1_\345\256\242\346\210\267\347\253\257.png" similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/1_\345\256\242\346\210\267\347\253\257.png" rename to "Basics/NetWork/images/http/1_\345\256\242\346\210\267\347\253\257.png" diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/2_.png" b/Basics/NetWork/images/http/2_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/2_.png" rename to Basics/NetWork/images/http/2_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/3_.png" b/Basics/NetWork/images/http/3_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/3_.png" rename to Basics/NetWork/images/http/3_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/4_.png" b/Basics/NetWork/images/http/4_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/4_.png" rename to Basics/NetWork/images/http/4_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/5_.png" b/Basics/NetWork/images/http/5_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/5_.png" rename to Basics/NetWork/images/http/5_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/6_DNS.png" b/Basics/NetWork/images/http/6_DNS.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/6_DNS.png" rename to Basics/NetWork/images/http/6_DNS.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/7_.png" b/Basics/NetWork/images/http/7_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/7_.png" rename to Basics/NetWork/images/http/7_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/8_.png" b/Basics/NetWork/images/http/8_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/8_.png" rename to Basics/NetWork/images/http/8_.png diff --git "a/\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/9_.png" b/Basics/NetWork/images/http/9_.png similarity index 100% rename from "\350\256\241\347\256\227\346\234\272\345\237\272\347\241\200/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/images/http/9_.png" rename to Basics/NetWork/images/http/9_.png diff --git a/Basics/NetWork/images/xin_1.png b/Basics/NetWork/images/xin_1.png new file mode 100644 index 00000000..088a6cfd Binary files /dev/null and b/Basics/NetWork/images/xin_1.png differ diff --git "a/Basics/NetWork/images/\346\265\201\351\207\217\346\216\247\345\210\266.png" "b/Basics/NetWork/images/\346\265\201\351\207\217\346\216\247\345\210\266.png" new file mode 100644 index 00000000..55ba3a2c Binary files /dev/null and "b/Basics/NetWork/images/\346\265\201\351\207\217\346\216\247\345\210\266.png" differ diff --git "a/Basics/NetWork/images/\346\273\221\345\212\250\347\252\227\345\217\243.png" "b/Basics/NetWork/images/\346\273\221\345\212\250\347\252\227\345\217\243.png" new file mode 100644 index 00000000..c8b97083 Binary files /dev/null and "b/Basics/NetWork/images/\346\273\221\345\212\250\347\252\227\345\217\243.png" differ diff --git "a/Basics/NetWork/images/\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223\350\204\221\345\233\276.png" "b/Basics/NetWork/images/\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223\350\204\221\345\233\276.png" new file mode 100644 index 00000000..0e1cbdca Binary files /dev/null and "b/Basics/NetWork/images/\347\254\254\345\233\233\347\253\240\346\200\273\347\273\223\350\204\221\345\233\276.png" differ diff --git a/Basics/OS/Linux/Command.md b/Basics/OS/Linux/Command.md new file mode 100644 index 00000000..cb4e1237 --- /dev/null +++ b/Basics/OS/Linux/Command.md @@ -0,0 +1,40 @@ +# 常用命令 + +1、`tail -f 文件`,实时动态的观察这个文件的变化,常用用监控日志。 + +2、用什么命令对一个文件的内容进行统计?(行号、单词数、字节数) + +答案: + +`wc 命令 - c 统计字节数 - l 统计行数 - w 统计字数`。 + +3、使用`pstree`查看进程树。 + +4、`pr`打印文件(可分页、分栏)。 + +5、`kill -l`查看可用的信号(15是`TERM`,即终止信号)。 + +6、`bc`进入计算器。 + +7、`jobs`,用于作业队列查询和管理。 + +8、`fg 作业编号n`,将作业编号为`n`的进程调度到前台运行。 + +9、`bg`,....调度到后台运行。 + +10、比较两个文件的内容`cmp file1 file2`。 + +11、`diff file1 file2`,比较两个文件的不同。 + +12、显示当前系统环境变量: `env`。(或者`export`) + +13、别名: `alias`,`unalias`。例如: `alias ll='ls -l'`,定义`ls -l`的别名为`ll`。 + +14、`netstat`显示网络相关的信息。 + +- `-t`: 使用TCP协议; +- `-u`: 使用UDP协议; +- `-l`: 监听; +- `-r`: 路由; +- `-n`: 显示IP地址和端口号; +- `netstat -tlun`: \ No newline at end of file diff --git a/Basics/OS/Linux/LinuxNotes.md b/Basics/OS/Linux/LinuxNotes.md new file mode 100644 index 00000000..40cf71e2 --- /dev/null +++ b/Basics/OS/Linux/LinuxNotes.md @@ -0,0 +1,286 @@ +# Linux知识总结 + +* [1、基础知识以及常用命令](#1基础知识以及常用命令) +* [2、远程管理](#2远程管理) +* [3、用户权限和用户管理](#3用户权限和用户管理) +* [4、其他命令](#4其他命令) + +*** +## 1、基础知识以及常用命令 +### 1.1、操作系统的基本知识 + +#### 1.1.1、操作系统概念 + +* 操作系统是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。 +* 操作系统也提供一个让用户与系统交互的操作界面。(软件之下,硬件之上) + +![在这里插入图片描述](images/linux1.png) +![在这里插入图片描述](images/linux2.png) + +#### 1.1.2、Unix和Linux之间的关系 +![在这里插入图片描述](images/linux3.png) +#### 1.1.3、发行版和内核之间的关系 +发行版只是在内核的基础少包了一层壳; + +![在这里插入图片描述](images/linux4.png) + +#### 1.1.4、多用户操作系统(不同于Windows的单用户操作系统) + +![1565965143246](assets/1565965143246.png) + +相关的目录的作用速查 + +* `/`:根目录,一般根目录下只存放目录,在Linux下有且只有一个根目录。所有的东西都是从这里开始。当你在终端里输入“/home”,你其实是在告诉电脑,先从/(根目录)开始,再进入到home目录。 +* `/bin`: /usr/bin: 可执行二进制文件的目录,如常用的命令ls、tar、mv、cat等。 +* `/boot`:放置linux系统启动时用到的一些文件,如Linux的内核文件:/boot/vmlinuz,系统引导管理器:/boot/grub。 +* `/dev`:存放linux系统下的设备文件,访问该目录下某个文件,相当于访问某个设备,常用的是挂载光驱 mount /dev/cdrom /mnt。 +* `/etc`:系统配置文件存放的目录,不建议在此目录下存放可执行文件,重要的配置文件有 /etc/inittab、/etc/fstab、/etc/init.d、/etc/X11、/etc/sysconfig、/etc/xinetd.d。 +* `/home`:系统默认的用户家目录,新增用户账号时,用户的家目录都存放在此目录下,~表示当前用户的家目录,~edu 表示用户 edu 的家目录。 +* `/lib`: /usr/lib: /usr/local/lib:系统使用的函数库的目录,程序在执行过程中,需要调用一些额外的参数时需要函数库的协助。 +* `/lost+fount`:系统异常产生错误时,会将一些遗失的片段放置于此目录下。 +* `/mnt`: /media:光盘默认挂载点,通常光盘挂载于 /mnt/cdrom 下,也不一定,可以选择任意位置进行挂载。 +* `/opt`:给主机额外安装软件所摆放的目录。 +* `/proc`:此目录的数据都在内存中,如系统核心,外部设备,网络状态,由于数据都存放于内存中,所以不占用磁盘空间,比较重要的目录有 /proc/cpuinfo、/proc/interrupts、/proc/dma、/proc/ioports、/proc/net/* 等。 +* `/root`:系统管理员root的家目录。 +* `/sbin`: /usr/sbin: /usr/local/sbin:放置系统管理员使用的可执行命令,如fdisk、shutdown、mount 等。与 /bin 不同的是,这几个目录是给系统管理员 root使用的命令,一般用户只能"查看"而不能设置和使用。 +* `/tmp`:一般用户或正在执行的程序临时存放文件的目录,任何人都可以访问,重要数据不可放置在此目录下。 +* `/srv`:服务启动之后需要访问的数据目录,如 www 服务需要访问的网页数据存放在 /srv/www 内。 +* `/usr`:应用程序存放目录,/usr/bin 存放应用程序,/usr/share 存放共享数据,/usr/lib 存放不能直接运行的,却是许多程序运行所必需的一些函数库文件。/usr/local: 存放软件升级包。/usr/share/doc: 系统说明文件存放目录。/usr/share/man: 程序说明文件存放目录。 +* `/var`:放置系统执行过程中经常变化的文件,如随时更改的日志文件 /var/log,/var/log/message:
所有的登录文件存放目录,/var/spool/mail:邮件存放的目录,/var/run:程序或服务启动后,其PID存放在该目录下。 + +### 1.2、常用命令 +终端命令格式以及常用linux命令: + +基本格式: + +```shell +command [-options] [parameter] +``` +* `command` : 命令名,相应功能的英文单词或单词的缩写; +* `[-options]`: 选项,可以用来对命令进行控制,也可以省略; +* `[parameter]` : 传给命令的参数,可以是**零个,一个或多个**; + +注: `[]`代表的是可选操作。 + + +> 小技巧: `ctrl + -/+ `可以缩小/放大终端的字体; + +常用的7个命令: + +![1565965155955](assets/1565965155955.png) + +#### 1.2.1、查看相关命令的帮助信息的两种方式 + +* `command --help`,比如 `ls -- help`查看ls的命令的帮助信息; +* `man`,比如`man ls`查看`ls`的帮助信息; + +#### 1.2.2、 Linux下文件和目录的特点 + +* Linux文件或目录名称最长可以有`256`个字符; +* 以`.`开头的文件为隐藏文件,需要用`-a`参数才能显示; +* `.`代表当前目录; +* `..`代表上一级目录; + + +#### 1.2.3、命令结合通配符的使用 +![1565965165001](assets/1565965165001.png) + +简单的使用举例: + +![1565965176041](assets/1565965176041.png) + +#### 1.2.4、`cd`命令的常用变化格式以及路径 +![1565965186170](assets/1565965186170.png) + +#### 1.2.5、创建、删除、移动、复制操作 +![1565965203591](assets/1565965203591.png) +#### 1.2.6、查看文件内容的三个命令 +![1565965223743](assets/1565965223743.png) + +![1565965237259](assets/1565965237259.png) + +grep使用案例: + +![1565965248252](assets/1565965248252.png) + +#### 1.2.7、echo、重定向、管道 +![1565965258142](assets/1565965258142.png) + +管道使用案例: + +![1565965268817](assets/1565965268817.png) + +*** +## 2、远程管理 + +### 2.1、网络的基本知识 +![1565965284384](assets/1565965284384.png) + +![1565965291661](assets/1565965291661.png) + +### 2.2、 远程登录 +![1565965300119](assets/1565965300119.png) + +关于域名以及端口号: + +![1565965311606](assets/1565965311606.png) + +SSH的简单使用: + +![1565965320720](assets/1565965320720.png) + +### 2.3、远程复制 +![1565965332005](assets/1565965332005.png) + +### 2.4、SSH高级一免密码登录和设置别名 +![1565965361697](assets/1565965361697.png) + + +![1565965369608](assets/1565965369608.png) + +*** +## 3、用户权限和用户管理 + +主要知识: + +* 用户和权限的基本概念; +* 用户管理终端命令; +* 组管理终端命令; +* 修改权限终端命令; + + +![1565965384666](assets/1565965384666.png) + +`ls -l`相关参数说明: + +![1565965396320](assets/1565965396320.png) + +终端测试说明: + +![1565965408320](assets/1565965408320.png) + +其中上面绿色字体的硬链接数就是有多少种方式可以到达这个目录或者文件: + +![1565965420202](assets/1565965420202.png) + +`chmod命令`改变`文件/目录`的权限: + +![1565965432327](assets/1565965432327.png) +测试使用`chmod`命令: + +![1565965449209](assets/1565965449209.png) + +执行可执行的权限`chmod +x 01.py`就可以执行了: + +![1565965458143](assets/1565965458143.png) + +`chmod`对目录的操作: + +![1565965473891](assets/1565965473891.png) + +### 3.1、超级用户 +![1565965480817](assets/1565965480817.png) +### 3.2、组管理命令 +![1565965489218](assets/1565965489218.png) + +### 3.3、用户管理终端命令 +![1565965498772](assets/1565965498772.png) + +在`unbutu`中创建一个`zhangsan`的用户并放到`dev`组: + +![1565965513219](assets/1565965513219.png) + +### 3.4、查看用户信息以及passwd文件和usermod命令 + +![1565965534442](assets/1565965534442.png)\ + +![1565965545645](assets/1565965545645.png) + +如果是在`Windows`下使用`XShell`来操作`Linux`,默认使用的`shell(终端)`是`dash`,但是这个`Shell`不好,所以可以使用下面的命令来更改默认的`Shell`: + +```shell +sudo usermod -s /bin/bash zhangsan // 指定张三用户的shell为bash,而不是默认的dash +``` + +### 3.5、which查看可执行文件的位置 +![1565965555263](assets/1565965555263.png) + +>可以使用`last`命令 查询用户登录情况; + +### 3.6、切换用户 +![1565965563100](assets/1565965563100.png) +### 3.7、修改文件权限的三个命令以及chmod和三个数字修改权限(常用) +![1565965572081](assets/1565965572081.png) +*** +### 3.8、系统信息相关命令 +![1565965587721](assets/1565965587721.png) + +![1565965599330](assets/1565965599330.png) + +![1565965609443](assets/1565965609443.png) + +*** +## 4、其他命令 +* 查找文件 `find` +* 软链接`ln` +* 打包和压缩`tar` +* 软件安装`apt-get` + + +### 4.1、查找文件 +![1565965618673](assets/1565965618673.png) + +演练: + +![1565965631860](assets/1565965631860.png) + +### 4.2、软链接(类似`Windows`的快捷方式) +![1565965639025](assets/1565965639025.png) + +举例: + + 先看相对路径建立软链接: + +![1565965655394](assets/1565965655394.png) + +通过绝对路径创建的、以及移动两个软链接之后,相对路径的失效效果: + + +![1565965665213](assets/1565965665213.png) + +**`注意`: 当省略了`-s`选项就是创建硬链接。** + +硬链接即使删除了源文件,也能继续查看硬链接的内容。 + +下面是软链接和硬链接的示意图: + + +![1565965672450](assets/1565965672450.png) + +### 4.3、打包压缩 + +![1565965684515](assets/1565965684515.png) + +演练: + +![1565965699155](assets/1565965699155.png) + +![1565965706514](assets/1565965706514.png) + +演练: + +![1565965718604](assets/1565965718604.png) + +注意,除了`.gzip`的压缩文件,还有一个`bz2`的文件压缩文件格式: + +![1565965726451](assets/1565965726451.png) + +### 4.4、软件安装 + +![1565965734612](assets/1565965734612.png) + +设置软件源: + +![1565965744510](assets/1565965744510.png) + + diff --git a/Basics/OS/Linux/Other.md b/Basics/OS/Linux/Other.md new file mode 100644 index 00000000..2af4c10a --- /dev/null +++ b/Basics/OS/Linux/Other.md @@ -0,0 +1,750 @@ +# Other +## 1、硬链接与软链接 + +
+### 1.1、硬链接 + +一般情况下,文件名和 inode 号码是 "一一对应" 关系,每个 inode 号码对应一个文件名。**但是,Unix/Linux 系统允许,多个文件名指向同一个 inode 号码**。 + +这意味着,**可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问**。这种情况就被称为 "硬链接"(hard link)。 + +运行上面这条命令以后,源文件与目标文件的 inode 号码相同,都指向同一个 inode。inode 信息中有一项叫做 "链接数",记录指向该 inode 的文件名总数,这时就会增加 1。 + +**反过来,删除一个文件名,就会使得 inode 节点中的 "链接数" 减1。当这个值减到 0,表明没有文件名指向这个 inode,系统就会回收这个 inode 号码,以及其所对应 block 区域**。 + +这里顺便说一下目录文件的 "链接数"。创建目录时,默认会生成两个目录项:`"."和".."`。前者的 inode 号码就是当前目录的 inode 号码,等同于当前目录的 "硬链接";后者的 inode 号码就是当前目录的父目录的inode号码,等同于父目录的 "硬链接"。所以,任何一个目录的 "硬链接" 总数,总是等于 2 加上它的子目录总数(含隐藏目录)。 + +几个硬连接=几个名字的同一个房子 + +**硬链接(Hard Link)**:硬连接不能跨越不同的文件系统,硬连接记录的是目标的 inode;只能指向文件。硬连接与原始文件都删除才意味着文件被删除。 + +- 特征 + - 拥有相同的 i 节点和存储 block 块,可以看做是同一个文件 + - 可通过 i 节点识别 + - 不能跨分区 + - 不能针对目录使用 + +### 1.2、软链接 + +除了硬链接以外,还有一种特殊情况。 + +**文件 A 和文件 B 的 inode 号码虽然不一样,但是文件 A 的内容是文件 B 的路径。读取文件 A 时,系统会自动将访问者导向文件 B。因此,无论打开哪一个文件,最终读取的都是文件 B。这时,文件 A 就称为文件 B 的"软链接"(soft link)或者"符号链接(symbolic link)**。 + +这意味着,文件 A 依赖于文件 B 而存在,如果删除了文件 B,打开文件 A 就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件 A 指向文件 B 的文件名,而不是文件 B 的 inode 号码,文件 B 的 inode "链接数"不会因此发生变化。 + +几个软链接=几个指向源文件的路标 + +**软链接(Symbolic Link,又称符号链接)**:软链接能跨越不同的文件系统,软链接记录的是目标的 path。源文件删除后,则软链接无效。**相当于Windows系统中的“快捷方式”** + +- 特征: + - 类似 windows 的快捷方式 + - 软链接拥有自己的 i 节点和 block 块,但是数据块中只保存原文件的文件名和 i 节点号,并没有实际的文件数据 + - 修改任意一个文件,另一个都会改变 + - 删除源文件,则软链接无法使用 + - 软链接的文件权限都为 rwxrwxrwx (文件权限以原文件为准) + - 若要创建软链接,则创建的源文件必须使用绝对路径,否则在使用软链接时会报错 + +注意:复制是建造一个一模一样的房子,inode是不同的。 + +命令 + +``` + 硬链接:ln 源文件 链接名 + 软链接:ln -s 源文件 链接名 +``` + +区别: 若将源文件删除,硬链接依旧有效,而软链接会无效,即找不到源文件 + +## 2、僵尸进程和孤儿进程 + +https://www.cnblogs.com/Anker/p/3271773.html + +我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。 当一个 进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。 + +  **孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。** + +  **僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。** + +**3、问题及危害** + +  unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。 但这样就导致了问题,**如果进程不调用wait / waitpid的话,** **那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。** + +  **孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上**,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。**因此孤儿进程并不会有什么危害。** + +  **任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。**这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 + +  僵尸进程危害场景: + +  例如有个进程,它定期的产 生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大 量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。 + +## 3、查看CPU占用 + +### 3.1、top + +**top命令**可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。通过top命令所提供的互动式界面,用热键可以管理。 + +### 3.2、cat /proc/meminfo + +查看RAM使用情况最简单的方法是通过 `/proc/meminfo`。这个动态更新的虚拟文件实际上是许多其他内存相关工具(如:free / ps / top)等的组合显示。`/proc/meminfo` 列出了所有你想了解的内存的使用情况。 + +### 3.3、free + +free 命令是一个快速查看内存使用情况的方法,它是对 `/proc/meminfo` 收集到的信息的一个概述。 + +这个命令用于显示系统当前内存的使用情况,包括已用内存、可用内存和交换内存的情况 + +默认情况下 free 会以字节为单位输出内存的使用量 + +```shell +$ free + total used free shared buffers cached +Mem: 3566408 1580220 1986188 0 203988 902960 +-/+ buffers/cache: 473272 3093136 +Swap: 4000176 0 4000176 +``` + +如果你想以其他单位输出内存的使用量,需要加一个选项,`-g` 为GB,`-m` 为MB,`-k` 为KB,`-b` 为字节 + +```shell +$ free -g + total used free shared buffers cached +Mem: 3 1 1 0 0 0 +-/+ buffers/cache: 0 2 +Swap: 3 0 3 +``` + +如果你想查看所有内存的汇总,请使用 -t 选项,使用这个选项会在输出中加一个汇总行 + +```shell +$ free -t + total used free shared buffers cached +Mem: 3566408 1592148 1974260 0 204260 912556 +-/+ buffers/cache: 475332 3091076 +Swap: 4000176 0 4000176 +Total: 7566584 1592148 5974436 +``` + +## 4、文件系统和innode + + + +如Windows所用的文件系统主要有FAT16、FAT32和NTFS,Linux所用的文件系统主要有ext2、ext3、Ext4和ReiserFS等。 + +### 4.1、文件系统工作原理 + +文件系统的工作与操作系统的文件数据有关。现在的操作系统的文件数据除了文件实际内容外,通常含有非常多的属性,例如文件权限(rwx)与文件属性(所有者、用户组、时间参数等)。 + +**文件系统通常会将这两部份的数据分别存放在不同的区块,权限与属性放到inode中,数据则放到block区块中**。另外,还有一个超级区块(super block)会记录整个文件系统的整体信息,包括 inode与block的总量、使用量、剩余量等等等。 + +每个 inode 与 block 都有编号,至于这三个数据的意义可以简略说明如下: + +- 1)、superblock:记录此 filesystem 的整体信息,包括inode/block的总量、使用量、剩余量, 以及文件系统的格式与相关信息等; +- 2)、inode:**记录文件的属性,一个文件占用一个inode,同时记录此文件的数据所在的 block 号码**; +- 3)、block:实际记录文件的内容,若文件太大时,会占用多个 block 。 + +由于每个 inode 与 block 都有编号,而每个文件都会占用一个 inode ,inode 内则有文件数据放置的 block 号码。 因此,我们可以知道的是,**如果能够找到文件的 inode 的话,那么自然就会知道这个文件所放置数据的 block 号码, 当然也就能够读出该文件的实际数据了**。这是个比较有效率的作法,因为如此一来我们的磁盘就能够在短时间内读取出全部的数据, 读写的效能比较好。 + +我们将 inode 与 block 区块用图解来说明一下,如下图所示,文件系统先格式化出 inode 与 block 的区块,假设某一个档案的属性与权限数据是放置到 inode 4 号(下图较小方格内),而这个 inode 记录了档案数据的实际放置点为 2, 7, 13, 15 这四个 block 号码,此时我们的操作系统就能够据此来排列磁盘的阅读顺序,可以一口气将四个 block 内容读出来! 那么数据的读取就如同下图中的箭头所指定的模样了。 + +![1555860724895](assets/1555860724895.png) + +这种数据存取的方法我们称为索引式文件系统(indexed allocation)。下面我们来看一下windows系统中的FAT,这种格式的文件系统并没有 inode 存在,所以 FAT 没有办法将这个文件的所有 block 在一开始就读取出来。每个 block 号码都记录在前一个 block 当中, 他的读取方式有点像底下这样: + +![1555860739978](assets/1555860739978.png) + +![1555860683719](assets/1555860683719.png) + +### 4.2、inode的内容 + +inode 包含文件的元信息,具体来说有以下内容: + +```java +* 文件的字节数 +* 文件拥有者的 User ID +* 文件的 Group ID +* 文件的读、写、执行权限 +* 文件的时间戳,共有三个:ctime:指inode上一次变动的时间,mtime:指文件内容上一次变动的时间,atime:指文件上一次打开的时间。 +* 链接数,即有多少文件名指向这个inode +* 文件数据block的位置 +``` + +可以用 `stat` 命令,查看某个文件的 inode 信息: + +```shell +$ stat abby.txt + File: ‘abby.txt’ + Size: 22 Blocks: 8 IO Block: 4096 regular file +Device: fd01h/64769d Inode: 2106782 Links: 2 +Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root) +Access: 2018-07-22 16:37:18.640787898 +0800 +Modify: 2018-07-22 16:37:10.678607855 +0800 +Change: 2018-07-22 16:37:10.833611360 +0800 + Birth: - +``` + +总之,除了文件名以外的所有文件信息,都存在inode之中。至于为什么没有文件名,下文会有详细解释。 + +### 4.3、inode的大小 + +

+inode 也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是**数据区**,存放文件数据;另一个是 **inode 区**(inode table),存放 inode 所包含的信息。 + +每个 inode 节点的大小,一般是 128 字节或 256 字节。inode 节点的总数,在格式化时就给定,一般是每 1KB 或每 2KB 就设置一个 inode。假定在一块 1GB 的硬盘中,每个 inode 节点的大小为 128 字节,每 1KB 就设置一个 inode,那么 inode table 的大小就会达到 128 MB,占整块硬盘的 12.8%。 + +查看每个硬盘分区的 inode 总数和已经使用的数量,可以使用df 命令。 + +```shell +$ df -i +Filesystem Inodes IUsed IFree IUse% Mounted on +/dev/vda1 3932160 189976 3742184 5% / +devtmpfs 998993 339 998654 1% /dev +tmpfs 1001336 1 1001335 1% /dev/shm +tmpfs 1001336 397 1000939 1% /run +tmpfs 1001336 16 1001320 1% /sys/fs/cgroup +tmpfs 1001336 1 1001335 1% /run/user/0 +``` + +查看每个 inode 节点的大小,可以用如下命令: + +```shell +$ sudo dumpe2fs -h /dev/vda1 | grep "Inode size" +dumpe2fs 1.42.9 (28-Dec-2013) +Inode size: 256 +``` + +由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。 + +### 4.4、inode号码 + +每个 inode 都有一个号码,**操作系统用 inode 号码来识别不同的文件**。 + +这里值得重复一遍,Unix/Linux 系统内部不使用文件名,而使用 inode 号码来识别文件。对于系统来说,文件名只是 inode 号码便于识别的别称或者绰号。 + +表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的 inode 号码;其次,通过 inode 号码,获取 inode 信息;最后,根据 inode 信息,找到文件数据所在的 block,读出数据。 + +使用 `ls -i` 命令,可以看到文件名对应的 inode 号码: + +```shell +$ ls -i test.txt +1712426 test.txt +``` + +## 5、用户管理 + +实现用户账号的管理,要完成的工作主要有如下几个方面: + +- 用户账号的添加、删除与修改。 +- 用户口令的管理。 +- 用户组的管理。 + +### 5.1、添加用户 + +```shell +useradd 选项 用户名 +参数说明: +选项: + -c comment 指定一段注释性描述。 + -d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。 + -g 用户组 指定用户所属的用户组。 + -G 用户组,用户组 指定用户所属的附加组。 + -s Shell文件 指定用户的登录Shell。 + -u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。 +用户名: + 指定新账号的登录名。 +``` + +实例: + +```shell +实例1 +# useradd –d /usr/zx -m zx +此命令创建了一个用户zx,其中-d和-m选项用来为登录名zx产生一个主目录/usr/sam(/usr为默认的用户主目录所在的父目录)。 + +实例2 +# useradd -s /bin/sh -g group –G adm,root zx +此命令新建了一个用户zx,该用户的登录Shell是 /bin/sh,它属于group用户组,同时又属于adm和root用户组,其中group用户组是其主组。 +这里可能新建组:#groupadd group及groupadd adm +增加用户账号就是在/etc/passwd文件中为新用户增加一条记录,同时更新其他系统文件如/etc/shadow, /etc/group等。 +``` + +### 5.2、删除用户 + +删除用户账号就是要将`/etc/passwd`等系统文件中的该用户记录删除,必要时还删除用户的主目录。 + +删除一个已有的用户账号使用`userdel`命令,其格式如下: + +`userdel 选项 用户名` +常用的选项是` -r`,**它的作用是把用户的主目录一起删除**(日常一般不会删除)。 + +例如: + +```shell +# userdel -r zx +``` + +此命令删除用户`zx`在系统文件中(主要是`/etc/passwd, /etc/shadow, /etc/group`等)的记录,同时删除用户的主目录。 + +### 5.3、修改用户 + +修改用户账号就是根据实际情况更改用户的有关属性,如用户号、主目录、用户组、登录Shell等。 + +```shell +修改已有用户的信息使用usermod命令,其格式如下: + +usermod 选项 用户名 + +常用的选项包括-c, -d, -m, -g, -G, -s, -u以及-o等,这些选项的意义与useradd命令中的选项一样,可以为用户指定新的资源值。 + +另外,有些系统可以使用选项:-l 新用户名 + +这个选项指定一个新的账号,即将原来的用户名改为新的用户名。 + +例如: + +# usermod -s /bin/ly -d /home/ly –g developer zx +此命令将用户zx的登录Shell修改为ly,主目录改为/home/ly,用户组改为developer。 +``` + +### 5.4、口令管理 + +超级用户可以为自己和其他用户指定口令,普通用户只能用它修改自己的口令。命令的格式为: + +```shell +passwd 选项 用户名 +可使用的选项: + +-l 锁定口令,即禁用账号。 +-u 口令解锁。 +-d 使账号无口令。 +-f 强迫用户下次登录时修改口令。 +如果默认用户名,则修改当前用户的口令。 + +例如,假设当前用户是sam,则下面的命令修改该用户自己的口令: +passwd +如果是超级用户,可以用下列形式指定任何用户的口令: +passwd sam +``` + +> `etc/password`下文件的格式: +> +> `用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell` + +## 6、用户组管理 + +每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理。不同Linux 系统对用户组的规定有所不同,**如Linux下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建**。 + +用户组的管理涉及用户组的添加、删除和修改。组的增加、删除和修改实际上就是对`/etc/group`文件的更新。 + +### 6.1、增加用户组 + +```shell +命令: +groupadd 选项 用户组 +可以使用的选项有: + +-g GID 指定新用户组的组标识号(GID)。 +-o 一般与-g选项同时使用,表示新用户组的GID可以与系统已有用户组的GID相同。 +实例1: +# groupadd group1 +此命令向系统中增加了一个新组group1,新组的组标识号是在当前已有的最大组标识号的基础上加1。 + +实例2: +# groupadd -g 101 group2 +此命令向系统中增加了一个新组group2,同时指定新组的组标识号是101。 +``` + +### 6.2、删除用户组 + +```shell +groupdel 用户组 +例如: +# groupdel group1 +此命令从系统中删除组group1。 +``` + +### 6.3、修改用户组 + +```shell +groupmod 选项 用户组 +常用的选项有: + + -g GID 为用户组指定新的组标识号。 + -o 与-g选项同时使用,用户组的新GID可以与系统已有用户组的GID相同。 + -n 新用户组 将用户组的名字改为新名字 +实例1: +# groupmod -g 102 group2 +此命令将组group2的组标识号修改为102。 + +实例2: +# groupmod –g 10000 -n group3 group2 +此命令将组group2的标识号改为10000,组名修改为group3。 +``` + +## 7、进程管理 + +### 7.1、查看进程 + +#### ps + +查看某个时间点的进程信息 + +示例一:查看自己的进程 + +``` +# ps -l + +``` + +示例二:查看系统所有进程 + +``` +# ps aux + +``` + +示例三:查看特定的进程 + +``` +# ps aux | grep threadx + +``` + +``` +-a:显示所有终端机下执行的程序,除了阶段作业领导者之外。 +-u<用户识别码>:此选项的效果和指定"-U"选项相同。 +x:显示所有程序,不以终端机来区分。 + +``` + +#### top + +实时显示进程信息 + +示例:两秒钟刷新一次 + +``` +# top -d 2 + +``` + +#### pstree + +查看进程树 + +示例:查看所有进程树 + +``` +# pstree -A + +``` + +#### netstat + +查看占用端口的进程 + +示例:查看特定端口的进程 + +``` +# netstat -anp | grep port +``` + +参考资料: + +[Linux基础13 进程管理_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av9539203?from=search&seid=12568422774751055363) + + + +### 7.2、进程状态 + +![1555862653942](assets/1555862653942.png) + +| 状态 | 说明 | +| ---- | ------------------------------------------------------------ | +| R | running or runnable (on run queue) | +| D | uninterruptible sleep (usually I/O) | +| S | interruptible sleep (waiting for an event to complete) | +| Z | zombie (terminated but not reaped by its parent) 僵尸进程 | +| T | stopped (either by a job control signal or because it is being traced) | + +#### SIGCHLD + +当一个子进程改变了它的状态时:停止运行,继续运行或者退出,有两件事会发生在父进程中: + +- 得到 SIGCHLD 信号; +- waitpid() 或者 wait() 调用会返回。 + +![1555862665632](assets/1555862665632.png) + +**其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。** + +**在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息**。父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。 + +#### wait() + +```c +pid_t wait(int *status) +``` + +父进程调用 wait() 会一直阻塞,直到收到一个子进程退出的 SIGCHLD 信号,之后 wait() 函数会销毁子进程并返回。 + +如果成功,返回被收集的子进程的进程 ID;如果调用进程没有子进程,调用就会失败,此时返回 -1,同时 errno 被置为 ECHILD。 + +参数 status 用来保存被收集的子进程退出时的一些状态,如果我们对这个子进程是如何死掉的毫不在意,只想把这个子进程消灭掉,可以设置这个参数为 NULL: + +``` +pid = wait(NULL); + +``` + + + +#### waitpid() + +```c +pid_t waitpid(pid_t pid, int *status, int options) +``` + +作用和 wait() 完全相同,但是多了两个可由用户控制的参数 pid 和 options。 + +pid 参数指示一个子进程的 ID,表示只关心这个子进程的退出 SIGCHLD 信号。如果 pid=-1 时,那么和 wait() 作用相同,都是关心所有子进程退出的 SIGCHLD 信号。 + +options 参数主要有 WNOHANG 和 WUNTRACED 两个选项,WNOHANG 可以使 waitpid() 调用变成非阻塞的,也就是说它会立即返回,父进程可以继续执行其它任务。 + + + +#### 孤儿进程 + +一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。 + +孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。 + +由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。 + + + +#### 僵尸进程 + +一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。 + +僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。 + +系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。 + +要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时所有的僵尸进程就会变成孤儿进程,从而被 init 所收养,这样 init 就会释放所有的僵死进程所占有的资源,从而结束僵尸进程。 + + + +参考资料: + +- [孤儿进程与僵尸进程[总结] - Anker's Blog - 博客园](https://www.cnblogs.com/Anker/p/3271773.html) +- [《深入理解计算机系统》异常控制流——读书笔记 - CSDN博客](https://blog.csdn.net/zhanghaodx082/article/details/12280689) +- [Linux系统学习笔记:异常控制流 - CSDN博客](https://blog.csdn.net/yangxuefeng09/article/details/10066357) +- [Linux 之守护进程、僵死进程与孤儿进程 | LiuYongbin](http://liubigbin.github.io/2016/03/11/Linux-%E4%B9%8B%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E3%80%81%E5%83%B5%E6%AD%BB%E8%BF%9B%E7%A8%8B%E4%B8%8E%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B/) +- [CSAPP笔记第八章异常控制流 呕心沥血千行笔记- DDUPzy - 博客园](https://www.cnblogs.com/zy691357966/p/5480537.html) + + + +## 8、kill用法,某个进程杀不掉的原因(进入内核态,忽略kill信号) + +1. 该进程是僵尸进程(STAT z),此时进程已经释放所有的资源,但是没有被父进程释放。僵尸进程要等到父进程结束,或者重启系统才可以被释放。 +2. 进程处于“核心态”,并且在等待不可获得的资源,处于“核心态 ”的资源默认忽略所有信号。只能重启系统。 + +参考资料: + +- [linux kill -9 杀不掉的进程 - CSDN博客](https://blog.csdn.net/lemontree1945/article/details/79169178) + +### kill + +kill命令用来删除执行中的程序或工作。kill可将指定的信息送至程序。预设的信息为`SIGTERM(15)`,可将指定程序终止。若仍无法终止该程序,可使用SIGKILL(9)信息尝试强制删除程序。程序或工作的编号可利用[ps](http://man.linuxde.net/ps)指令或job指令查看。 + +**语法** + +``` +kill(选项)(参数) + +``` + +**选项** + +``` +-a:当处理当前进程时,不限制命令名和进程号的对应关系; +-l <信息编号>:若不加<信息编号>选项,则-l参数会列出全部的信息名称; +-p:指定kill 命令只打印相关进程的进程号,而不发送任何信号; +-s <信息名称或编号>:指定要送出的信息; +-u:指定用户。 + +``` + +**参数** + +进程或作业识别号:指定要删除的进程或作业。 + +**实例** + +列出所有信号名称: + +``` + kill -l + 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL + 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE + 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 +13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT +17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP +21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU +25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH +29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN +35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 +39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 +43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 +47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 +51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 +55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 +59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 +63) SIGRTMAX-1 64) SIGRTMAX + +``` + +只有第9种信号(SIGKILL)才可以无条件终止进程,其他信号进程都有权利忽略,**下面是常用的信号:** + +``` +HUP 1 终端断线 +INT 2 中断(同 Ctrl + C) +QUIT 3 退出(同 Ctrl + \) +TERM 15 终止 +KILL 9 强制终止 +CONT 18 继续(与STOP相反, fg/bg命令) +STOP 19 暂停(同 Ctrl + Z) + +``` + +先用ps查找进程,然后用kill杀掉: + +``` +ps -ef | grep vim +root 3268 2884 0 16:21 pts/1 00:00:00 vim install.log +root 3370 2822 0 16:21 pts/0 00:00:00 grep vim + +kill 3268 +kill 3268 +-bash: kill: (3268) - 没有那个进程 + +``` + +### kill all + +**killall命令使用进程的名称来杀死进程**,使用此指令可以杀死一组同名进程。我们可以使用[kill](http://man.linuxde.net/kill)命令杀死指定进程PID的进程,如果要找到我们需要杀死的进程,我们还需要在之前使用[ps](http://man.linuxde.net/ps)等命令再配合[grep](http://man.linuxde.net/grep)来查找进程,而killall把这两个过程合二为一,是一个很好用的命令。 + +**语法** + +``` +killall(选项)(参数) + +``` + +选项 + +``` +-e:对长名称进行精确匹配; +-l:忽略大小写的不同; +-p:杀死进程所属的进程组; +-i:交互式杀死进程,杀死进程前需要进行确认; +-l:打印所有已知信号列表; +-q:如果没有进程被杀死。则不输出任何信息; +-r:使用正规表达式匹配要杀死的进程名称; +-s:用指定的进程号代替默认信号“SIGTERM”; +-u:杀死指定用户的进程。 + +``` + +**参数** + +进程名称:指定要杀死的进程名称。 + +**实例** + +杀死所有同名进程 + +``` +killall vi + +``` + + + + + +## 9、Linux文件系统介绍 + +在 Linux操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录**都被看作是一个文件**。也就是说在LINUX系统中有一个重要的概念:**一切都是文件**。 + +Linux支持的5种文件类型: + +| 文件类型 | 描述 | 示例 | +| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 普通文件 | 用来在辅助设备(如磁盘)上存储信息和数据 | 包含程序源代码、可执行程序、图片、声音、图像等 | +| 目录文件 | 用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名 | /root、/home | +| 链接文件 | **用于不同目录下文件的共享** | 当创建一个已存在文件的符号链接时,系统就创建一个链接文件,这个链接文件指向已存在的文件 | +| 设备文件 | 用来访问硬件设备 | 包括键盘、硬盘、光驱、打印机等 | +| 命名管道(FIFO) | 是一种特殊类型的文件、Linux系统下,**进程之间通信可以通过该文件完成** | | + +## 10、linux运行级别 + +运行级别就是操作系统当前正在运行的功能级别。 + +级别是从`0`到`6`,具有不同的功能。这些级别定义在`/ect/inittab`文件中。 + +这个文件是init程序寻找的主要文件,最先运行的服务是那些放在`/ect/rc.d`目录下的文件。 + +Linux下的7个运行级别: + +- `0`: 系统停机状态,系统默认运行级别不能设置为0,否则不能正常启动,机器关闭。 +- `1`: 单用户工作状态,root权限,用于系统维护,禁止远程登陆,就像Windows下的安全模式登录。 +- `2`: **多用户状态**,没有NFS支持。 +- `3`: **完整的多用户模式**,有NFS,登陆后进入**控制台命令行模式**。 +- `4`: 系统未使用,保留一般不用,在一些特殊情况下可以用它来做一些事情。例如在笔记本电脑的电池用尽时,可以切换到这个模式来做一些设置。 +- `5`: X11控制台,登陆后进入图形GUI模式,XWindow系统。 + +**标准的Linux运行级别为5或者3** + +## 11、运行级别原理 + +总结: + +- 在目录`/etc/rc.d/init.d`下有许多服务器脚本程序,一般称为服务(service) +- 在`/etc/rc.d`下有7个名为rcN.d的目录,对应系统的7个运行级别 +- `rcN.d`目录下都是一些符号链接文件,这些链接文件都指向init.d目录下的service脚本文件,命名规则为K+nn+服务名或S+nn+服务名,其中nn为两位数字。 +- 系统会根据指定的运行级别进入对应的rcN.d目录,并按照文件名顺序检索目录下的链接文件:对于以K(Kill)开头的文件,系统将终止对应的服;对于以S(Start)开头的文件,系统将启动对应的服务 +- **查看运行级别用**:`runlevel` +- 进入其它运行级别用:`init N`,如果`init 3`则进入终端模式,`init 5`则又登录图形GUI模式 +- 另外`init 0`为关机,`init 6`为重启系统 + 标准的Linux运行级别为3或5,如果是3的话,系统就在多用户状态;如果是5的话,则是运行着XWindow系统。不同的运行级别有不同的用处,也应该根据自己的不同情形来设置。例如,如果丢失了root口令,那么可以让机器启动进入单用户状态来设置。在启动后的lilo提示符下输入: + `init=/bin/shrw` + +这样就可以使机器进入运行级别1,并把root文件系统挂为读写。它会路过所有系统认证,让你使用passwd程序来改变root口令,然后启动到一个新的运行级。 + +## 12、Linux进程状态 + +Linux 中进程有哪几种状态?在 ps 显示出来的信息中,分别用什么符号表示的? + +**答案**: + +- 1)、不可中断状态:进程处于睡眠状态,但是此刻进程是不可中断的。不可中断, 指进程不响应异步信号; +- 2)、暂停状态/跟踪状态:向进程发送一个 SIGSTOP 信号,它就会因响应该信号 而进入 TASK_STOPPED 状态;当进程正在被跟踪时,它处于 TASK_TRACED 这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作; +- 3)、就绪状态:在 `run_queue` 队列里的状态; +- 4)、运行状态:在 `run_queue` 队列里的状态; +- 5)、可中断睡眠状态:处于这个状态的进程因为等待某某事件的发生(比如等待 socket 连接、等待信号量),而被挂起; +- 6)、zombie 状态(僵尸):父亲没有通过 wait 系列的系统调用会顺便将子进程的尸体(`task_struct`)也释放掉; +- 7)、退出状态; + +符号: + +- `D` : 不可中断 Uninterruptible(usually IO) +- `R` : 正在运行,或在队列中的进程 +- `S` : 处于休眠状态 +- `T` : 停止或被追踪 +- `Z` : 僵尸进程 +- `W` : 进入内存交换(从内核 2.6 开始无效) +- `X` : 死掉的进程 + + + diff --git a/Basics/OS/Linux/Vim.md b/Basics/OS/Linux/Vim.md new file mode 100644 index 00000000..f4805fd0 --- /dev/null +++ b/Basics/OS/Linux/Vim.md @@ -0,0 +1,90 @@ +# Vim编辑器总结 +* [一、日常发现总结](#一日常发现总结) +* [二、基本知识总结](#二基本知识总结) +* [三、命令表](#三命令表) +*** +## 一、日常发现总结(持续更新) +* 日常开发中,知道某一行有错,打开文件的时候同时定位到对应的行。命令: `vim 文件名 +行数`; +* 如果后面没有加上函数,也就是`vim 文件名 +`,则定位到文件末尾; +* 如果在终端强制退出`vim`,会产生一个`.swp`的交换文件,再次编辑的时候会出现需要选择编辑的情况,此时选择`D`选项,删除之前的`.swp`文件,然后编辑即可; +* `"+y`将Vim中的内容复制到系统剪切板; +* `"+p`将系统剪切板的内容拷贝到vim中(非编辑模式下)。 +*** +## 二、基本知识总结 +### 1、工作模式 +![1565964389885](assets/1565964389885.png) + +![1565964401993](assets/1565964401993.png) + +注意末行模式的常见命令: + +![1565964419977](assets/1565964419977.png) + +![1565964463324](assets/1565964463324.png) + +![1565964519436](assets/1565964519436.png) +### 2、移动、选中文本(可视化) +![1565964550546](assets/1565964550546.png) + +![1565964571182](assets/1565964571182.png) +### 3、撤销、恢复、删除 +![1565964583608](assets/1565964583608.png) +### 4、复制、粘贴、替换 +![1565964660005](assets/1565964660005.png) +### 5、 缩排、重复执行 +![1565964689620](assets/1565964689620.png) +### 6、查找、替换 +![1565964701388](assets/1565964701388.png) +### 7、查找、替换 +![1565964718679](assets/1565964718679.png) + + + +![1565964762783](assets/1565964762783.png) + +替换结果: + +![1565964782977](assets/1565964782977.png) + +### 8、插入命令的扩展 +![1565964805843](assets/1565964805843.png) + +插入命令的两个日常使用 + +![1565964820606](assets/1565964820606.png) + +### 9、分屏命令 +![1565964856379](assets/1565964856379.png) + +`Vim`默认的内置文件浏览器 + +![在这里插入图片描述](images/vim1.png) + +![1565964882432](assets/1565964882432.png) + +展示一个为目录,一个来编辑文件。 + +![1565964898514](assets/1565964898514.png) + +*** +## 三、命令表 + +### 1、移动光标 +![1565964919850](assets/1565964919850.png) + +![1565964931958](assets/1565964931958.png) + +### 2、搜寻与取代 +![1565964942539](assets/1565964942539.png) + +### 3、删除、复制、粘贴 +![1565964957118](assets/1565964957118.png) +![1565964968950](assets/1565964968950.png) + +### 4、插入模式 +![1565964982096](assets/1565964982096.png) +### 5、末行模式命令 +![1565964998863](assets/1565964998863.png) +### 6、区块选择、分屏 +![1565965012509](assets/1565965012509.png) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181106154533295.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) diff --git a/Basics/OS/Linux/assets/0417_WTD_Linux_F1.gif b/Basics/OS/Linux/assets/0417_WTD_Linux_F1.gif new file mode 100644 index 00000000..197832fe Binary files /dev/null and b/Basics/OS/Linux/assets/0417_WTD_Linux_F1.gif differ diff --git a/Basics/OS/Linux/assets/1555398889781.png b/Basics/OS/Linux/assets/1555398889781.png new file mode 100644 index 00000000..c79e43ff Binary files /dev/null and b/Basics/OS/Linux/assets/1555398889781.png differ diff --git a/Basics/OS/Linux/assets/1555860683719.png b/Basics/OS/Linux/assets/1555860683719.png new file mode 100644 index 00000000..9f53c877 Binary files /dev/null and b/Basics/OS/Linux/assets/1555860683719.png differ diff --git a/Basics/OS/Linux/assets/1555860724895.png b/Basics/OS/Linux/assets/1555860724895.png new file mode 100644 index 00000000..876b365e Binary files /dev/null and b/Basics/OS/Linux/assets/1555860724895.png differ diff --git a/Basics/OS/Linux/assets/1555860739978.png b/Basics/OS/Linux/assets/1555860739978.png new file mode 100644 index 00000000..ac571151 Binary files /dev/null and b/Basics/OS/Linux/assets/1555860739978.png differ diff --git a/Basics/OS/Linux/assets/1555862651653.png b/Basics/OS/Linux/assets/1555862651653.png new file mode 100644 index 00000000..a9e27f02 Binary files /dev/null and b/Basics/OS/Linux/assets/1555862651653.png differ diff --git a/Basics/OS/Linux/assets/1555862653942.png b/Basics/OS/Linux/assets/1555862653942.png new file mode 100644 index 00000000..a9e27f02 Binary files /dev/null and b/Basics/OS/Linux/assets/1555862653942.png differ diff --git a/Basics/OS/Linux/assets/1555862665632.png b/Basics/OS/Linux/assets/1555862665632.png new file mode 100644 index 00000000..4c15d9bd Binary files /dev/null and b/Basics/OS/Linux/assets/1555862665632.png differ diff --git a/Basics/OS/Linux/assets/1565964389885.png b/Basics/OS/Linux/assets/1565964389885.png new file mode 100644 index 00000000..f3e52778 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964389885.png differ diff --git a/Basics/OS/Linux/assets/1565964401993.png b/Basics/OS/Linux/assets/1565964401993.png new file mode 100644 index 00000000..88809b15 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964401993.png differ diff --git a/Basics/OS/Linux/assets/1565964419977.png b/Basics/OS/Linux/assets/1565964419977.png new file mode 100644 index 00000000..0eeaca5d Binary files /dev/null and b/Basics/OS/Linux/assets/1565964419977.png differ diff --git a/Basics/OS/Linux/assets/1565964463324.png b/Basics/OS/Linux/assets/1565964463324.png new file mode 100644 index 00000000..78611fc1 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964463324.png differ diff --git a/Basics/OS/Linux/assets/1565964519436.png b/Basics/OS/Linux/assets/1565964519436.png new file mode 100644 index 00000000..d0e53c39 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964519436.png differ diff --git a/Basics/OS/Linux/assets/1565964550546.png b/Basics/OS/Linux/assets/1565964550546.png new file mode 100644 index 00000000..18bf75c1 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964550546.png differ diff --git a/Basics/OS/Linux/assets/1565964571182.png b/Basics/OS/Linux/assets/1565964571182.png new file mode 100644 index 00000000..bf93f3ab Binary files /dev/null and b/Basics/OS/Linux/assets/1565964571182.png differ diff --git a/Basics/OS/Linux/assets/1565964583608.png b/Basics/OS/Linux/assets/1565964583608.png new file mode 100644 index 00000000..ef850e61 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964583608.png differ diff --git a/Basics/OS/Linux/assets/1565964660005.png b/Basics/OS/Linux/assets/1565964660005.png new file mode 100644 index 00000000..4da8c880 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964660005.png differ diff --git a/Basics/OS/Linux/assets/1565964689620.png b/Basics/OS/Linux/assets/1565964689620.png new file mode 100644 index 00000000..79ae0186 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964689620.png differ diff --git a/Basics/OS/Linux/assets/1565964701388.png b/Basics/OS/Linux/assets/1565964701388.png new file mode 100644 index 00000000..334fe757 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964701388.png differ diff --git a/Basics/OS/Linux/assets/1565964718679.png b/Basics/OS/Linux/assets/1565964718679.png new file mode 100644 index 00000000..57ad6a68 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964718679.png differ diff --git a/Basics/OS/Linux/assets/1565964762783.png b/Basics/OS/Linux/assets/1565964762783.png new file mode 100644 index 00000000..fe950ef2 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964762783.png differ diff --git a/Basics/OS/Linux/assets/1565964782977.png b/Basics/OS/Linux/assets/1565964782977.png new file mode 100644 index 00000000..bc6f564b Binary files /dev/null and b/Basics/OS/Linux/assets/1565964782977.png differ diff --git a/Basics/OS/Linux/assets/1565964805843.png b/Basics/OS/Linux/assets/1565964805843.png new file mode 100644 index 00000000..8a1f95b9 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964805843.png differ diff --git a/Basics/OS/Linux/assets/1565964820606.png b/Basics/OS/Linux/assets/1565964820606.png new file mode 100644 index 00000000..7e2ba1b9 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964820606.png differ diff --git a/Basics/OS/Linux/assets/1565964856379.png b/Basics/OS/Linux/assets/1565964856379.png new file mode 100644 index 00000000..1c3e43a0 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964856379.png differ diff --git a/Basics/OS/Linux/assets/1565964882432.png b/Basics/OS/Linux/assets/1565964882432.png new file mode 100644 index 00000000..ece10978 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964882432.png differ diff --git a/Basics/OS/Linux/assets/1565964898514.png b/Basics/OS/Linux/assets/1565964898514.png new file mode 100644 index 00000000..f153ddf6 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964898514.png differ diff --git a/Basics/OS/Linux/assets/1565964919850.png b/Basics/OS/Linux/assets/1565964919850.png new file mode 100644 index 00000000..48290738 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964919850.png differ diff --git a/Basics/OS/Linux/assets/1565964931958.png b/Basics/OS/Linux/assets/1565964931958.png new file mode 100644 index 00000000..6d620f38 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964931958.png differ diff --git a/Basics/OS/Linux/assets/1565964942539.png b/Basics/OS/Linux/assets/1565964942539.png new file mode 100644 index 00000000..092a3be2 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964942539.png differ diff --git a/Basics/OS/Linux/assets/1565964957118.png b/Basics/OS/Linux/assets/1565964957118.png new file mode 100644 index 00000000..232bfcd2 Binary files /dev/null and b/Basics/OS/Linux/assets/1565964957118.png differ diff --git a/Basics/OS/Linux/assets/1565964968950.png b/Basics/OS/Linux/assets/1565964968950.png new file mode 100644 index 00000000..95271d8b Binary files /dev/null and b/Basics/OS/Linux/assets/1565964968950.png differ diff --git a/Basics/OS/Linux/assets/1565964982096.png b/Basics/OS/Linux/assets/1565964982096.png new file mode 100644 index 00000000..ac449f0d Binary files /dev/null and b/Basics/OS/Linux/assets/1565964982096.png differ diff --git a/Basics/OS/Linux/assets/1565964996880.png b/Basics/OS/Linux/assets/1565964996880.png new file mode 100644 index 00000000..7041850c Binary files /dev/null and b/Basics/OS/Linux/assets/1565964996880.png differ diff --git a/Basics/OS/Linux/assets/1565964998863.png b/Basics/OS/Linux/assets/1565964998863.png new file mode 100644 index 00000000..7041850c Binary files /dev/null and b/Basics/OS/Linux/assets/1565964998863.png differ diff --git a/Basics/OS/Linux/assets/1565965012509.png b/Basics/OS/Linux/assets/1565965012509.png new file mode 100644 index 00000000..de2ff778 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965012509.png differ diff --git a/Basics/OS/Linux/assets/1565965143246.png b/Basics/OS/Linux/assets/1565965143246.png new file mode 100644 index 00000000..16dc163e Binary files /dev/null and b/Basics/OS/Linux/assets/1565965143246.png differ diff --git a/Basics/OS/Linux/assets/1565965155955.png b/Basics/OS/Linux/assets/1565965155955.png new file mode 100644 index 00000000..5cd8e1a9 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965155955.png differ diff --git a/Basics/OS/Linux/assets/1565965165001.png b/Basics/OS/Linux/assets/1565965165001.png new file mode 100644 index 00000000..54712677 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965165001.png differ diff --git a/Basics/OS/Linux/assets/1565965176041.png b/Basics/OS/Linux/assets/1565965176041.png new file mode 100644 index 00000000..01cf7b92 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965176041.png differ diff --git a/Basics/OS/Linux/assets/1565965186170.png b/Basics/OS/Linux/assets/1565965186170.png new file mode 100644 index 00000000..17953e93 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965186170.png differ diff --git a/Basics/OS/Linux/assets/1565965203591.png b/Basics/OS/Linux/assets/1565965203591.png new file mode 100644 index 00000000..ac2fd901 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965203591.png differ diff --git a/Basics/OS/Linux/assets/1565965223743.png b/Basics/OS/Linux/assets/1565965223743.png new file mode 100644 index 00000000..cd12f944 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965223743.png differ diff --git a/Basics/OS/Linux/assets/1565965237259.png b/Basics/OS/Linux/assets/1565965237259.png new file mode 100644 index 00000000..8da68d21 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965237259.png differ diff --git a/Basics/OS/Linux/assets/1565965248252.png b/Basics/OS/Linux/assets/1565965248252.png new file mode 100644 index 00000000..431b2253 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965248252.png differ diff --git a/Basics/OS/Linux/assets/1565965258142.png b/Basics/OS/Linux/assets/1565965258142.png new file mode 100644 index 00000000..531c2f6f Binary files /dev/null and b/Basics/OS/Linux/assets/1565965258142.png differ diff --git a/Basics/OS/Linux/assets/1565965268817.png b/Basics/OS/Linux/assets/1565965268817.png new file mode 100644 index 00000000..1737b9b0 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965268817.png differ diff --git a/Basics/OS/Linux/assets/1565965284384.png b/Basics/OS/Linux/assets/1565965284384.png new file mode 100644 index 00000000..1267fa25 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965284384.png differ diff --git a/Basics/OS/Linux/assets/1565965291661.png b/Basics/OS/Linux/assets/1565965291661.png new file mode 100644 index 00000000..12284add Binary files /dev/null and b/Basics/OS/Linux/assets/1565965291661.png differ diff --git a/Basics/OS/Linux/assets/1565965300119.png b/Basics/OS/Linux/assets/1565965300119.png new file mode 100644 index 00000000..6fd2fd36 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965300119.png differ diff --git a/Basics/OS/Linux/assets/1565965311606.png b/Basics/OS/Linux/assets/1565965311606.png new file mode 100644 index 00000000..878c5adf Binary files /dev/null and b/Basics/OS/Linux/assets/1565965311606.png differ diff --git a/Basics/OS/Linux/assets/1565965320720.png b/Basics/OS/Linux/assets/1565965320720.png new file mode 100644 index 00000000..fedbbb4c Binary files /dev/null and b/Basics/OS/Linux/assets/1565965320720.png differ diff --git a/Basics/OS/Linux/assets/1565965332005.png b/Basics/OS/Linux/assets/1565965332005.png new file mode 100644 index 00000000..b46474d3 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965332005.png differ diff --git a/Basics/OS/Linux/assets/1565965361697.png b/Basics/OS/Linux/assets/1565965361697.png new file mode 100644 index 00000000..10a3e951 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965361697.png differ diff --git a/Basics/OS/Linux/assets/1565965369608.png b/Basics/OS/Linux/assets/1565965369608.png new file mode 100644 index 00000000..1b3e3d27 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965369608.png differ diff --git a/Basics/OS/Linux/assets/1565965384666.png b/Basics/OS/Linux/assets/1565965384666.png new file mode 100644 index 00000000..de553603 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965384666.png differ diff --git a/Basics/OS/Linux/assets/1565965396320.png b/Basics/OS/Linux/assets/1565965396320.png new file mode 100644 index 00000000..f01db45d Binary files /dev/null and b/Basics/OS/Linux/assets/1565965396320.png differ diff --git a/Basics/OS/Linux/assets/1565965408320.png b/Basics/OS/Linux/assets/1565965408320.png new file mode 100644 index 00000000..dec6ffa8 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965408320.png differ diff --git a/Basics/OS/Linux/assets/1565965420202.png b/Basics/OS/Linux/assets/1565965420202.png new file mode 100644 index 00000000..e34bf2b9 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965420202.png differ diff --git a/Basics/OS/Linux/assets/1565965432327.png b/Basics/OS/Linux/assets/1565965432327.png new file mode 100644 index 00000000..b7c345f9 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965432327.png differ diff --git a/Basics/OS/Linux/assets/1565965449209.png b/Basics/OS/Linux/assets/1565965449209.png new file mode 100644 index 00000000..dbc43b9a Binary files /dev/null and b/Basics/OS/Linux/assets/1565965449209.png differ diff --git a/Basics/OS/Linux/assets/1565965458143.png b/Basics/OS/Linux/assets/1565965458143.png new file mode 100644 index 00000000..7123e98f Binary files /dev/null and b/Basics/OS/Linux/assets/1565965458143.png differ diff --git a/Basics/OS/Linux/assets/1565965473891.png b/Basics/OS/Linux/assets/1565965473891.png new file mode 100644 index 00000000..58bddbfd Binary files /dev/null and b/Basics/OS/Linux/assets/1565965473891.png differ diff --git a/Basics/OS/Linux/assets/1565965480817.png b/Basics/OS/Linux/assets/1565965480817.png new file mode 100644 index 00000000..91bbcc9c Binary files /dev/null and b/Basics/OS/Linux/assets/1565965480817.png differ diff --git a/Basics/OS/Linux/assets/1565965489218.png b/Basics/OS/Linux/assets/1565965489218.png new file mode 100644 index 00000000..dc8c2d48 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965489218.png differ diff --git a/Basics/OS/Linux/assets/1565965498772.png b/Basics/OS/Linux/assets/1565965498772.png new file mode 100644 index 00000000..c8858358 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965498772.png differ diff --git a/Basics/OS/Linux/assets/1565965513219.png b/Basics/OS/Linux/assets/1565965513219.png new file mode 100644 index 00000000..8a623a22 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965513219.png differ diff --git a/Basics/OS/Linux/assets/1565965534442.png b/Basics/OS/Linux/assets/1565965534442.png new file mode 100644 index 00000000..54977cab Binary files /dev/null and b/Basics/OS/Linux/assets/1565965534442.png differ diff --git a/Basics/OS/Linux/assets/1565965545645.png b/Basics/OS/Linux/assets/1565965545645.png new file mode 100644 index 00000000..ab328c02 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965545645.png differ diff --git a/Basics/OS/Linux/assets/1565965555263.png b/Basics/OS/Linux/assets/1565965555263.png new file mode 100644 index 00000000..026f4a4a Binary files /dev/null and b/Basics/OS/Linux/assets/1565965555263.png differ diff --git a/Basics/OS/Linux/assets/1565965563100.png b/Basics/OS/Linux/assets/1565965563100.png new file mode 100644 index 00000000..ae84a92c Binary files /dev/null and b/Basics/OS/Linux/assets/1565965563100.png differ diff --git a/Basics/OS/Linux/assets/1565965572081.png b/Basics/OS/Linux/assets/1565965572081.png new file mode 100644 index 00000000..24d6d80a Binary files /dev/null and b/Basics/OS/Linux/assets/1565965572081.png differ diff --git a/Basics/OS/Linux/assets/1565965587721.png b/Basics/OS/Linux/assets/1565965587721.png new file mode 100644 index 00000000..d90ebb9f Binary files /dev/null and b/Basics/OS/Linux/assets/1565965587721.png differ diff --git a/Basics/OS/Linux/assets/1565965599330.png b/Basics/OS/Linux/assets/1565965599330.png new file mode 100644 index 00000000..f154011c Binary files /dev/null and b/Basics/OS/Linux/assets/1565965599330.png differ diff --git a/Basics/OS/Linux/assets/1565965609443.png b/Basics/OS/Linux/assets/1565965609443.png new file mode 100644 index 00000000..4af4b634 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965609443.png differ diff --git a/Basics/OS/Linux/assets/1565965618673.png b/Basics/OS/Linux/assets/1565965618673.png new file mode 100644 index 00000000..19a9ac83 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965618673.png differ diff --git a/Basics/OS/Linux/assets/1565965631860.png b/Basics/OS/Linux/assets/1565965631860.png new file mode 100644 index 00000000..0d2d93c4 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965631860.png differ diff --git a/Basics/OS/Linux/assets/1565965639025.png b/Basics/OS/Linux/assets/1565965639025.png new file mode 100644 index 00000000..56002653 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965639025.png differ diff --git a/Basics/OS/Linux/assets/1565965655394.png b/Basics/OS/Linux/assets/1565965655394.png new file mode 100644 index 00000000..323275ed Binary files /dev/null and b/Basics/OS/Linux/assets/1565965655394.png differ diff --git a/Basics/OS/Linux/assets/1565965665213.png b/Basics/OS/Linux/assets/1565965665213.png new file mode 100644 index 00000000..892ce4f0 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965665213.png differ diff --git a/Basics/OS/Linux/assets/1565965672450.png b/Basics/OS/Linux/assets/1565965672450.png new file mode 100644 index 00000000..4905fd14 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965672450.png differ diff --git a/Basics/OS/Linux/assets/1565965684515.png b/Basics/OS/Linux/assets/1565965684515.png new file mode 100644 index 00000000..22aae909 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965684515.png differ diff --git a/Basics/OS/Linux/assets/1565965699155.png b/Basics/OS/Linux/assets/1565965699155.png new file mode 100644 index 00000000..67496de4 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965699155.png differ diff --git a/Basics/OS/Linux/assets/1565965706514.png b/Basics/OS/Linux/assets/1565965706514.png new file mode 100644 index 00000000..d4e46c58 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965706514.png differ diff --git a/Basics/OS/Linux/assets/1565965718604.png b/Basics/OS/Linux/assets/1565965718604.png new file mode 100644 index 00000000..a0026583 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965718604.png differ diff --git a/Basics/OS/Linux/assets/1565965726451.png b/Basics/OS/Linux/assets/1565965726451.png new file mode 100644 index 00000000..452ffdca Binary files /dev/null and b/Basics/OS/Linux/assets/1565965726451.png differ diff --git a/Basics/OS/Linux/assets/1565965734612.png b/Basics/OS/Linux/assets/1565965734612.png new file mode 100644 index 00000000..3e737742 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965734612.png differ diff --git a/Basics/OS/Linux/assets/1565965744510.png b/Basics/OS/Linux/assets/1565965744510.png new file mode 100644 index 00000000..91612951 Binary files /dev/null and b/Basics/OS/Linux/assets/1565965744510.png differ diff --git a/Basics/OS/Linux/assets/393890-20151128142803015-292063645.png b/Basics/OS/Linux/assets/393890-20151128142803015-292063645.png new file mode 100644 index 00000000..d5bc07fc Binary files /dev/null and b/Basics/OS/Linux/assets/393890-20151128142803015-292063645.png differ diff --git a/Basics/OS/Linux/images/linux1.png b/Basics/OS/Linux/images/linux1.png new file mode 100644 index 00000000..8b7c9dc3 Binary files /dev/null and b/Basics/OS/Linux/images/linux1.png differ diff --git a/Basics/OS/Linux/images/linux2.png b/Basics/OS/Linux/images/linux2.png new file mode 100644 index 00000000..86bd9b5e Binary files /dev/null and b/Basics/OS/Linux/images/linux2.png differ diff --git a/Basics/OS/Linux/images/linux3.png b/Basics/OS/Linux/images/linux3.png new file mode 100644 index 00000000..38747578 Binary files /dev/null and b/Basics/OS/Linux/images/linux3.png differ diff --git a/Basics/OS/Linux/images/linux4.png b/Basics/OS/Linux/images/linux4.png new file mode 100644 index 00000000..3f4074b3 Binary files /dev/null and b/Basics/OS/Linux/images/linux4.png differ diff --git a/Basics/OS/Linux/images/vim1.png b/Basics/OS/Linux/images/vim1.png new file mode 100644 index 00000000..a440f35b Binary files /dev/null and b/Basics/OS/Linux/images/vim1.png differ diff --git "a/Basics/OS/OsBasic/1_\345\237\272\346\234\254\346\246\202\345\277\265\343\200\201\347\263\273\347\273\237\350\260\203\347\224\250\343\200\201\344\270\255\346\226\255.md" "b/Basics/OS/OsBasic/1_\345\237\272\346\234\254\346\246\202\345\277\265\343\200\201\347\263\273\347\273\237\350\260\203\347\224\250\343\200\201\344\270\255\346\226\255.md" new file mode 100644 index 00000000..23e2adf6 --- /dev/null +++ "b/Basics/OS/OsBasic/1_\345\237\272\346\234\254\346\246\202\345\277\265\343\200\201\347\263\273\347\273\237\350\260\203\347\224\250\343\200\201\344\270\255\346\226\255.md" @@ -0,0 +1,136 @@ + +# 操作系统总结 - 基本概念、系统调用、中断(一) + + +* [一、操作系统基本概念和作用](#一操作系统基本概念和作用) +* [二、操作系统特征](#二、操作系统特征) +* [三、操作系统分类](#三操作系统分类) +* [四、系统调用](#四、系统调用) +* [五、操作系统的体系结构(大内核微内核)](#五操作系统的体系结构(大内核微内核)) +* [六、中断和异常](#六中断和异常) + +*** +## 一、操作系统基本概念和作用 + +* 基本概念: 操作系统是指控制和管理整个计算机系统的硬件和软件资源,并合理的组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境。它是计算机系统的最基本的**系统软件**。 +* 是系统**软硬资源的管理控制中心**,它以尽量合理有效的方法组织多个用户(进程)共享计算机的各种资源并提供使用接口。 + +图: + +![1564666591843](assets/1564666591843.png) + + + +![1564669510548](assets/1564669510548.png) + + + +## 二、操作系统特征 + +![1564669559612](assets/1564669559612.png) + + + +![1564670857759](assets/1564670857759.png) + +*** +## 三、操作系统分类 + +![1564670940081](assets/1564670940081.png) + +**注意:单批道操作系统和多批道操作系统(实现多道程序并发执行)的区别**: + +![1564671005552](assets/1564671005552.png) + +例题: 试述多道程序设计技术的基本思想。为什么采用多道程序设计技术可以提高资源利用率? + +>答:多道程序设计技术的基本思想是,在主存同时保持多道程序,主机以交替的方式同时处理多道程序。从宏观上看,主机内同时保持和处理若干道已开始运行但尚未结束的程序。从微观上看,某一时刻处理机只运行某道程序。 +可以提高资源利用率的原因:由于任何一道作业的运行总是交替地串行使用CPU、外设等资源,即使用一段时间的CPU,然后使用一段时间的I/O设备,由于采用多道程序设计技术,加之对多道程序实施合理的运行调度,则可以实现CPU和I/O设备的高度并行,可以大大提高CPU与外设的利用率。 + +例题: 什么是分时系统?其主要特征是什么?适用于哪些应用? +>答:分时系统是以多道程序设计技术为基础的交互式系统,在此系统中,一台计算机与多台终端相连接,用户通过各自的终端和终端命令以交互的方式使用计算机系统。每个用户都感觉到好像是自己在独占计算机系统,而在系统内部则由操作系统以时间片轮转的方式负责协调多个用户分享CPU。 +主要特征是: +**并行性**:系统能协调多个终端用户同时使用计算机系统,能控制多道程序同时运行。 +**共享性**:对资源而言,系统在宏观上使各终端用户共享计算机系统中的各种资源,而在微观上它们则分时使用这些资源。 +**交互性**:人与计算机以交互的方式进行工作。 +**独占性**:使用户感觉到他在独占使用计算机。 +现在的系统大部分都是分时系统,主要应用于人机交互的方面。 + +*** + ## 四、系统调用 +如果一个进程在用户态需要使用内核态的功能,就进行系统调用从而陷入内核,由操作系统代为完成 。 + +![1564671145789](assets/1564671145789.png) + +系统调用的通俗解释: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181210234418541.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +简单的来说系统调用就是: 如果一个进程在用户态需要使用内核态的功能,就进行系统调用从而陷入内核,由操作系统代为完成。`Linux`的系统调用主要有以下这些: + +![1564671253555](assets/1564671253555.png) + +![1564671363367](assets/1564671363367.png) + +*** + ## 五、操作系统的体系结构(大内核微内核) +![1564671643577](assets/1564671643577.png) + + + +![1564671754319](assets/1564671754319.png) + +![1564671813302](assets/1564671813302.png) + +大内核是将操作系统功能作为一个紧密结合的整体放到内核。由于各模块共享信息,因此有很高的性能。 + +在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。 + +*** +## 六、中断和异常 +* 中断: 是指来自`CPU`执行指令以外的事件发生后,处理机暂停正在运行的程序,转去执行处理该事件的程序的过程。 +* 异常: 是指源自`CPU`执行指令内部的事件发生后,处理机暂停正在执行的程序,转去处理该事件的过程。 +* 这两者的区别:广义的中断包括中断和异常,统一称为中断。狭义的中断(外中断,平常说的中断)和异常的区别在于是否与正在执行的指令有关,中断可以屏蔽,而异常不可屏蔽。 + +![1564672024820](assets/1564672024820.png) + + + +![1564672061391](assets/1564672061391.png) + + + +![1564672085529](assets/1564672085529.png) + +分类: +* 外中断: 由 CPU 执行指令以外的事件引起,如`I/O`完成中断,表示设备输入/输出处理已经完成,处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。 +* 内中断: ①异常: 由 CPU 执行指令的内部事件引起,如非法操作码、地址越界、算术溢出等。②陷入: 在用户程序中使用系统调用。 + +![1564672124791](assets/1564672124791.png) + +总结中断、异常和系统调用: +|类型|源头|响应方式|处理机制| +|--|--|--|--| +|中断(外中断)|外设|异步|持续、对用户应用程序是透明的| +|异常(内中断)|应用程序未知的行为|同步|杀死或重新执行这些未知的应用程序指令| +|系统调用|应用程序请求操作系统提供服务|异步或者同步|等待和持续| +![1564672143996](assets/1564672143996.png) + +例题: 什么是中断向量?其内容是什么?试述中断的处理过程。 + +> 答:中断向量:为处理方便,一般为系统中每个中断信号编制一个相应的中断处理程序,并把这些程序的入口地址放在特定的主存单元中。通常将这一片存放中断处理程序入口地址的主存单元称为中断向量。 +中断向量的内容:对不同的系统,中断向量中的内容也不尽相同。一般每一个中断信号占用连续的两个单元:一个用来存放中断处理程序的入口地址,另一个用来保存在处理中断时CPU应具有的状态。 +中断的处理过程:一般包括保存现场,分析中断原因,进入相应的中断处理程序,最后重新选择程序运行,恢复现场等过程。 + + + +例题: 为什么要把中断分级?如何设定中断的优先级?试述多级中断的处理原则。 (有关中断优先级) + +> 答:为什么要把中断分级:在计算机系统中,不同的中断源可能在同一时刻向CPU发出不同的中断信号,也可能前一中断尚未处理完,紧接着又发生了新的中断。此时,存在谁先被响应和谁先被处理的优先次序问题。为了使系统能及时地响应和处理所发生的紧急中断,根据中断的轻重缓急,对各类中断规定了高低不同的响应级别。 +如何设定中断的优先级:中断分级的原则是根据中断的轻重缓急来排序,把紧迫程度大致相当的中断源归并在同一级,而把紧迫程度差别较大的中断源放在不同的级别。一般来说,高速设备的中断优先级高,慢速设备的中断优先级低。 +多级中断的处理原则:当多级中断同时发生时,CPU按照由高到低的顺序响应。高级中断可以打断低级中断处理程序的运行,转而执行高级中断处理程序。当同级中断同时到时,则按位响应。 + + + +*** + diff --git "a/Basics/OS/OsBasic/2_\350\277\233\347\250\213\346\246\202\345\277\265\343\200\201\350\277\233\347\250\213\345\244\204\347\220\206\346\234\272\350\260\203\345\272\246.md" "b/Basics/OS/OsBasic/2_\350\277\233\347\250\213\346\246\202\345\277\265\343\200\201\350\277\233\347\250\213\345\244\204\347\220\206\346\234\272\350\260\203\345\272\246.md" new file mode 100644 index 00000000..76b37b6e --- /dev/null +++ "b/Basics/OS/OsBasic/2_\350\277\233\347\250\213\346\246\202\345\277\265\343\200\201\350\277\233\347\250\213\345\244\204\347\220\206\346\234\272\350\260\203\345\272\246.md" @@ -0,0 +1,317 @@ +# 操作系统总结 - 进程概念、进程处理机调度(二) + +* [一、进程的定义、组成、组织、特征](#一进程的定义组成组织特征) +* [二、进程的状态与转换](#二进程的状态与转换) +* [三、进程控制](#三进程控制) +* [四、进程通信](#四进程通信) +* [五、线程、多线程概念模型](#五线程多线程概念模型) +* [六、处理机调度的概念、层次](#六处理机调度的概念层次) +* [七、进程调度的时机、切换与过程、方式](#七进程调度的时机切换与过程方式) +* [八、调度算法的评价指标](#八调度算法的评价指标) +* [九、调度算法:先来先服务、最短作业优先、最高响应比优先](#九调度算法先来先服务最短作业优先最高响应比优先) +* [十、调度算法:时间片轮转、优先级、多级反馈队列](#十调度算法时间片轮转优先级多级反馈队列) + +*** +## 一、进程的定义、组成、组织、特征 + +**进程定义** + +* 进程的定义: 进程是支持程序执行的机制,是程序针对某一数据集合的执行过程。 +* 进程可以理解为程序对数据或请求的处理过程。 + +![1564705309384](assets/1564705309384.png) + + + +![1564705469489](assets/1564705469489.png) + + + + +**进程(进程实体)的组成: 程序段、数据段、PCB** + +![1564705494336](assets/1564705494336.png) + + + +* `PCB`(进程管理块): 为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(`Process Control Block`)。它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,在创建进程时,建立PCB,伴随进程运行的全过程,直到进程撤消而撤消。所谓的创建进程和撤销进程,都是指对 PCB 的操作。 +* 程序段: 存放执行的代码; +* 数据段: 存放程序运行过程中处理的各种数据; + + +例题: 进程控制块的作用是什么?PCB中应包括哪些信息? + +> 答:进程控制块的作用是:进程控制块用于保存每个进程和资源的相关信息,包括进程标识、空间、运行状态、资源等信息。以便于操作系统管理和控制进程和资源。 +`PCB`中应包括:1、进程标识信息:本进程的标识、父进程的标识、进程所属用户的标识。2、处理机状态信息。保存进程的运行现场信息,包括用户可用寄存器的信息;控制和状态寄存器的信息;栈指针。 + +**进程的组织方式** + +![1564705815567](assets/1564705815567.png) + +![1564705846740](assets/1564705846740.png) + +**进程特征** + +![1564706216905](assets/1564706216905.png) + +**本节小结** + +![1564706421144](assets/1564706421144.png) + +*** + +## 二、进程的状态与转换 +![1564706934865](assets/1564706934865.png) + +**状态转换**: + +![1564707018939](assets/1564707018939.png) + +注意以下内容: +* 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态; +* 而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态; +* 进程只能自己阻塞自己,因为只有进程自身才知道何时需要等待某种事件的发生; + +**本节小结**: + +![1564707081934](assets/1564707081934.png) + +例题: 进程创建的主要工作是什么? +> 答:进程创建时的主要工作如下: +1、接收进程运行现场初始值,初始优先级,初始执行程序描述,其它资源等参数。 +2、请求分配进程描述块PCB空间,得到一个内部数字进程标识。 +3、用从父进程传来的参数初始化PCB表。 +4、产生描述进程空间的数据结构,用初始执行文件初始化进程空间,建立程序段,数据段、栈段等。 +5、用进程运行现场初始值设置处理机现场保护区。造一个进程运行栈帧。 +6、置好父进程等关系域。 +7、将进程置成就绪状态。 +8、将PCB表挂入就绪队列,等待时机被调度运行 +*** + +## 三、进程控制 + +**进程控制的主要功能是**: + +* 对系统中所有的进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能;(简单理解就是进程状态转换) +* 主要通过就绪队列和阻塞队列来切换线程状态; + +![1564743273635](assets/1564743273635.png) + +![1564744515435](assets/1564744515435.png) + + + +![1564744699297](assets/1564744699297.png) + +例题: 何谓原语?它与系统调用有何区别?如何实现原语执行的不可分割性? +> 答:**原语是指完成某种功能且不被分割、不被中断执行的操作序列**。有时也称为原子操作。 + 它与系统调用的区别:原语和系统调用是两个不同的概念,原语主要强调操作的不可分割性,可以认为是一个不可中断的子程序调用,但是系统调用是由用户态进入核心态,虽然系统调用一般也不被中断,但是如果有更高更紧迫的系统调用的话,还是能够打断原来的系统调用的。 + 实现原语执行的不可分割性:通常由硬件来实现,也可以由软件通过中断屏蔽的方法来实现。 + +例题: 如果P,V操作不作为原语(可分割执行),那么是否还可用于解决互斥问题?如果不能,则举例说明。 +> 答:如果`P,V`操作不作为原语,那么不可用于解决互斥问题。因为如果那样的话,则:程序语言`s = s-1`;翻译成机器语言为:`load R1, s; load R2, 1; sub R1, R2;` 此时,他们之间的操作可以分割执行,假设有两个进程`P1`、`P2`,`s`初值为`1`,当`P1`进入`P`操作时,`s`大于`0`,可以进入,因此会执行上面的机器语言,将`s`的值取出来,放入`R1`寄存器中,而此时,有可能`P2`进程要进入临界段,因此,它也比较`s`的值是否小于`0`,因为此时`s`的值仍为`1`,所以`P2`也进入临界段,出现错误。 + +**注意阻塞原语和唤醒原语必须成对使用**。 + +![1564744891166](assets/1564744891166.png) + +**本节小结**: + +![1564744917574](assets/1564744917574.png) + +*** +## 四、进程通信 +**知识总览** : + +![1564745589934](assets/1564745589934.png) + +**进程通信引入**: + +![1564745607862](assets/1564745607862.png) + +**三种通信方式: 共享存储、 管道通信、消息传递** + +![1564745669863](assets/1564745669863.png) + +![1564745718470](assets/1564745718470.png) + +限制: + +* 只支持半双工通信(单向传输); +* 只能在父子进程中使用。 + +![1564745755239](assets/1564745755239.png) + +**本节小结** : + +![1564746139767](assets/1564746139767.png) + +*** + +## 五、线程、多线程概念模型 + +![1564901164705](assets/1564901164705.png) + +**引入线程机制带来的变化**: + +![1564901075525](assets/1564901075525.png) + + +* 进程是资源分配的基本单位。 +* 线程是独立调度的基本单位。 + +**线程属性**: + +![1564901511545](assets/1564901511545.png) + +**线程的实现方式: 用户级线程和内核级线程** + +![1564901539450](assets/1564901539450.png) + +![1564901560985](assets/1564901560985.png) + +**三种多线程模型**: + +![1564901595674](assets/1564901595674.png) + +**本节小结** + +注意进程和线程区别: + + * ①拥有资源: 进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源; + * ②调度: 线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。 + * ③系统开销: 由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。 + * ④通信: 进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。 + +![1564901613490](assets/1564901613490.png) +*** +## 六、处理机调度的概念、层次 + +**知识总览** + +![1564901920209](assets/1564901920209.png) + +![1564902128632](assets/1564902128632.png) + +**三种调度:高级调度(作业调度)、中级调度(内存调度)、低级调度(进程调度**) + +![1564902628377](assets/1564902628377.png) + +![1564902254761](assets/1564902254761.png) + +![1564902685329](assets/1564902685329.png) + +![1564906264238](assets/1564906264238.png) + +**本节小结** + +![1564906282123](assets/1564906282123.png) + +*** +## 七、进程调度的时机、切换与过程、方式 + +**知识总览** + +![1564906381292](assets/1564906381292.png) + +![1564906640159](assets/1564906640159.png) + +**由被动放弃处理机调度方式引出进程调度的方式: 非剥夺调度方式和剥夺调度方式** + +![1564906967285](assets/1564906967285.png) + +![1564907339172](assets/1564907339172.png) + +**本节小结**: + +![1564907414454](assets/1564907414454.png) + +*** +## 八、调度算法的评价指标 +**知识总览** + +![1564907624795](assets/1564907624795.png) + +**`CPU`利用率和系统吞吐量** + +![1564907646589](assets/1564907646589.png) +> 批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。 + +![1564907938534](assets/1564907938534.png) + +![1564908118174](assets/1564908118174.png) + +**本节小结** + +![1564908138788](assets/1564908138788.png) + +*** +## 九、调度算法:先来先服务、最短作业优先、最高响应比优先 +**知识总览** + +![1564908187083](assets/1564908187083.png) + +**第一种: 先来先服务(`FCFS`)** + +![1564909685101](assets/1564909685101.png) + +![1564909708414](assets/1564909708414.png) + +**第二种: 短作业优先** + +![1564909832949](assets/1564909832949.png) + +![1564909958234](assets/1564909958234.png) + +**最短剩余时间优先(短作业优先的抢占式版本)** + +![1564909980198](assets/1564909980198.png) + +![1564910443297](assets/1564910443297.png) + +**高响应比优先** + +![1564910464059](assets/1564910464059.png) + +![1564910551610](assets/1564910551610.png) + +**本节小结:三种算法对比** + +![1564910641514](assets/1564910641514.png) + +*** +## 十、调度算法:时间片轮转、优先级、多级反馈队列 +**时间片轮转** + +![1564910771514](assets/1564910771514.png) + + +![1564910891925](assets/1564910891925.png) + + + +![1564910946228](assets/1564910946228.png) + +**优先级调度算法** + +![1564910975684](assets/1564910975684.png) + +![1564910997690](assets/1564910997690.png) + +**抢占式的优先级调度** + +![1564911411008](assets/1564911411008.png) + +![1564911438521](assets/1564911438521.png) + +**多级反馈队列调度算法: 优先级+时间片** + +![1564911773787](assets/1564911773787.png) + +![1564912318712](assets/1564912318712.png) + +**本节小结** + +![1564912345520](assets/1564912345520.png) + +*** diff --git "a/Basics/OS/OsBasic/3_\350\277\233\347\250\213\345\220\214\346\255\245\343\200\201\351\200\232\344\277\241\343\200\201\346\255\273\351\224\201.md" "b/Basics/OS/OsBasic/3_\350\277\233\347\250\213\345\220\214\346\255\245\343\200\201\351\200\232\344\277\241\343\200\201\346\255\273\351\224\201.md" new file mode 100644 index 00000000..38ff4a19 --- /dev/null +++ "b/Basics/OS/OsBasic/3_\350\277\233\347\250\213\345\220\214\346\255\245\343\200\201\351\200\232\344\277\241\343\200\201\346\255\273\351\224\201.md" @@ -0,0 +1,438 @@ +# 操作系统总结 - 进程同步、通信、死锁(三) + +* [一、什么是进程同步、进程互斥](#一什么是进程同步进程互斥) +* [二、进程互斥的软件实现方法](#二进程互斥的软件实现方法) +* [三、进程互斥的硬件实现方法](#三进程互斥的硬件实现方法) +* [四、信号量机制](#四信号量机制) +* [五、用信号量实现进程互斥、同步、前驱关系](#五用信号量实现进程互斥同步前驱关系) +* [六、生产者-消费者问题](#六生产者-消费者问题) +* [七、多生产者-多消费者](#七多生产者-多消费者) +* [八、吸烟者问题](#八吸烟者问题) +* [九、读者-写者问题](#九读者-写者问题) +* [十、哲学家进餐问题](#十哲学家进餐问题) +* [十一、管程](#十一管程) +* [十二、死锁的概念](#十二死锁的概念) +* [十三、死锁的处理策略—预防死锁](#十三死锁的处理策略—预防死锁) +* [十四、死锁的处理策略—避免死锁(银行家算法)](#十四死锁的处理策略—避免死锁(银行家算法)) +* [十五、死锁的处理策略—死锁的检测与解除](#十五死锁的处理策略—死锁的检测与解除) + +*** + +## 一、什么是进程同步、进程互斥 + +**基本定义**: + +* 进程具有异步性的特征,异步性是指 : 各并发执行的进程以各自独立的、不可预知的速度向前推进(之前提到); +* 进程同步:指相互合作去完成相同的任务的进程间,由同步机构对执行次序进行协调。(在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。); +* 进程互斥:指多个进程在对临界资源进行访问的时候,应采用互斥方式; +* 简单来说,同步:多个进程按一定顺序执行;互斥:多个进程在同一时刻只有一个进程能进入临界区。 + +**进程同步** + +![1564994152870](assets/1564994152870.png) + +**进程互斥** + +![1565056520103](assets/1565056520103.png) + +![1565056546358](assets/1565056546358.png) + +**本节小结**: + +![1565056643566](assets/1565056643566.png) + +*** +## 二、进程互斥的软件实现方法 +![1565152593960](assets/1565152593960.png) + +![1565158406242](assets/1565158406242.png) + +![1565158785659](assets/1565158785659.png) + +![1565159124556](assets/1565159124556.png) + +![1565159426132](assets/1565159426132.png) + +**本节小结** + +![1565160868370](assets/1565160868370.png) + +*** +## 三、进程互斥的硬件实现方法 +主要有三种: + +* 中断屏蔽方法; +* TestAndSet( TS指令 / TSL指令) +* Swap指令 (XCHG指令) + +![1565162005635](assets/1565162005635.png) + +![1565162107623](assets/1565162107623.png) + +![1565162127265](assets/1565162127265.png) + +**本节小结** + +![1565162668815](assets/1565162668815.png) + +*** +## 四、信号量机制(`important`) +**知识总览以及问题引入** + +![1565162970457](assets/1565162970457.png) + +**两种信号量机制:整型(`S`是一个整形变量)和记录型(`S`在一个结构体中)** + +![1565163194994](assets/1565163194994.png) + +![1565163666787](assets/1565163666787.png) + +**下面看一个栗子的运行过程(重点)** + +**①初始化** + +![1565164013902](assets/1565164013902.png) + +**②、③ 分别给`P1`进程和`P2`进程分配资源,使得`S.value = 0`** + +![1565164532538](assets/1565164532538.png) + +**此时为`P3`、`P4`进程服务,但是剩余资源数为`S.value = -2`,所以只能进入等待队列** + +![1565164569520](assets/1565164569520.png) + +**所以`CPU`接下来只能为`P1`、`P2`服务,此时服务完之后调用 `signal`并`wake up`在等待队列中的`P3`、`P4`(每次空闲一个就唤醒等待队列中的一个)**(下图只画出将`P2`从队列中抽取出来,最后`P3`的过程也是一样的) + +![1565164590125](assets/1565164590125.png) + +![1565164628137](assets/1565164628137.png) + +**本节小结** + +![1565165120404](assets/1565165120404.png) + +*** +## 五、用信号量实现进程互斥、同步、前驱关系 + +信号量机制: + +* 实现进程互斥 +* 实现进程同步 +* 实现进程的前驱关系 + +![1565166566230](assets/1565166566230.png) + +![1565166589754](assets/1565166589754.png) + +![1565166740562](assets/1565166740562.png) + +**本节小结** + +![1565168502229](assets/1565168502229.png) + +*** +## 六、生产者-消费者问题 + +![1565171779448](assets/1565171779448.png) + +![1565232898568](assets/1565232898568.png) + +![1565243526838](assets/1565243526838.png) + +![1565243555856](assets/1565243555856.png) + +**本节小结** + +![1565244484013](assets/1565244484013.png) + +*** +## 七、多生产者-多消费者 + +![1565244574886](assets/1565244574886.png) + +![1565244775230](assets/1565244775230.png) + +![1565244877245](assets/1565244877245.png) + +![1565244904870](assets/1565244904870.png) + +![1565244983630](assets/1565244983630.png) + +**本节小结** + +![1565245006118](assets/1565245006118.png) + +*** +## 八、吸烟者问题 +![1565245873940](assets/1565245873940.png) + +![1565245928917](assets/1565245928917.png) + +![1565246079236](assets/1565246079236.png) + +![1565246102396](assets/1565246102396.png) + +*** +## 九、读者-写者问题 +![1565246328994](assets/1565246328994.png) + +![1565246439260](assets/1565246439260.png) + +![1565246591452](assets/1565246591452.png) + +代码实现: + +```c +Rcount = 0; // 当前有几个读进程在访问文件 +semaphore CountMutex = 1;// 用于保证对count变量的互斥访问 +semaphore WriteMutex = 1; // 用于实现对文件的互斥访问(写操作) + +void writer(){ + while(true){ + sem_wait(WriteMutex);// P + // TO DO write(); + sem_post(WriteMutex);// V + } +} + +// 读者优先策略 +void reader(){ + while(true){ + sem_wait(CountMutex); //P + if(Rcount == 0) // 第一个进程负责加锁 + sem_wait(WriteMutex); + Rcount++; + sem_post(CountMutex); //V + + // TO DO read(); + + sem_wait(CountMutex); + Rcount--; // 访问文件的读进程数-1 + if(Rcount == 0) + sem_post(WriteMutex); // 最后一个进程负责解锁 + sem_post(CountMutex); + } +} +``` +**防止写进程饿死的方法**: + +![1565246913387](assets/1565246913387.png) + +**本节小结** + +![1565246929714](assets/1565246929714.png) + +*** +## 十、哲学家进餐问题 +![1565247091643](assets/1565247091643.png) + +![1565247115868](assets/1565247115868.png) + +```c +//一种错误的解法,考虑到如果所有哲学家同时拿起左手边的筷子, +//那么就无法拿起右手边的筷子,造成死锁。 + +#define N 5 // 哲学家个数 +void philosopher(int i) // 哲学家编号:0 - 4 +{ + while(1) + { + think(); // 哲学家在思考 + take_fork(i); // 去拿左边的叉子 + take_fork((i + 1) % N); // 去拿右边的叉子 + eat(); // 吃饭 + put_fork(i); // 放下左边的叉子 + put_fork((i + 1) % N); // 放下右边的叉子 + } +} +``` + +三种方案 + +![1565247986227](assets/1565247986227.png) + +**第三种方案实现** + +![1565248038259](assets/1565248038259.png) + +为了防止死锁的发生,可以设置两个条件(临界资源): +* 必须同时拿起左右两根筷子; +* 只有在两个邻居都没有进餐的情况下才允许进餐。 + +实现思路: +```c +//1. 必须有一个数据结构,来描述每个哲学家当前的状态 +#define N 5 +#define LEFT i // 左邻居 +#define RIGHT (i + 1) % N // 右邻居 +#define THINKING 0 +#define HUNGRY 1 +#define EATING 2 +typedef int semaphore; +int state[N]; // 跟踪每个哲学家的状态 + +//2. 该状态是一个临界资源,对它的访问应该互斥地进行 +semaphore mutex = 1; // 临界区的互斥,互斥初始值一般是1 + +//3. 一个哲学家吃饱后,可能要唤醒邻居,存在着同步关系 +semaphore s[N]; // 每个哲学家一个信号量 + +void philosopher(int i) { + while(1) { + think(); // 思考 + take_two(i); // 拿起两个筷子 + eat(); + put_tow(i); + } +} + +//拿走两只筷子 +void take_two(int i) { + P(&mutex); // 进入临界区 + + state[i] = HUNGRY; // 我饿了 + try(i); // 试图拿两只筷子 + + V(&mutex); // 退出临界区 + P(&s[i]); // 没有筷子便阻塞 +} + +//放回两只筷子 +void put_tow(i) { + P(&mutex); + + state[i] = THINKING; + try(LEFT); // 左边的人尝试 + try(RIGHT); //右边的人尝试 + + V(&mutex); +} + +void try(i) { // 尝试拿起两把筷子 + if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) { + state[i] = EATING; + V(&s[i]); // 通知第i个人可以吃饭了 + } +} +``` + +**本节小结** +* 哲学家问题关键在于解决进程死锁; +* 这些进程之间只存在互斥关系,但是和之前的互斥关系不同的是: 每个进程都需要同时持有两个临界资源,因此有死锁的可能; +*** +## 十一、管程(高级同步机制) +**基本总结** + +* 为什么要引入管程 +* 管程的定义和基本特征 +* 拓展1: 用管程解决生产者消费者问题 +* 拓展2: Java中类似管程的机制 + +**引入** + +![1565248774361](assets/1565248774361.png) + +![1565248808896](assets/1565248808896.png) + +![1565248868738](assets/1565248868738.png) + +![1565249030385](assets/1565249030385.png) + +![1565249280650](assets/1565249280650.png) + +**本节小结** + +![1565249297402](assets/1565249297402.png) + +*** +## 十二、死锁的概念 +**知识总览** + +![1565249978225](assets/1565249978225.png) + +![1565307324781](assets/1565307324781.png) + +* 死锁: 如果一个进程集合里面的每个进程都在等待只能由这个集合中的其他一个进程(包括他自身)才能引发的事件,这种情况就是死锁。 + +![1565307354230](assets/1565307354230.png) + +![1565307377909](assets/1565307377909.png) + +* 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。 +* 不可剥夺:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。 +* 请求和保持:已经得到了某个资源的进程可以再请求新的资源。 +* 循环等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。 + +![1565308929135](assets/1565308929135.png) + +**本节小结** + +![1565309314853](assets/1565309314853.png) + +*** +## 十三、死锁的处理策略—预防死锁 +**知识总览** + +![1565309347427](assets/1565309347427.png) + +![1565310189361](assets/1565310189361.png) + +![1565310212866](assets/1565310212866.png) + +![1565310770571](assets/1565310770571.png) + +![1565310789425](assets/1565310789425.png) + +**本节小结** + +![1565310943347](assets/1565310943347.png) + +*** +## 十四、死锁的处理策略—避免死锁(银行家算法) +**知识总览** + +![1565311107760](assets/1565311107760.png) + +![1565311129619](assets/1565311129619.png) + +![1565311262841](assets/1565311262841.png) + +![1565311688801](assets/1565311688801.png) + +* 注意安全状态是只要找到一个安全序列即可。 + +**不会发生死锁的举例** + +![1565311904800](assets/1565311904800.png) + +![1565311934216](assets/1565311934216.png) + +![1565312054458](assets/1565312054458.png) + +**可能发生死锁的情况举例** + +![1565312383504](assets/1565312383504.png) + +**实现银行家算法** + +![1565312416553](assets/1565312416553.png) + +*** +## 十五、死锁的处理策略—死锁的检测与解除 +**知识总览** + +![1565312616767](assets/1565312616767.png) + +![1565312636547](assets/1565312636547.png) + +![1565312667296](assets/1565312667296.png) + +![1565312694837](assets/1565312694837.png) + +![1565312717623](assets/1565312717623.png) + +**本节小结** + +![1565312738111](assets/1565312738111.png) + +*** diff --git "a/Basics/OS/OsBasic/4_\345\206\205\345\255\230\347\256\241\347\220\206\343\200\201\350\256\276\345\244\207\347\256\241\347\220\206\343\200\201IO\343\200\201\346\226\207\344\273\266\347\263\273\347\273\237.md" "b/Basics/OS/OsBasic/4_\345\206\205\345\255\230\347\256\241\347\220\206\343\200\201\350\256\276\345\244\207\347\256\241\347\220\206\343\200\201IO\343\200\201\346\226\207\344\273\266\347\263\273\347\273\237.md" new file mode 100644 index 00000000..cd8f93ff --- /dev/null +++ "b/Basics/OS/OsBasic/4_\345\206\205\345\255\230\347\256\241\347\220\206\343\200\201\350\256\276\345\244\207\347\256\241\347\220\206\343\200\201IO\343\200\201\346\226\207\344\273\266\347\263\273\347\273\237.md" @@ -0,0 +1,38 @@ +## 操作系统总结 - 内存管理、设备管理(IO)、文件系统(四) +* [内存的基础知识](#1) +* [内存管理的概念](#2) +* [覆盖与交换](#3) +* [连续分配管理方式](#4) +*** + +### 内存的基础知识 +**本节知识总览** + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221238815.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221406658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221459438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221559846.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221658569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212221822948.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212222239975.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212222653487.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212222807965.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212222945261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212223123438.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212223250506.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +**本节小结** +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212223325524.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70)  + +*** +### 内存管理的概念 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212223824472.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) +*** +### 覆盖与交换 +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181212224320682.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +*** +### 连续分配管理方式 + +### ![在这里插入图片描述](https://img-blog.csdnimg.cn/20181213000532284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + + ![在这里插入图片描述](https://img-blog.csdnimg.cn/20181213000547400.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) diff --git a/Basics/OS/OsBasic/assets/1564666591843.png b/Basics/OS/OsBasic/assets/1564666591843.png new file mode 100644 index 00000000..f7e85918 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564666591843.png differ diff --git a/Basics/OS/OsBasic/assets/1564669510548.png b/Basics/OS/OsBasic/assets/1564669510548.png new file mode 100644 index 00000000..d36ea092 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564669510548.png differ diff --git a/Basics/OS/OsBasic/assets/1564669559612.png b/Basics/OS/OsBasic/assets/1564669559612.png new file mode 100644 index 00000000..7ace3a27 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564669559612.png differ diff --git a/Basics/OS/OsBasic/assets/1564670857759.png b/Basics/OS/OsBasic/assets/1564670857759.png new file mode 100644 index 00000000..cd714ab0 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564670857759.png differ diff --git a/Basics/OS/OsBasic/assets/1564670940081.png b/Basics/OS/OsBasic/assets/1564670940081.png new file mode 100644 index 00000000..f660dc02 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564670940081.png differ diff --git a/Basics/OS/OsBasic/assets/1564671005552.png b/Basics/OS/OsBasic/assets/1564671005552.png new file mode 100644 index 00000000..317e5b1c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671005552.png differ diff --git a/Basics/OS/OsBasic/assets/1564671139314.png b/Basics/OS/OsBasic/assets/1564671139314.png new file mode 100644 index 00000000..e9a2ba97 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671139314.png differ diff --git a/Basics/OS/OsBasic/assets/1564671145789.png b/Basics/OS/OsBasic/assets/1564671145789.png new file mode 100644 index 00000000..e9a2ba97 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671145789.png differ diff --git a/Basics/OS/OsBasic/assets/1564671253555.png b/Basics/OS/OsBasic/assets/1564671253555.png new file mode 100644 index 00000000..1fe0ac23 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671253555.png differ diff --git a/Basics/OS/OsBasic/assets/1564671363367.png b/Basics/OS/OsBasic/assets/1564671363367.png new file mode 100644 index 00000000..05ab3ea6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671363367.png differ diff --git a/Basics/OS/OsBasic/assets/1564671643577.png b/Basics/OS/OsBasic/assets/1564671643577.png new file mode 100644 index 00000000..184a783b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671643577.png differ diff --git a/Basics/OS/OsBasic/assets/1564671754319.png b/Basics/OS/OsBasic/assets/1564671754319.png new file mode 100644 index 00000000..59500e68 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671754319.png differ diff --git a/Basics/OS/OsBasic/assets/1564671813302.png b/Basics/OS/OsBasic/assets/1564671813302.png new file mode 100644 index 00000000..0d488712 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564671813302.png differ diff --git a/Basics/OS/OsBasic/assets/1564672024820.png b/Basics/OS/OsBasic/assets/1564672024820.png new file mode 100644 index 00000000..7d1eb002 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564672024820.png differ diff --git a/Basics/OS/OsBasic/assets/1564672061391.png b/Basics/OS/OsBasic/assets/1564672061391.png new file mode 100644 index 00000000..b795153d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564672061391.png differ diff --git a/Basics/OS/OsBasic/assets/1564672085529.png b/Basics/OS/OsBasic/assets/1564672085529.png new file mode 100644 index 00000000..305ea1d6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564672085529.png differ diff --git a/Basics/OS/OsBasic/assets/1564672124791.png b/Basics/OS/OsBasic/assets/1564672124791.png new file mode 100644 index 00000000..2b0e32e5 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564672124791.png differ diff --git a/Basics/OS/OsBasic/assets/1564672143996.png b/Basics/OS/OsBasic/assets/1564672143996.png new file mode 100644 index 00000000..625dd045 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564672143996.png differ diff --git a/Basics/OS/OsBasic/assets/1564705309384.png b/Basics/OS/OsBasic/assets/1564705309384.png new file mode 100644 index 00000000..3935484d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564705309384.png differ diff --git a/Basics/OS/OsBasic/assets/1564705469489.png b/Basics/OS/OsBasic/assets/1564705469489.png new file mode 100644 index 00000000..8b0489fa Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564705469489.png differ diff --git a/Basics/OS/OsBasic/assets/1564705494336.png b/Basics/OS/OsBasic/assets/1564705494336.png new file mode 100644 index 00000000..a521e7d6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564705494336.png differ diff --git a/Basics/OS/OsBasic/assets/1564705815567.png b/Basics/OS/OsBasic/assets/1564705815567.png new file mode 100644 index 00000000..02c67d86 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564705815567.png differ diff --git a/Basics/OS/OsBasic/assets/1564705846740.png b/Basics/OS/OsBasic/assets/1564705846740.png new file mode 100644 index 00000000..bfd07089 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564705846740.png differ diff --git a/Basics/OS/OsBasic/assets/1564706216905.png b/Basics/OS/OsBasic/assets/1564706216905.png new file mode 100644 index 00000000..2ef9746f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564706216905.png differ diff --git a/Basics/OS/OsBasic/assets/1564706421144.png b/Basics/OS/OsBasic/assets/1564706421144.png new file mode 100644 index 00000000..cc70a5d2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564706421144.png differ diff --git a/Basics/OS/OsBasic/assets/1564706934865.png b/Basics/OS/OsBasic/assets/1564706934865.png new file mode 100644 index 00000000..840570be Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564706934865.png differ diff --git a/Basics/OS/OsBasic/assets/1564707018939.png b/Basics/OS/OsBasic/assets/1564707018939.png new file mode 100644 index 00000000..308c5e35 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564707018939.png differ diff --git a/Basics/OS/OsBasic/assets/1564707081934.png b/Basics/OS/OsBasic/assets/1564707081934.png new file mode 100644 index 00000000..c662d33a Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564707081934.png differ diff --git a/Basics/OS/OsBasic/assets/1564743273635.png b/Basics/OS/OsBasic/assets/1564743273635.png new file mode 100644 index 00000000..1ca18f21 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564743273635.png differ diff --git a/Basics/OS/OsBasic/assets/1564744515435.png b/Basics/OS/OsBasic/assets/1564744515435.png new file mode 100644 index 00000000..61a3a68c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564744515435.png differ diff --git a/Basics/OS/OsBasic/assets/1564744699297.png b/Basics/OS/OsBasic/assets/1564744699297.png new file mode 100644 index 00000000..7a504434 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564744699297.png differ diff --git a/Basics/OS/OsBasic/assets/1564744891166.png b/Basics/OS/OsBasic/assets/1564744891166.png new file mode 100644 index 00000000..f2c7bac8 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564744891166.png differ diff --git a/Basics/OS/OsBasic/assets/1564744917574.png b/Basics/OS/OsBasic/assets/1564744917574.png new file mode 100644 index 00000000..7a0e107b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564744917574.png differ diff --git a/Basics/OS/OsBasic/assets/1564745589934.png b/Basics/OS/OsBasic/assets/1564745589934.png new file mode 100644 index 00000000..3dd3d0d6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564745589934.png differ diff --git a/Basics/OS/OsBasic/assets/1564745607862.png b/Basics/OS/OsBasic/assets/1564745607862.png new file mode 100644 index 00000000..29e086ba Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564745607862.png differ diff --git a/Basics/OS/OsBasic/assets/1564745669863.png b/Basics/OS/OsBasic/assets/1564745669863.png new file mode 100644 index 00000000..5bcbf7a3 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564745669863.png differ diff --git a/Basics/OS/OsBasic/assets/1564745718470.png b/Basics/OS/OsBasic/assets/1564745718470.png new file mode 100644 index 00000000..e55a5fbb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564745718470.png differ diff --git a/Basics/OS/OsBasic/assets/1564745755239.png b/Basics/OS/OsBasic/assets/1564745755239.png new file mode 100644 index 00000000..b79dcc63 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564745755239.png differ diff --git a/Basics/OS/OsBasic/assets/1564746139767.png b/Basics/OS/OsBasic/assets/1564746139767.png new file mode 100644 index 00000000..3615dbac Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564746139767.png differ diff --git a/Basics/OS/OsBasic/assets/1564901075525.png b/Basics/OS/OsBasic/assets/1564901075525.png new file mode 100644 index 00000000..a0bbca31 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901075525.png differ diff --git a/Basics/OS/OsBasic/assets/1564901164705.png b/Basics/OS/OsBasic/assets/1564901164705.png new file mode 100644 index 00000000..69d7ef9d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901164705.png differ diff --git a/Basics/OS/OsBasic/assets/1564901511545.png b/Basics/OS/OsBasic/assets/1564901511545.png new file mode 100644 index 00000000..12bad2fb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901511545.png differ diff --git a/Basics/OS/OsBasic/assets/1564901539450.png b/Basics/OS/OsBasic/assets/1564901539450.png new file mode 100644 index 00000000..740502bd Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901539450.png differ diff --git a/Basics/OS/OsBasic/assets/1564901560985.png b/Basics/OS/OsBasic/assets/1564901560985.png new file mode 100644 index 00000000..95030657 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901560985.png differ diff --git a/Basics/OS/OsBasic/assets/1564901595674.png b/Basics/OS/OsBasic/assets/1564901595674.png new file mode 100644 index 00000000..4b232658 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901595674.png differ diff --git a/Basics/OS/OsBasic/assets/1564901613490.png b/Basics/OS/OsBasic/assets/1564901613490.png new file mode 100644 index 00000000..c7cac469 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901613490.png differ diff --git a/Basics/OS/OsBasic/assets/1564901920209.png b/Basics/OS/OsBasic/assets/1564901920209.png new file mode 100644 index 00000000..5c09b8d0 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564901920209.png differ diff --git a/Basics/OS/OsBasic/assets/1564902128632.png b/Basics/OS/OsBasic/assets/1564902128632.png new file mode 100644 index 00000000..31cbba11 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902128632.png differ diff --git a/Basics/OS/OsBasic/assets/1564902254761.png b/Basics/OS/OsBasic/assets/1564902254761.png new file mode 100644 index 00000000..3d5cf55c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902254761.png differ diff --git a/Basics/OS/OsBasic/assets/1564902273672.png b/Basics/OS/OsBasic/assets/1564902273672.png new file mode 100644 index 00000000..f1392a7a Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902273672.png differ diff --git a/Basics/OS/OsBasic/assets/1564902607602.png b/Basics/OS/OsBasic/assets/1564902607602.png new file mode 100644 index 00000000..86dc6bb9 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902607602.png differ diff --git a/Basics/OS/OsBasic/assets/1564902628377.png b/Basics/OS/OsBasic/assets/1564902628377.png new file mode 100644 index 00000000..7ee8f4c7 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902628377.png differ diff --git a/Basics/OS/OsBasic/assets/1564902685329.png b/Basics/OS/OsBasic/assets/1564902685329.png new file mode 100644 index 00000000..210095a7 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564902685329.png differ diff --git a/Basics/OS/OsBasic/assets/1564906264238.png b/Basics/OS/OsBasic/assets/1564906264238.png new file mode 100644 index 00000000..96a5cc66 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564906264238.png differ diff --git a/Basics/OS/OsBasic/assets/1564906282123.png b/Basics/OS/OsBasic/assets/1564906282123.png new file mode 100644 index 00000000..f3a222d8 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564906282123.png differ diff --git a/Basics/OS/OsBasic/assets/1564906381292.png b/Basics/OS/OsBasic/assets/1564906381292.png new file mode 100644 index 00000000..56d9d7fb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564906381292.png differ diff --git a/Basics/OS/OsBasic/assets/1564906640159.png b/Basics/OS/OsBasic/assets/1564906640159.png new file mode 100644 index 00000000..607103b7 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564906640159.png differ diff --git a/Basics/OS/OsBasic/assets/1564906967285.png b/Basics/OS/OsBasic/assets/1564906967285.png new file mode 100644 index 00000000..cc7cac9e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564906967285.png differ diff --git a/Basics/OS/OsBasic/assets/1564907339172.png b/Basics/OS/OsBasic/assets/1564907339172.png new file mode 100644 index 00000000..e587926b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564907339172.png differ diff --git a/Basics/OS/OsBasic/assets/1564907414454.png b/Basics/OS/OsBasic/assets/1564907414454.png new file mode 100644 index 00000000..d5227a52 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564907414454.png differ diff --git a/Basics/OS/OsBasic/assets/1564907624795.png b/Basics/OS/OsBasic/assets/1564907624795.png new file mode 100644 index 00000000..279bf995 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564907624795.png differ diff --git a/Basics/OS/OsBasic/assets/1564907646589.png b/Basics/OS/OsBasic/assets/1564907646589.png new file mode 100644 index 00000000..5668d283 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564907646589.png differ diff --git a/Basics/OS/OsBasic/assets/1564907938534.png b/Basics/OS/OsBasic/assets/1564907938534.png new file mode 100644 index 00000000..3feefca6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564907938534.png differ diff --git a/Basics/OS/OsBasic/assets/1564908118174.png b/Basics/OS/OsBasic/assets/1564908118174.png new file mode 100644 index 00000000..1dbfeb20 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564908118174.png differ diff --git a/Basics/OS/OsBasic/assets/1564908138788.png b/Basics/OS/OsBasic/assets/1564908138788.png new file mode 100644 index 00000000..8445ccd2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564908138788.png differ diff --git a/Basics/OS/OsBasic/assets/1564908187083.png b/Basics/OS/OsBasic/assets/1564908187083.png new file mode 100644 index 00000000..f82f6554 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564908187083.png differ diff --git a/Basics/OS/OsBasic/assets/1564909685101.png b/Basics/OS/OsBasic/assets/1564909685101.png new file mode 100644 index 00000000..f755c613 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564909685101.png differ diff --git a/Basics/OS/OsBasic/assets/1564909708414.png b/Basics/OS/OsBasic/assets/1564909708414.png new file mode 100644 index 00000000..93a37576 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564909708414.png differ diff --git a/Basics/OS/OsBasic/assets/1564909832949.png b/Basics/OS/OsBasic/assets/1564909832949.png new file mode 100644 index 00000000..2544dffe Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564909832949.png differ diff --git a/Basics/OS/OsBasic/assets/1564909958234.png b/Basics/OS/OsBasic/assets/1564909958234.png new file mode 100644 index 00000000..22711662 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564909958234.png differ diff --git a/Basics/OS/OsBasic/assets/1564909980198.png b/Basics/OS/OsBasic/assets/1564909980198.png new file mode 100644 index 00000000..90d05c07 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564909980198.png differ diff --git a/Basics/OS/OsBasic/assets/1564910443297.png b/Basics/OS/OsBasic/assets/1564910443297.png new file mode 100644 index 00000000..e76a1b7d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910443297.png differ diff --git a/Basics/OS/OsBasic/assets/1564910464059.png b/Basics/OS/OsBasic/assets/1564910464059.png new file mode 100644 index 00000000..75db885e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910464059.png differ diff --git a/Basics/OS/OsBasic/assets/1564910551610.png b/Basics/OS/OsBasic/assets/1564910551610.png new file mode 100644 index 00000000..5cddb724 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910551610.png differ diff --git a/Basics/OS/OsBasic/assets/1564910641514.png b/Basics/OS/OsBasic/assets/1564910641514.png new file mode 100644 index 00000000..b8346d4f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910641514.png differ diff --git a/Basics/OS/OsBasic/assets/1564910771514.png b/Basics/OS/OsBasic/assets/1564910771514.png new file mode 100644 index 00000000..e64c42eb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910771514.png differ diff --git a/Basics/OS/OsBasic/assets/1564910891925.png b/Basics/OS/OsBasic/assets/1564910891925.png new file mode 100644 index 00000000..4fc68a24 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910891925.png differ diff --git a/Basics/OS/OsBasic/assets/1564910946228.png b/Basics/OS/OsBasic/assets/1564910946228.png new file mode 100644 index 00000000..20662575 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910946228.png differ diff --git a/Basics/OS/OsBasic/assets/1564910975684.png b/Basics/OS/OsBasic/assets/1564910975684.png new file mode 100644 index 00000000..eee1d8db Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910975684.png differ diff --git a/Basics/OS/OsBasic/assets/1564910997690.png b/Basics/OS/OsBasic/assets/1564910997690.png new file mode 100644 index 00000000..d563bfc2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564910997690.png differ diff --git a/Basics/OS/OsBasic/assets/1564911411008.png b/Basics/OS/OsBasic/assets/1564911411008.png new file mode 100644 index 00000000..68000d6b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564911411008.png differ diff --git a/Basics/OS/OsBasic/assets/1564911438521.png b/Basics/OS/OsBasic/assets/1564911438521.png new file mode 100644 index 00000000..2bc18391 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564911438521.png differ diff --git a/Basics/OS/OsBasic/assets/1564911773787.png b/Basics/OS/OsBasic/assets/1564911773787.png new file mode 100644 index 00000000..8ec9e139 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564911773787.png differ diff --git a/Basics/OS/OsBasic/assets/1564912318712.png b/Basics/OS/OsBasic/assets/1564912318712.png new file mode 100644 index 00000000..1a972ff0 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564912318712.png differ diff --git a/Basics/OS/OsBasic/assets/1564912345520.png b/Basics/OS/OsBasic/assets/1564912345520.png new file mode 100644 index 00000000..80318213 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564912345520.png differ diff --git a/Basics/OS/OsBasic/assets/1564994144030.png b/Basics/OS/OsBasic/assets/1564994144030.png new file mode 100644 index 00000000..d1b10f4d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564994144030.png differ diff --git a/Basics/OS/OsBasic/assets/1564994152870.png b/Basics/OS/OsBasic/assets/1564994152870.png new file mode 100644 index 00000000..d256a427 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1564994152870.png differ diff --git a/Basics/OS/OsBasic/assets/1565056520103.png b/Basics/OS/OsBasic/assets/1565056520103.png new file mode 100644 index 00000000..69e6eb69 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565056520103.png differ diff --git a/Basics/OS/OsBasic/assets/1565056546358.png b/Basics/OS/OsBasic/assets/1565056546358.png new file mode 100644 index 00000000..4ebe671e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565056546358.png differ diff --git a/Basics/OS/OsBasic/assets/1565056643566.png b/Basics/OS/OsBasic/assets/1565056643566.png new file mode 100644 index 00000000..b7d52338 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565056643566.png differ diff --git a/Basics/OS/OsBasic/assets/1565152593960.png b/Basics/OS/OsBasic/assets/1565152593960.png new file mode 100644 index 00000000..4c402f7d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565152593960.png differ diff --git a/Basics/OS/OsBasic/assets/1565158406242.png b/Basics/OS/OsBasic/assets/1565158406242.png new file mode 100644 index 00000000..6eca2daa Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565158406242.png differ diff --git a/Basics/OS/OsBasic/assets/1565158785659.png b/Basics/OS/OsBasic/assets/1565158785659.png new file mode 100644 index 00000000..28de9a3d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565158785659.png differ diff --git a/Basics/OS/OsBasic/assets/1565159124556.png b/Basics/OS/OsBasic/assets/1565159124556.png new file mode 100644 index 00000000..686f0fb6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565159124556.png differ diff --git a/Basics/OS/OsBasic/assets/1565159426132.png b/Basics/OS/OsBasic/assets/1565159426132.png new file mode 100644 index 00000000..9888626c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565159426132.png differ diff --git a/Basics/OS/OsBasic/assets/1565160868370.png b/Basics/OS/OsBasic/assets/1565160868370.png new file mode 100644 index 00000000..0bda6a4a Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565160868370.png differ diff --git a/Basics/OS/OsBasic/assets/1565162005635.png b/Basics/OS/OsBasic/assets/1565162005635.png new file mode 100644 index 00000000..0d5de597 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565162005635.png differ diff --git a/Basics/OS/OsBasic/assets/1565162107623.png b/Basics/OS/OsBasic/assets/1565162107623.png new file mode 100644 index 00000000..41378d9d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565162107623.png differ diff --git a/Basics/OS/OsBasic/assets/1565162127265.png b/Basics/OS/OsBasic/assets/1565162127265.png new file mode 100644 index 00000000..0b596ddb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565162127265.png differ diff --git a/Basics/OS/OsBasic/assets/1565162668815.png b/Basics/OS/OsBasic/assets/1565162668815.png new file mode 100644 index 00000000..9168448c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565162668815.png differ diff --git a/Basics/OS/OsBasic/assets/1565162970457.png b/Basics/OS/OsBasic/assets/1565162970457.png new file mode 100644 index 00000000..efb9af1f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565162970457.png differ diff --git a/Basics/OS/OsBasic/assets/1565163194994.png b/Basics/OS/OsBasic/assets/1565163194994.png new file mode 100644 index 00000000..944849fc Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565163194994.png differ diff --git a/Basics/OS/OsBasic/assets/1565163666787.png b/Basics/OS/OsBasic/assets/1565163666787.png new file mode 100644 index 00000000..adc3163e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565163666787.png differ diff --git a/Basics/OS/OsBasic/assets/1565164013902.png b/Basics/OS/OsBasic/assets/1565164013902.png new file mode 100644 index 00000000..3e38d92c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565164013902.png differ diff --git a/Basics/OS/OsBasic/assets/1565164532538.png b/Basics/OS/OsBasic/assets/1565164532538.png new file mode 100644 index 00000000..08c3e6e2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565164532538.png differ diff --git a/Basics/OS/OsBasic/assets/1565164569520.png b/Basics/OS/OsBasic/assets/1565164569520.png new file mode 100644 index 00000000..e8d58ffe Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565164569520.png differ diff --git a/Basics/OS/OsBasic/assets/1565164590125.png b/Basics/OS/OsBasic/assets/1565164590125.png new file mode 100644 index 00000000..c0cfb871 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565164590125.png differ diff --git a/Basics/OS/OsBasic/assets/1565164628137.png b/Basics/OS/OsBasic/assets/1565164628137.png new file mode 100644 index 00000000..4a637d58 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565164628137.png differ diff --git a/Basics/OS/OsBasic/assets/1565165120404.png b/Basics/OS/OsBasic/assets/1565165120404.png new file mode 100644 index 00000000..90cfcca2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565165120404.png differ diff --git a/Basics/OS/OsBasic/assets/1565166566230.png b/Basics/OS/OsBasic/assets/1565166566230.png new file mode 100644 index 00000000..3c56a26e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565166566230.png differ diff --git a/Basics/OS/OsBasic/assets/1565166589754.png b/Basics/OS/OsBasic/assets/1565166589754.png new file mode 100644 index 00000000..28f7c19f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565166589754.png differ diff --git a/Basics/OS/OsBasic/assets/1565166740562.png b/Basics/OS/OsBasic/assets/1565166740562.png new file mode 100644 index 00000000..23bc6d31 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565166740562.png differ diff --git a/Basics/OS/OsBasic/assets/1565168502229.png b/Basics/OS/OsBasic/assets/1565168502229.png new file mode 100644 index 00000000..2528ec4f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565168502229.png differ diff --git a/Basics/OS/OsBasic/assets/1565171779448.png b/Basics/OS/OsBasic/assets/1565171779448.png new file mode 100644 index 00000000..2e7086eb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565171779448.png differ diff --git a/Basics/OS/OsBasic/assets/1565232898568.png b/Basics/OS/OsBasic/assets/1565232898568.png new file mode 100644 index 00000000..88d2c268 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565232898568.png differ diff --git a/Basics/OS/OsBasic/assets/1565243526838.png b/Basics/OS/OsBasic/assets/1565243526838.png new file mode 100644 index 00000000..497437cb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565243526838.png differ diff --git a/Basics/OS/OsBasic/assets/1565243555856.png b/Basics/OS/OsBasic/assets/1565243555856.png new file mode 100644 index 00000000..07d342cf Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565243555856.png differ diff --git a/Basics/OS/OsBasic/assets/1565243881519.png b/Basics/OS/OsBasic/assets/1565243881519.png new file mode 100644 index 00000000..917da2d7 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565243881519.png differ diff --git a/Basics/OS/OsBasic/assets/1565244484013.png b/Basics/OS/OsBasic/assets/1565244484013.png new file mode 100644 index 00000000..cf9732f9 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244484013.png differ diff --git a/Basics/OS/OsBasic/assets/1565244574886.png b/Basics/OS/OsBasic/assets/1565244574886.png new file mode 100644 index 00000000..e6ee3f41 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244574886.png differ diff --git a/Basics/OS/OsBasic/assets/1565244775230.png b/Basics/OS/OsBasic/assets/1565244775230.png new file mode 100644 index 00000000..71ae20bb Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244775230.png differ diff --git a/Basics/OS/OsBasic/assets/1565244877245.png b/Basics/OS/OsBasic/assets/1565244877245.png new file mode 100644 index 00000000..320e2d0e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244877245.png differ diff --git a/Basics/OS/OsBasic/assets/1565244904870.png b/Basics/OS/OsBasic/assets/1565244904870.png new file mode 100644 index 00000000..6f3fb87f Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244904870.png differ diff --git a/Basics/OS/OsBasic/assets/1565244983630.png b/Basics/OS/OsBasic/assets/1565244983630.png new file mode 100644 index 00000000..f769a0d5 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565244983630.png differ diff --git a/Basics/OS/OsBasic/assets/1565245006118.png b/Basics/OS/OsBasic/assets/1565245006118.png new file mode 100644 index 00000000..6005cbbe Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565245006118.png differ diff --git a/Basics/OS/OsBasic/assets/1565245873940.png b/Basics/OS/OsBasic/assets/1565245873940.png new file mode 100644 index 00000000..f44914a5 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565245873940.png differ diff --git a/Basics/OS/OsBasic/assets/1565245928917.png b/Basics/OS/OsBasic/assets/1565245928917.png new file mode 100644 index 00000000..98212cb2 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565245928917.png differ diff --git a/Basics/OS/OsBasic/assets/1565246079236.png b/Basics/OS/OsBasic/assets/1565246079236.png new file mode 100644 index 00000000..e5f46d2e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246079236.png differ diff --git a/Basics/OS/OsBasic/assets/1565246102396.png b/Basics/OS/OsBasic/assets/1565246102396.png new file mode 100644 index 00000000..51000325 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246102396.png differ diff --git a/Basics/OS/OsBasic/assets/1565246328994.png b/Basics/OS/OsBasic/assets/1565246328994.png new file mode 100644 index 00000000..52e874dd Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246328994.png differ diff --git a/Basics/OS/OsBasic/assets/1565246439260.png b/Basics/OS/OsBasic/assets/1565246439260.png new file mode 100644 index 00000000..a5eeb10d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246439260.png differ diff --git a/Basics/OS/OsBasic/assets/1565246591452.png b/Basics/OS/OsBasic/assets/1565246591452.png new file mode 100644 index 00000000..dddd314c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246591452.png differ diff --git a/Basics/OS/OsBasic/assets/1565246913387.png b/Basics/OS/OsBasic/assets/1565246913387.png new file mode 100644 index 00000000..53b48bed Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246913387.png differ diff --git a/Basics/OS/OsBasic/assets/1565246929714.png b/Basics/OS/OsBasic/assets/1565246929714.png new file mode 100644 index 00000000..d174ea9a Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565246929714.png differ diff --git a/Basics/OS/OsBasic/assets/1565247091643.png b/Basics/OS/OsBasic/assets/1565247091643.png new file mode 100644 index 00000000..e43e567c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565247091643.png differ diff --git a/Basics/OS/OsBasic/assets/1565247115868.png b/Basics/OS/OsBasic/assets/1565247115868.png new file mode 100644 index 00000000..9ed81a20 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565247115868.png differ diff --git a/Basics/OS/OsBasic/assets/1565247986227.png b/Basics/OS/OsBasic/assets/1565247986227.png new file mode 100644 index 00000000..42923a5c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565247986227.png differ diff --git a/Basics/OS/OsBasic/assets/1565248038259.png b/Basics/OS/OsBasic/assets/1565248038259.png new file mode 100644 index 00000000..c4a51732 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565248038259.png differ diff --git a/Basics/OS/OsBasic/assets/1565248774361.png b/Basics/OS/OsBasic/assets/1565248774361.png new file mode 100644 index 00000000..d40b5738 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565248774361.png differ diff --git a/Basics/OS/OsBasic/assets/1565248808896.png b/Basics/OS/OsBasic/assets/1565248808896.png new file mode 100644 index 00000000..853eac73 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565248808896.png differ diff --git a/Basics/OS/OsBasic/assets/1565248868738.png b/Basics/OS/OsBasic/assets/1565248868738.png new file mode 100644 index 00000000..10cfdb62 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565248868738.png differ diff --git a/Basics/OS/OsBasic/assets/1565249030385.png b/Basics/OS/OsBasic/assets/1565249030385.png new file mode 100644 index 00000000..adef1d48 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565249030385.png differ diff --git a/Basics/OS/OsBasic/assets/1565249280650.png b/Basics/OS/OsBasic/assets/1565249280650.png new file mode 100644 index 00000000..677f083e Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565249280650.png differ diff --git a/Basics/OS/OsBasic/assets/1565249297402.png b/Basics/OS/OsBasic/assets/1565249297402.png new file mode 100644 index 00000000..3153540b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565249297402.png differ diff --git a/Basics/OS/OsBasic/assets/1565249978225.png b/Basics/OS/OsBasic/assets/1565249978225.png new file mode 100644 index 00000000..de2fe98d Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565249978225.png differ diff --git a/Basics/OS/OsBasic/assets/1565307324781.png b/Basics/OS/OsBasic/assets/1565307324781.png new file mode 100644 index 00000000..77ea9e47 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565307324781.png differ diff --git a/Basics/OS/OsBasic/assets/1565307354230.png b/Basics/OS/OsBasic/assets/1565307354230.png new file mode 100644 index 00000000..253c0599 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565307354230.png differ diff --git a/Basics/OS/OsBasic/assets/1565307377909.png b/Basics/OS/OsBasic/assets/1565307377909.png new file mode 100644 index 00000000..edb08d00 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565307377909.png differ diff --git a/Basics/OS/OsBasic/assets/1565308929135.png b/Basics/OS/OsBasic/assets/1565308929135.png new file mode 100644 index 00000000..ed5feb32 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565308929135.png differ diff --git a/Basics/OS/OsBasic/assets/1565309314853.png b/Basics/OS/OsBasic/assets/1565309314853.png new file mode 100644 index 00000000..f12ea4bc Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565309314853.png differ diff --git a/Basics/OS/OsBasic/assets/1565309347427.png b/Basics/OS/OsBasic/assets/1565309347427.png new file mode 100644 index 00000000..bb2d78e6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565309347427.png differ diff --git a/Basics/OS/OsBasic/assets/1565310189361.png b/Basics/OS/OsBasic/assets/1565310189361.png new file mode 100644 index 00000000..b3e28138 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565310189361.png differ diff --git a/Basics/OS/OsBasic/assets/1565310212866.png b/Basics/OS/OsBasic/assets/1565310212866.png new file mode 100644 index 00000000..6f46f596 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565310212866.png differ diff --git a/Basics/OS/OsBasic/assets/1565310770571.png b/Basics/OS/OsBasic/assets/1565310770571.png new file mode 100644 index 00000000..9b875224 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565310770571.png differ diff --git a/Basics/OS/OsBasic/assets/1565310789425.png b/Basics/OS/OsBasic/assets/1565310789425.png new file mode 100644 index 00000000..4ee1232b Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565310789425.png differ diff --git a/Basics/OS/OsBasic/assets/1565310943347.png b/Basics/OS/OsBasic/assets/1565310943347.png new file mode 100644 index 00000000..2aa1a402 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565310943347.png differ diff --git a/Basics/OS/OsBasic/assets/1565311107760.png b/Basics/OS/OsBasic/assets/1565311107760.png new file mode 100644 index 00000000..5effdcc0 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311107760.png differ diff --git a/Basics/OS/OsBasic/assets/1565311129619.png b/Basics/OS/OsBasic/assets/1565311129619.png new file mode 100644 index 00000000..b6b782be Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311129619.png differ diff --git a/Basics/OS/OsBasic/assets/1565311262841.png b/Basics/OS/OsBasic/assets/1565311262841.png new file mode 100644 index 00000000..4fcab766 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311262841.png differ diff --git a/Basics/OS/OsBasic/assets/1565311688801.png b/Basics/OS/OsBasic/assets/1565311688801.png new file mode 100644 index 00000000..bd0547f5 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311688801.png differ diff --git a/Basics/OS/OsBasic/assets/1565311904800.png b/Basics/OS/OsBasic/assets/1565311904800.png new file mode 100644 index 00000000..18fdd0b6 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311904800.png differ diff --git a/Basics/OS/OsBasic/assets/1565311934216.png b/Basics/OS/OsBasic/assets/1565311934216.png new file mode 100644 index 00000000..5eb80367 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565311934216.png differ diff --git a/Basics/OS/OsBasic/assets/1565312054458.png b/Basics/OS/OsBasic/assets/1565312054458.png new file mode 100644 index 00000000..fcd63c6a Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312054458.png differ diff --git a/Basics/OS/OsBasic/assets/1565312383504.png b/Basics/OS/OsBasic/assets/1565312383504.png new file mode 100644 index 00000000..46b5a599 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312383504.png differ diff --git a/Basics/OS/OsBasic/assets/1565312416553.png b/Basics/OS/OsBasic/assets/1565312416553.png new file mode 100644 index 00000000..e5ecf3ea Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312416553.png differ diff --git a/Basics/OS/OsBasic/assets/1565312616767.png b/Basics/OS/OsBasic/assets/1565312616767.png new file mode 100644 index 00000000..b3b91722 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312616767.png differ diff --git a/Basics/OS/OsBasic/assets/1565312636547.png b/Basics/OS/OsBasic/assets/1565312636547.png new file mode 100644 index 00000000..0ad9c889 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312636547.png differ diff --git a/Basics/OS/OsBasic/assets/1565312667296.png b/Basics/OS/OsBasic/assets/1565312667296.png new file mode 100644 index 00000000..730dd2d3 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312667296.png differ diff --git a/Basics/OS/OsBasic/assets/1565312694837.png b/Basics/OS/OsBasic/assets/1565312694837.png new file mode 100644 index 00000000..63cf7760 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312694837.png differ diff --git a/Basics/OS/OsBasic/assets/1565312717623.png b/Basics/OS/OsBasic/assets/1565312717623.png new file mode 100644 index 00000000..0962b08c Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312717623.png differ diff --git a/Basics/OS/OsBasic/assets/1565312738111.png b/Basics/OS/OsBasic/assets/1565312738111.png new file mode 100644 index 00000000..68d55fae Binary files /dev/null and b/Basics/OS/OsBasic/assets/1565312738111.png differ diff --git a/Basics/OS/OsBasic/assets/1568044771293.png b/Basics/OS/OsBasic/assets/1568044771293.png new file mode 100644 index 00000000..c5391293 Binary files /dev/null and b/Basics/OS/OsBasic/assets/1568044771293.png differ diff --git "a/C++/C++/C++\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" "b/C++/C++/C++\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" deleted file mode 100644 index 4bbc124f..00000000 --- "a/C++/C++/C++\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" +++ /dev/null @@ -1,1605 +0,0 @@ -## C++基础知识总结(一) - -* [C++对C语言的提高](#c对c语言的提高) - * [命令空间简单使用](#命令空间简单使用) - * [`const`关键字的加强](#const关键字的加强) - * [引用-重点](#引用-重点) - * [指针引用](#指针引用) - * [没有引用指针](#没有引用指针) - * [`const`引用](#const引用) - * [默认参数函数重载作用域运算符](#默认参数函数重载作用域运算符) - * [`new`、`delete`的使用](#newdelete的使用) -* [C++面向对象基础](#c面向对象基础) - * [一个简单案例](#一个简单案例) - * [构造函数和析构函数](#构造函数和析构函数) - * [深拷贝和浅拷贝](#深拷贝和浅拷贝) - * [指向对象成员函数的指针](#指向对象成员函数的指针) - * [常对象](#常对象) - * [常对象成员-常数据成员&常成员函数](#常对象成员-常数据成员常成员函数) - * [指向对象的常指针](#指向对象的常指针) - * [指向常变量、对象的指针](#指向常变量对象的指针) - * [静态成员](#静态成员) - * [友元](#友元) -* [C++重载运算符](#c重载运算符) - * [重载基本运算符](#重载基本运算符) - * [重载`=`号操作符](#重载号操作符) - * [重载流插入运算符和流提取运算符](#重载流插入运算符和流提取运算符) - * [综合案例-矩阵加法以及输入输出](#综合案例-矩阵加法以及输入输出) -* [函数模板、类模板](#函数模板类模板) - * [案例-简单实现的Vector类模板中的`push_back`](#案例-简单实现的vector类模板中的push-back) -* [文件操作](#文件操作) - * [输入输出流](#输入输出流) - * [文件读写](#文件读写) -*** -### C++对C语言的提高 -##### 命令空间简单使用 -引用命令空间的三种方式: - -* 直接指定标识符。例如`std::cout<<"hello"< - -//定义一个命令空间 -namespace space1{ - int a = 100; - int b = 200; -} - -//using namespace std; -//只导入其中的cout和endl -using std::cout; -using std::endl; - -int main(int argc, char const **argv) -{ - cout<<"hello"<真正的不能改变); - -测试: - -```cpp -#include - -int main(int argc, char const **argv) -{ - //a此时是一个真正的常量 : 不能通过指针修改(C语言中的const是一个冒牌货) - const int a = 100; - //如果对一个常量取地址,编译器会临时开辟一个空间temp,让这个指针指向temp - int *p = (int *)&a; //注意要强制转换一下 - *p = 200; - printf("a = %d, *p = %d\n", a, *p); - - //可以使用const定义的"常量"来定义数组(C语言中不能这样) - int arr[a]; //ok - return 0; -} -``` - -输出(可以看到`a`没有改变(`C`语言中会变)): -``` -a = 100, *p = 200 -``` -##### 引用-重点 - -* 变量名,本身是一段内存的引用,即别名(`alias`). 引用可以看作一个已定义变量的别名; -* 对变量声明一个引用,并不另外开辟内存单元,例如`int &b = a`,则`b`和`a`都代表同一个内存单元(使用`sizeof()`测`a`、`b`大小是相同的)。引用与被引用的变量有相同的地址; -* 在声明一个引用时,必须同时初始化(和常量有点类似),即声明它代表哪一个变量; -* 当声明一个变量的引用后,改引用一直与其代表的变量相联系,不能再作为其他变量的别名。 -* `&`符号前有数据类型时,是引用。其他都是代表取地址; -* 引用所占用的大小跟指针是相等的,引用可能是一个"常指针"(`int *const p`); -* 对引用的初始化,可以是一个变量名,也可以是另一个引用。如`int a = 3; int &b = a; int &c = b;`此时,整形变量`a`有两个别名`b`、`c`; -* 不能建立`void`类型的引用(但是有`void *`类型指针(万能指针))。不能建立引用的数组(可以有指针数组); - - -使用经典的`swap`问题来看引用作为形参的简单使用: - -```cpp -#include - -//值传递(不行) -int swap1(int a, int b){ - int t = a; - a = b; - b = t; -} - -//指针(也是值传递) -int swap2(int *a, int *b){ - int t = *a; - *a = *b; - *b = t; -} - -//引用(引用传递) -int swap3(int &a, int &b){ - int t = a; - a = b; - b = t; -} - -int main(int argc, char const **argv) -{ - int a = 100, b = 200; - swap1(a, b); - printf("a = %d, b = %d\n", a , b); - swap2(&a, &b); - printf("a = %d, b = %d\n", a , b); - swap3(a, b); - printf("a = %d, b = %d\n", a , b); - return 0; -} -``` -输出: - -```cpp -a = 100, b = 200 -a = 200, b = 100 -a = 100, b = 200 -``` - -##### 指针引用 -可以建立指针变量的引用,如: - -```cpp -int a = 5; -int *p = &a; -int* &ref = p; //ref是一个指向 “整形变量的指针变量” 的引用,初始化为p -``` -下面看一个使用指针引用的例子,对比使用二级指针和使用指针引用的区别: -```cpp -#include -#include -#include - -struct Student{ - int age; - char name[20]; -}; - -//通过二级指针 -void getMem(Student **temp){ - Student *p = (Student *)malloc(sizeof(Student)); - p->age = 13; - strcpy(p->name, "zhangsan"); - *temp = p; -} -//通过引用 -void getMem2(Student* &p){ //将Student*看成一个类型 - p = (Student *)malloc(sizeof(Student)); - p->age = 14; - strcpy(p->name, "lisi"); -} - -//通过指针 -void free1(Student **temp){ - Student *p = *temp; - if(p != NULL){ - free(p); - *temp = NULL; - } -} -//通过指向指针的引用 -void free2(Student* &p){ //指向指针的引用 - if(p != NULL){ - free(p); - p = NULL; - } -} - -int main(int argc, char const **argv) -{ - Student *p = NULL; - getMem(&p); - printf("age = %d, name = %s\n", p->age, p->name); - free1(&p); - - printf("------------------\n"); - - getMem2(p); - printf("age = %d, name = %s\n", p->age, p->name); - free2(p); - return 0; -} - -``` -输出: - -```cpp -age = 13, name = zhangsan ------------------- -age = 14, name = lisi -``` - -##### 没有引用指针 -* 由于引用不是一种独立的数据类型,所以不能建立指向引用类型的指针变量。 -* 但是,可以将变量的引用的地址赋值给一个指针,此时指针指向的是原来的变量。 - -例如: - -```cpp -int a = 3; -int &b = a; -int *p = &b; //指针变量p指向变量a的引用b,相当于指向a,合法 -``` -上面代码和下面一行代码相同: - -```cpp -int *p = &a; -``` -输出`*p`的值,就是`b`的值,即`a`的值。 -不能定义指向引用类型的指针变量,不能写成: - -```cpp -int& *p = &a; //企图定义指向引用类型的指针变量,错误 -``` - -##### `const`引用 - -* 如果想对一个常量进行引用,必须是一个`const`引用; -* 可以对一个变量进行 常引用(此时引用不可修改,但是原变量可以修改)。这个特征一般是用在函数形参修饰上,不希望改变原来的实参的值; -* 可以用常量或表达式对引用进行初始化,但此时必须用`const`作声明(内部是使用一个`temp`临时变量转换); - - -测试: - -```cpp -#include -#include -using namespace std; - -int main(int argc, char const **argv) -{ - //对一个常量进行引用 - const int a = 10; - //int& ref1 = a; //err - const int &ref1 = a; //ok - - //可以对一个变量进行 常引用(此时引用不可修改,但是原变量可以修改) - //这个特征一般是用在函数形参修饰上,有时候不希望改变原来的实参的值 - int b = 10; - const int& ref2 = b; - //ref2 = 20; //err - b = 20; // ok - printf("b = %d, ref2 = %d\n", b, ref2); - - //对表达式做引用 - // 内部系统处理 int temp = c+10; const int& ref3 = temp; - int c = 30; - const int& ref3 = c + 10; //合法 - - //也可以对不同类型进行转换 - double d = 3.14; - const int& ref4 = d; // int temp = d; const int& ref4 = temp - cout< 如果默认参数出现,那么右边的都必须有默认参数,也就是只有参数列表后面部分的参数才可以提供默认参数值; -* 函数重载规则: ①函数名相同。②参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。 ③返回值类型不同则不可以构成重载。 -* 一个函数,不能既作重载,又作默认参数的函数。 - -简单使用: - -```cpp -#include - -int fun(int a, int b = 20, int c = 30){ - return a + b + c; -} - -int a = 100; - -int main(int argc, char const **argv) -{ - // 作用域运算符 :: - int a = 200; - printf("a = %d\n", a); - printf("a2 = %d\n", ::a); //全局的a - - printf("%d\n",fun(10)); - printf("%d\n",fun(10, 10)); - printf("%d\n",fun(10, 10, 10)); - return 0; -} -``` -##### `new`、`delete`的使用 - -* `C`语言中使用`malloc`函数必须指定开辟空间的大小,即`malloc(size)`,且`malloc`函数只能从用户处知道应开辟空间的大小而不知道数据的类型,因此无法使其返回的指针指向具体的数据类型。其返回值一律为`void *`,使用时必须强制转换; -* `C++`中提供了`new`和`delete`运算符来替代`malloc`和`free`函数; -* 差别: `malloc`不会调用类的构造函数,而`new`会调用类的构造函数。②`free`不会调用类的析构函数,而`delete`会调用类的析构函数;(析构函数释放的是对象内部的内存,而`delete`释放的是对象,而`delete`也出发析构,所以都可以释放) - - -例如 - -```cpp -new int; //开辟一个存放整数的空间,返回一个指向整形数据的指针 -new int(100); //开辟一个存放整数的空间,并指定初始值为100 -new char[10]; // 开辟一个存放字符数组的空间,size = 10 -new int[5][4]; -float *p = new float(3.14); //将返回的指向实型数据的指针赋给指针变量p -delete p; -delete []p; //释放对数组空间的操作(加方括号) -``` -简单测试: - -```cpp -#include - -int main(int argc, char const **argv) -{ - // new 和 delete 使用 - int *p1 = new int(10); - printf("*p1 = %d\n", *p1); - delete p1; - - int *p2 = new int[10]; - for(int i = 0;i < 10; i++) - p2[i] = i; - for(int i = 0;i < 10; i++) - printf("%d ",p2[i]); - printf("\n"); - - delete []p2; - return 0; -} -``` -输出: -```cpp -*p1 = 10 -0 1 2 3 4 5 6 7 8 9 -``` - -*** -### C++面向对象基础 - -##### 一个简单案例 -> 题目,判断两个圆是否相交。 -```cpp -#include -#include -#include -using namespace std; - -//点(坐标类) -class Point{ - public: - void setXY(int x, int y); - double getPointDis(Point &thr); //计算两点距离 - private: - int x; - int y; -}; - -//圆类 -class Circle{ - public: - void setR(int r); - void setXY(int x, int y); - bool isInterSection(Circle &thr); - private: - int r; - Point p0; //圆心 -}; - -void Point::setXY(int x, int y){ - this->x = x; - this->y = y; -} -double Point::getPointDis(Point &thr){ - int dx = x - thr.x; - int dy = y - thr.y; - double dis = sqrt(dx*dx + dy*dy); - return dis; -} - -void Circle::setR(int r){ - this->r = r; //注意不能写成this.r (this是一个指针,每个对象都可以通过this指针来访问自己的地址) -} -void Circle::setXY(int x,int y){ - p0.setXY(x, y); -} -bool Circle::isInterSection(Circle &thr){ - int R = r + thr.r; //两个圆的半径之和 - double dis = p0.getPointDis(thr.p0); //两个圆心的距离 - if(dis <= R) - return true; - else - return false; -} - -int main(int argc, char const **argv) -{ - Circle c1,c2; - c1.setR(1); - c1.setXY(0,0); - c2.setR(3); - c2.setXY(2,2); - - Circle *pc1 = &c1; //定义指向对象的指针 - - if(pc1->isInterSection(c2))//通过指针的方式调用 - printf("两圆相交!\n"); - else - printf("两圆不相交!\n"); - return 0; -} - -``` -输出: -```cpp -两圆相交! -``` -##### 构造函数和析构函数 -* 如果用户没有定义默认的,系统会提供一个默认构造函数,但是如果用户已经定义了构造函数,系统就不会提供默认的构造函数; -* 系统也会提供一个默认的拷贝构造函数,默认是浅拷贝,形如`Clazz c1(c2)`的使用,将`c1`拷贝给`c2`; -* `C++`有一个参数初始化列表的特性,注意不能用在数组上; -* 构造函数也可以是带有默认参数; -* 析构函数被调用的情况:① 如果用`new`运算符动态的建立了一个对象,当用`delete`运算符释放该对象时,先调用该对象的析构函数。②`static`局部对象在函数调用结束之后对象并不是放,因此也不调用析构函数。只有在调用`exit`或者`main`函数结束的时候才会调用`static`的析构函数。 -* 构造函数和析构函数的顺序: ①先构造的后析构,后构造的先析构; - - -拷贝构造函数 -```cpp -#include - -class Clazz{ - public: - Clazz(){ - x = 0; - y = 0; - } - Clazz(int x, int y){ - this->x = x; - this->y = y; - } - void printXY(){ - printf("%d, %d\n", x, y); - } - - //显示的拷贝构造函数 如果不写,默认也有这个 - Clazz(const Clazz &thr){ - x = thr.x; - y = thr.y; - } - private: - int x; - int y; -}; - -int main(int argc, char const **argv) -{ - Clazz c1(100, 200); - - Clazz c2(c1); //拷贝 - c2.printXY(); - - //构造函数是对象初始化的时候调用 - Clazz c3 = c1; // 调用的依然是c3的拷贝构造函数 - c3.printXY(); - - - Clazz c4; //调用的是无参构造器 - c4 = c1; //不是调用拷贝构造函数,而是重载操作符 - c4.printXY(); - - return 0; -} - -``` - -构造函数中的参数初始化列表: - -* 注意构造对象成员的 顺序跟初始化列表的顺序无关; -* 而是跟成员对象的定义顺序有关; -```cpp -#include -#include - -class Box{ - public: - Box(); //默认的无参 - //参数初始化表 - Box(int l, int w, int h):l(l),w(w),h(h) - { - strcpy(this->name, "zhangsan"); //默认 - } - Box(int l, int w, int h, const char *name):l(l),w(w),h(h) - { - strcpy(this->name,name); //字符串不能那样初始化 - } - int volume(); - private: - char name[10]; - int l,w,h; -}; - -Box::Box() -{ - l = 10; - w = 10; - h = 10; - strcpy(this->name, "zhangsan"); -} - -int Box::volume() -{ - return l*w*h; -} - -int main(int argc, char const **argv) -{ - Box b1; - printf("%d\n", b1.volume()); - Box b2(20, 20, 20); - printf("%d\n", b2.volume()); - Box b3(30, 30, 30, "lisi"); - printf("%d\n", b3.volume()); - return 0; -} -``` -输出: - -```cpp -1000 -8000 -27000 -``` - -##### 深拷贝和浅拷贝 -* 深拷贝是有必要的,因为析构函数释放内存的时候,如果使用浅拷贝拷贝了一个对象,释放两个对象指向得到同一个内存两次,就会产生错误。 -```cpp -#include -#include -#include - -class Teacher{ - public: - Teacher(int id, const char* name){ - this->id = id; - int len = strlen(name); - this->name = (char*)malloc(len+1); - strcpy(this->name, name); - } - - //显式的提供一个拷贝构造函数,完成深拷贝动作 - // 防止在析构函数中 释放堆区空间两次 - Teacher(const Teacher &thr){ - id = thr.id; - //深拷贝 - int len = strlen(thr.name); - name = (char*)malloc(len + 1); - strcpy(name, thr.name); - } - - //必须要显式的提供深拷贝构造函数,不然会释放两次 - ~Teacher(){ - if(name != NULL){ - free(name); - name = NULL; - } - } - - void print(){ - printf("id: %d, name = %s\n", id, name); - } - private: - int id; - char *name; -}; - -int main(int argc, char const **argv) -{ - Teacher t1(1, "zhangsan"); - t1.print(); - Teacher t2(t1); - t2.print(); - return 0; -} -``` - - -##### 指向对象成员函数的指针 -* 注意: 定义指向对象成员函数的指针变量的方法和定义[**指向普通函数的指针变量**](https://blog.csdn.net/zxzxzx0119/article/details/84001021#t3)不同,定义指向成员函数的指针变量的方法: `void (Time:: *p)();`(对比普通的: `void (*p)();`) : 定义`p`为指向`Time`类中公共成员函数的指针变量; -* 可以使用上面的`p`指针指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个公用成员函数的指针即可,如`p = &Time::get_time`; - -简单测试: -```cpp -#include - -class Time{ - public: - Time(int h, int m, int s):hour(h),minute(m),sec(s){ } - void get_time(); - private: - int hour; - int minute; - int sec; -}; - -void Time::get_time() -{ - printf("%d:%d:%d\n", hour, minute, sec); -} - -int main(int argc, char const **argv) -{ - Time t1(10,10,30); //10:10:30 - Time *p = &t1; //定义指向对象的指针 - p->get_time(); - - void (Time::*p2)(); //定义指向Time类公共成员函数的指针变量 - p2 = &Time::get_time; - (t1.*p2)(); // 调用对象t1中p2所指向的成员函数(即t1.get_time()) - - return 0; -} -``` -输出: -```cpp -10:10:30 -10:10:30 -``` -##### 常对象 -* 可以在定义对象时加上关键字`const`,指定对象为常对象。常对象必须要有初值,凡是希望保护数据成员不被改变的对象,都可以声明为常对象。 -* 基本语法: `类名 const 对象名 [(实参表)]` 或者`const 类名 对象名 [(实参表)]`,举例: `Time const t(10, 10, 30)`和`const Time t(10, 10, 30)`; -* 如果一个对象被声明为常对象,则通过改对象只能调用它的常成员函数,不能调用对象的普通成员函数;例如`void get_time() const` (常成员函数); -* 常成员函数可以访问常对象中的数据成员,但不允许修改常对象中数据成员的值; - -##### 常对象成员-常数据成员&常成员函数 -①常数据成员 -* 只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据赋值; -* 因为常数据成员不能被赋值; - -②常成员函数 -* 常成员函数声明: 例如`void get_time() const`,常成员函数只能引用本类中的数据成员,不能修改它们; -* 注意: 如果定义了一个常对象,只能调用其中的`const`成员函数,不能调用非`const`成员函数(不论这些函数是否会修改对象中的数据); -* 常成员函数不能调用另一个非`const`成员函数; - -|数据成员|非`const`成员函数|`const`成员函数| -|-|-|-| -|非`const`数据成员|可以引用,可以改变值|可以引用,不可改变值| -|`const`数据成员|可以引用,不可改变值|可以引用,不可改变值| -|`const`对象|不允许|可以引用,不可改变值| - -##### 指向对象的常指针 -* 基本语法`Time * const p = &t1`和基本的常指针差不多; -* 只能指向同一个对象不能改变指针的指向; -* 意义是作为函数的形参,不允许在函数执行过程中改变指针变量,使其始终指向原来的对象; -##### 指向常变量、对象的指针 -①指向常变量的指针 -* 如果一个变量已经被声明为常变量,只能用指向常变量的指针变量指向它,而不能用非`const`指针指向它; - -```cpp -const char c[] = "boy"; -const char *p1 = c; // ok -char *p2 = c; // err -``` -* `const`指针除了可以指向`const`变量,还可以指向非`const`变量,此时不能通过指针来改变变量的值; -* 如果函数的形参是指向非`const`变量的指针变量,实参只能使用指向非`const`变量的指针;更多形参和实参指针变量关系看下表; - -|形参|实参|合法否|改变指针所指向的变量的值| -|-|-|-|-| -|指向非`const`变量的指针|非`const`变量的地址|合法|可以| -|指向非`const`变量的指针|`const`变量的地址|不合法|| -|指向`const`变量的指针|`const`变量的地址|合法|不可以| -|指向`const`变量的指针|非`const`变量的地址|合法|不可以| - -②指向常对象的指针 -* 和指向常变量的指针类似,只能用指向常对象的指针变量指向它,不能用指向非`const`对象的指针指向它; -* 如果定义了一个指向常对象的指针变量,并指向了一个非`const`对象,则不能通过改指针变量来改变变量的值; - -```cpp -Time t1(10, 10, 30); -const Time *p = &t1; -(*p).hour = 20; //err -``` -* 使用意义也是在形参上,希望调用函数的时候对象的值不被改变,就把形参指定为指向常对象的指针变量; - -##### 静态成员 -①静态数据成员 -* 静态数据成员在内存中只占一份空间(不是每一个对象都为它保留一份空间),静态数据成员的值对所有对象都是一样的,如果改变它的值,在其他对象中也会改变; -* 静态数据成员只能在类体外进行初始化。如`int Box::height = 10;`,只在类体中声明静态数据成员时加上`static`,不必在初始化语句中加`static`。 -* 不能在参数初始化列表中对静态数据成员进行初始化; - -②静态成员函数 -* 静态成员函数和普通函数最大的区别就在静态成员函数没有`this`指针,决定了静态成员函数与不能访问本类中的非静态成员。所以静态成员函数的作用就是用来访问静态数据成员; -* 但是普通成员函数(非静态)可以引用类中的静态数据成员; -##### 友元 -① 将普通函数声明为友元函数 - -* 这样这个普通函数就可以访问声明了友元函数的那个类的私有成员; - -```cpp -#include - -class Time{ - public: - Time(int, int, int); - friend void display(Time&); - private: - int hour; - int minute; - int sec; -}; - -Time::Time(int h, int m, int s){ - hour = h; - minute = m; - sec = s; -} - -//普通的友元函数 -void display(Time& t){ - printf("%d:%d:%d\n", t.hour, t.minute, t.sec); -} - -int main(int argc, const char **argv) -{ - Time t(10, 10, 30); - display(t); - return 0; -} -``` -输出: -```cpp -10:10:30 -``` - -② 将别的类中的成员函数声明为友元函数 - -```cpp -#include - -class Date; //对Date类声明 - -class Time{ - public: - Time(int, int, int); - void display(Date &); //要访问Date类的成员 - private: - int hour; - int minute; - int sec; -}; - -Time::Time(int h, int m, int s){ - hour = h; - minute = m; - sec = s; -} - -class Date{ - public: - Date(int, int, int); - friend void Time::display(Date &); //声明Time类中的display函数为本类的友元函数 - private: - int month; - int day; - int year; -}; - -Date::Date(int m, int d, int y){ - month = m; - day = d; - year = y; -} - -void Time::display(Date &d){ - printf("%d/%d/%d\n",d.month, d.day, d.year); - printf("%d:%d:%d\n", hour, minute, sec); -} - -int main(int argc, const char **argv) -{ - Time t(10, 10, 30); - Date d(12,25,2018); - t.display(d); - return 0; -} - -``` - -③ 友元类 - -* 在`A`类中声明`B`类是自己的友元类,这样`B`类就可以访问`A`的所有私有成员了。 -* 但是要注意友元的关系是单向的而不是双向的。且友元的关系不能传递; - -* ** -### C++重载运算符 - -##### 重载基本运算符 -**案例一: 重载`+`号来计算复数** - -```cpp -#include - - -class Complex{ - public: - Complex(int a, int b):a(a),b(b){ } - - // 方法一.使用成员函数 - //不能写成返回& (引用)因为temp是局部的 ,函数调用完毕就会释放 - Complex operator+ (const Complex &thr){ - Complex temp(a + thr.a, b + thr.b); - return temp; - } - - // 方法二.使用友元函数 - //friend Complex operator+ (const Complex &c1, const Complex &c2); - - // 打印 - void printC() - { - printf("(%d,%di)\n",a,b); - } - private: - int a; //实部 - int b; //虚部 -}; - -//Complex operator+ (const Complex &c1, const Complex &c2){ //注意也不能返回局部的引用 -// return Complex(c1.a + c2.a, c1.b + c2.b); -//} - -int main(int argc, char const **argv) -{ - Complex c1(1,2); - Complex c2(3,4); - Complex c3 = c1 + c2; - - //下面的显示调用也是和上面等同的,但是一般不会这么写 - //Complex c3 = c1.operator+(c2); //成员函数 - //Complex c3 = operator+(c1, c2); //友元函数 - c3.printC(); -} - -``` -输出: -```cpp -(4,6i) -``` - -**案例二: 重载双目运算符** - -* 注意这个要返回的是引用,因为运算符支持连续的相加操作; -* 在成员函数中返回 `this`指针指向的内容,在友元函数中可以返回第一个参数的引用(不是局部变量的,所以可以返回); - -```cpp -#include - -//重载 += 运算符 -class Complex{ - public: - Complex(int a, int b):a(a), b(b){ } - - // 方法一:使用成员函数 - // 注意这里和上一个不同,这里要返回引用 - //Complex& operator+= (const Complex &thr){ - // this->a += thr.a; - // this->b += thr.b; - // return *this; //注意这里返回this指针指向的内容 - //} - - // 方法二: 使用友元函数 - friend Complex& operator+= (Complex& c1, const Complex& c2); - - void printC() - { - printf("(%d,%di)\n", a, b); - } - private: - int a; - int b; -}; - -//也要返回引用 因为要支持连续操作 类似(c1 += c2) += c3 -Complex& operator+= (Complex& c1, const Complex& c2){ - c1.a += c2.a; - c1.b += c2.b; - return c1; -} - -int main(int argc, char const **argv) -{ - Complex c1(1,2); - Complex c2(3,4); - (c1 += c2) += c2; - c1.printC(); - - return 0; -} - -``` -**案例三: 重载单目运算符** - -```cpp -#include - - -class Complex{ - public: - Complex(int a, int b):a(a),b(b){ } - - //重载 前置++ - Complex operator++ (){ - a++; - b++; - return *this; - } - - // 重载后置++ 需要使用一个占位符 - friend const Complex operator++ (Complex& c1, int); - void printC() - { - printf("(%d,%di)\n", a, b); - } - private: - int a; - int b; - -}; - -//后置++ -const Complex operator++ (Complex& c1, int) -{ - Complex temp(c1.a, c1.b); - c1.a++; - c1.b++; - return temp; -} - -int main(int argc, char const **argv) -{ - Complex c(1,2); - c++; - c.printC(); - ++c; - c.printC(); - return 0; -} - -``` -输出: - -```cpp -(2,3i) -(3,4i) -``` - - -##### 重载`=`号操作符 -* 这个重点是在当类中有指针的时候,就要注意堆中分配空间的问题,如果不是在初始化的时候使用的`=`操作符,就是代表的赋值,其中的指针不能使用浅拷贝; -* 需要我们重写`=`操作符,实现深拷贝。(就是不能让两个对象同时指向堆中的同一块内存,因为释放内存的时候不能释放两次); -```cpp -#include -#include - -class Student{ - public: - Student() { - this->id = 0; - this->name = NULL; - } - Student(int id, const char *name){ - this->id = id; - - int len = strlen(name); - this->name = new char[len+1]; - strcpy(this->name, name); - } - Student(const Student& thr){ // 拷贝构造 - this->id = thr.id; - //深拷贝 - int len = strlen(thr.name); - this->name = new char[len+1]; - strcpy(this->name, thr.name); - } - - //重载=号,防止 s2 = s1的时候内部的name直接指向堆中的同一个内容,析构时发生错误 - Student& operator= (const Student& thr){ - //1. 防止自身赋值 - if(this == &thr) //&是取地址 - return *this; - //2. 将自身的空间回收 - if(this->name != NULL){ - delete[] this->name; - this->name = NULL; - this->id = 0; - } - - //3. 执行深拷贝 - this->id = thr.id; - int len = strlen(thr.name); - this->name = new char[len + 1]; - strcpy(this->name, thr.name); - - //4. 返回本身的对象 - return *this; - } - ~Student(){ - if(this->name != NULL){ - delete[] this->name; - this->name = NULL; - this->id = 0; - } - } - void printS(){ - printf("%s\n", name); - } - private: - int id; - char *name; -}; - - -int main(int argc,const char **argv) -{ - Student s1(1, "zhangsan"); - Student s2(s1); //拷贝构造 - Student s3 = s1; //这个调用的还是拷贝构造函数,不是=操作符,初始化的都是拷贝构造函数 - s1.printS(); - s2.printS(); - s3.printS(); - - - //但是这样就是调用的等号操作符 - Student s4; - s4 = s1; //不是初始化的时候 - s4.printS(); - return 0; -} -``` - -##### 重载流插入运算符和流提取运算符 - -* 重载流插入运算符的基本格式: `istream& operator>> (istream&, 自定义类&);` ,也就是返回值和第一个参数必须是`istream&`类型; -* 重载流提取运算符的基本格式: `ostream& operator<< (istream&, 自定义类&);`,也就是返回值和第一个参数必须是`ostream&`类型; -* 注意: 重载这两个运算符的时候,只能将他们作为友元函数,不能作为成员函数。避免修改 `c++`的标准库。 - -案例: -```cpp -#include -#include -using namespace std; - -class Complex{ - public: - friend ostream& operator<< (ostream&, Complex&); - friend istream& operator>> (istream&, Complex&); - private: - int a; - int b; -}; - -ostream& operator<< (ostream& os, Complex& c) -{ - os<<"("<> (istream& is, Complex& c) -{ - cout<<"a:"; - is>>c.a; - cout<<"b:"; - is>>c.b; - return is; -} - -int main(int argc, char const **) -{ - Complex c; - cin>>c; - cout< -#include -using namespace std; - -const int N = 2; -const int M = 3; - -class Matrix{ - public: - Matrix(); - friend Matrix operator+(Matrix&, Matrix&); - //千万注意: 只能将"<<"和">>"定义为友元函数,不能作为成员函数 - friend ostream& operator<<(ostream&, Matrix&); - friend istream& operator>>(istream&, Matrix&); - private: - int mat[N][M]; -}; - -Matrix::Matrix(){ - for(int i = 0; i < N; i++){ - for(int j = 0; j < M; j++){ - mat[i][j] = 0; - } - } -} - -//注意返回局部变量对象,不能返回引用 -Matrix operator+ (Matrix& a, Matrix& b){ - Matrix c; - for(int i = 0; i < N; i++){ - for(int j = 0; j < M; j++){ - c.mat[i][j] = a.mat[i][j] + b.mat[i][j]; - } - } - return c; -} - -ostream& operator<<(ostream& os, Matrix& m){ - for(int i = 0; i < N; i++){ - for(int j = 0; j < M; j++){ - os<>(istream& is, Matrix& m){ - for(int i = 0; i < N; i++){ - for(int j = 0; j < M; j++){ - is>>m.mat[i][j]; - } - } - return is; -} - -int main(int argc, const char **argv) -{ - Matrix matrix1,matrix2; - cin>>matrix1; - cin>>matrix2; - Matrix matrix3 = matrix1 + matrix2; - cout<函数模板、类模板 - -* 函数模板基本语法: `template `; -* 类模板基本语法: `template `,注意函数声明在外面的时候也要在函数的上面写上这个语句; -* 函数模板实际上是建立一个通用函数, 其函数类型和形参类型不具体制定, 用 一个虚拟的类型来代表。 这 个通用函数就成为函数模板; -* 注意: ①函数模板不允许自动类型转化;②普通函数能够自动进行类型转化; -* <函数模板和普通函数在一起调用规则:① 函数模板可以想普通函数那样可以被重载;②`C++`编 译器优先考虑普通函数; -* 类模板中的 `static`关 键字: ①从 类模板实例化的每一个模板类有自己的类模板数据成员, 该 模板的所有对象共享一 个 `statci`数 据成员②每 个模板类有自己类模板的`static`数据成员的副本; - - -测试程序: -```cpp -#include -#include -using namespace std; - -template -void swap1(T& a, T& b){ - T t = a; - a = b; - b = t; -} - -template -class Complex{ - public: - Complex(T a, R b):a(a),b(b){ } - //方法一: 下面的printC函数是在类内实现 - //void printC() - //{ - // cout<<"("<& c){ - os<<"("< -void Complex::printC() -{ - cout<<"("<(a,b);//显示的调用 - //自动类型推导 - //swap1(a, b); - printf("a = %d,b = %d\n", a, b); - - printf("-------类模板-------\n"); - Complexc1(2.2, 3.3); - c1.printC(); - - Complexc2(4, 5.5); - cout< -#include -#include -using namespace std; - -template -class MyArray{ - public: - MyArray(int capacity){ //构造函数 - this->capacity = capacity; - this->size = 0; - this->p = new T[this->capacity]; - } - MyArray(const MyArray& thr){ //拷贝构造 - this->size = thr.size; - this->capacity = thr.capacity; - this->p = new T[this->capacity]; - for(int i = 0; i < this->size; i++){ - this->p[i] = thr.p[i]; - } - } - - //重载=号 - MyArray& operator= (const MyArray& thr){ - //1. 防止自身赋值 - if(this == &thr){ - return *this; - } - //2. 删除原来的 - if(p != NULL){ - delete[] this->p; - this->p = NULL; - } - - this->size = thr.size; - this->capacity = thr.capacity; - this->p = new T[this->capacity]; - for(int i = 0; i < this->size; i++){ - this->p[i] = thr.p[i]; - } - return *this; - } - - - //重载[]运算符: 提取元素 - T& operator[](int index){ - return this->p[index]; - } - - void pushBack(T& ele){ - if(size == capacity){ - return; - } - // 调用的是 = 号运算符,如果成员是指针,要注意深拷贝问题 - p[size++] = ele; - } - //搞定插入常量(右值)的引用问题(C++11特性) - void pushBack(T&& ele){ - if(size == capacity){ - return; - } - // 调用的是 = 号运算符,如果成员是指针,要注意深拷贝问题 - p[size++] = ele; - } - - ~MyArray(){ - if(this->p != NULL){ - delete[] this->p; - } - } - - int getSize(){ - return this->size; - } - private: - int capacity;//容量 - int size; //当前数组元素 - T *p; //数组首地址 -}; - - -int main(int argc, const char **argv) -{ - MyArrayarray(20); - int a = 10, b = 20; - array.pushBack(a); - array.pushBack(b); - - array.pushBack(30); - - for(int i = 0; i < array.getSize(); i++){ - cout<文件操作 - -#### 输入输出流  - -`C++`输入输出包含以下三个方面的内容: -* 对 系统指定的标准设备的输入和输出。 即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准 I /O。 -* 以外存磁盘文件为对象进行输入和输出, 即从磁盘文件输入数据, 数据输出到磁盘文件。 以外存文件为对象的输入输出称为文件的输入输出,简称文件 I /O。 -* 对 内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间( 实际上可以利用该空间存储任何信息) 。 这种输入和输出称为字符串输入输出,简称串 I /O。 - - -缓冲区的概念: -* 要注意; -* 读和写是站在应用程序的角度来看的; -![在这里插入图片描述](images/cpp2.png) - - -标准输入流对象 `cin`, 重点函数: -```cpp -cin.get() // 一次只能读取一个字符 -cin.get(一个参数) // 读 一个字符 -cin.get(两个参数) // 可以读字符串 -cin.getline() -cin.ignore() -cin.peek() -cin.putback() -``` - -测试`cin.get()`、`cin.getline()`的简单使用: -```cpp -#include -#include -using namespace std; - -int main(int argc, char const **argv) -{ - char ch; - //while( (ch = cin.get()) != EOF) - // cout << ch << endl; - //char ch; cin.get(ch); //same as above - char buf[256] = { 0 }; - cin.get(buf, 256); // 两个参数,从缓冲区读取一个字符串指定长度 - cout << buf; - cin.get(buf, 256, '\n'); //三个参数,指定终止的字符 - cout << buf; - cin.getline(buf, 256); //读取一行数据,不读换行符 - cout << buf; - return 0; -} -``` -测试`cin.ignore()`使用: - -```cpp -#include - -using namespace std; - -int main(int argc, char const **argv) -{ - char ch; - cin.get(ch); //从缓冲区要数据 阻塞 - cout << ch << endl; - cin.ignore(1); //忽略当前字符 从缓冲区取走了 - cin.get(ch); - cout << ch << endl; - return 0; -} -``` -运行效果: -![在这里插入图片描述](images/cpp3.png) - -测试`cin.peek()`函数的使用: -```cpp -#include - -using namespace std; - -int main(int argc, char const** argv) -{ - cout << "请输入数组或者字符串:" << endl; - char ch; - ch = cin.peek(); //偷窥一下缓冲区,并不会取走, 返回第一个字符 - if (ch >= '0' && ch <= '9'){ - int number; - cin >> number; // 从缓冲区读取这个数字 - cout << "您输入的是数字:" << number << endl; - } - else{ - char buf[256] = { 0 }; - cin >> buf; // 从缓冲区读取这个字符串 - cout << "您输入的是字符串:" << buf << endl; - } - return 0; -} - -``` -运行结果:  -![在这里插入图片描述](images/cpp4.png) - -测试`cin.putback()`函数的使用: - -```cpp -#include - -using namespace std; - -int main(int argc, char const **argv) -{ - cout << "请输入字符串或者数字:" << endl; - char ch; - cin.get(ch); //从缓冲区取走一个字符 - if (ch >= '0' && ch <= '9'){ - cin.putback(ch); //ch放回到缓冲区 - int number; - cin >> number; - cout << "您输入的是数字:" << number << endl; - }else{ - cin.putback(ch);// 将字符放回缓冲区 - char buf[256] = { 0 }; - cin >> buf; - cout << "您输入的是字符串: " << buf << endl; - } - return 0; -} -``` -效果和测试`cin.peek()`函数的一样。 - -标准输出流对象 `cout`,重点函数: -```cpp -cout.flush() // 刷新缓冲区 -cout.put() // 向缓冲区写字符 -cout.write() // 二进制流的输出 -cout.width() // 输出格式控制 -cout.fill() -cout.setf(标记) -``` -`cout`比较简单不做案例演示。 - - -#### 文件读写 -输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。 在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。 和 文件有关系的输入输出类主要在 `fstream.h` 这 个头文件中被定义,在这个头文件中主要被定义了三个类, 由这三个类控制对文件的各种输入输出操作 , 他们分别是 `ifstream`、 `ofstream`。 - -![在这里插入图片描述](images/cpp5.png) -由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在 `fstream.h` 头 文件中 -是没有像 `cout` 那 样预先定义的全局对象,所以我们必须自己定义一个该类的对象。 `ifstream` 类 ,它是从 `istream` 类 派生的,用来支持从磁盘文件的输入。`ofstream` 类 ,它是从`ostream`类 派生的,用来支持向磁盘文件的输出。 -`fstream` 类 ,它是从` iostream `类 派生的,用来支持对磁盘文件的输入输出。 - -所谓打开( `open`)文 件是一种形象的说法,如同打开房门就可以进入房间活动一样。 打 -开 文件是指在文件读写之前做必要的准备工作,包括: -* 为文件流对象和指定的磁盘文件建立关联,以便使文件流 向指定的磁盘文件。 -* 指定文件的工作方式,如,该文件是作为输入文件还是输出文件,是 `ASCII` 文件还是二进制文件等。 - -以上工作可以通过两种不同的方法实现。 - -```cpp -#include -#include //文件读写的头文件 - -using namespace std; - -int main(int argc, char const **argv) -{ - const char* srcName = "src.txt"; - const char* destName = "dest.txt"; - ifstream is(srcName, ios::in); //只读方式打开 - ofstream os(destName, ios::out | ios::app); // app表示是追加 - if(!is){ - cout << "打开文件失败!" << endl; - return -1; - } - char ch; - while(is.get(ch)){ - cout << ch; - os.put(ch); // 输出到os指向的文件 - } - is.close(); - os.close(); - return 0; -} -``` - -演示结果: -![在这里插入图片描述](images/cpp6.png) - - -测试按照二进制方式写入和读取: -```cpp -#include -#include -using namespace std; - -class Person{ -public: - int age; - int id; - Person(){ } - Person(int age, int id):age(age), id(id){ } - void show() - { - cout << "age: " << age << ", id: " << id << endl; - } -}; - -#if 0 -int main(int argc, char const **argv) -{ - Person p1(10, 20), p2(30, 40); - const char *destFile = "dest.txt"; - ofstream os(destFile, ios::out | ios::binary); // 二进制方式写入 - os.write((char*)&p1, sizeof(Person)); - os.write((char*)&p2, sizeof(Person)); - os.close(); - return 0; -} -#endif - -int main(int argc, char const** argv) -{ - const char *destFile = "dest.txt"; - ifstream is(destFile, ios::in | ios::binary); - Person p1, p2; - is.read((char*)&p1, sizeof(Person)); - is.read((char*)&p2, sizeof(Person)); - p1.show(); - p2.show(); - return 0; -} - -``` - diff --git "a/C++/C/C\350\257\255\350\250\200\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" "b/C++/C/C\350\257\255\350\250\200\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" deleted file mode 100644 index eba49064..00000000 --- "a/C++/C/C\350\257\255\350\250\200\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223(\344\270\200).md" +++ /dev/null @@ -1,1255 +0,0 @@ -## C语言基础知识总结(一) - -* [编译、运行](#编译运行) - * [编译格式](#编译格式) - * [C语言编译过程](#c语言编译过程) - * [CPU、寄存器](#cpu寄存器) - * [关于VS的C4996错误](#关于vs的c4996错误) -* [进制,原、反、补码](#进制原反补码) - * [进制相关](#进制相关) - * [原码、反码、补码](#原码反码补码) - * [原码和补码互换](#原码和补码互换) - * [有符号和无符号的区别](#有符号和无符号的区别) - * [数据类型取值分析](#数据类型取值分析) - * [越界问题](#越界问题) -* [数据类型、运算符等基础](#数据类型运算符等基础) - * [C语言数据类型](#c语言数据类型) - * [sizeof关键字](#sizeof关键字) - * [char数据类型-char的本质就是一个1字节大小的整型](#char数据类型-char的本质就是一个1字节大小的整型) - * [浮点数不准确、类型限定符](#浮点数不准确类型限定符) - * [字符的输入问题](#字符的输入问题) - * [运算符以及优先级](#运算符以及优先级) - * [类型转换](#类型转换) -* [数组、字符串、函数](#数组字符串函数) - * [数组初始化的几种方式](#数组初始化的几种方式) - * [字符数组与字符串](#字符数组与字符串) - * [字符串输出乱码问题以及一系列字符串要注意的问题](#字符串输出乱码问题以及一系列字符串要注意的问题) - * [随机数生成](#随机数生成) - * [字符串相关函数](#字符串相关函数) - * [函数](#函数) - -*** -### 编译、运行 - -##### 编译格式 - `gcc -o main main.cpp`生成`main`可执行文件,可以有两种运行方式: - * 当前目录运行`./main`; - * 绝对路径运行,例如`/home/zxzxin/C/main`,要注意的是绝对路 径没有`.`,因为`.`代表的是当前路径,也就是说我们只需要写上完整路径即可; - - - 编译命令格式 -```shell -gcc [-option] ... //c语言编译 -g++ [-option] ... //c++编译 -``` -* gcc、g++编译常用选项说明: - -|选项|含义| -|--|--| -|-o file|指定生成的输出文件名为file| -|-E|只进行预处理| -|-S(大写)|只进行预处理和编译| -|-c(小写)|只进行预处理、编译和汇编| - -* 注意下面的两种方式生成可执行文件的效果是一样的: - - - -![在这里插入图片描述](images/c1.png) -**平台问题:** -① Linux编译后的可执行程序只能在Linux运行,Windows编译后的程序只能在Windows下运行; -②64位的Linux编译后的程序只能在64位Linux下运行,32位Linux编译后的程序只能在32位的Linux运行; -③64位的Windows编译后的程序只能在64位Windows下运行,32位Windows编译后的程序可以在64位的Windows运行; - - - -**可以在程序中嵌套`Linux`的命令,会在可执行文件的对应目录执行相应的命令;** -```cpp -#include -#include - -int main(int argc, char const *argv[]) -{ - printf("before\n"); - system("ls -alh"); // system内放置 linux的命令 甚至也可以在""内放 ./a.out这种,也可以调用 - printf("after\n"); - return 0; -} -``` -运行: - -![在这里插入图片描述](images/c2.png) - - - -* `#include< >` 与 `#include ""`的区别: -①`< >` 表示系统直接按系统指定的目录检索; -② `""` 表示系统先在 `""` 指定的路径(没写路径代表当前路径)查找头文件,如果找不到,再按系统指定的目录检索; - -##### C语言编译过程 -C代码编译成可执行程序经过4步: - -* 1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法; -* 2)编译:检查语法,将预处理后文件编译生成汇编文件; -* 3)汇编:将汇编文件生成目标文件(二进制文件); -* 4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去; - - - -![在这里插入图片描述](images/c3.png) - -![在这里插入图片描述](images/c4.png) -![在这里插入图片描述](https://img-blog.csdnimg.cn/20181106195818281.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) - -然而一般编译的时候都是使用的一步编译。 (但是这样还是经过:预处理、编译、汇编、链接的过程。) - -```shell -gcc hello.c -o hello -``` -* `Linux`下查找程序所依赖的动态库 - -```shell -ldd hello -``` -##### CPU、寄存器 - -* 寄存器是CPU内部最基本的存储单元; -* CPU对外是通过总线(地址、控制、数据)来和外部设备交互的,总线的宽度是8位,同时CPU的寄存器也是8位,那么这个CPU就叫8位CPU -* CPU计算时,先预先把要用的数据从硬盘读到内存,然后再把即将要用的数据读到寄存器。于是` CPU<--->寄存器<--->内存`,这就是它们之间的信息交换,看下面的图片说明: - - 寄存器、缓存、内存三者关系: - -![在这里插入图片描述](images/c5.png) - -##### 关于VS的C4996错误 - -* 由于微软在VS2013中不建议再使用C的传统库函数scanf,strcpy,sprintf等,所以直接使用这些库函数会提示C4996错误。VS建议采用带`_s`的函数,如`scanf_s`、`strcpy_s`,但这些并不是标准C函数。 -* 要想继续使用此函数,需要在源文件中添加以下两个指令中的一个就可以避免这个错误提示: -```cpp -#define _CRT_SECURE_NO_WARNINGS //这个宏定义最好要放到.c文件的第一行 -#pragma warning(disable:4996) //或者使用这个 -``` - -*** -### 进制,原、反、补码 -##### 进制相关 -```cpp -#include - -int main(int argc, char const *argv[]) -{ - int a = 123; //十进制方式赋 - int b = 0123; //八进制方式赋值, 以数字0开 - int c = 0xABC; //十六进制方式赋值 - - //如果在printf中输出一个十进制数那么用%d,八进制用%o,十六进制是%x - printf("十进制:%d\n",a ); - printf("八进制:%o\n", b); // %o,为字母o,不是数字 - printf("十六进制:%x\n", c); - return 0; -} -``` -输出: - -```cpp -十进制:123 -八进制:123 -十六进制:abc -``` - -##### 原码、反码、补码 -###### 原码 - -一个数的原码(原始的二进制码)有如下特点: -* 最高位做为符号位,0表示正,为1表示负; -* 其它数值部分就是数值本身绝对值的二进制数; -* 负数的原码是在其绝对值(相反数)的基础上,最高位变为1; - -例如:(以一个字节(8bit)来看)  -|十进制数|原码| -|--|--| -|+15|0000 1111| -|-15|1000 1111| -|+0|0000 0000| -|-0|1000 0000| - -原码存储导致2个问题: - -* 0有两种存储方式; -* 正数和负数相加,结果不正确(计算机只会加不会减); - -例如: -以原码来算不同符号的数: -```cpp -1-1 = 1 + -1 - 1: 0000 0001 - -1: 1000 0001 - 1000 0010 = -2 //答案错误 -``` - -###### 反码 -* 正数的原码和反码是一样; -* 对于负数,先求原码,在原码基础上,符号位不变,其它位取反(0为1, 1变0); - -例如:(以一个字节(8bit)来看)  - -|十进制数|原码|反码| -|--|--|--| -|+15|0000 1111|0000 1111| -|-15|1000 1111|1111 0000| -|+0|0000 0000|0000 0000| -|-0|1000 0000|1111 1111| - -反码计算两个符号不同的数: - -```cpp -1-1 = 1 + -1 - 1:0000 0001 --1:1111 1110 - 1111 1111 = -0 //答案是对的 -``` - -但是反码还是没有解决0有两种存储方式的问题。 -###### 补码 - -综上,计算机存储数字以补码方式存储(为了解决负数的存储); - -补码特点: -* 对于正数,原码、反码、补码相同; -* 对于负数,其补码为它的反码加1; - -例如:(以一个字节(8bit)来看)  - -|十进制数|原码|反码|补码| -|--|--|--|--| -|+15|0000 1111|0000 1111|0000 1111| -|-15|1000 1111|1111 0000|1111 0001||| -|+0|0000 0000|0000 0000|0000 0000|| -|-0|1000 0000|1111 1111|0000 0000| - -补码计算: - -```cpp -1-1 = 1 + -1 - 1:0000 0001 --1:1111 1111 - 10000 0000(最高位丢弃) = 0000 0000 //注意1字节,所以丢弃最高位 -``` - -记得一个原则: -* 十进制数 --> 站在用户的角度 --> 原码; -* 二进制、八进制、十六进制 --> 站在计算机角度 --> 补码; - - -##### 原码和补码互换 - -原码求补码: -* ①. 先求原码: 最高位符号位,其它位就是二进制; -* ②. 在①基础上,符号位不变,其它位取反; -* ③. 在②基础上加1; - -补码求原码: -* ①. 先得到补码; -* ②. 求补码的反码,符号位不变,其它位取反; -* ③. 在②基础上加1(注意不要人为的相乘相反是减) - - - -综上,在计算机系统中,数值一律用补码来存储,主要原因是: -* 统一了零的编码; -* 将符号位和其它位统一处理; -* 将减法运算转变为加法运算; -* 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃; - - -相关案例计算转换: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char a = 0x81; // 计算机的角度(补码) - printf("a = %d\n",a); - - char b = 0xe5;//计算机的角度(补码) - printf("b = %d\n",b); - - char c = 0x6f; //计算机的角度(补码) - printf("c = %d\n",c); - - char d = -123; //人类的角度 --> 看成原码 - printf("d = %x\n",d);//输出16进制(计算机的角度) - return 0; -} -``` -输出: - -```cpp -a = -127 -b = -27 -c = 111 -d = ffffff85 -``` -对于上面程序的分析: -```cpp -//二进制、八进制、十六进制,站在计算机角度,补码 -//0x81转为为二进制位1000 0001,最高位为1,说明是负数 -char a = 0x81; -补码:1000 0001 -反码:1111 1110 -原码:1111 1111 = -127 -//10进制数,站在用户的角度,原码 -printf("%d\n", a); //-127 - - -//二进制、八进制、十六进制,站在计算机角度,补码 -//0xe5二进制为1110 0101,最高位为1,说明是负数,它是负数的补码 -char b = 0xe5; -补码:1110 0101 -反码:1001 1010 -原码:1001 1011 = -27 -//10进制数,站在用户的角度,原码 -printf("%d\n", b);// -27 - - -0x6f的二级制为0110 1111,最高位为0,它是正数 -char c = 0x6f; -printf("%d\n", c);// 111 原码、反码、补码都相同 - - -//10进制数,站在用户的角度,原码 -int a = -123;//注意这个是十进制,所以直接就是原码 -原码:1000 0000 0000 0000 0000 0000 0111 1011 -反码:1111 1111 1111 1111 1111 1111 1000 0100 -补码:1111 1111 1111 1111 1111 1111 1000 0101 - f f f f f f 8 5 -%x,默认以4个字节(32位)大小打印 -//二进制、八进制、十六进制,站在计算机角度,补码 -printf("%x\n", a);// 16进制打印 ffffff85 -``` - -##### 有符号和无符号的区别 - -* 有符号,最高位是符号位,如果是1代表为负数,如果为0代表为正数; -* 无符号,最高位不是符号位,是数的一部分,无符号不可能是负数; - - -测试: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - // 1000 0000 0000 0000 0000 0000 0111 1011 - // 8000007b - // %d按照有符号数来打印 - printf("%d\n", 0x8000007b); - // %u按照无符号数打印 - printf("%u\n", 0x8000007b); - - // signed 和 unsigned - signed int a = 10; - unsigned int b = -10; - - //注意输出结果 以%d 为准,%d表示按照有符号数来输出 - unsigned int c = 0x8000007b; - printf("c[有符号] = %d\n",c); - printf("c[无符号] = %u\n",c); - return 0; -} -``` -输出: - -```cpp --2147483525 -2147483771 -c[有符号] = -2147483525 -c[无符号] = 2147483771 -``` -分析: - -```cpp -1000 0000 0000 0000 0000 0000 0111 1011 //二进制 -8000007b //16进制 - -//有符号 ---> %d, 默认以有符号的方式打印 -补码:1000 0000 0000 0000 0000 0000 0111 1011 -反码:1111 1111 1111 1111 1111 1111 1000 0100 -原码:1111 1111 1111 1111 1111 1111 1000 0101 - - 7 f f f f f 8 5 = -2147483525 - -2147483525 -printf("%d\n", 0x8000007b); //-2147483525 - -//无符号(最高位看做是数的一部分) ---> %u, 以无符号的方式打印 -1000 0000 0000 0000 0000 0000 0111 1011 -8000007b = 2147483771 - 2147483771 -printf("%u\n", 0x8000007b); //2147483771 -``` - -##### 数据类型取值分析 - -```cpp -数据类型范围(站在10进制角度,原码): -char 1个字节(8位,8bit) -有符号的范围: -正数: -0000 0000 ~ 0111 1111 -0 127 - -负数: -1000 0000 ~ 1111 1111 --0 ~ -127 - -注意这里比较特使: --0 当做 -128使用 --128: -原码: 1 1000 0000 -反码: 1 0111 1111 -补码: 1 1000 0000 -这个很特别: -128的原码和补码是一样的,发现和0 (1000 0000)也是一样的。 - -无符号范围: -0000 0000 ~ 1111 1111 -0 ~ 255 -``` -综上,char: -* 有符号:-128 ~ 127; -* 无符号:0 ~ 255; -##### 越界问题 -赋值或者运算,记得不要越界,下面展示了越界的情况: -```cpp -#include - -int main(int argc, char const *argv[]) -{ - //有符号越界 - char a = 127 + 2; - printf("a = %d\n",a); //-127 - - //无符号越界 - unsigned char b = 255 + 2; - printf("b = %u\n",b); //1 - return 0; -} -``` -输出: -```cpp --127 -1 -``` -分析: -```cpp -char a = 127 + 2; -129转换为二进制:1000 0001,这是负数补码(计算机角度) -补码:1000 0001 -反码:1111 1110 -原码:1111 1111(最高位是符号位) = -127 -printf("%d\n", a);//-127 - -unsigned char b = 255 + 2; -257转化为二进制 :0001 0000 0001 (只取后8位) -printf("%u\n", b);// 1 -``` -*** -### 数据类型、运算符等基础 -##### C语言数据类型 -* 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。 - - - - ![在这里插入图片描述](images/c6.png) - - - -**变量特点:** -* 变量在编译时为其分配相应的内存空间; - -* 可以通过其名字和地址访问相应内存; - - - - ![在这里插入图片描述](images/c7.png) - - - -**声明和定义的区别:** - -* 声明变量不需要建立存储空间,如:`extern int a`; (使用`extern`关键字) -* 定义变量需要建立存储空间,如:`int b`; -* 从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义: - ① `int b` 它既是声明,同时又是定义; - ②对于`extern int b`来讲它只是声明不是定义; - -* 一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”。 - - -##### sizeof关键字 - -* `sizeof`不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节; -* `sizeof`的返回值为`size_t`; -* `size_t`类型在32位操作系统下是`unsigned int`,是一个无符号的整数; - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - //数据类型的作用: 告诉编译器,分配此类型的变量需要多大的空间 - printf("sizeof(char) = %u\n",sizeof(char)); - - int a; - printf("sizeof(a) = %d\n",sizeof(a)); - - size_t len = sizeof(long); - printf("sizeof(long) = %u\n",len); - return 0; -} -``` -输出: -```cpp -sizeof(char) = 1 -sizeof(a) = 4 -sizeof(long) = 8 -``` -##### char数据类型-char的本质就是一个1字节大小的整型 - -* 内存中没有字符,只有数字; -* 一个数字,对应一个字符,这种规则就是`ascii`; -* 使用字符或数字给字符变量赋值是等价的; -* 字符类型本质就是1个字节大小的整形; -* 字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码放到变量的存储单元中。 - -测试: -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char ch = 'a'; - printf("ch[c] = %c, ch[d] = %d\n",ch, ch); - - char ch2 = 97; - printf("ch2[c] = %c\n", ch2); - - printf("ch2 - 32 = %c\n",ch2 - 32); - return 0; -} -``` -输出: - -```cpp -ch[c] = a, ch[d] = 97 -ch2[c] = a -ch2 - 32 = A -``` -以及注意转义字符: - -![在这里插入图片描述](images/c8.png) - -转义字符简单测试: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char ch = '\r'; - printf("abcdddd%cefg\n",ch); - - ch = '\b'; - printf("12%c345\n",ch); - - ch = ' '; - printf("%d\n",ch); // 打印' '的对应数字 - - // 8进制转义字符 - printf("%d\n", '\123'); //8进制123 --> 对应10进制83 - printf("%d\n", '\x23'); //16进制23 --> 对应10进制35 - - // '\0'和0等价 - printf("%d,%d\n",'\0',0); - return 0; -} -``` -输出: -```cpp -efgdddd -1345 -32 -83 -35 -0,0 -``` -##### 浮点数不准确、类型限定符 - -* 浮点数不准确: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - float a = 100.9; - printf("a = %f\n",a); - return 0; -} -``` -输出: - -```cpp -a = 100.900002 -``` -原因还是计算机按照`二进制`存储。 - -* 类型限定符: - - -![在这里插入图片描述](images/c9.png) - -##### 字符的输入问题 - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char ch1; - printf("请输入字符:"); - scanf("%c",&ch1); - printf("ch1 = %c\n",ch1); - - char ch2; - printf("请输入字符:"); - scanf("%c",&ch2); - printf("ch2 = %c\n",ch2); - //输出ch2的ascii码 - printf("ch2[d] = %d\n",ch2); - return 0; -} - -``` -输入输出演示: - -![在这里插入图片描述](images/c10.png) - -这个程序要特别注意: - -* 当我们输入完一个字符时,按下回车,会直接结束程序; -* 因为第二个字符在按下回车的时候已经输入完成了,也就是`ch2 = '\n'`;而`'\n'`的`ascii`码为10; - -![在这里插入图片描述](images/c11.png) - -处理上面的问题,可以使用另一个字符变量过滤掉`\n`,也可以使用`getchar()`来过滤一个字符: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char ch1; - printf("请输入字符:"); - scanf("%c",&ch1); - printf("ch1 = %c\n",ch1); - - //为了处理上面的问题,可以自己用scanf处理这个回车 - char tmp; - //scanf("%c",&tmp); //或者使用下面的方法输入一个字符 - // tmp = getchar(); //或者也可以直接调用getchar()即可 - getchar(); - - char ch2; - printf("请输入字符:"); - scanf("%c",&ch2); - printf("ch2 = %c\n",ch2); - //输出ch2的ascii码 - printf("ch2[d] = %d\n",ch2); - return 0; -} - -``` - -##### 运算符以及优先级 -**运算符优先级: ** - - - -![在这里插入图片描述](images/c12.png) - -![在这里插入图片描述](images/c13.png) - -> * 另外,注意 逻辑判断中的 短路原则。 - -##### 类型转换 - -* 隐式转换: 编译器内部自动转换; -* 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。 -* 浮点型打印说明: 不要以为指定了打印的类型就会自动转换,必须要强制转换; -* 转换原则: 数据类型小的往大的转换; -```cpp -#include - -int main(int argc, char const *argv[]) -{ - printf("------------隐式转换-----------\n"); - //隐式转换 - double a; - int b = 10; - //编译器内部自动转换,在第10行自动转换,其他地方b还是int - a = b; - printf("a = %lf\n",a); - - - printf("------------强制类型转换-----------\n"); - //强制类型转换 - double c = (double)1/2; //强制将1转换成double - printf("c = %lf\n",c); - printf("sizeof(int) = %u\n",(unsigned int)sizeof(int)); - - - printf("------------浮点型打印说明-----------\n"); - //浮点型打印说明 - int d = 11; - printf("d[wrong] = %lf\n",d); //以为指定了%lf就会转换成浮点数,其实是错的 - printf("d[right] = %lf\n",(double)d); // 正确的打印是要强制类型转换的 - double e = 88.14; - printf("e[wrong] = %d\n",e); - printf("e[right] = %d\n",int(e)); - - printf("-------------转换原则----------\n"); - //转换原则: 数据类型 小的往大的 转换 - int g = 129; - char h = 111; - //g = (int)h; // 这个是可以转换的 - //printf("g = %d\n",g); - h = (char)g; //这个转换是出错的,h会变成一个负数 - printf("h = %d\n",h); - return 0; -} -``` -输出: - -```cpp -------------隐式转换----------- -a = 10.000000 -------------强制类型转换----------- -c = 0.500000 -sizeof(int) = 4 -------------浮点型打印说明----------- -d[wrong] = 0.000000 -d[right] = 11.000000 -e[wrong] = 1519177328 -e[right] = 88 --------------转换原则---------- -h = -127 -``` -*** -### 数组、字符串、函数 - -##### 数组初始化的几种方式 - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - int arr1[5] = {1,2,3,4,5}; //数组的定义需要{},只有在定义的时候才能初始化 - int arr2[5]; - //arr2 = {1,2,3,4,5}; //err 这个是错误的 - - //数组全部元素初始化为 某个值 - int arr3[5] = {0}; - - //如果定义的同时 初始化,第一个[]内可以不写内容 - int arr4[] = {1,2,3,4,5}; //编译器会根据用户初始化的元素来确定数组的大小 - //int arr4[]; //err //必须初始化的时候才能省略 - int arr5[][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //第一个可以省略 - return 0; -} -``` - -* 数组的名字代表着数组的首地址,以及使用`sizeof`来求数组长度的方法; - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - printf("------------一维数组------------\n"); - - int arr[10]; - //1. 数组名是数组首元素地址 - printf("arr = %p, &arr[0] = %p\n", arr, &arr[0]); //两个是一样的 - - //2. 测试数组总的字节大小 : sizeof(数组名) 10 * 4 = 40 - printf("sizeof(arr) = %u\n",sizeof(arr)); - - //3. 得到数组长度 数组总大小/每个元素大小 (常用) - int len = sizeof(arr)/sizeof(arr[0]); - printf("len(arr) = %d\n",len); - - - printf("------------二维数组------------\n"); - - int arr2[5][10]; - // 1. 数组名是常量,不能修改 - // a = 10; //err - //2. sizeof(数组名),测数组的总大小 5*int[10] = 5*4*10 - printf("sizeof(arr2) = %u\n",sizeof(arr2)); - - //3. 求行数 - int n = sizeof(arr2)/sizeof(arr2[0]); - printf("行数 = %d\n",n); - - //4. 求列数 - int m = sizeof(arr2[0])/sizeof(arr2[0][0]); - printf("列数 = %d\n",m); - - //5. 总个数 - printf("总个数 = %d\n",sizeof(arr2)/sizeof(arr2[0][0])); - return 0; -} - -``` -输出: - -```cpp -------------一维数组------------ -arr = 0x7ffc86eac190, &arr[0] = 0x7ffc86eac190 -sizeof(arr) = 40 -len(arr) = 10 -------------二维数组------------ -sizeof(arr2) = 200 -行数 = 5 -列数 = 10 -总个数 = 50 -``` -可以看出`arr`和`arr[0]`的地址是一样的。 - -##### 字符数组与字符串 -* C语言中没有字符串这种数据类型,可以通过`char`的数组来替代; -* 字符串一定是一个`char`的数组,但`char`的数组未必是字符串; -* 数字`0`(和字符`‘\0’`等价)结尾的`char`数组就是一个字符串,但如果`char`数组没有以数字`0`结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的`char`的数组。 -```cpp -#include - -int main(int argc, char const *argv[]) -{ - // 1. c语言没有字符串类型,用字符数组模拟 - char str[10]; - - // 2. 字符串一定是字符数组,字符数组不一定是字符串 - // 3. 如果字符数组以'\0'(0)结尾,就是字符串 - - char str2[] = {'a', 'b', 'c'};//字符数组 --> 注意这里[]内没有指定数字 - char str3[10] = {'a', 'b', 'c', '\0'}; //字符串 - char str4[10] = {'a', 'b', 'c', 0}; //字符串 - return 0; -} -``` - -##### 字符串输出乱码问题以及一系列字符串要注意的问题 - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - // 1. 字符数组打印乱码问题 - char a1[] = {'a', 'b', 'c'}; //字符数组 - printf("a1 = %s\n",a1); //这个是乱码,因为字符数组后面没有'\0' - - // 2. 正确的字符串 - char a2[] = {'a', 'b', 'c', '\0'}; - //char a2[] = {'a', 'b', 'c', 0}; // 0也可以 - - // 3. 遇到'\0'提前截断 - char a3[] = {'a', 'b', 'c', '\0', 'h', 'e', '\0'}; - printf("a3 = %s\n", a3);//"abc" - - // 4. 前3个字符赋值为a, b, c, 后面自动赋值为0 - char a4[10] = {'a', 'b', 'c'}; - printf("a4 = %s\n", a4); - - // 5. 常用的初始化方法 --> 使用字符串初始化,在字符串结尾自动加结束符数字0 - // 这个结束符,用户看不到(隐藏),但是是存在的 - char a5[10] = "abc"; - printf("a5 = %s\n", a5); - - // 6. 使用sizeof()测试隐藏的那个'\0' - char a6[] = "abc"; - printf("sizeof(a6) = %d\n", sizeof(a6)); - - // 7. \0 后面最好别跟数字,不然有可能变成转义字符 例如\012 就是'\n' - char a7[] = "\012abc"; - printf("a7 = %s\n", a7); - - // 8. 最多写9个字符,留一个位置放结束符 - char a8[10] = "123456789"; - - // 9. sizeof()测数据类型大小,不会因为结束符提前结束 sizeof("123\045") = 5 - char a9[] = "abc\0de"; - printf("%u\n", sizeof(a9)); // 7 --> a b c \0 d e \0 不要忘记最后还有一个\0 - - // 10. scanf("%s", a); //a没有&,原因数组名是首元素地址,本来就是地址 - return 0; -} -``` -输出结果: - -![在这里插入图片描述](images/c14.png) - - - -##### 随机数生成 - -```cpp -#include -#include -#include - -int main(int argc, char const *argv[]) -{ - // 1. 先设置种子,种子设置一次即可 - // 2. 如果srand()参数一样,随机数就一样 - //srand(100); - - // 由于希望产生的随机数不同,所以要使用系统时间作为随机数 - srand( (unsigned int)time(NULL) ); // 返回的是time_t类型 相当于long,单位为毫秒,注意强制转换一下 - - for(int i = 0; i < 3; i++){ - printf("the rand number is = %d\n", rand()); - } - return 0; -} -``` -##### 字符串相关函数 -###### 输入函数 -① `gets(str)`与`scanf(“%s”,str)`的区别: -* `gets(str)`允许输入的字符串含有空格; -* `scanf(“%s”,str)`不允许含有空格; - -> * 注意:由于`scanf()`和`gets()`无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。 - -② `fgets函数`相关: -   - -```c -char *fgets(char *s, int size, FILE *stream); -``` -* 功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束; -* 参数 : - s:字符串; - size:指定最大读取字符串的长度(size - 1)(大于这个长度就舍弃); - stream:文件指针,如果读键盘输入的字符串,固定写为stdin; -* 返回值: - 成功:成功读取的字符串; - 读到文件尾或出错: NULL; - -* 注意,`fgets()`在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车('\n')也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。 -* fgets()函数是安全的,不存在缓冲区溢出的问题。 - -简单测试: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - // 1. scanf()输入字符串 不做越界检查,此函数不安全 - - // 2. gets(已抛弃,不安全) - char buf[10]; - // gets(buf); //不安全 已废弃 - // printf("buf = %s\n", buf); - - // 3. fgets(安全,可以指定大小) - fgets(buf,sizeof(buf),stdin); - printf("buf = '%s'\n", buf); - - // 4. 测试读入了 最后的换行 - fgets(buf,sizeof(buf),stdin); // 注意虽然这里从键盘读入,但是如果缓冲区还有内容就不会读这里的 - printf("buf2 = '%s'\n", buf); - return 0; -} -``` -输入输出结果: - -![在这里插入图片描述](images/c15.png) - - - -###### 输出函数 - -```cpp -int puts(const char *s); -功能:标准设备输出s字符串,在输出完成后自动输出一个'\n'。 -int fputs(const char * str, FILE * stream); //文件操作 -功能: 将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0' 不写入文件。 -``` - -简单测试: - -```cpp -#include - -int main(int argc, char const *argv[]) -{ - char str[] = "hello world!"; - puts(str); //会自动加上一个 '\n' - - fputs(str,stdout); // 不会自动加上 '\n' - return 0; -} -``` -输出: - - - -![在这里插入图片描述](images/c16.png) - - - -###### sizeof()和strlen()的区别 - -注意: -* `strlen()` 从首元素开始,到结束符为止的长度,结束符不算(遇到'\0'结束); -* 而`sizeof()`则不管遇不遇到`'\0'`都会计算整个数据类型大小; -```cpp -#include -#include - -int main(int argc, char const *argv[]) -{ - - char buf[] = "hello"; - - // strlen 从首元素开始,到结束符为止的长度,结束符不算(遇到'\0'结束) - int len = strlen(buf); - printf("strlen(buf) = %d\n", len); //5 - printf("sizeof(buf) = %d\n", sizeof(buf)); // 6 这个还包括 '\0' - - char buf2[] = "\0hello"; - printf("strlen(buf2) = %d\n", strlen(buf2)); // 0 - printf("sizeof(buf2) = %d\n", sizeof(buf2)); // 7 注意不要忘记最后还有一个 '\0' - - - char buf3[100] = "zxzxin"; - printf("strlen(buf3) = %d\n", strlen(buf3)); //6 - printf("sizeof(buf3) = %d\n", sizeof(buf3)); //100 - - return 0; -} -``` -###### 字符串拷贝strcpy()和strncpy() -注意两者区别: -* `char *strcpy(char *dest, const char *src) `:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去。(如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。) -* `char *strncpy(char *dest, const char *src, size_t n)`:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。 -```cpp -#include -#include - -int main(int argc, char const *argv[]) -{ - - //strcpy: 把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去 - char src[] = "hello world!"; - char dest[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; - strcpy(dest,src); - printf("dest = %s\n", dest);// hello world! 不会输出后面的aaaa, 因为'\0'也拷贝在后面了 - - // strncpy : 把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。 - char dest2[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; - strncpy(dest2, src, strlen(src)); - printf("dest2 = %s\n", dest2); //hello world!aaaaaaaaaaa - - //但是如果拷贝的长度大于strlen(src) - char dest3[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; - strncpy(dest3, src, strlen(src)+1); - printf("dest3 = %s\n", dest3); //hello world! - return 0; -} -``` -输出: - -![在这里插入图片描述](images/c17.png) - - - -###### strcat()、strncat()、 strcmp()、strncmp() - -* `char *strcat(char *dest, const char *src);`: 将src字符串连接到dest的尾部,`‘\0’`也会追加过去; -* `char *strncat(char *dest, const char *src, size_t n);`: 将src字符串前n个字符连接到dest的尾部,`‘\0’`也会追加过去; -* `int strcmp(const char *s1, const char *s2);`: 比较 s1 和 s2 的大小,比较的是字符ASCII码大小; -* `int strncmp(const char *s1, const char *s2, size_t n);`:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小; - -测试: - -```cpp -#include -#include - -int main(int argc, char const *argv[]) -{ - char s1[] = "abc"; - char s2[] = "abcd"; - int flag = strcmp(s1, s2); - printf("flag = %d\n",flag);// <0 - - char s3[] = "abc"; - char s4[] = "Abcd"; - int flag2 = strncmp(s3, s4, 3); //指定比较前3个字符 - printf("flag2 = %d\n",flag2);// >0s - - printf("-------------strcat和strncat------------\n"); - char src[] = " hello mike"; - char dst[100] = "abc"; - //把src的内容追加到dst的后面 - //strcat(dst, src); //dst = "abc hello mike" - strncat(dst, src, strlen(" hello")); //指定长度追加 dst = "abc hello" - printf("dst = %s\n", dst); - return 0; -} -``` -输出: - -```cpp -flag = -100 -flag2 = 32 --------------strcat和strncat------------ -dst = abc hello -``` -###### sprintf()、sscanf() -* `int sprintf(char *str, const char *format, ...);`: 根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0' 为止。 -* `int sscanf(const char *str, const char *format, ...);`: 从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。 -```cpp -#include - -int main(int argc, char const *argv[]) -{ - printf("--------------sprintf-------------------\n"); - int n = 10; - char ch = 'a'; - char buf[10] = "hello"; - char dest[30]; - sprintf(dest,"n = %d, ch = %c, buf = %s\n", n, ch, buf); - printf("dest: %s", dest); // 注意这里没有加上 '\n' 但是之前里面有 - - printf("--------------sscanf-------------------\n"); - // sscanf和spirnf相反 这里从dest中读取 - int n2; - char ch2; - char buf2[10]; - sscanf(dest, "n = %d, ch = %c, buf = %s\n", &n2, &ch2, &buf2); //记得加上 & - printf("n2 = %d\n", n2); - printf("ch2 = %c\n", ch2); - printf("buf2 = %s\n", buf2); - - printf("-----------字符串提取注意的地方--------------\n"); - // 从字符串中提取 内容最好按照空格进行分割 ,不然有可能提取不出来 - // 1. 按照空格分割 --> 正确 - char buf3[] = "aaa bbb ccc"; - char a[10],b[10],c[10]; - sscanf(buf3, "%s %s %s", a,b,c); //注意没有& - printf("a = %s, b = %s, c = %s\n", a, b, c); - // 2. 按照逗号分割 --> 错误 - char buf4[] = "aaa,bbb,ccc"; - char a2[10],b2[10],c2[10]; - sscanf(buf4, "%s,%s,%s", a2,b2,c2); //注意没有& - printf("a2 = %s, b2 = %s, c2 = %s\n", a2, b2, c2); - - return 0; -} -``` -结果: - -![在这里插入图片描述](images/c18.png) - - - -###### strchr()、strstr()、strtok() - -* `char *strchr(const char *s, char c);`: 在字符串s中查找字母c出现的位置; -* `char *strstr(const char *haystack, const char *needle);`: 在字符串haystack中查找字符串needle出现的位置; -* `char *strtok(char *str, const char *delim);` -①来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0; -②在第一次调用时:strtok()必需给予参数s字符串; -③往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针; -```cpp -#include -#include - -int main(int argc, char const *argv[]) -{ - printf("-------------strchr------------\n"); - char str1[] = "aaabbbccc"; - char *p = strchr(str1, 'b'); - printf("p = %s\n", p); - - printf("-------------strstr------------\n"); - char str2[] = "ddddabcd123abcd333abcd"; - char *p2 = strstr(str2, "abcd"); - printf("p2 = %s\n", p2); - - printf("-------------strtok------------\n"); - char str3[100] = "adc*fvcv*ebcy*hghbdfg*casdert"; - char *s = strtok(str3, "*"); //将"*"分割的子串取出 - while (s != NULL){ - printf("%s\n", s); - s = strtok(NULL, "*");//往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针 - } - return 0; -} -``` -输出: - -```cpp --------------strchr------------ -p = bbbccc --------------strstr------------ -p2 = abcd123abcd333abcd --------------strtok------------ -adc -fvcv -ebcy -hghbdfg -casdert -``` -##### 函数 - -* 函数内部,包括()内部的形参变量,只有在调用时分配空间,调用完毕自动释放; -* `return`和`exit()`函数区别,只要一调用 `exit()`函数(不管在什么地方),整个程序就结束,但是只有在`main`函数中调用`return`才会结束程序; -* 声明函数加不加 `extern`关键字都一样, 声明函数可以不指定形参名称,只指定形参形参类型,但是定义不可以。 -* 头文件一般是放函数声明; - -看下面两张图解释`.h`文件的作用: - - - -![在这里插入图片描述](images/c19.png) - -解决办法: - -![在这里插入图片描述](images/c20.png) - -* 多个文件中(同一项目),不能出现同名函数(static除外)。这就是为什么 `.h`文件只放函数的声明,不放函数的定义; - -* 防止头文件重复包含: 当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 `include` 多次,或者头文件嵌套包含。 - - -防止办法: -①`#ifndef` 方式; - -```cpp -#ifndef __SOMEFILE_H__ -#define __SOMEFILE_H__ - -// 声明语句 - -#endif -``` - -②`#pragma once` 方式。 - diff --git a/CPlus/C/C_1.md b/CPlus/C/C_1.md new file mode 100644 index 00000000..43aa5818 --- /dev/null +++ b/CPlus/C/C_1.md @@ -0,0 +1,1219 @@ +# C语言基础知识总结(一) + +* 1、编译、运行 +* 2、进制,原、反、补码 +* 3、数据类型、运算符等基础 +* 4、数组、字符串、函数 + +*** +## 1、编译、运行 + +### 1.1、编译格式 + `gcc -o main main.cpp`生成`main`可执行文件,可以有两种运行方式: + * 当前目录运行`./main`; + * 绝对路径运行,例如`/home/zxzxin/C/main`,要注意的是绝对路 径没有`.`,因为`.`代表的是当前路径,也就是说我们只需要写上完整路径即可; + + + 编译命令格式 +```shell +gcc [-option] ... //c语言编译 +g++ [-option] ... //c++编译 +``` +* gcc、g++编译常用选项说明: + +|选项|含义| +|--|--| +|-o file|指定生成的输出文件名为file| +|-E|只进行预处理| +|-S(大写)|只进行预处理和编译| +|-c(小写)|只进行预处理、编译和汇编| + +* 注意下面的两种方式生成可执行文件的效果是一样的: + +![在这里插入图片描述](images/c1.png) +**平台问题**: + +① Linux编译后的可执行程序只能在Linux运行,Windows编译后的程序只能在Windows下运行; + +②64位的Linux编译后的程序只能在64位Linux下运行,32位Linux编译后的程序只能在32位的Linux运行; + +③64位的Windows编译后的程序只能在64位Windows下运行,32位Windows编译后的程序可以在64位的Windows运行; + +**可以在程序中嵌套`Linux`的命令,会在可执行文件的对应目录执行相应的命令** + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + printf("before\n"); + system("ls -alh"); // system内放置 linux的命令 甚至也可以在""内放 ./a.out这种,也可以调用 + printf("after\n"); + return 0; +} +``` +运行: + +![在这里插入图片描述](images/c2.png) + +* `#include< >` 与 `#include ""`的区别: +①`< >` 表示系统直接按系统指定的目录检索; +② `""` 表示系统先在 `""` 指定的路径(没写路径代表当前路径)查找头文件,如果找不到,再按系统指定的目录检索; + +### 1.2、C语言编译过程 +C代码编译成可执行程序经过4步: + +* 1)预处理:宏定义展开、头文件展开、条件编译等,同时将代码中的注释删除,这里并不会检查语法; +* 2)编译:检查语法,将预处理后文件编译生成汇编文件; +* 3)汇编:将汇编文件生成目标文件(二进制文件); +* 4)链接:C语言写的程序是需要依赖各种库的,所以编译之后还需要把库链接到最终的可执行程序中去; + +![在这里插入图片描述](images/c3.png) + +![在这里插入图片描述](images/c4.png) +![在这里插入图片描述](https://img-blog.csdnimg.cn/20181106195818281.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3p4enh6eDAxMTk=,size_16,color_FFFFFF,t_70) + +然而一般编译的时候都是使用的一步编译。 (但是这样还是经过:预处理、编译、汇编、链接的过程。) + +```shell +gcc hello.c -o hello +``` +`Linux`下查找程序所依赖的动态库 + +```shell +ldd hello +``` +### 1.3、CPU、寄存器 + +* 寄存器是CPU内部最基本的存储单元; +* CPU对外是通过总线(地址、控制、数据)来和外部设备交互的,总线的宽度是8位,同时CPU的寄存器也是8位,那么这个CPU就叫8位CPU +* CPU计算时,先预先把要用的数据从硬盘读到内存,然后再把即将要用的数据读到寄存器。于是` CPU<--->寄存器<--->内存`,这就是它们之间的信息交换,看下面的图片说明: + +寄存器、缓存、内存三者关系: + +![在这里插入图片描述](images/c5.png) + +>关于VS的C4996错误 + +* 由于微软在VS2013中不建议再使用C的传统库函数scanf,strcpy,sprintf等,所以直接使用这些库函数会提示C4996错误。VS建议采用带`_s`的函数,如`scanf_s`、`strcpy_s`,但这些并不是标准C函数。 +* 要想继续使用此函数,需要在源文件中添加以下两个指令中的一个就可以避免这个错误提示: +```cpp +#define _CRT_SECURE_NO_WARNINGS //这个宏定义最好要放到.c文件的第一行 +#pragma warning(disable:4996) //或者使用这个 +``` + +*** +## 2、进制,原、反、补码 +### 2.1、进制 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int a = 123; //十进制方式赋 + int b = 0123; //八进制方式赋值, 以数字0开 + int c = 0xABC; //十六进制方式赋值 + + //如果在printf中输出一个十进制数那么用%d,八进制用%o,十六进制是%x + printf("十进制:%d\n",a ); + printf("八进制:%o\n", b); // %o,为字母o,不是数字 + printf("十六进制:%x\n", c); + return 0; +} +``` +输出: + +```cpp +十进制:123 +八进制:123 +十六进制:abc +``` + +### 2.2、原码、反码、补码 +#### 2.2.1、原码 + +一个数的原码(原始的二进制码)有如下特点: +* 最高位做为符号位,0表示正,为1表示负; +* 其它数值部分就是数值本身绝对值的二进制数; +* 负数的原码是在其绝对值(相反数)的基础上,最高位变为1; + +例如:(以一个字节(8bit)来看)  +|十进制数|原码| +|--|--| +|+15|0000 1111| +|-15|1000 1111| +|+0|0000 0000| +|-0|1000 0000| + +原码存储导致2个问题: + +* 0有两种存储方式; +* 正数和负数相加,结果不正确(计算机只会加不会减); + +例如: + +以原码来算不同符号的数: + +```cpp +1-1 = 1 + -1 + 1: 0000 0001 + -1: 1000 0001 + 1000 0010 = -2 //答案错误 +``` + +#### 2.2.2、反码 +* 正数的原码和反码是一样; +* 对于负数,先求原码,在原码基础上,符号位不变,其它位取反(0为1, 1变0); + +例如:(以一个字节(8bit)来看)  + +|十进制数|原码|反码| +|--|--|--| +|+15|0000 1111|0000 1111| +|-15|1000 1111|1111 0000| +|+0|0000 0000|0000 0000| +|-0|1000 0000|1111 1111| + +反码计算两个符号不同的数: + +```cpp +1-1 = 1 + -1 + 1:0000 0001 +-1:1111 1110 + 1111 1111 = -0 //答案是对的 +``` + +但是反码还是没有解决0有两种存储方式的问题。 + +#### 2.2.3、补码 + +综上,计算机存储数字以补码方式存储(为了解决负数的存储); + +补码特点: +* 对于正数,原码、反码、补码相同; +* 对于负数,其补码为它的反码加1; + +例如:(以一个字节(8bit)来看)  + +|十进制数|原码|反码|补码| +|--|--|--|--| +|+15|0000 1111|0000 1111|0000 1111| +|-15|1000 1111|1111 0000|1111 0001||| +|+0|0000 0000|0000 0000|0000 0000|| +|-0|1000 0000|1111 1111|0000 0000| + +补码计算: + +```cpp +1-1 = 1 + -1 + 1:0000 0001 +-1:1111 1111 + 10000 0000(最高位丢弃) = 0000 0000 //注意1字节,所以丢弃最高位 +``` + +记得一个原则: +* 十进制数 --> 站在用户的角度 --> 原码; +* 二进制、八进制、十六进制 --> 站在计算机角度 --> 补码; + + +### 2.3、原码和补码互换 + +原码求补码: +* ①. 先求原码: 最高位符号位,其它位就是二进制; +* ②. 在①基础上,符号位不变,其它位取反; +* ③. 在②基础上加1; + +补码求原码: +* ①. 先得到补码; +* ②. 求补码的反码,符号位不变,其它位取反; +* ③. 在②基础上加1(**注意不要人为的认为相反是减**) + + + +综上,在计算机系统中,数值一律用补码来存储,主要原因是: +* 统一了零的编码; +* 将符号位和其它位统一处理; +* 将减法运算转变为加法运算; +* 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃; + + +相关案例计算转换: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char a = 0x81; // 计算机的角度(补码) + printf("a = %d\n",a); + + char b = 0xe5;//计算机的角度(补码) + printf("b = %d\n",b); + + char c = 0x6f; //计算机的角度(补码) + printf("c = %d\n",c); + + char d = -123; //人类的角度 --> 看成原码 + printf("d = %x\n",d);//输出16进制(计算机的角度) + return 0; +} +``` +输出: + +```cpp +a = -127 +b = -27 +c = 111 +d = ffffff85 +``` +对于上面程序的分析: +```cpp +//二进制、八进制、十六进制,站在计算机角度,补码 +//0x81转为为二进制位1000 0001,最高位为1,说明是负数 +char a = 0x81; +补码:1000 0001 +反码:1111 1110 +原码:1111 1111 = -127 +//10进制数,站在用户的角度,原码 +printf("%d\n", a); //-127 + + +//二进制、八进制、十六进制,站在计算机角度,补码 +//0xe5二进制为1110 0101,最高位为1,说明是负数,它是负数的补码 +char b = 0xe5; +补码:1110 0101 +反码:1001 1010 +原码:1001 1011 = -27 +//10进制数,站在用户的角度,原码 +printf("%d\n", b);// -27 + + +0x6f的二级制为0110 1111,最高位为0,它是正数 +char c = 0x6f; +printf("%d\n", c);// 111 原码、反码、补码都相同 + + +//10进制数,站在用户的角度,原码 +int a = -123;//注意这个是十进制,所以直接就是原码 +原码:1000 0000 0000 0000 0000 0000 0111 1011 +反码:1111 1111 1111 1111 1111 1111 1000 0100 +补码:1111 1111 1111 1111 1111 1111 1000 0101 + f f f f f f 8 5 +%x,默认以4个字节(32位)大小打印 +//二进制、八进制、十六进制,站在计算机角度,补码 +printf("%x\n", a);// 16进制打印 ffffff85 +``` + +### 2.4、有符号和无符号的区别 + +* 有符号,最高位是符号位,如果是1代表为负数,如果为0代表为正数; +* 无符号,最高位不是符号位,是数的一部分,无符号不可能是负数; + + +测试: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 1000 0000 0000 0000 0000 0000 0111 1011 + // 8000007b + // %d按照有符号数来打印 + printf("%d\n", 0x8000007b); + // %u按照无符号数打印 + printf("%u\n", 0x8000007b); + + // signed 和 unsigned + signed int a = 10; + unsigned int b = -10; + + //注意输出结果 以%d 为准,%d表示按照有符号数来输出 + unsigned int c = 0x8000007b; + printf("c[有符号] = %d\n",c); + printf("c[无符号] = %u\n",c); + return 0; +} +``` +输出: + +```cpp +-2147483525 +2147483771 +c[有符号] = -2147483525 +c[无符号] = 2147483771 +``` +分析: + +```cpp +1000 0000 0000 0000 0000 0000 0111 1011 //二进制 +8000007b //16进制 + +//有符号 ---> %d, 默认以有符号的方式打印 +补码:1000 0000 0000 0000 0000 0000 0111 1011 +反码:1111 1111 1111 1111 1111 1111 1000 0100 +原码:1111 1111 1111 1111 1111 1111 1000 0101 + - 7 f f f f f 8 5 = -2147483525 + -2147483525 +printf("%d\n", 0x8000007b); //-2147483525 + +//无符号(最高位看做是数的一部分) ---> %u, 以无符号的方式打印 +1000 0000 0000 0000 0000 0000 0111 1011 +8000007b = 2147483771 + 2147483771 +printf("%u\n", 0x8000007b); //2147483771 +``` + +### 2.5、数据类型取值分析 + +```cpp +数据类型范围(站在10进制角度,原码): +char 1个字节(8位,8bit) +有符号的范围: +正数: +0000 0000 ~ 0111 1111 +0 127 + +负数: +1000 0000 ~ 1111 1111 +-0 ~ -127 + +注意这里比较特使: +-0 当做 -128使用 +-128: +原码: 1 1000 0000 +反码: 1 0111 1111 +补码: 1 1000 0000 +这个很特别: -128的原码和补码是一样的,发现和0 (1000 0000)也是一样的。 + +无符号范围: +0000 0000 ~ 1111 1111 +0 ~ 255 +``` +综上,char: +* 有符号:-128 ~ 127; +* 无符号:0 ~ 255; +### 2.6、越界问题 +赋值或者运算,记得不要越界,下面展示了越界的情况: +```cpp +#include + +int main(int argc, char const *argv[]) +{ + //有符号越界 + char a = 127 + 2; + printf("a = %d\n",a); //-127 + + //无符号越界 + unsigned char b = 255 + 2; + printf("b = %u\n",b); //1 + return 0; +} +``` +输出: +```cpp +-127 +1 +``` +分析: +```cpp +char a = 127 + 2; +129转换为二进制:1000 0001,这是负数补码(计算机角度) +补码:1000 0001 +反码:1111 1110 +原码:1111 1111(最高位是符号位) = -127 +printf("%d\n", a);//-127 + +unsigned char b = 255 + 2; +257转化为二进制 :0001 0000 0001 (只取后8位) +printf("%u\n", b);// 1 +``` +*** +## 3、数据类型、运算符等基础 +### 3.1、C语言数据类型 +* 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。 + +![在这里插入图片描述](images/c6.png) + +**变量特点**: +* 变量在编译时为其分配相应的内存空间; + +* 可以通过其名字和地址访问相应内存; + +![在这里插入图片描述](images/c7.png) + +**声明和定义的区别**: + +* 声明变量不需要建立存储空间,如:`extern int a`; (使用`extern`关键字) +* 定义变量需要建立存储空间,如:`int b`; +* 从广义的角度来讲声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义: + ① `int b` 它既是声明,同时又是定义; + ② 对于`extern int b`来讲它只是声明不是定义; + +* 一般的情况下,把建立存储空间的声明称之为“定义”,而把不需要建立存储空间的声明称之为“声明”; + + +### 3.2、sizeof关键字 + +* `sizeof`不是函数,所以不需要包含任何头文件,它的功能是计算一个数据类型的大小,单位为字节; +* `sizeof`的返回值为`size_t`; +* `size_t`类型在32位操作系统下是`unsigned int`,是一个无符号的整数; + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + //数据类型的作用: 告诉编译器,分配此类型的变量需要多大的空间 + printf("sizeof(char) = %u\n",sizeof(char)); + + int a; + printf("sizeof(a) = %d\n",sizeof(a)); + + size_t len = sizeof(long); + printf("sizeof(long) = %u\n",len); + return 0; +} +``` +输出: +```cpp +sizeof(char) = 1 +sizeof(a) = 4 +sizeof(long) = 8 +``` +### 3.3、char数据类型-char的本质就是一个1字节大小的整型 + +* 内存中没有字符,只有数字; +* 一个数字,对应一个字符,这种规则就是`ascii`; +* 使用字符或数字给字符变量赋值是等价的; +* **字符类型本质就是1个字节大小的整形**; +* 字符变量实际上并不是把该字符本身放到变量的内存单元中去,而是将该字符对应的 ASCII 编码放到变量的存储单元中。 + +测试: +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char ch = 'a'; + printf("ch[c] = %c, ch[d] = %d\n",ch, ch); + + char ch2 = 97; + printf("ch2[c] = %c\n", ch2); + + printf("ch2 - 32 = %c\n",ch2 - 32); + return 0; +} +``` +输出: + +```cpp +ch[c] = a, ch[d] = 97 +ch2[c] = a +ch2 - 32 = A +``` +以及注意转义字符: + +![在这里插入图片描述](images/c8.png) + +转义字符简单测试: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char ch = '\r'; + printf("abcdddd%cefg\n",ch); + + ch = '\b'; + printf("12%c345\n",ch); + + ch = ' '; + printf("%d\n",ch); // 打印' '的对应数字 + + // 8进制转义字符 + printf("%d\n", '\123'); //8进制123 --> 对应10进制83 + printf("%d\n", '\x23'); //16进制23 --> 对应10进制35 + + // '\0'和0等价 + printf("%d,%d\n",'\0',0); + return 0; +} +``` +输出: +```cpp +efgdddd +1345 +32 +83 +35 +0,0 +``` +### 3.4、浮点数不准确、类型限定符 + +浮点数不准确: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + float a = 100.9; + printf("a = %f\n",a); + return 0; +} +``` +输出: + +```cpp +a = 100.900002 +``` +原因还是计算机按照`二进制`存储。 + +* 类型限定符: + + +![在这里插入图片描述](images/c9.png) + +### 3.5、字符的输入问题 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char ch1; + printf("请输入字符:"); + scanf("%c",&ch1); + printf("ch1 = %c\n",ch1); + + char ch2; + printf("请输入字符:"); + scanf("%c",&ch2); + printf("ch2 = %c\n",ch2); + //输出ch2的ascii码 + printf("ch2[d] = %d\n",ch2); + return 0; +} + +``` +输入输出演示: + +![在这里插入图片描述](images/c10.png) + +这个程序要特别注意: + +* 当我们输入完一个字符时,按下回车,会直接结束程序; +* 因为第二个字符在按下回车的时候已经输入完成了,也就是`ch2 = '\n'`;而`'\n'`的`ascii`码为10; + +![在这里插入图片描述](images/c11.png) + +处理上面的问题,可以使用另一个字符变量过滤掉`\n`,也可以使用`getchar()`来过滤一个字符: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char ch1; + printf("请输入字符:"); + scanf("%c",&ch1); + printf("ch1 = %c\n",ch1); + + //为了处理上面的问题,可以自己用scanf处理这个回车 + char tmp; + //scanf("%c",&tmp); //或者使用下面的方法输入一个字符 + // tmp = getchar(); //或者也可以直接调用getchar()即可 + getchar(); + + char ch2; + printf("请输入字符:"); + scanf("%c",&ch2); + printf("ch2 = %c\n",ch2); + //输出ch2的ascii码 + printf("ch2[d] = %d\n",ch2); + return 0; +} + +``` + +### 3.6、运算符以及优先级 +运算符优先级 + +![在这里插入图片描述](images/c12.png) + +![在这里插入图片描述](images/c13.png) + +> 另外,注意 逻辑判断中的短路原则。 + +### 3.7、类型转换 + +* 隐式转换: 编译器内部自动转换; +* 强制类型转换:把表达式的运算结果强制转换成所需的数据类型。 +* 浮点型打印说明: 不要以为指定了打印的类型就会自动转换,必须要强制转换; +* 转换原则: 数据类型小的往大的转换; +```cpp +#include + +int main(int argc, char const *argv[]) +{ + printf("------------隐式转换-----------\n"); + //隐式转换 + double a; + int b = 10; + //编译器内部自动转换,在第10行自动转换,其他地方b还是int + a = b; + printf("a = %lf\n",a); + + + printf("------------强制类型转换-----------\n"); + //强制类型转换 + double c = (double)1/2; //强制将1转换成double + printf("c = %lf\n",c); + printf("sizeof(int) = %u\n",(unsigned int)sizeof(int)); + + + printf("------------浮点型打印说明-----------\n"); + //浮点型打印说明 + int d = 11; + printf("d[wrong] = %lf\n",d); //以为指定了%lf就会转换成浮点数,其实是错的 + printf("d[right] = %lf\n",(double)d); // 正确的打印是要强制类型转换的 + double e = 88.14; + printf("e[wrong] = %d\n",e); + printf("e[right] = %d\n",int(e)); + + printf("-------------转换原则----------\n"); + //转换原则: 数据类型 小的往大的 转换 + int g = 129; + char h = 111; + //g = (int)h; // 这个是可以转换的 + //printf("g = %d\n",g); + h = (char)g; //这个转换是出错的,h会变成一个负数 + printf("h = %d\n",h); + return 0; +} +``` +输出: + +```cpp +------------隐式转换----------- +a = 10.000000 +------------强制类型转换----------- +c = 0.500000 +sizeof(int) = 4 +------------浮点型打印说明----------- +d[wrong] = 0.000000 +d[right] = 11.000000 +e[wrong] = 1519177328 +e[right] = 88 +-------------转换原则---------- +h = -127 +``` +*** +## 4、数组、字符串、函数 + +### 4.1、数组初始化的几种方式 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int arr1[5] = {1,2,3,4,5}; //数组的定义需要{},只有在定义的时候才能初始化 + int arr2[5]; + //arr2 = {1,2,3,4,5}; //err 这个是错误的 + + //数组全部元素初始化为 某个值 + int arr3[5] = {0}; + + //如果定义的同时 初始化,第一个[]内可以不写内容 + int arr4[] = {1,2,3,4,5}; //编译器会根据用户初始化的元素来确定数组的大小 + //int arr4[]; //err //必须初始化的时候才能省略 + int arr5[][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //第一个可以省略 + return 0; +} +``` + +数组的名字代表着数组的首地址,以及使用`sizeof`来求数组长度的方法; + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + printf("------------一维数组------------\n"); + + int arr[10]; + //1. 数组名是数组首元素地址 + printf("arr = %p, &arr[0] = %p\n", arr, &arr[0]); //两个是一样的 + + //2. 测试数组总的字节大小 : sizeof(数组名) 10 * 4 = 40 + printf("sizeof(arr) = %u\n",sizeof(arr)); + + //3. 得到数组长度 数组总大小/每个元素大小 (常用) + int len = sizeof(arr)/sizeof(arr[0]); + printf("len(arr) = %d\n",len); + + + printf("------------二维数组------------\n"); + + int arr2[5][10]; + // 1. 数组名是常量,不能修改 + // a = 10; //err + //2. sizeof(数组名),测数组的总大小 5*int[10] = 5*4*10 + printf("sizeof(arr2) = %u\n",sizeof(arr2)); + + //3. 求行数 + int n = sizeof(arr2)/sizeof(arr2[0]); + printf("行数 = %d\n",n); + + //4. 求列数 + int m = sizeof(arr2[0])/sizeof(arr2[0][0]); + printf("列数 = %d\n",m); + + //5. 总个数 + printf("总个数 = %d\n",sizeof(arr2)/sizeof(arr2[0][0])); + return 0; +} + +``` +输出: + +```cpp +------------一维数组------------ +arr = 0x7ffc86eac190, &arr[0] = 0x7ffc86eac190 +sizeof(arr) = 40 +len(arr) = 10 +------------二维数组------------ +sizeof(arr2) = 200 +行数 = 5 +列数 = 10 +总个数 = 50 +``` +可以看出`arr`和`arr[0]`的地址是一样的。 + +### 4.2、字符数组与字符串 +* C语言中没有字符串这种数据类型,可以通过`char`的数组来替代; +* 字符串一定是一个`char`的数组,但`char`的数组未必是字符串; +* **数字`0`(和字符`‘\0’`等价)结尾的`char`数组就是一个字符串,但如果`char`数组没有以数字`0`结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的`char`的数组**。 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 1. c语言没有字符串类型,用字符数组模拟 + char str[10]; + + // 2. 字符串一定是字符数组,字符数组不一定是字符串 + // 3. 如果字符数组以'\0'(0)结尾,就是字符串 + + char str2[] = {'a', 'b', 'c'};//字符数组 --> 注意这里[]内没有指定数字 + char str3[10] = {'a', 'b', 'c', '\0'}; //字符串 + char str4[10] = {'a', 'b', 'c', 0}; //字符串 + return 0; +} +``` + +#### 4.2.1、字符串输出乱码问题以及一系列字符串要注意的问题 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 1. 字符数组打印乱码问题 + char a1[] = {'a', 'b', 'c'}; //字符数组 + printf("a1 = %s\n",a1); //这个是乱码,因为字符数组后面没有'\0' + + // 2. 正确的字符串 + char a2[] = {'a', 'b', 'c', '\0'}; + //char a2[] = {'a', 'b', 'c', 0}; // 0也可以 + + // 3. 遇到'\0'提前截断 + char a3[] = {'a', 'b', 'c', '\0', 'h', 'e', '\0'}; + printf("a3 = %s\n", a3);//"abc" + + // 4. 前3个字符赋值为a, b, c, 后面自动赋值为0 + char a4[10] = {'a', 'b', 'c'}; + printf("a4 = %s\n", a4); + + // 5. 常用的初始化方法 --> 使用字符串初始化,在字符串结尾自动加结束符数字0 + // 这个结束符,用户看不到(隐藏),但是是存在的 + char a5[10] = "abc"; + printf("a5 = %s\n", a5); + + // 6. 使用sizeof()测试隐藏的那个'\0' + char a6[] = "abc"; + printf("sizeof(a6) = %d\n", sizeof(a6)); + + // 7. \0 后面最好别跟数字,不然有可能变成转义字符 例如\012 就是'\n' + char a7[] = "\012abc"; + printf("a7 = %s\n", a7); + + // 8. 最多写9个字符,留一个位置放结束符 + char a8[10] = "123456789"; + + // 9. sizeof()测数据类型大小,不会因为结束符提前结束 sizeof("123\045") = 5 + char a9[] = "abc\0de"; + printf("%u\n", sizeof(a9)); // 7 --> a b c \0 d e \0 不要忘记最后还有一个\0 + + // 10. scanf("%s", a); //a没有&,原因数组名是首元素地址,本来就是地址 + return 0; +} +``` +输出结果: + +![在这里插入图片描述](images/c14.png) + +#### 4.2.2、随机数生成 + +```cpp +#include +#include +#include + +int main(int argc, char const *argv[]) +{ + // 1. 先设置种子,种子设置一次即可 + // 2. 如果srand()参数一样,随机数就一样 + //srand(100); + + // 由于希望产生的随机数不同,所以要使用系统时间作为随机数 + srand( (unsigned int)time(NULL) ); // 返回的是time_t类型 相当于long,单位为毫秒,注意强制转换一下 + + for(int i = 0; i < 3; i++){ + printf("the rand number is = %d\n", rand()); + } + return 0; +} +``` +### 4.3、字符串函数 + +#### 4.3.1、字符串输入函数 + +① `gets(str)`与`scanf(“%s”,str)`的区别: +* `gets(str)`允许输入的字符串含有空格; +* `scanf(“%s”,str)`不允许含有空格; + +> * 注意:由于`scanf()`和`gets()`无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。 + +② `fgets函数`相关: + +```c +char *fgets(char *s, int size, FILE *stream); +``` +* 功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束; +* 参数 : + s:字符串; + size:指定最大读取字符串的长度(size - 1)(大于这个长度就舍弃); + stream:文件指针,如果读键盘输入的字符串,固定写为stdin; +* 返回值: + 成功:成功读取的字符串; + 读到文件尾或出错: NULL; + +* 注意,`fgets()`在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车('\n')也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。 +* fgets()函数是安全的,不存在缓冲区溢出的问题。 + +简单测试: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 1. scanf()输入字符串 不做越界检查,此函数不安全 + + // 2. gets(已抛弃,不安全) + char buf[10]; + // gets(buf); //不安全 已废弃 + // printf("buf = %s\n", buf); + + // 3. fgets(安全,可以指定大小) + fgets(buf,sizeof(buf),stdin); + printf("buf = '%s'\n", buf); + + // 4. 测试读入了 最后的换行 + fgets(buf,sizeof(buf),stdin); // 注意虽然这里从键盘读入,但是如果缓冲区还有内容就不会读这里的 + printf("buf2 = '%s'\n", buf); + return 0; +} +``` +输入输出结果: + + + +![在这里插入图片描述](images/c15.png) + + + +#### 4.3.2、字符串输出函数 + +```cpp +int puts(const char *s); +功能:标准设备输出s字符串,在输出完成后自动输出一个'\n'。 +int fputs(const char * str, FILE * stream); //文件操作 +功能: 将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0' 不写入文件。 +``` + +简单测试: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char str[] = "hello world!"; + puts(str); //会自动加上一个 '\n' + + fputs(str,stdout); // 不会自动加上 '\n' + return 0; +} +``` +输出: + + + +![在这里插入图片描述](images/c16.png) + + + +#### 4.3.3、sizeof()和strlen()的区别 + +注意: +* `strlen()` 从首元素开始,到结束符为止的长度,结束符不算(遇到'\0'结束); +* 而`sizeof()`则不管遇不遇到`'\0'`都会计算整个数据类型大小; +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + + char buf[] = "hello"; + + // strlen 从首元素开始,到结束符为止的长度,结束符不算(遇到'\0'结束) + int len = strlen(buf); + printf("strlen(buf) = %d\n", len); //5 + printf("sizeof(buf) = %d\n", sizeof(buf)); // 6 这个还包括 '\0' + + char buf2[] = "\0hello"; + printf("strlen(buf2) = %d\n", strlen(buf2)); // 0 + printf("sizeof(buf2) = %d\n", sizeof(buf2)); // 7 注意不要忘记最后还有一个 '\0' + + + char buf3[100] = "zxzxin"; + printf("strlen(buf3) = %d\n", strlen(buf3)); //6 + printf("sizeof(buf3) = %d\n", sizeof(buf3)); //100 + + return 0; +} +``` +#### 4.3.4、字符串拷贝strcpy()和strncpy() +注意两者区别: +* `char *strcpy(char *dest, const char *src) `:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去。(如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。) +* `char *strncpy(char *dest, const char *src, size_t n)`:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。 +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + + //strcpy: 把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去 + char src[] = "hello world!"; + char dest[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; + strcpy(dest,src); + printf("dest = %s\n", dest);// hello world! 不会输出后面的aaaa, 因为'\0'也拷贝在后面了 + + // strncpy : 把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。 + char dest2[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; + strncpy(dest2, src, strlen(src)); + printf("dest2 = %s\n", dest2); //hello world!aaaaaaaaaaa + + //但是如果拷贝的长度大于strlen(src) + char dest3[100] = "aaaaaaaaaaaaaaaaaaaaaaa"; + strncpy(dest3, src, strlen(src)+1); + printf("dest3 = %s\n", dest3); //hello world! + return 0; +} +``` +输出: + +![在这里插入图片描述](images/c17.png) + + + +#### 4.3.5、strcat()、strncat()、 strcmp()、strncmp() + +* `char *strcat(char *dest, const char *src);`: 将src字符串连接到dest的尾部,`‘\0’`也会追加过去; +* `char *strncat(char *dest, const char *src, size_t n);`: 将src字符串前n个字符连接到dest的尾部,`‘\0’`也会追加过去; +* `int strcmp(const char *s1, const char *s2);`: 比较 s1 和 s2 的大小,比较的是字符ASCII码大小; +* `int strncmp(const char *s1, const char *s2, size_t n);`:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小; + +测试: + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + char s1[] = "abc"; + char s2[] = "abcd"; + int flag = strcmp(s1, s2); + printf("flag = %d\n",flag);// <0 + + char s3[] = "abc"; + char s4[] = "Abcd"; + int flag2 = strncmp(s3, s4, 3); //指定比较前3个字符 + printf("flag2 = %d\n",flag2);// >0s + + printf("-------------strcat和strncat------------\n"); + char src[] = " hello mike"; + char dst[100] = "abc"; + //把src的内容追加到dst的后面 + //strcat(dst, src); //dst = "abc hello mike" + strncat(dst, src, strlen(" hello")); //指定长度追加 dst = "abc hello" + printf("dst = %s\n", dst); + return 0; +} +``` +输出: + +```cpp +flag = -100 +flag2 = 32 +-------------strcat和strncat------------ +dst = abc hello +``` +#### 4.3.6、sprintf()、sscanf() +* `int sprintf(char *str, const char *format, ...);`: 根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0' 为止。 +* `int sscanf(const char *str, const char *format, ...);`: 从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + printf("--------------sprintf-------------------\n"); + int n = 10; + char ch = 'a'; + char buf[10] = "hello"; + char dest[30]; + sprintf(dest,"n = %d, ch = %c, buf = %s\n", n, ch, buf); + printf("dest: %s", dest); // 注意这里没有加上 '\n' 但是之前里面有 + + printf("--------------sscanf-------------------\n"); + // sscanf和spirnf相反 这里从dest中读取 + int n2; + char ch2; + char buf2[10]; + sscanf(dest, "n = %d, ch = %c, buf = %s\n", &n2, &ch2, &buf2); //记得加上 & + printf("n2 = %d\n", n2); + printf("ch2 = %c\n", ch2); + printf("buf2 = %s\n", buf2); + + printf("-----------字符串提取注意的地方--------------\n"); + // 从字符串中提取 内容最好按照空格进行分割 ,不然有可能提取不出来 + // 1. 按照空格分割 --> 正确 + char buf3[] = "aaa bbb ccc"; + char a[10],b[10],c[10]; + sscanf(buf3, "%s %s %s", a,b,c); //注意没有& + printf("a = %s, b = %s, c = %s\n", a, b, c); + // 2. 按照逗号分割 --> 错误 + char buf4[] = "aaa,bbb,ccc"; + char a2[10],b2[10],c2[10]; + sscanf(buf4, "%s,%s,%s", a2,b2,c2); //注意没有& + printf("a2 = %s, b2 = %s, c2 = %s\n", a2, b2, c2); + + return 0; +} +``` +结果: + +![在这里插入图片描述](images/c18.png) + + + +#### 4.3.7、strchr()、strstr()、strtok() + +* `char *strchr(const char *s, char c);`: 在字符串s中查找字母c出现的位置; +* `char *strstr(const char *haystack, const char *needle);`: 在字符串haystack中查找字符串needle出现的位置; +* `char *strtok(char *str, const char *delim);` +①来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0; +②在第一次调用时:strtok()必需给予参数s字符串; +③往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针; +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + printf("-------------strchr------------\n"); + char str1[] = "aaabbbccc"; + char *p = strchr(str1, 'b'); + printf("p = %s\n", p); + + printf("-------------strstr------------\n"); + char str2[] = "ddddabcd123abcd333abcd"; + char *p2 = strstr(str2, "abcd"); + printf("p2 = %s\n", p2); + + printf("-------------strtok------------\n"); + char str3[100] = "adc*fvcv*ebcy*hghbdfg*casdert"; + char *s = strtok(str3, "*"); //将"*"分割的子串取出 + while (s != NULL){ + printf("%s\n", s); + s = strtok(NULL, "*");//往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针 + } + return 0; +} +``` +输出: + +```cpp +-------------strchr------------ +p = bbbccc +-------------strstr------------ +p2 = abcd123abcd333abcd +-------------strtok------------ +adc +fvcv +ebcy +hghbdfg +casdert +``` +### 4.4、函数 + +* 函数内部,包括()内部的形参变量,只有在调用时分配空间,调用完毕自动释放; +* `return`和`exit()`函数区别,只要一调用 `exit()`函数(不管在什么地方),整个程序就结束,但是只有在`main`函数中调用`return`才会结束程序; +* 声明函数加不加 `extern`关键字都一样, 声明函数可以不指定形参名称,只指定形参形参类型,但是定义不可以。 +* 头文件一般是放函数声明; + +看下面两张图解释`.h`文件的作用: + +![在这里插入图片描述](images/c19.png) + +解决办法: + +![在这里插入图片描述](images/c20.png) + +* 多个文件中(同一项目),不能出现同名函数(static除外)。这就是为什么 `.h`文件只放函数的声明,不放函数的定义; + +* 防止头文件重复包含: 当一个项目比较大时,往往都是分文件,这时候有可能不小心把同一个头文件 `include` 多次,或者头文件嵌套包含。 + +防止办法: +①`#ifndef` 方式; + +```cpp +#ifndef __SOMEFILE_H__ +#define __SOMEFILE_H__ + +// 声明语句 + +#endif +``` + +②`#pragma once` 方式。 + diff --git "a/CPlus/C/C_2_\346\214\207\351\222\210.md" "b/CPlus/C/C_2_\346\214\207\351\222\210.md" new file mode 100644 index 00000000..b308f4c5 --- /dev/null +++ "b/CPlus/C/C_2_\346\214\207\351\222\210.md" @@ -0,0 +1,849 @@ +# C语言知识总结(二)一指针 +* 1、内存相关 +* 2、指针基础 +* 3、数组和指针 +* 4、指针和函数 +* 5、字符串和指针 + +*** +## 1、内存相关 +### 1.1、内存 + +内存含义 + +* 存储器:计算机的组成中,用来存储程序和数据,辅助`CPU`进行运算处理的重要部分。 +* 内存:内部存贮器,暂存程序/数据——掉电丢失 `SRAM`、`DRAM`、`DDR`、`DDR2`、`DDR3`。 +* 外存:外部存储器,长时间保存程序/数据—掉电不丢 `ROM `、 `ERRROM `、 `FLASH `( `NAND `、 `NOR `)、硬盘、光盘。 + +内存是沟通CPU与硬盘的桥梁: +* 暂存放CPU中的运算数据; +* 暂存与硬盘等外部存储器交换的数据; + + +### 1.2、物理存储器和存储地址空间 +有关内存的两个概念:物理存储器和存储地址空间。 + +物理存储器:实际存在的具体存储器芯片。 + +* 主板上装插的内存条; +* 显示卡上的显示RAM芯片; +* 各种适配卡上的RAM芯片和ROM芯片; + +存储地址空间:对存储器编码的范围。我们在软件上常说的内存是指这一层含义。 +* 编码:对每个物理存储单元(一个字节)分配一个号码; +* 寻址:可以根据分配的号码找到相应的存储单元,完成数据的读写; + +### 1.3、内存地址 +* 将内存抽象成一个很大的一维字符数组; +* 编码就是对内存的每一个字节分配一个32位或64位的编号(与32位或者64位处理器相关); +* 这个内存标号就是内存地址; + +![1563676358953](assets/1563676358953.png) + +*** +## 2、指针基础 +### 2.1、指针和指针变量 + +指针 +* 内存区的每一个字节都有一个编号,这就是“地址”; +* 如果在程序中定义了一个变量,在对程序进行编译或运行时,系统就会给这个变量分配内存单元,并确定它的内存地址(编号); +* 指针的实质就是内存“地址”。指针就是地址,地址就是指针; + +指针变量的定义: +* **指针是内存单元的编号,指针变量是存放地址的变量**; +* 通常我们叙述时会把指针变量简称为指针,实际他们含义并不一样。 + + +指针变量: +* 指针也是一种数据类型,指针变量也是一种变量; +* **指针变量指向谁,就把谁的地址赋值给指针变量**; +* `“*”`操作符操作的是指针变量指向的内存空间;注意,`*`有两层含义: +①在定义指针变量的时候,是表示指针的数据类型,比如`int *`、`int **`、`int ***`; +②在操作指针变量地址的时候又是另一层含义; +> * 注意: `&`可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int *p; //指针变量(p的类型是int *),保存的是 变量的地址 + int a = 100; + p = &a; // 指针指向谁,就把谁的地址赋给指针 p保存了a的地址 + printf("%p, %p\n", p, &a); // 16进制的数值(地址) 是相等的 + + //1. 直接操作指针变量没有任何意义 + //2. 需要操作*p,操作指针所指向的内存 + printf("%d, %d\n", *p, a); + *p = 200; // 改变*p指向的内存的值 + printf("%d, %d\n", *p, a); + return 0; +} +``` + +![1563676374043](assets/1563676374043.png) + +### 2.2、野指针和空指针 +野指针 + +* 指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节); +* 但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域); +* 所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int *p; // *p会随机赋值一个地址 + printf("p = %d\n", p);// 输出地址 没有问题 + //*p = 100; //但是这里会有问题 p指向的地址是随机的,有可能是系统没有授权的内存, 不能随便操作 + return 0; +} +``` +输出: + +```cpp +段错误 +``` +解释: + +![1563676388437](assets/1563676388437.png) + +空指针: + +* 野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。 + +```cpp +int *p = NULL; +``` +注意: NULL是一个值为0的宏常量: +```cpp +#define NULL ((void *)0) +``` +### 2.3、指针大小 + +* 使用`sizeof()`测量指针的大小,得到的总是:4或8; +* `sizeof()`测的是指针变量指向存储地址的大小; +* 不是看指针类型有多少个`*`,而是看平台; +* 在32位平台,所有的指针(地址)都是32位(4字节); +* 在64位平台,所有的指针(地址)都是64位(8字节) + +比如我的机器是`64`位的,一下代码全部输出`8`: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int *p1; + int **p2; + char *p3; + char **p4; + printf("%d\n", sizeof(p1)); + printf("%d\n", sizeof(p2)); + printf("%d\n", sizeof(p3)); + printf("%d\n", sizeof(p4)); + printf("%d\n", sizeof(double *)); + return 0; +} +``` +### 2.4、多级指针 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int a = 10; + + int *p1 = &a; + int **p2 = &p1; + int ***p3 = &p2; + int ****p4 = &p3; + + printf("*p4 = %d\n", *p4); //就是p3的地址 + printf("**p4 = %d\n", **p4); // 就是p2的地址 + printf("***p4 = %d\n", ***p4); //就是p1的地址 + printf("****p4 = %d\n", ****p4); //就是p1指向的内存的值 + + return 0; +} +``` +输出: + +```cpp +*p4 = -733946608 +**p4 = -733946600 +***p4 = -733946588 +****p4 = 10 +``` +可以看到前三个就是内存地址的值的10进制表示,最后一个是`p1`指向的内存地址的值,也就是`a`的值; + +内存图如下: + +![1563676408651](assets/1563676408651.png) + +### 2.5、`[]`不是数组的专属 +也可以使用类似数组`[]`的方式来访问指针: +```cpp +#include + +int main(int argc, char *argv[]) +{ + int a = 100; + int *p = &a; + //通过指针间接操作a的内存 + *p = 200; + printf("a = %d\n", a); + printf("*p = %d\n", *p); + printf("p[0] = %d\n", p[0]);//*p 等价于*(p+0), 同时等价于p[0] + + printf("----------使用[]来改变指针指向的内存地址---------\n"); + //*p 等价于 p[0],操作的是指针所指向的内存 + p[0] = 300; + printf("a = %d\n", a); + + //操作野指针指向的内存 + //p[1] = 111; + // 等价于 *(p + 1) = 111; + return 0; +} +``` +输出:  + +```cpp +a = 200 +*p = 200 +p[0] = 200 +----------使用[]来改变指针指向的内存地址--------- +a = 300 + +``` + +### 2.6、指针的步长、 `void *`万能指针 + +* 指针的加法,不是传统的加法; +* 步长由指针指向的数据类型来决定 ` int:4 , char:1`; +```cpp + #include + + int main(int argc, char const *argv[]) + { + // 1. 指针的加法,不是传统的加法 + // 2. 步长由指针指向的数据类型来决定 int:4 , char:1 + int a; + int *p = &a; + printf("p: %d, p+1 : %d\n", p, p+1); + + char b; + char *q = &b; + printf("q: %d, q+1 : %d\n", q, q+1); + return 0; + } + +``` +输出: + +```cpp +p: -968682948, p+1 : -968682944 +q: -968682949, q+1 : -968682948 +``` +可以看出: +第一个`p+1`比`p`大`4`,而`q+1`比`q`大1。 + +再看`void *`万能指针: void *指针可以指向任意变量的内存空间。 +* `void *p`万能指针,**一般用在函数返回值**,函数参数; +* 可以理解为`Java`中的`Object`或者类似泛型; +* 不能声明`void`变量,因为不能确定分配多少空间。但是可以使用`void *`指针,因为指针确定了空间(`4`或`8`字节); +* `void*`指针作为左值**用于“接收”任意类型的指针**; +* `void*`指针作为右值赋值给其它指针时需要强制类型转换。比如 `malloc`返回的就是`void *`类型,则使用的时候`int a = (int *)malloc(sizeof(int ))`;还有`memcpy`的形参类型就是`void *`,所以可以拷贝字符数组和其他数组; +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // void * 可以指向任何类型的变量,使用该指针所指向的内存时,最好强制转换成它本身的类型 + void *p = NULL; + int a = 100; + p = &a; + + *( (int *)p ) = 200; //注意这里强制转化成 int * 类型 + printf("*p = %d\n", *( (int *)p ));// 输出200 + return 0; +} +``` + +### 2.7、`const`修饰指针变量和修饰内存地址 +注意要分清修饰的是指针还是指针指向的内存地址 + +* `const int *p`和`int const *p`都是修饰的`*`,也就是修饰的是指针指向的内存; +* `int * const p`修饰的是`p`这个指针变量,所以此时指针变量不能修改了; +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int a = 100; + int *p1 = &a; + *p1 = 200; //ok + p1 = NULL; //ok 原先指向a的内存,现在改为指向NULL + + // const修饰符 修饰内存地址 + // const int *p2 = &a; //此时const修饰的是int * 也就是指针变量指向的内存地址 + int const *p2 = &a; //和上面的一样的 + // *p2 = 300; // err代码不能编译,因为p2指向的内存 是只读的(const修饰) + p2 = NULL; //但是 const没有修饰 p2指针变量, p2是可以改变的 + + // const修饰符 修饰指针变量 + int * const p3 = &a; + *p3 = 300; //ok //因为修饰的p3指针, 所以p3指向的内存地址是可以改变的 + // p3 = NULL; //err 但是此时就不能改变指针变量本身了, + + // 指针和内存地址都用const修饰,--> 啥都不能改了 + const int * const p4 = &a; + // *p4 = 400; //err + // p4 = NULL; //err + return 0; +} +``` +*** +## 3、数组和指针 +### 3.1、数组和指针 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int arr[10] = {0,1,2,3,4,5,6,7,8,9}; + + // int *p = &arr[0]; + int *p = arr; //和上面的代码等价 + + for(int i = 0 ; i < sizeof(arr)/sizeof(arr[0]); i++){ + //以下四种方式 是等价的 + // printf("%d ", arr[i]); + printf("%d ", *(arr + i)); + // printf("%d ", *(p + i)); + // printf("%d ", p[i]); + } + printf("\n"); + return 0; +} + +``` +指针和数组的内存图: + +![1563676424030](assets/1563676424030.png) + +### 3.2、通过指针加减法来访问数组 + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + + int arr[10] = {0,1,2,3,4,5,6,7,8,9}; + int *p = arr; + int len = sizeof(arr)/sizeof(*arr); // *a 等价与 arr[0] + for(int i = 0; i < len; i++){ + printf("%d ", *p); + p++; //直接指针做加法 + } + printf("\n"); + + //反向 来访问 + int *q = &arr[len-1]; + for(int i = 0; i < len; i++){ + printf("%d ",*q); + q--; + } + printf("\n"); + return 0; +} +``` +输出: +```cpp +0 1 2 3 4 5 6 7 8 9 +9 8 7 6 5 4 3 2 1 0 +``` +### 3.3、指针数组 + +每一个元素都是一个指针; + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int a[3] = {1,2,3}; + int *p[3]; + int n = sizeof(p)/sizeof(*p);// 同样也可以这样得到数组的长度 + for(int i = 0; i < n; i++){ + p[i] = &a[i]; //给每一个指针数组赋值为a数组中的一个内存 + } + for(int i = 0; i < n; i++){ + // printf("%d ", *p[i]); + printf("%d ", *(*(p+i))); //和上面的等价 + } + printf("\n"); + return 0; +} +``` +输出: + +```cpp +1 2 3 +``` +*** +## 4、指针和函数 +### 4.1、指针和函数以及值传递 + +经典问题: `swap`函数。 + +* 简单的通过值传递函数不能改变n、m的值; +* 通过形参指针变量,实参传递变量的地址,在函数中操作指针变量所指向的内存才可以实现,这就是指针在函数中出现的意义; +* 即: 如果想通过函数改变实参,必须地址传递; +```cpp +#include + +void swap(int a,int b){ // 值传递 --> 变量传递 + int t = a; + a = b; + b = t; + printf("a = %d, b = %d\n", a, b); +} + +void swap2(int *a,int *b){ //此时a,b是指针变量 ,指向的是n,m的内存 + int t = *a; + *a = *b; + *b = t; +} + +int main(int argc, char const *argv[]) +{ + int n = 10, m = 20; + //值传递 --> 不管这个变量是什么类型,只要是变量本身传递,就是值传递, + // 只是一个变量的值的拷贝(就是这个内存的内容,而不是地址) + // swap(n,m); // 这个不能交换n、m的值,这个就是指针存在的意义 + + swap2(&n,&m); //地址传递,将a,b的地址传递过去 + + printf("n = %d, m = %d\n", n, m);// n = 20, m = 10 + return 0; +} +``` +内存图如下: + +![1563676435095](assets/1563676435095.png) + +### 4.2、形参中的数组(`"假数组"`(实际上是一个指针)) + +* 注意在函数的形参中,出现的数组其实是指针,通过`sizeof()`关键字测试就知道,`sizeof(arr) = 8`(64位操作系统); +* 所以不管数组方括号内的数字是没有还是多少,都是和一个普通的指针变量是一样; +* 所以要想得到数组的元素个数,最好是直接将数组元素传递到函数中,具体看`print_array2`; +```cpp +#include + + +// 1. 形参中的数组,不是数组,而是普通的指针变量(只有一个) +// 2. 以下三种写法都是一样的 编译器都是当做int *处理 +// void print_array(int arr[]){ +// void print_array(int arr[1000000]){ +void print_array(int *arr){ // 所以简单的通过下面的方式来打印a数组是不正确的 + + int n = sizeof(arr)/sizeof(arr[0]); + printf("sizeof(arr) = %d\n",sizeof(arr)); //8 --> arr是一个指针变量,因为是64位操作系统,所以是8 + printf("sizeof(arr[0]) = %d\n",sizeof(arr[0]));//4 --> arr[0] 是int类型,所以是4 + + for(int i = 0; i < n; i++){ // n = 2 + printf("%d ", arr[i]); + } + printf("\n"); + + arr = NULL; //ok 不会报编译错误,因为这个只是一个普通的指针变量 +} + +// 所以要传递一个 数组个数的整形变量 +void print_array2(int *arr, int n){ + for(int i = 0; i < n; i++){ + printf("%d ", arr[i]); + } + printf("\n"); +} + +int main(int argc, char const *argv[]) +{ + int arr[10] = {0,1,2,3,4,5,6,7,8,9}; + print_array(arr); + printf("--------------------------\n"); + print_array2(arr, sizeof(arr)/sizeof(arr[0])); + // arr = NULL: + return 0; +} +``` +### 4.3、局部变量和全局变量返回的地址 +注意局部变量和全局变量: +* 局部变量在函数调用完之后就释放了。所以将局部变量的地址返回,然后在`main`函数中操作这个地址的内存是不安全的,在`linux`下发生段错误; +* 全局变量在程序结束才释放,所以可以操作这个地址; + +局部变量: +```cpp +#include + +int *fun() // 注意,当fun()函数执行完毕, a就自动释放了 +{ + int a; + return &a;// 返回a的地址 +} + +int main(int argc, char const *argv[]) +{ + int *p = NULL; + p = fun(); //接受函数返回的地址 + printf("p = %d\n", p); //linux64位gcc,不允许返回局部变量的地址 //发生段错误 + + //相当于野指针了 + *p = 100; // 操作那个局部变量所指向的内存(但是那个内存已经被释放) + return 0; +} +``` +输出: + +```cpp +p = 0 +段错误 +``` + +全局变量(注意第二种灵活的改变方式): + +```cpp +#include + +int a; //全局变量只有在程序结束之后才释放 + +int *fun() +{ + return &a;// 返回全局变量的地址 +} + +int main(int argc, char const *argv[]) +{ + int *p = fun(); + printf("%d\n", p); //打印返回的地址 + *p = 100; + printf("*p = %d, a = %d\n", *p, a);//*p = 100, a = 100 + + // 也可以不使用p来中转 + *( fun() ) = 200; + printf("a = %d\n", a);//a = 200 + return 0; +} + +``` +输出:  + +```cpp +1263386676 +*p = 100, a = 100 +a = 200 +``` +### 4.4、`%s`打印的内部实现 + +* 注意`%s`打印的时候,不要将`str`写成`*str`; +* `%s`操作的是指针指向的内存 , `str`是首元素的地址 , `*str`是字符数组首元素的值(即`str[0]`); +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char str[] = "hello world!"; + for(int i = 0; str[i] != '\0'; i++) + printf("%c", str[i]); + printf("\n"); + // printf("%s\n", *str); // 段错误 + printf("%s\n", str); // 为什么不是 *str呢 + // %s操作的是指针指向的内存 , str是首元素的地址 , *str是字符数组首元素的值 + printf("%c\n", *str); // h + return 0; +} +``` +输出: + +```cpp +hello world! +hello world! +h +``` +*** +## 5、字符串和指针 +### 5.1、字符指针 +注意字符串特殊的地方,操作的时候就是操作内存了,而不是变量 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char str[] = "hello"; + + str[0] = '1'; + *(str+1) = '2'; + printf("str = %s\n", str); //12llo + printf("str = %s\n", &str[0]); //12llo + printf("str = %s\n", str+1); //2llo + printf("str = %s\n", &str[0]+1); //2llo + + //定义一个指针,指向首元素 + char *p = &str[0];// 或者char *p = str //数组名就是首元素地址 + *p = 'a'; + p++; + *p = 'b'; + printf("str = %s\n", str); //abllo + printf("p = %s\n", p); //bllo + printf("p = %s\n", p-1);//abllo +} +``` +输出: + +```cpp +str = 12llo +str = 12llo +str = 2llo +str = 2llo +str = abllo +p = bllo +p = abllo +``` +### 5.2、字符串的拷贝 + +* 可以用指针变量来操作字符串,操作指针所指向的内存就是操作字符串; +* 所以指针需要先指向特定字符串的内存地址,不能将字符串拷贝给野指针; + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + char buf[100]; + // p指向buf的首元素 + // strcpy是给p所指向的内存拷贝内容,字符串拷贝给了buf + char *p = buf; + strcpy(p,"hello world!"); + printf("p = %s\n", p); + printf("buf = %s\n", buf); + return 0; +} + +int main01(int argc, char const *argv[])//段错误 +{ + char *p; + // 1.不是给p变量拷贝内容 + // 2.给p所指向的内存拷贝内容 + // 3.p是野指针,给野指针所指向的内存拷贝内容,导致段错误 + strcpy(p, "hello world!"); + return 0; +} +``` +输出: +```cpp +p = hello world! +buf = hello world! +``` +> 注意`0`和`'\0'`以及`'0'`的区别: +> * 前两个是等价的,都是字符串的结束符标志; +> * `'0'`: 字符`'0'`,它的的ascii码是`48`,不是字符串的结束表示; + +### 5.3、注意字符串常量以及它的首元素地址 + +* 注意每一个字符串常量都有一个特定的地址,不会改变,这个地址就是字符串的首元素地址; +* 再重复一遍,字符串常量就是此字符串的首元素地址。所以`char *p = "hello world";`自然就很合理了; +* 字符串常量放在`data`区,也就是文字常量区; +* 可以使用类似指针的加减法来访问字符串; +```cpp +#include + +void fun() +{ + printf("%p\n","hello world"); +} + +int main(int argc,char const *argv[]) +{ + //1.每个字符串都是一个地址,这个地址就是字符串的首元素地址 + //2.字符串常量放在data区,也就是文字常量区 + printf("%p\n","hello world"); + fun(); + // 输出"ello world" + printf("%s\n","hello world" + 1); + printf("%c\n",*("hello world"));//h + return 0; +} +``` +输出: + +```cpp +0x55fc6cde07b4 +0x55fc6cde07b4 +ello world +h +``` +### 5.4、字符串不能修改(不能通过指针修改) +* 字符串常量不可修改,有一个固定的地址,可以使用多个指针来指向它; +* 如果某个字符指针指向了常量池中的字符串常量,就不能修改这个指针指向的内存(常量池),因为字符串常量是不可修改的; +```cpp +#include +#include + +int main(int argc,char const *argv[]) +{ + printf("s = %p\n","hello world"); + + char *p1 = "hello world"; + printf("p1 = %p\n", p1); + char *p2 = "hello world"; + printf("p2 = %p\n", p2); + + //2、字符串常量,文字常量区的字符串,只读,不能修改 + printf("*p1 = %c\n", *p1); //读, ok + //3、p1指向字符串常量,字符串常量为只读,不能修改 + //*p1 = 'a'; //修改, err + + char *p3 = "hello"; //指向了data区中的常量 此时不能通过p3修改p3所指向的内存,不然会发生段错误 + strcpy(p3,"world"); // err 段错误 + return 0; +} +``` +输出: + +```cpp +s = 0x561b8fe9c7a4 +p1 = 0x561b8fe9c7a4 +p2 = 0x561b8fe9c7a4 +*p1 = h +段错误 +``` +内存图: + +![1563676593021](assets/1563676593021.png) + +### 5.5、字符串常量区初始化字符指针和字符数组的区别 + +* 注意字符串数组和字符指针不同,字符串数组是一个一个的拷贝的; +* 而字符指针是保存的字符串常量的地址; +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 1. p指针保存了"hello"的地址 + // 2. 指针所指向的内存不能修改 + char *p = "hello"; + + // 1. 把"hello"一个一个字符放在buf数组中 + // 2. 数组的元素可以修改 + char buf[] = "hello"; + return 0; +} +``` +![1563676610872](assets/1563676610872.png) +### 5.6、`main`函数形参说明(字符指针数组) + +```cpp +#include + +// 以下三种写法也是一样的 +// void print_array(char *p[10000],int n){ +// void print_array(char *p[],int n){ +void print_array(char **p,int n){ + for(int i = 0; i < n; i++) + printf("%s\n",p[i]); +} + +int main(int argc, char const *argv[]) +{ + char *p[] = {"aaa", "bbb", "ccc"}; + int n = sizeof(p)/sizeof(*p); + print_array(p,n); + return 0; +} +``` +输出: + +```cpp +aaa +bbb +ccc +``` +再来看`main`函数: +* `main`函数是操作系统调用的,第一个参数标明`argv`数组的成员数量,`argv`数组的每个成员都`是char *`类型; +* `argv`是命令行参数的字符串数组; +* `argc`代表命令行参数的数量,程序名字本身算一个参数; + +```cpp +#include + +// 1. argc 相等于n (argv[]数组的元素个数) +// 2. argv就是那个二维数组,数组每个元素都是char *(字符地址) --> 可以写成 char **argv +int main(int argc, char const **argv) +{ + for(int i = 0; i < argc; i++){ + // printf("%s\n", argv[i]); + printf("%s\n", *(argv+i)); + } + return 0; +} +``` +运行结果: + +![1563676624900](assets/1563676624900.png) + +### 5.7、字符指针练习: `A`字符串在`B`字符串中出现的次数 +熟练掌握使用指针变量移动来实现寻找出现的次数 + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + char *B = "13435aaa345564aaa34589345aaa78345aaa890345"; + char A[] = "aaa"; + char *t = NULL; + int count = 0; + while(1){ + t = strstr(B,A); + if(t == NULL) + break; + else { + count++; + B = t + strlen(A); // 这是关键,要移动指针变量 + } + } + printf("%s出现的次数 = %d\n", A,count);// 4 + return 0; +} +``` +### 5.8、不要搞混二级指针和字符数组指针使用 + +```cpp +#include + +// 2. 二级指针 当做数组指针 +// 本质是指针 +void fun(char **p){ +// void fun(char *p[]){ +// void fun(char *p[1000]){ + +} + +int main(int argc, char const *argv[]) +{ + // 二级指针使用要分情况 + // 1. 二级指针 不能当做初始化 + // char **p = {"aaa","bbb","ccc"}; //err + return 0; +} +``` + + diff --git a/CPlus/C/C_3.md b/CPlus/C/C_3.md new file mode 100644 index 00000000..0d806d26 --- /dev/null +++ b/CPlus/C/C_3.md @@ -0,0 +1,1646 @@ +# C语言知识总结(三) + +* 1、内存管理 +* 2、内存布局 +* 3、结构体、共用体 +* 4、文件 + + +## 1、内存管理 + +### 1.1、作用域 + +C语言变量的作用域分为: +* 代码块作用域(代码块是{}之间的一段代码); +* 函数作用域; +* 文件作用域; + +### 1.2、普通局部变量 +局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点: +* 在`{}`内部定义的变量就是局部变量(在一个函数内定义,只在函数范围内有效)(在复合语句中定义,只在复合语句中有效); +* 只有执行到定义变量的这个语句,系统才会给这个变量分配空间; +* 随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束; +* `{}`内的普通局部变量,加不加`auto`(注意和`C++`的不同)关键字等价,普通局部变量也加`auto`; +* 如果没有赋初值,内容为随机; + +### 1.3、static局部变量 +* `static`局部变量的作用域也是在定义的函数内有效; +* `static`局部变量的生命周期和程序运行周期一样,同时`staitc`局部变量的值只初始化一次,但可以赋值多次; +* static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值`0`,字符型变量赋空字符; + +```cpp +#include + +void fun(){ + int i = 0; + i++; + printf("i = %d\n",i); +} + +//1、static局部变量,是在编译阶段就已经分配空间,函数没有调用前,它已经存在 +//2、当离开{},static局部变量不会释放,只有程序结束,static变量才自动释放 +//3、局部变量的作用域在当前的{},离开此{},无法使用此变量 +//4、如果static局部变量不初始化,它的值默认为0 +//5、static局部变量初始化语句,只会执行一次,但是可以赋值多次 +//6、static变量只能用常量初始化(注意) +void static_fun(){ + static int i = 0; //只初始化一次(只执行一次),只能使用常量初始化 + i++; + printf("i = %d\n",i); +} + +int main(int argc, char const *argv[]) +{ + fun(); fun(); fun(); // 输出都是i = 1 + printf("-----------------\n"); + static_fun(); static_fun(); static_fun(); + return 0; +} +``` + +输出: + +```cpp +i = 1 +i = 1 +i = 1 +----------------- +i = 1 +i = 2 +i = 3 +``` +### 1.4、普通局部变量和`static`局部变量的区别 + +从内存分配和释放的角度看: + + * ①普通局部变量只有执行到定义变量的语句才分配空间; + ②`static`局部变量在编译阶段(函数还没有执行),变量的空间已经分配; +* ①普通局部变量离开作用域`{}`,自动释放; +②`static`局部变量只有在整个程序结束才自动释放; + + +从初始化的角度来看: +* ①普通局部变量不初始化,值为随机数; +②`static`局部变量不初始化,值为0; +* `static`局部变量初始化语句只有第一次执行有效; +* `static`局部变量只能用常量初始化; + +### 1.5、普通的全局变量(外部链接) + +* 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用`extern`声明; +* 全局变量的生命周期和程序运行周期一样; +* 不同文件,普通全局变量只能定义一次,可以声明多次。所以`.h`文件中不能放定义只能放声明; +```cpp +#include + +//1、只有定义了全局变量,任何地方都能使用此变量 +//2、如果使用变量时,在前面找不到此全局变量的定义,需要声明后才能使用 +//3、全局变量不初始化,默认赋值为0 +//4、声明只是针对全局变量,不是针对局部变量 +//5、全局变量在编译阶段已经分配空间(函数没有执行前),只有在整个程序结束,才自动释放 +void fun2() +{ + extern int a; //声明时,不要赋值 + extern int a; //可以声明多次 + printf("fun2 a = %d\n", a);// 200 +} + +int a = 100; //在fun2后定义 + +void fun() +{ + a = 200; +} + +int main(int argc, char *argv[]) +{ + fun(); + fun2(); + return 0; +} +``` +> C语言全局变量的缺陷: +> * ① 如果定义一个全局变量,没有赋值(初始化),无法确定是定义,还是声明; +> * ② 如果定义一个全局变量,同时初始化,这个肯定是定义; +> +> C语言定义全局变量的建议: +> * ①如果定义一个全局变量,建议初始化; +> * ②如果声明一个全局变量,建议加`extern`; + +### 1.6、`static`全局变量(内部链接) +* `static`全局变量和普通全局变量的区别就是,作用域不一样(文件作用域): +① 普通全局变量: 所有文件都能使用,使用前需要使用`extern`关键字声明; +② `static`全局变量: 只能在本文件使用,别的文件不能使用;(可以保证数据安全) +* 一个文件只能有一个`static`全局变量的定义,不同文件静态全局变量可以重名,但作用域不冲突; +* `extern`关键字只适用于普通全局变量的声明; +* `static`全局变量的生命周期和程序运行周期一样,同时`staitc`全局变量的值只初始化一次; + +### 1.7、全局函数和静态函数 +在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,**函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用**,即使在其他文件中声明这个函数都没用。 +* 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用; +* 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。 + +看一个分文件编程的例子,加深对`static`函数和普通全局函数的理解: + +![1563676643955](assets/1563676643955.png) + +### 1.8、总结 +![1563676653222](assets/1563676653222.png) +*** +## 2、内存布局 + +### 2.1、内存分区 + +* 在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(`text`)、数据区(`data`)和未初始化数据区(`bss`)3 个部分(有些人直接把`data`和`bss`合起来叫做静态区或全局区)。 +* 当运行程序,加载内存,首先根据前面确定的内存分区: + (`text`, `data`, `bss`)先加载: +①`text`(代码区):只读,共享的,函数。(存放 CPU 执行的机器指令)(使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可); +②`data`: 初始化的数据,全局变量,static变量, 文字常量区(只读); +③`bss`: 没有初始化的数据, 全局变量,static变量; + 然后额外加载2个区: +④`stack`(栈区):普通局部变量,自动管理内存,先进后出的特点; +⑤`heap`(堆区):手动申请空间,手动释放,整个程序结束,系统也会自动回收,如果没有手动释放,程序也没有结束,这个堆区空间不会自动释放; + +> * 代码区`text segment`: 加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。 +> * 未初始化数据区`BSS`: 加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。 +> * 全局初始化数据区/静态数据区`data segment`: 加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。 +> * 栈区`stack`: 栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。 +> * 堆区 `heap`: 堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。 + +![1563676670688](assets/1563676670688.png) + +![1563676697144](assets/1563676697144.png) + +### 2.2、memset使用 +`void *memset(void *s, int c, size_t n);` + +* 功能:将`s`的内存区域的前`n`个字节以参数`c`填入; +* 参数:①`s`:需要操作内存`s`的首地址;②`c`:填充的字符,`c`虽然参数为`int`,但必须是`unsigned char` , 范围为`0~255`③`n`:指定需要设置的大小; +* 返回值:`s`的首地址; +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + int a; + memset(&a, 0, sizeof(a)); // 清0 ---> 常用的方式 + printf("%d\n", a); // 0 --> ok + + // 中间参数虽然是整形,但是当做字符串处理 + memset(&a, 10, sizeof(a)); + printf("%d\n", a); //168430090 得不到想要的结果 + memset(&a, 97, sizeof(a)); + printf("%c\n", a); // 打印为 'a' + + //常用的,对数组清0 + int b[10]; + memset(b, 0, sizeof(b)); //注意不要加 & 数组名就是首地址 + memset(b, 0, 10 * sizeof(int)); //和上面的写法一样 + for(int i = 0; i < sizeof(b)/sizeof(*b); i++) + printf("%d ", b[i]); + printf("\n"); + return 0; +} +``` +输出: +```cpp +0 +168430090 +a +0 0 0 0 0 0 0 0 0 0 +``` +使用的意义: + +```cpp +#include +#include + +int main(int argc,char const **argv) +{ + int arr[10] = {0}; + + // 中间操作:对arr修改了 + + // 想重新清0 --> 使用这个函数就可以快速的清0 + memset(arr, 0, sizeof(arr)); + //arr = { 0 }; //err + return 0; +} + +``` + +### 2.3、memcpy、memmove、memcmp使用 + +* 使用`strcpy`拷贝的时候,不能将`'\0'`拷贝进去; +* 可以使用`memcpy`拷贝,可以将`'\0'`也拷贝进去; +* `void *memcpy(void *dest, const void *src, size_t n);`: 拷贝`src`所指的内存内容的前`n`个字节到`dest`所值的内存地址上。 +```cpp +#include +#include + +int main(int argc, char const **argv) +{ + char p[] = "hello\0world"; + char dest[100]; + printf("sizeof(p) = %d\n", sizeof(p)); + strncpy(dest, p, sizeof(p)); + printf("dest = %s\n",dest); + printf("dest2 = %s\n",dest + strlen("hello") + 1); //后面是没有拷贝的 + + memset(dest,0,sizeof(dest)); + memcpy(dest,p,sizeof(p)); + printf("dest = %s\n",dest);//注意这里也不是输出完整的因为有\0就输出不了 + printf("dest2 = %s\n",dest + strlen("hello") + 1); //后面能输出 + return 0; +} +``` +运行结果: + +![1563676717889](assets/1563676717889.png) + +`memmove`和`memcmp`简单使用测试: + +* `memmove` : `memmove()`功能用法和`memcpy()`一样,区别在于:`dest`和`src`所指的内存空间重叠时,`memmove()`仍然能处理,不过执行效率比`memcpy()`低些; +* `int memcmp(const void *s1, const void *s2, size_t n);`: 比较s1和s2所指向内存区域的前n个字节; + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int b[10]; + + //第3个参数是指拷贝内存的总大小 + memcpy(b, a, 10 * sizeof(int) ); + memcpy(b, a, sizeof(a) ); + + // memmove 移动(出现内存重叠的时候使用) + //使用memcpy()最好别出现内存重叠 + //如果出现内存重叠,最好使用memmove + //memcpy(&a[2], a, 5*sizeof(int) ); //err + memmove(&a[2], a, 5*sizeof(int) ); + + // memcmp : 主要是看两个数组内容是不是完全一样 + int arr1[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int arr2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int res = memcmp(arr1, arr2, 10 * sizeof(int) ); + printf("res = %d\n", res); // 0 + return 0; +} +``` +### 2.4、堆区 + +在堆区分配内存(使用`malloc`函数分配): + + * 动态分配的空间,如果程序没有结束,不会自动释放; +* 一般使用完,需要人为的释放: `free(p)`; +* `free(p)`,不是释放`p`变量,而是释放`p`所指向的内存; +* 同一块堆区内存只能释放一次; +* 所谓的释放不是指内存消失,指这块内存用户不能再次使用(系统回收),如果用户再用,就是操作非法内存; + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + //在堆区分配sizeof(int)的内存,返回内存地址 + int *p = (int *)malloc( sizeof(int) ); + if(p == NULL){ + printf("分配失败!"); + return -1; + } + *p = 10; + printf("*p = %d\n", *p); //10 + free(p); // 人为释放 + return 0; +} +``` +分析: + +![1563676734004](assets/1563676734004.png) + +> * 内存泄露: 动态分配了空间,不释放; +> * 内存污染: 非法使用内存; + + +### 2.5、内存分区代码分析(5个例子(重点)) + +①. 返回栈区地址(不合法): +```cpp +#include + +int *fun(){ + int a = 10; + return &a; //函数调用完毕,a释放 +} + +main(int argc, char const *argv[]) +{ + int *p = NULL; + p = fun(); + *p = 100; //操作野指针指向的内存 // err + return 0; +} +``` +局部变量返回地址: + +![1563676746208](assets/1563676746208.png) + +②. 返回data区地址(合法): + +```cpp +#include + +int *fun(){ + static int a = 100; //静态,函数调用完毕,a不释放 + return &a; +} + +int main(int argc, char const *argv[]) +{ + int *p = NULL; + p = fun(); + *p = 200; + printf("*p = %d\n", *p);// 200 + return 0; +} +``` +③. 值传递一(不合法): + +```cpp +#include +#include + +void fun(int *tmp) +{ + tmp = (int *)malloc(sizeof(int)); + *tmp = 100; +} + +int main(int argc, char const *argv[]) +{ + int *p = NULL; + fun(p); //值传递,形参修改不会影响实参 + printf("*p = %d\n", *p);//err,操作空指针指向的内存 -->段错误 + return 0; +} +``` +![1563676762623](assets/1563676762623.png) +④. 值传递二(合法): + +* 和上面的程序唯一的不同就是此时`p`是在函数调用之前分配空间; + +```cpp +#include +#include + +void fun(int *tmp){ + *tmp = 100; +} + +int main(int argc, char const *argv[]) +{ + int *p = NULL; + p = (int *)malloc(sizeof(int)); + fun(p); + printf("*p = %d\n",*p);// ok,*p为100 不会发生段错误 + return 0; +} +``` +同样看内存图: + +![1563676775722](assets/1563676775722.png) +⑤. 返回堆区地址(合法): + +```cpp +#include +#include + +int *fun(){ + int *tmp = NULL; + tmp = (int *)malloc(sizeof(int)); + *tmp = 100; + return tmp; +} + +int main(int argc, char const *argv[]) +{ + int *p = NULL; + p = fun(); + printf("*p = %d\n", *p); // ok + + //堆区空间,使用完毕,手动释放 + if(NULL != p){ + free(p); //不是释放p,而是p指向的内存空间 + p = NULL; // p重新指向NULL + } + return 0; +} +``` +![1563676790063](assets/1563676790063.png) + +*** +## 3、结构体、共用体 + +### 3.1、结构体的基本使用 + +```cpp +#include +#include + +// 1. struct Student合起来才是结构体类型 +// 2. 结构体内部定义的变量不能直接赋值 +// 结构体只是一个类型,没有定义变量前,是没有分配空间,没有空间,就不能赋值 +struct Student{ + int age; + char name[50]; + int score; +}; + +int main(int argc, char const *argv[]) +{ + struct Student stu;// 别忘了struct关键字 + struct Student stu2 = {10, "zxzxin", 90}; //只有在定义时可以初始化 + printf("stu2.age = %d, stu2.name = %s, stu2.score = %d\n", stu2.age, stu2.name, stu2.score); + + // 如果是普通变量,使用 . 运算符 + stu.age = 23; + strcpy(stu.name, "zhangsan"); + stu.score = 60; + printf("stu.age = %d, stu.name = %s, stu.score = %d\n", stu.age, stu.name, stu.score); + + // 如果是指针变量,使用-> + // 注意: 指针有合法指向,才能操作结构体成员 (小心野指针) + struct Student *p; + p = &stu2; + p->age = 20; + strcpy(p->name, "luoying"); + p->score = 80; + printf("stu2.age = %d, stu2.name = %s, stu2.score = %d\n", stu2.age, stu2.name, stu2.score); + + // 任何结构体变量都能使用 -> 或者 . 来操作 , *p也可以写成p[](类似指针) + (&stu)->age = 33; + (*p).age = 30; + p[0].age = 40; + + return 0; +} +``` +定义结构体变量的方式: +* 先声明结构体类型再定义变量名; +* 在声明类型的同时定义变量; +* 直接定义结构体类型变量(无类型名); + +三种方式对应下图: + +![1563676803049](assets/1563676803049.png) + +结构体类型和结构体变量关系: + +* 结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元; +* 结构体变量:系统根据结构体类型(内部成员状况)为之分配空间; + +### 3.2、结构体数组 + +* 注意结构体数组的初始化方式,和数组类似; +* 巩固几种访问成员列表的方式; +```cpp +#include +#include + +struct Student{ + int age; + char name[50]; + int score; +}; + +int main(int argc, char const *argv[]) +{ + struct Student arr[4]; + //演示几种访问的方式 + arr[0].age = 13; + strcpy(arr[0].name, "zhangsan"); + arr[0].score = 63; + + (*(arr + 1)).age = 14; + strcpy((*(arr + 1)).name, "lisi"); + (*(arr + 1)).score = 64; + + (&arr[2])->age = 15; + strcpy((&arr[2])->name, "wangwu"); + (&arr[2])->score = 65; + + (arr + 3)->age = 16; + strcpy((arr + 3)->name, "zhaoliu"); + (arr + 3)->score = 66; + + for(int i = 0; i < sizeof(arr)/sizeof(*arr); i++) + printf("%d, %s, %d\n", arr[i].age, arr[i].name, arr[i].score); + printf("\n"); + + + printf("-----------------------------\n"); + // 另一种初始化的方式 + struct Student arr2[4] = { + {13, "zhangsan", 63}, + {14, "lisi", 64}, + {15, "wangwu", 65}, + {16, "zhaoliu", 66} + }; + for(int i = 0; i < sizeof(arr2)/sizeof(*arr2); i++) + printf("%d, %s, %d\n", arr2[i].age, arr2[i].name, arr2[i].score); + printf("\n"); + return 0; +} +``` +输出: + +```cpp +13, zhangsan, 63 +14, lisi, 64 +15, wangwu, 65 +16, zhaoliu, 66 + +----------------------------- +13, zhangsan, 63 +14, lisi, 64 +15, wangwu, 65 +16, zhaoliu, 66 + +``` +### 3.3、值传递也适用结构体 + +```cpp +#include +#include + +struct Student{ + int age; + char name[50]; + int score; +}; + +void setStu(struct Student tmp){ + tmp.age = 14; + strcpy(tmp.name, "lisi"); + tmp.score = 64; +} + +int main(int argc, char const *argv[]) +{ + struct Student stu = {13, "zhangsan", 63}; + setStu(stu); + printf("%d, %s, %d\n", stu.age, stu.name, stu.score); + return 0; +} +``` +输出: + +```cpp +13, zhangsan, 63 +``` +分析: + +![1563676817117](assets/1563676817117.png) + +### 3.4、结构体的打印 +有两种打印方式: + +* 通过值传递,这种方式形参是实参的拷贝,改变形参不会改变实参,但是拷贝导致效率会比较低; +* 通过地址传递,这种方式是直接操作实参的内容,但是最好使用`const`来修饰地址的内容,防止被改变; +```cpp +#include + +struct Student { + int age; + char name[50]; + int score; +}; + +// 这种是结构体的拷贝(改变tmp也不会改变原来的结构体),效率没有那么高 +void print_stu(struct Student tmp){ + printf("%d, %s, %d\n", tmp.age, tmp.name, tmp.score); +} + +// 最好是传递地址,直接打印原结构体, 这种方式最好设定指针指向的内存为const --> 只读(安全) +void print_stu2(const struct Student *p){ + printf("%d, %s, %d\n", p->age, p->name, p->score); + //p->age = 22; // err +} + +int main(int argc, char const *argv[]) +{ + struct Student stu = {13, "zhangsan", 63}; + print_stu(stu); // 13, zhangsan, 63 + print_stu2(&stu);// 13, zhangsan, 63 + return 0; +} +``` +### 3.5、指针在堆区分配空间 + +```cpp +#include +#include +#include + +struct Student{ + int age; + char name[50]; + int score; +}; + +int main(int argc, char const **argv) +{ + + struct Student *p; + p = (struct Student*)malloc(sizeof(struct Student)); + if(NULL == p){ + printf("分配失败!"); + return -1; + } + //已经分配好了空间,可以操作了(不是野指针了) + p->age = 13; + strcpy(p->name, "zhangsan"); + p->score = 63; + printf("%d, %s, %d\n", p->age, p->name, p->score); + + if(NULL != p){ + free(p); + p = NULL; + } + return 0; +} +``` +内存分析: + +![1563676830225](assets/1563676830225.png) + +### 3.6、成员列表中有指针 +①. 成员变量保存字符串常量的首地址: + +![1563676853582](assets/1563676853582.png) + +②. 成员变量指向栈区空间: + +![1563676863442](assets/1563676863442.png) + +③. 成员变量指针指向堆区空间: + +![1563676877818](assets/1563676877818.png) + +总的测试代码: + +```cpp +#include +#include +#include + +struct Student{ + int age; + char *name; + int score; +}; + +int main(int argc, char const *argv[]) +{ + struct Student s1; + s1.age = 13; + s1.name = "zhangsan";// 指针变量保存字符串常量的首地址 + s1.score = 63; + + // 成员变量指针指向栈区空间 + struct Student s2; + s2.age = 14; + char buf[50]; + s2.name = buf;//s2的指针指向了字符数组 + strcpy(s2.name, "lisi"); + s2.score = 64; + printf("buf = %s\n",buf); + + //成员变量指向堆区空间 + struct Student s3; + s3.age = 15; + //s3.name = (char *)malloc( (strlen("wangwu")+1) * sizeof(char) ); + s3.name = (char *)malloc( (strlen("wangwu")+1)); + strcpy(s3.name, "wangwu"); + s3.score = 65; + printf("%d, %s, %d\n", s3.age, s3.name, s3.score); + if(s3.name != NULL){//释放空间 + free(s3.name); + s3.name = NULL; + } + return 0; +} +``` +④. 给结构体变量分配堆区空空间(注意内部的指针也要同时分配): + +```cpp +#include +#include +#include + +struct Student +{ + int age; + char *name; + int score; +}; + +int main(int argc, char const **argv) +{ + struct Student *p; + + // 给结构体变量在堆上分配空间 + p = (struct Student*)malloc(sizeof(struct Student)); + + p->age = 13; + //strcpy(p->name, "zhangsan");//err + + //这里注意上面只是给p分配了,但是p中的name还是没有分配 + p->name = (char*)malloc(sizeof(char) * strlen("zhangsan") + 1); //+1是为了'\0' + strcpy(p->name,"zhangsan"); + + p->score = 63; + + printf("%d, %s, %d\n", p->age, p->name, p->score); + + //释放的时候先释放 p->name + if(NULL != p->name){ + free(p->name); + p->name = NULL; + } + + if(NULL != p){ + free(p); + p = NULL; + } + return 0; +} +``` +示意图: + +![1563676892955](assets/1563676892955.png) + +### 3.7、共用体 + +* 联合`union`是一个能在同一个存储空间存储不同类型数据的类型; +* 联合体所占的内存长度等于其最长成员的长度,也有叫做共用体; +* 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用; +* 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖; +* 共用体变量的地址和它的各成员的地址都是同一地址。 +```cpp +#include + +union Unon +{ + unsigned char a; + unsigned short b; + unsigned int c; + //double d; +}; + +int main(int argc, char *argv[]) + { + //1、结构体的大小可以简单认为成员大小的累加 + //2、共用体的大小为最大成员的大小 + printf("%lu\n", sizeof(union Unon)); + + //3、共用体公有一块内存,所有成员的地址都一样 + union Unon un; + printf("%p, %p, %p, %p\n", &un, &un.a, &un.b, &un.c); + + //4、给某个成员赋值,会影响到另外的成员 + //左边是高位,右边是低位 --> 高位放高地址,低位放低地址(小端) + + un.c = 0x44332211; + printf("un.c = %x\n", un.c); + printf("un.a = %x\n", un.a); + printf("un.b = %x\n", un.b); + + un.a = 0xaa; + printf("un.c = %x\n", un.c); + printf("un.a = %x\n", un.a); + printf("un.b = %x\n", un.b); + + un.b = 0xccdd; + printf("un.c = %x\n", un.c); + + return 0; +} +``` +运行结果: + +![1563676911604](assets/1563676911604.png) + +分析: + +![1563676920269](assets/1563676920269.png) + +### 3.8、typedef关键字 +* 作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型; +* 与`#define`不同,`typedef`仅限于数据类型,而不是能是表达式或具体的值; +* `#define`发生在预处理,`typedef`发生在编译阶段; + +简单使用测试: + +```cpp +#include + +typedef int INT; +typedef char BYTE; +typedef BYTE T_BYTE; +typedef unsigned char UBYTE; + +typedef struct type +{ + UBYTE a; + INT b; + T_BYTE c; +}TYPE, *PTYPE; //注意*PTYPE是这个类型的指针 + +int main() +{ + TYPE t; + t.a = 254; + t.b = 10; + t.c = 'c'; + + PTYPE p = &t; + printf("%u, %d, %c\n", p->a, p->b, p->c);// 254, 10, c + return 0; +} +``` + +*** +## 4、文件 + + +### 4.1、磁盘文件和设备文件 +* 磁盘文件: 指一组相关数据的有序集合,通常存储在外部介质(如磁盘)上,使用时才调入内存。 +* 设备文件:在操作系统中把每一个与主机相连的输入、输出设备看作是一个文件,把它们的输入、输出等同于对磁盘文件的读和写。 + + +### 4.2、磁盘文件的分类 +计算机的存储在物理上是二进制的,所以物理上所有的磁盘文件本质上都是一样的:以字节为单位进行顺序存储。从用户或者操作系统使用的角度(逻辑上)把文件分为: +* 文本文件:基于字符编码的文件 ; +* 二进制文件:基于值编码的文件 + +### 4.3、文本文件和二进制文件 +①文本文件: +* 基于字符编码,常见编码有ASCII、UNICODE等; +* 一般可以使用文本编辑器直接打开; +* 数`5678`的以ASCII存储形式(ASCII码)为: +`00110101 00110110 00110111 00111000` + +②二进制文件: +* 基于值编码,自己根据具体应用,指定某个值是什么意思; +* 把内存中的数据按其在内存中的存储形式原样输出到磁盘上; +* 数`5678`的存储形式(二进制码)为:`00010110 00101110`; + +### 4.4、文件的打开和关闭 +文件指针`FILE`(文件句柄): +* 在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。 +* FILE是系统使用`typedef`定义出来的有关文件信息的一种结构体类型,结构中含有文件名、文件状态和文件当前位置等信息。 +* 声明FILE结构体类型的信息包含在头文件`“stdio.h”`中,一般设置一个指向FILE类型变量的指针变量,然后通过它来引用这些FILE类型变量。通过文件指针就可对它所指的文件进行各种操作。 +* `FILE`指针`fp`,调用了`fopen()`,就在堆上分配空间,把地址返回给`fp`; +* `fp`指针不是指向文件,`fp`指针和文件关联,`fp`内部成员保存了文件的状态; +* 操作`fp`指针,不能直接操作,必须通过文件库函数来操作`fp`指针;(对文件的任何操作,`fp`里面内容发生变化) +![1563676935545](assets/1563676935545.png) + +C语言中有三个特殊的文件指针由系统默认打开,用户无需定义即可直接使用: +* `stdin`: 标准输入,默认为当前终端(键盘),我们使用的`scanf`、`getchar`函数默认从此终端获得数据。 +* `stdout`:标准输出,默认为当前终端(屏幕),我们使用的`printf`、`puts`函数默认输出信息到此终端。 +* `stderr`:标准出错,默认为当前终端(屏幕),我们使用的`perror`函数默认输出信息到此终端。 + +### 4.5、文件的打开 + +* `FILE * fopen(const char * filename, const char * mode);`: filename:①需要打开的文件名,根据需要加上路径;②mode:打开文件的模式设置; + +![1570423375986](assets/1570423375986.png) + +> 注意: +> * 只要有`w`就会清空,只要有`r`就必须存在(不然会报错); +> * `b`是二进制模式的意思,`b`只是在`Windows`有效,在`Linux`用`r`和`rb`的结果是一样的; +> * `Unix`和`Linux`下所有的文本文件行都是`\n`结尾,而`Windows`所有的文本文件行都是`\r\n`结尾; +> * 在`Windows`平台下,以“文本”方式打开文件,不加`b`:①当读取文件的时候,系统会将所有的` "\r\n" `转换成 `"\n"`;②当写入文件的时候,系统会将` "\n" `转换成 `"\r\n"` 写入 ;③以"二进制"方式打开文件,则读写都不会进行这样的转换; +> * 在Unix/Linux平台下,“文本”与“二进制”模式没有区别,"\r\n" 作为两个字符原样输入输出;\ + + +> 关于相对路径: +> * `VS`相对路径: ①如果编译运行,相对于项目;②如果直接运行程序,相对于程序; +> * `Qt`相对路径: ①如果编译运行,相对于`Debug`目标;②如果直接运行程序,相对于程序; + +### 4.6、文件的关闭 +任何文件在使用后应该关闭: + +* 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存; +* 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用`fopen`打开文件会失败; +* 如果没有明确的调用`fclose`关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭; +* `int fclose(FILE * stream);`: 关闭先前`fopen()`打开的文件。此动作让缓冲区的数据写入文件中,并释放系统所提供的文件资源。 + +### 4.8、按照字符读写文件`fgetc`、`fputc` + +* `int fputc(int ch, FILE * stream);`: 将ch转换为`unsigned char`后写入`stream`指定的文件中; +* `int fgetc(FILE * stream);`: 从`stream`指定的文件中读取一个字符; + +> 文件结尾: +> * 在C语言中,`EOF`表示文件结束符(`end of file`)。在`while`循环中以`EOF`作为文件结束标志,这种以`EOF`作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是`0~127`,不可能出现`-1`,因此可以用`EOF`作为文件结束标志。 +> * `#define EOF (-1)`;这个就是`-1`的宏; +> * 当把数据以二进制形式存放到文件中时,就会有`-1`值的出现,因此不能采用`EOF`作为二进制文件的结束标志。为解决这一个问题,ANSIC提供一个`feof`函数,用来判断文件是否结束。`feof`函数既可用以判断二进制文件又可用以判断文本文件。 +> * `int feof(FILE * stream);`: 检测是否读取到了文件结尾。判断的是最后一次`“读操作的内容”`,不是当前位置内容(上一个内容)。 + +测试: + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen("02.txt", "w"); + if(NULL == fp){ + perror("fopen"); + return -1; + } + + // fputc一次只能写一个字符 专门写字符的 + fputc('a', fp); + fputc('b', fp); + fputc('c', fp); + fputc('\n', fp); + + //也可以写到屏幕 + fputc('a', stdout); + fputc('b', stdout); + fputc('c', stdout); + fputc('\n', stdout); + + const char *buf = "hello world"; + for(int i = 0; i < strlen(buf); i++) + fputc( *(buf+i), fp); + + fclose(fp); + fp = NULL; + return 0; +} +``` +屏幕输出: +``` +abc +``` +`02.txt`文件内容: +```cpp +abc +hello world +``` +测试`fgetc`和`feof()`函数: (读取的是上一个程序写入的`02.txt`内容) + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + // 打开文件,以读的方式 + FILE *fp = fopen("02.txt", "r"); + + // 读文件,每次读一个字符 + char ch = fgetc(fp); + printf("%c",ch); + + // 循环读取, 文件结尾就是-1 当ch = -1就是文件结尾, 而C内部有一个宏EOF = -1 + // while( (ch = fgetc(fp)) != -1){ + while( (ch = fgetc(fp)) != EOF){ + printf("%c", ch); + } + printf("\n"); + + fp = fopen("02.txt", "r"); + while(1){ + ch = fgetc(fp); + if(feof(fp))//如果不想打印最后的-1,这个必须在printf("%c",ch)的前面 + break; + printf("%c", ch); + } + printf("\n"); + + // 1.如果是文本文件,可以通过-1(EOF)判断文件是否结尾 + // 2.如果是二进制文件,不能以-1判断文件结尾 + // 3. feof()判断文件是否结尾,任何文件都可以判断 + return 0; +} +``` +输出: +```cpp +abc +hello world +abc +hello world + +``` +> `feof`函数补充: +> * 如果第一次没有对文件进行读操作,直接调用此函数,永远返回假(文件没有到结尾); +> * 此函数必须先读,然后调用`feof`,才有意义; +> * 调用此函数,光标不会往后移动; +> * 必须读取后,才能判断是否结束,判断的是读取(后)的字符; + + +### 4.7、案例: `cat`、 `vi` 命令实现 +①`cat`命令实现: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen(argv[1], "r"); + char ch; + while(1){ + ch = fgetc(fp); + if(feof(fp)) + break; + printf("%c", ch); + } + fclose(fp); + return 0; +} +``` +![1563676956880](assets/1563676956880.png) + +②`vi`命令的简单模仿实现:   + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen(argv[1], "w"); //以写的方式 (不存在就新建) + char buf[100]; + while(true){ + fgets(buf, sizeof(buf), stdin); //这个函数默认读取一行,也就是遇到换行符(注意换行符也会读进去),此次就结束读取 + if(strncmp(buf, ":wq", 3) == 0) //比较这一行的前三个字符,如果是:wq就退出 + break; + // 然后将这一行从buf中写入指定的文件 + for(int i = 0; buf[i] != '\0'; i++) + fputc(buf[i],fp); + } + fclose(fp); + return 0; +} +``` +> **注意`fgets`也会将换行符`\n`读进去**。 + +演示效果: + +![1563676980786](assets/1563676980786.png) + +### 4.8、按照行读写文件`fgets`、`fputs` +* `int fputs(const char * str, FILE * stream);`: 将str所指定的字符串写入到stream指定的文件中,字符串结束符 `'\0'` 不写入文件; +* `char * fgets(char * str, int size, FILE * stream);`: 从`stream`指定的文件内读入字符,保存到`str`所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了`size - 1`个字符为止,最后会自动加上字符 `'\0'` 作为字符串结束; + +①`fputs`使用: +```cpp +#include + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen("06.txt", "w"); + const char *p[] = {"zhangsan\n", "lisi\n", "wangwu\n"}; //字符指针数组 + for(int i = 0; i < sizeof(p)/sizeof(*p); i++) + fputs(p[i], fp); + fclose(fp); + return 0; +} +``` +`06.txt`文件内容: +```cpp +zhangsan +lisi +wangwu +``` +②`fgets`使用: +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen("06.txt", "r"); + + // 一次一次的读取 遇到换行符,文件结尾,出错,结束本次读取 + char buf[100]; + fgets(buf, sizeof(buf), fp); + printf("buf = %s", buf); + fgets(buf, sizeof(buf), fp); + printf("buf = %s", buf); + fgets(buf, sizeof(buf), fp); + printf("buf = %s", buf); + + // memset(buf, 0, sizeof(buf)); + // 以下的读取,由于到文件结尾了,读取失败 + // buf保存的是上一次读取的内容 + fgets(buf, sizeof(buf), fp); + printf("buf = %s", buf); + fgets(buf, sizeof(buf), fp); + printf("buf = %s", buf); + + printf("-------------------------\n"); + + fp = fopen("06.txt", "r"); + while(true){ + fgets(buf, sizeof(buf), fp); + if(feof(fp)) //这个必须要写在printf()的上面 + break; + printf("buf = %s", buf); + } + fclose(fp); + return 0; +} +``` +输出: +```cpp +buf = zhangsan +buf = lisi +buf = wangwu +buf = wangwu +buf = wangwu +------------------------- +buf = zhangsan +buf = lisi +buf = wangwu +``` + +### 4.9、按照格式化文件`fprintf`、`fscanf` +* `int fprintf(FILE * stream, const char * format, ...);` : 根据参数`format`字符串来转换并格式化数据,然后将结果输出到`stream`指定的文件中,指定出现字符串结束符` '\0' ` 为止; +* `int fscanf(FILE * stream, const char * format, ...);` : 从`stream`指定的文件读取字符串,并根据参数`format`字符串来转换并格式化数据; + +这里要注意`fscanf()`函数处理边界时的一个问题,和`fgets()`有点不同: +* `feof()`函数判断是在输出之后; +* 也就是不同于`fgets()`判断文件结尾,这个是按照自己的格式去判断; +```cpp +#include + +int main(int argc, char const *argv[]) +{ + //打开文件,写方式 + FILE *fp = fopen("./08.txt", "r+"); + int num; + char buf[100]; + while(1) + { + fscanf(fp, "%d\n", &num); //这个就不会导致这种问题s + printf("%d\n", num); + // memset(buf, 0, sizeof(buf) ); + // fgets(buf, sizeof(buf), fp); + // printf("buf = %d\n", buf[0]); + if( feof(fp) ) + break; + } + fclose(fp); + return 0; +} + +void test() +{ + //打开文件,写方式 + FILE *fp = fopen("4.txt", "r+"); + int num; + fscanf(fp, "%d\n", &num); + printf("num = %d\n", num); + + fscanf(fp, "%d\n", &num); + printf("num = %d\n", num); + + fscanf(fp, "%d\n", &num); + printf("num = %d\n", num); + + //读取失败,导致num保存上一次的值 --> 同之前的 + fscanf(fp, "%d\n", &num); + printf("num = %d\n", num); + + fclose(fp); +} +``` + +### 4.10、练习案例: 文件版排序(三种写法) +> 要求:实现向`08.txt`文件中写入`500`个随机数(`write_file()`函数),并通过`read_file()`函数来读取其中的数据,排序后又重新写入原文件。 + +① 实现方法一: 通过`fgets`和`fputs`(先写到`dest`数组,再一次性写入): + +```cpp +#include +#include +#include +#include +#include +using namespace std; +#define MAX 500 + +void bubble_sort(int *arr,int n){ + for(int end = n - 1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if(arr[i] > arr[i+1]) + swap(arr[i], arr[i+1]); + } + } +} + +void write_file(){ + FILE *fp = fopen("./08.txt", "w"); + srand( (unsigned int )time(NULL)); //设置随机种子 + char buf[100]; + for(int i = 0; i < MAX; i++){ + int num = rand() % 100; //产生1-100随机数 + sprintf(buf, "%d\n", num); + fputs(buf, fp); //往文件写内容 + } + fclose(fp); + fp = NULL; +} + +void read_file(){ + FILE *fp = fopen("./08.txt", "r");// ./代表的是当前路径 + + char buf[100]; + int tmp, num = 0; + int arr[500]; + while(true){ + fgets(buf, sizeof(buf), fp); + if(feof(fp)) + break; + sscanf(buf, "%d\n", &tmp); + arr[num++] = tmp; + } + + bubble_sort(arr, num); //排序 + + char dest[500]; + for(int i = 0; i < num; i++){ + sprintf(buf, "%d\n", arr[i]); + strcat(dest, buf); + } + + fclose(fp);fp = NULL; + fp = fopen("./08.txt", "w"); + + fputs(dest,fp);//将dest中的内容全部写入fp指向的文件 dest就是一个整体的字符串 + fclose(fp); fp = NULL; +} + +int main(int argc, char const *argv[]) +{ + // write_file(); + read_file(); + return 0; +} +``` +②实现方法二: 通过`fgets`和`fputs`(一行一行写入): + +```cpp +#include +#include +#include +#include +#include +using namespace std; +#define MAX 500 + +void bubble_sort(int *arr,int n){ + for(int end = n - 1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if(arr[i] > arr[i+1]) + swap(arr[i], arr[i+1]); + } + } +} + +void write_file(){ + FILE *fp = fopen("./08.txt", "w"); + srand( (unsigned int )time(NULL)); //设置随机种子 + char buf[100]; + for(int i = 0; i < MAX; i++){ + int num = rand() % 100; //产生1-100随机数 + sprintf(buf, "%d\n", num); + fputs(buf, fp); //往文件写内容 + } + fclose(fp); + fp = NULL; +} + +void read_file(){ + FILE *fp = fopen("./08.txt", "r");// ./代表的是当前路径 + + char buf[100]; + int tmp, num = 0; + int arr[500]; + while(true){ + fgets(buf, sizeof(buf), fp); + if(feof(fp)) + break; + sscanf(buf, "%d\n", &tmp); + arr[num++] = tmp; + } + + bubble_sort(arr, num); //排序 + + fp = fopen("./08.txt", "w"); + for(int i = 0; i < num; i++){ + sprintf(buf, "%d\n", arr[i]); + fputs(buf,fp); //一行一行的写 + } + + fclose(fp); + fp = NULL; +} + +int main(int argc, char const *argv[]) +{ + // write_file(); + read_file(); + return 0; +} +``` +③实现方法三: 通过`fprintf()`和`fscanf()`格式化写入(最简单) + +```cpp +#include +#include +#include +#include +#include +using namespace std; +#define MAX 500 + +void bubble_sort(int *arr,int n){ + for(int end = n - 1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if(arr[i] > arr[i+1]) + swap(arr[i], arr[i+1]); + } + } +} + +void write_file(){ + FILE *fp = fopen("./08.txt", "w"); + srand( (unsigned int )time(NULL)); //设置随机种子 + for(int i = 0; i < MAX; i++){ + int num = rand() % 100; //产生1-100随机数 + fprintf(fp,"%d\n",num); + } + fclose(fp); + fp = NULL; +} + +void read_file(){ + FILE *fp = fopen("./08.txt", "r");// ./代表的是当前路径 + + char buf[100]; + int tmp, num = 0; + int arr[500]; + while(true){ + fscanf(fp, "%d\n", &tmp); //注意这里和之前的fgets不同,不会产生最后的边界问题 + arr[num++] = tmp; + if(feof(fp)) + break; + } + + bubble_sort(arr, num); //排序 + + fp = fopen("./08.txt", "w"); + for(int i = 0; i < num; i++) + fprintf(fp, "%d\n", arr[i]); + + fclose(fp); + fp = NULL; +} + +int main(int argc, char const *argv[]) +{ + // write_file(); + read_file(); + return 0; +} +``` +### 4.11、按照块读写文件`fread`、`fwrite` + + `size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);` + * 功能:以数据块的方式给文件写入内容; +* 参数: + `ptr`:准备写入文件数据的地址; + `size`: `size_t `为 `unsigned int`类型,此参数指定写入文件内容的块数据大小; + `nmemb`:写入文件的块数,写入文件数据总大小为:`size * nmemb`; + `stream`:已经打开的文件指针; +* 返回值: + 成功:实际成功写入文件数据的块数目,此值和`nmemb`相等; + 失败:`0`; + +`size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);` +* 功能:以数据块的方式从文件中读取内容; +* 参数: + `ptr`:存放读取出来数据的内存空间; + `size`: `size_t` 为 `unsigned int`类型,此参数指定读取文件内容的块数据大小; + `nmemb`:读取文件的块数,读取文件数据总大小为:`size * nmemb`; + `stream`:已经打开的文件指针; +* 返回值: + 成功实际成功读取到内容的块数,如果此值比`nmemb`小,但大于0,说明读到文件的结尾; + 失败:`0`; + + +测试: +> * `write()`函数将`arr[4]`(结构体数组)写入到`12.txt`文件中; +> * `read()`函数将`12.txt`读出来; + +```cpp +#include + +typedef struct Student +{ + int age; + char name[50]; + int score; +}Student; + +void write(){ + FILE *fp = fopen("12.txt", "w"); + // Student s = {19, "zxzxin",100}; + Student arr[4] = { + {13, "zhangsan",33}, + {14, "lisi",44}, + {15, "wangwu", 55}, + {16, "zhaoliu", 66} + }; + // int ret1 = fwrite(&s, sizeof(Student), 1, fp); + int ret2 = fwrite(arr, 1, sizeof(arr), fp); //4 * 60 arr也可以写成 &arr[0] + // printf("ret1 = %d, ret2 = %d\n", ret1, ret2); + fclose(fp); +} + +void read(){ + FILE *fp = fopen("12.txt", "r"); + Student arr[5]; + + //1、如果文件内容大小 > 用户指定的读取大小,返回值为用户指定的块数目 + //int ret = fread(&arr[0], sizeof(Student), 2, fp); + + //2、如果文件内容大小 < 用户指定的读取大小,返回值为实际读取的块数目 + // 实际读取的块数目 < 用户指定的读取大小,也可能为0 + + //int ret = fread(&arr[0], sizeof(Student), 10, fp); + //用户指定要10 * sizeof(arr)的大小,文件只有0.4个sizeof(arr),返回值为0 + // int ret = fread(&arr[0], sizeof(arr), 10, fp); + + // 如果把块大小指定为1,返回值就是读取文件的总大小 (很稳妥的方式) + int ret = fread(&arr[0], 1, sizeof(arr), fp); + printf("ret = %d\n", ret); + for(int i = 0; i < 4; i++) + printf("%d, %s, %d\n", arr[i].age, arr[i].name, arr[i].score); + + printf("--------------循环读------------\n"); + fp = fopen("./12.txt", "r"); + Student arr2[10]; + int num = 0; + while(true){ + // ret = fread(&arr2[num], 1, sizeof(Student), fp); + ret = fread(arr2+num, 1, sizeof(Student), fp); + printf("ret = %d\n", ret); + if(ret == 0) //读取结束 + break; + num++; + } + printf("num = %d\n", num); + for(int i = 0; i < num; i++) + printf("%d, %s, %d\n", arr2[i].age, arr2[i].name, arr2[i].score); + fclose(fp); +} + +int main(int argc, char const *argv[]) +{ + // write(); + read(); + return 0; +} +``` +测试结果: + +![1563677002327](assets/1563677002327.png) + +### 4.12、案例: 使用`fwrite`和`fread`实现`cp`命令 +> 简单的实现了`linux`下的`cp`拷贝命令,具体细节没有考虑 +```cpp +#include + +int main(int argc, char const *argv[]) +{ + if(argc < 3){// 参数个数不对 cp 源文件 目标文件 + printf("error"); + return -1; + } + FILE *sFp = fopen(argv[1], "rb"); // b是为了程序在windows也能正常操作 + FILE *dFp = fopen(argv[2], "wb"); + char buf[4*1024]; + while(true){ + int len = fread(buf, 1, sizeof(buf), sFp); + // printf("len = %d\n", len); + if(len == 0) + break; + fwrite(buf, 1, len, dFp); + } + return 0; +} + +``` +演示结果: + +![1563677026375](assets/1563677026375.png) + +> 注意这里打开文件的时候是`rb`和`wb`,这里涉及到 Windows和Linux文本文件区别: +> * `b`是二进制模式的意思,`b`只是在`Windows`有效,在`Linux`用`r`和`rb`的结果是一样的; +> * `Unix`和`Linux`下所有的文本文件行都是`\n`结尾,而`Windows`所有的文本文件行都是`\r\n`结尾; +> * 在`Windows`平台下,以“文本”方式打开文件,不加`b`: +> > * 当读取文件的时候,系统会将所有的 `"\r\n"` 转换成 `"\n"`; +> > * 当写入文件的时候,系统会将` "\n" `转换成 `"\r\n"` 写入 ; +> > * 以"二进制"方式打开文件,则读和写都不会进行这样的转换; +> +> * 在`Unix/Linux`平台下,“文本”与“二进制”模式没有区别,`"\r\n" `作为两个字符原样输入输出 + +### 4.13、文件的随机读写`fseek`、`ftell`、`rewind` + +* `int fseek(FILE *stream, long offset, int whence);`: 移动文件流(文件光标)的读写位置。 + `stream`:已经打开的文件指针; + `offset`:根据`whence`来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于`whence`往右移动,如果是负数,则相对于`whence`往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸; + `whence`其取值如下: ①` SEEK_SET`:从文件开头移动`offset`个字节②`SEEK_CUR`:从当前位置移动`offset`个字节③`SEEK_END`:从文件末尾移动`offset`个字节;(注意: 开头不能往左移动,末尾可以往后移动) + +>例如: +>* `fseek(fp, 0, SEEK_SET);` 在开头偏移0个字节,回到开头 +>* `fseek(fp, 100, SEEK_SET);` 在开头向右偏移100个字节,回到开头 +>* `fseek(fp, 0, SEEK_CUR);` 在当前位置偏移0个字节 +>* `fseek(fp, 100, SEEK_CUR);` 在当前位置向右偏移100个字节 +>* `fseek(fp, -100, SEEK_CUR);` 在当前位置向左偏移100个字节 +>* `fseek(fp, 0, SEEK_END);` 在结尾位置偏移0个字节,移动到最后 + + +测试(文件用的还是上一个程序的`12.txt`文件): + +```cpp +#include + +typedef struct Student{ + int age; + char name[50]; + int score; +}Student; + +int main(int argc, char const *argv[]) +{ + FILE *fp = fopen("12.txt", "r"); + Student s; + + fseek(fp, 3*sizeof(Student), SEEK_SET);// 从开始便宜3个Student大小的位置 + + fread(&s, 1, sizeof(Student), fp); // 读的不是第一个,而是第四个 + printf("%d, %s, %d\n", s.age, s.name, s.score); + + rewind(fp); //回到开始 + + fread(&s, 1, sizeof(Student), fp); // 读的不是第一个,而是第四个 + printf("%d, %s, %d\n", s.age, s.name, s.score); + + // 获取文件大小 + // 先把光标移动到文件末尾 + fseek(fp, 0, SEEK_END); + printf("file size = %lld\n", ftell(fp)); + + fclose(fp); + return 0; +} +``` +效果: + +![1563677038950](assets/1563677038950.png) + +### 4.14、文件缓冲区 +>ANSI C标准采用“缓冲文件系统”处理数据文件。所谓缓冲文件系统是指: +> * 系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。 +>* 如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量) 。 + +缓冲区的四个状态: + +* 默认情况,程序没有结束,也没有关闭文件缓冲区满,自动刷新缓冲区; +* 文件正常关闭,缓冲区的内容也会写入文件; +* 文件不关闭,程序没有结束,实时刷新,调用`fflush`。函数`int fflush(FILE *stream);`:更新缓冲区,让缓冲区的数据立马写到文件中; +* 程序正常关闭,缓冲区的内容也会写入文件; + +磁盘文件的存取: + +![1563677052177](assets/1563677052177.png) + +* 磁盘文件,一般保存在硬盘、U盘等掉电不丢失的磁盘设备中,在需要时调入内存; +* 在内存中对文件进行编辑处理后,保存到磁盘中; +* 程序与磁盘之间交互,不是立即完成,系统或程序可根据需要设置缓冲区,以提高存取效率; + + + diff --git a/CPlus/C/C_4.md b/CPlus/C/C_4.md new file mode 100644 index 00000000..6e12002d --- /dev/null +++ b/CPlus/C/C_4.md @@ -0,0 +1,1586 @@ +# C语言知识总结(四)一内存、指针、字符串(提高) + +* 1、内存四区 +* 2、指针 +* 3、函数指针 +* 4、字符串 +* 5、二级指针 +* 6、多维数组 +* 7、结构体 +*** +## 1、内存四区 +### 1.1、数据类型概念 +* "类型"是对数据的抽象; +* 类型相同的数据有相同的表示形式、存储格式以及相关的操作 ; +* 程序中使用的所有数据都必定属于某一种数据类型 ; + +### 1.2、数据类型本质 +* 数据类型可理解为创建变量的模具:是固定内存大小的别名; +* 数据类型的作用:编译器预算对象(变量)分配的内存空间大小; +* 注意:数据类型只是模具,编译器并没有分配空间,只有根据类型(模具)创建变量(实物),编译器才会分配空间。 +* `sizeof`是操作符,不是函数;`sizeof`测量的实体大小为编译期间就已确定。 + +```cpp +#include + +int main(int argc, char const **argv) +{ + int a; //告诉编译器,分配 4个字节 + int arr[10]; //告诉编译器,分配 4 * 10 个字节 + //类型的本质:固定内存块大小别名 ,可以通过 sizeof()测试 + printf("sizeof(a) = %d, sizeof(arr) = %d\n", sizeof(a), sizeof(arr)); + + //数组名字,数组首元素地址,数组首地址 + printf("arr:%d, &arr:%d\n", arr, &arr); //是一样的 + + //arr, &arr的数组类型不一样 + //arr, 数组首元素地址, 一个元素4字节,+1, +4 + //&arr, 整个数组的首地址,一个数组4*10 = 40字节,+1, +40 + printf("arr+1:%d, &arr+1:%d\n", arr+1, &arr+1); //这两个不同 + + //指针类型长度,32位程序, 长度4 + // 64位程序, 长度8 + char ***********************p = NULL; + int *q = NULL; + printf("%d, %d\n", sizeof(p), sizeof(q)); + return 0; +} +``` +输出结果: + +![1563678465719](assets/1563678465719.png) + +### 1.3、程序内存四区模型 +* 操作系统把物理硬盘代码load到内存; +* 操作系统把`C`语言代码分成四个区; + +![1563678484021](assets/1563678484021.png) + +各个区的功能: + +![1563678501109](assets/1563678501109.png) + +**注意全局区包括: 全局变量、静态变量、文字常量区**; + +①测试全局文字常量区: + +```cpp +#include + +char *get_str1(){ + char *p = "abc"; + return p; +} + +char *get_str2(){ + char *p = "abc"; //指针指向文字常量区,所以和上面的结果是一样的 + return p; +} + +int main(int argc, char const *argv[]) +{ + char *p1 = NULL; + char *p2 = NULL; + + p1 = get_str1(); + p2 = get_str2(); + printf("p1 = %p, p2 = %p\n", p1, p2); + return 0; +} +``` +输出: +```cpp +p1 = 0x55806a717774, p2 = 0x55806a717774 +``` +分析: (字符串常量放在文字常量区 ) + +![1563678609815](assets/1563678609815.png) + + +② 栈区测试: +```cpp +#include +#include + +char *get_str(){ + char str[] = "abcd"; + printf("str = %s\n", str); + return str; +} + +int main(int argc, char const *argv[]) +{ + char buf[128] = {0}; + + // strcpy(buf, get_str()); + // printf("buf = %s\n", buf);//乱码,不确定 + + char *p = get_str(); + // 乱码 不确定 因为get_str()函数中 str数组在栈区中的空间已经释放了 + printf("p = %s\n", p); + return 0; +} +``` +输出: + +```cpp +str = abcd +p = (null) +``` + +内存分析: + +![1563678668687](assets/1563678668687.png) + +③堆区: + +```cpp +#include +#include +#include + +char *get_str(){ + char *str = (char*)malloc(100 * sizeof(char));//分配100个字符的空间 + if(str == NULL){ + return NULL; + } + strcpy(str, "abcd"); + return str; +} + +int main(int argc, char const *argv[]) +{ + char *p = get_str(); + if(p != NULL){ + printf("p = %s\n", p); + + free(p); + p = NULL; + } + return 0; +} +``` +输出: + +```cpp +p = abcd +``` + +![1563678683569](assets/1563678683569.png) +> 关于 `free()`函数: +> * `free(p)`意义: 告诉系统,`p`原来指向的内存可以被别人使用了; +> * 释放完指针p `free(p)`,此时`p`的地址还在,没有变,只是此时的`p`相当于一个野指针,所以最好是重新赋值为`NULL`; + +**总结:** + +* 在堆和全局区分配的: 其他函数都能用; +* 在栈上分配的内存,子函数执行完成,父函数不能使用; + +### 1.4、函数的调用模型 + +![1563678722598](assets/1563678722598.png) + +### 1.5、栈的生长方向和内存存放方向 + +代码验证: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + int a; + int b; + printf("&a = %d, &b = %d\n", &a, &b); + + char buf[100]; + printf("buf = %d, buf+1 = %d\n", buf, buf+1); + return 0; +} +``` +运行结果: + +![1563678738192](assets/1563678738192.png) + +分析: + +![1563678749399](assets/1563678749399.png) + +*** +## 2、指针 + +### 2.1、指针是一种数据类型 +* 指针是一种数据类型,是指它指向的内存空间的数据类型 ; +* 在指针声明时,`*` 号表示所声明的变量为指针; +* 在指针使用时,`*` 号表示操作指针所指向的内存空间中的值。①`*p`相当于通过地址(`p`变量的值)找到一块内存,然后操作内存;② `*p`放在等号的左边赋值(给内存赋值,写内存);③ `*p`放在等号的右边取值(从内存获取值,读内存); +* 指针变量和它指向的内存块是两个不同的概念,各自的值没有任何关系; + +* 当我们不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系)。指针指向谁,就把谁的地址赋值给指针; +* 不允许向NULL和未知非法地址拷贝内存(野指针和空指针); +* 间接赋值是指针存在的最大意义; + +注意 ,写内存时,一定要确保内存可写: + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + char *p = "abcd"; + // *(p+1) = 'e'; //发生段错误 常量区不可以修改 + + char p2[] = "abcd"; //这个是从常量区拷贝到了栈区,所以可以修改 + p2[1] = 'e'; + return 0; +} +``` +分析: + +![1563678768829](assets/1563678768829.png) + +### 2.2、函数传递(值传递和地址传递) + +```cpp +// .c 文件下 +#include + +int fun1(int *p){ + p = 0x200; +} + +int fun2(int **p){ //指向指针的指针变量 + *p = 0x200; //通过* 来操作指针变量指向的内存 +} + +int main(int argc, char const *argv[]) +{ + int *p = 0x100; + + fun1(p); //值传递 + printf("p1 = %p\n", p);//100 + + fun2(&p); //地址传递 + printf("p2 = %p\n", p);//200 + return 0; +} +``` +输出: + +![1563678781957](assets/1563678781957.png) + +总结: + +* 还是和之前的一样,只不过这里的变量是指针变量,指针是指向指向指针的指针而已; +* 地址传递前面一定要记得加上`&`,然后函数中通过`*`来操作指针指向的内存; + +> 注意: +> * 地址传递的思想: 函数调用时,形参传给实参,用实参取地址,传给形参,在被调用函数里面用`*p`,来改变实参,把运算结果传出来; +> * 主调函数、被调函数 : +① 主调函数可把堆区、栈区、全局数据内存地址传给被调用函数; +② 被调用函数只能返回堆区、全局数据; +> * 间接赋值的推论: +> > * 用`1`级指针形参,去间接修改了`0`级指针(实参)的值。 +> > * 用`2`级指针形参,去间接修改了`1`级指针(实参)的值。 +> > * 用`3`级指针形参,去间接修改了`2`级指针(实参)的值。 +> > * 用`n`级指针形参,去间接修改了`n-1`级指针(实参)的值。 + + +### 2.3、指针做参数输入输出特性(重要) + +* 输入: 在主调函数中分配内存; +* 输出: 在被调函数中分配内存; + + +区别下面两种赋值(还是值传递和地址传递问题): + +代码① +```cpp +#include +#include +#include + +void fun(char *p){ + p = (char*)malloc(sizeof(10)); + if(NULL == p) + return; + strcpy(p, "abcd"); +} + +int main(int argc, char const **argv) +{ + char *p = NULL; + fun(p); + printf("p = %s\n", p); + return 0; +} +``` +输出: + +```cpp +p = (null) +``` + +代码② +```cpp +#include +#include +#include + +void fun(char **p, int *len){ + if(NULL == p) + return; + char *temp = (char*)malloc(sizeof(10)); + if(NULL == temp) + return; + strcpy(temp, "abcd"); + + //间接赋值 + *p = temp; + *len = strlen(temp); +} + +int main(int argc, char const **argv) +{ + char *p = NULL; + int len = 0; + fun(&p, &len); + printf("p = %s, len = %d\n", p, len); + return 0; +} +``` +输出: +```cpp +p = abcd, len = 4 +``` +看第二种的分析: +* 第一步,先给`temp`在堆区分配空间,此时`temp`指针变量的值是`oxaabb`; +* 第二步,将`"abcd"`拷贝到`temp`指向的堆区空间; +* 第三步,将`temp`的值(地址的值)赋值给`p`指向的内存,而`p`指向的内存就是`main`函数中的`p`;此时`main`函数中的`p` = `0xaabb`,所以`main`函数中的`p`也指向了堆区空间,所以得到上面的打印结果; + +![1563678806538](assets/1563678806538.png) + +*** +## 3、函数指针 + +### 3.1、简单应用 +* 一个函数在编译时分配给一个入口地址,这个函数的入口地址就成为函数的指针。 可以用一个指针变量指向函数,然后通过该指针变量调用此函数; + +看一个简单的使用: + +```cpp +#include + +int max(int a, int b){ + return a> b ? a : b; +} + +int main(int argc, char const **argv) +{ + int a,b; + //定义一个p是一个指向函数的指针变量,该函数有两个整形参数 + int (*p)(int, int); + //指向max函数 --> 将max()的入口地址赋给指针变量p + //和数组名代表数组首元素地址类似,函数名代表改函数的入口地址 + p = max; + scanf("%d %d", &a, &b); + int c = (*p)(a, b);//调用p指向的函数 等价于 c = max(a,b) + printf("max(%d, %d) = %d\n", a, b, c); + return 0; +} +``` +程序很简单,就是输入`a`、`b`,求`a`、`b`的最大值。 +分析: +* 类似数组首地址,此时`p`和`max`都是函数的开头。调用`*p`就是调用`max`函数; +* 注意指向函数的指针,只能指向函数的入口处,而不能指向中间的某一条指令处,因此不能用`*(p+1)`来表示函数的下一个指令,向`p+n`、`p++`、`p--`等运算是无意义的; + +### 3.2、用指向函数的指针做函数参数 +使用模型: + +```cpp +void sub(int (*p1)(int), int (*p2)(int, int)){ //形参是两个指向函数的指针 + int res1,res2,a,b; + res1 = (*p1)(a); //调用p1指向的函数 (传递参数a) + res2 = (*p2)(a, b); //调用p2指向的函数 (传递参数a,b) +} +``` +使用的意义: +* 每次调用`sub`函数时,要调用的函数不是固定的,这次调用的是`f1`、`f2`,下次调用的是`f3`、`f4`,这时,使用指针变量就比较方便; +* 只要每次在调用`sub`函数时给出不同的函数名作为实参即可,`sub`函数不必做任何修改; + +使用举例: + +```cpp +#include + +int max(int a, int b){ + return a > b ? a : b; +} + +int min(int a, int b){ + return a < b ? a : b; +} + +int sum(int a, int b){ + return a + b; +} + +int fun(int a, int b, int (*p)(int, int)){ + return (*p)(a, b); +} + +int main(int argc, char const **argv) +{ + int a = 10, b = 20; + printf("max(%d, %d) = %d\n", a, b, fun(a,b,max)); + printf("min(%d, %d) = %d\n", a, b, fun(a,b,min)); + printf("sum(%d, %d) = %d\n", a, b, fun(a,b,sum)); + return 0; +} +``` +输出: + +```cpp +max(10, 20) = 20 +min(10, 20) = 10 +sum(10, 20) = 30 +``` +上面的程序也可以看到函数指针作为形参的作用。 +*** +## 4、字符串 + +### 4.1、通过数组法和指针法访问字符串 + +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + char buf[] = "abc"; + int n = strlen(buf); //编译器转换了类型 + char *p = buf; + + for (int i = 0; i < n; i++){ + printf("%c", buf[i]); + printf("%c", p[i]); // []方式 + printf("%c", *(p+i) ); + printf("%c", *(buf+i) ); + } + printf("\n"); + + //buf和p完全等价吗? + // p++; // ok + // buf++; // err --> buf只是一个常量,不能修改 + return 0; +} +``` +输出: +```cpp +aaaabbbbcccc +``` +> 注意`buf`和`p`不完全等价,因为`buf`是一个常量,系统要释放空间的时候使用。 + +### 4.2、字符串拷贝函数的多种写法 + +```cpp +#include + +void copy1(char *src, char *dest){ + int i; + for( i = 0; *(src + i) != '\0'; i++) + *(dest + i) = *(src + i); + *(dest + i) = 0; // '\0' +} + +void copy2(char *src, char *dest){ + while(*src != 0){ + *dest = *src; + dest++; + src++; + } + *dest = 0; +} + +void copy3(char *src, char *dest){ + while(*dest++ = *src++); //最后会自动有'\0' +} + +// 完整版 : 最好不要直接使用形参 +void copy4(char *src, char *dest){ + if(src == NULL || dest == NULL) + return ; + char *to = dest; + char *from = src; + // while(*dest++ = *src++); + while(*to++ = *from++); //注意不要写成 (*dest)++ = (*src)++ + // printf("dest = %s\n", dest); //如果是 直接操作形参,就改变了指针 +} + +int main(int argc, char const *argv[]) +{ + char src[] = "abcd"; + char dest[10]; + // copy1(src, dest); + // copy2(src, dest); + // copy3(src, dest); + copy4(src, dest); + + printf("dest = %s\n", dest);// abcd + return 0; +} +``` +注意两个问题 + +* `copy3`中的`++`运算符操作的是地址的加减法,而不是内存的值; +* 最好不要修改形参,可以使用变量存储; + +### 4.3、C语言的`const`是一个冒牌货 + +```cpp +// .c文件 +#include + +int main(int argc, char const *argv[]) +{ + // 在C语言中, const一个冒牌货 + const int b = 10; + // b = 20; //err + int *p = &b; + *p = 20; //ok + printf("b = %d, *p = %d\n", b, *p); + return 0; +} +``` +输出: + +![1563678825779](assets/1563678825779.png) + + + +> * 所以可以看到还是可以通过指针来修改`const`修饰的变量的值; +> * 还有一点 : 在另一.c源文件中引用const常量时,只能声明,不能改变; + +*** +## 5、二级指针 + +### 5.1、基本概念 + +* 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”。 +* 通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“一级指针”。而如果通过指向指针的指针变量来访问变量则构成“二级指针”。 + + +![1563678837574](assets/1563678837574.png) + +### 5.2、二级指针第一种输入模型(指针数组) + +* 当做形参使用的时候, 就是指针数组,和二级指针等价; +* 但是注意不能在初始化字符数组的时候使用,`char **p = {"cba", "abd", "abc", "bac", "bab"};` +```cpp +#include +#include + +// void bubble_sort(char *p[], int n){ +void bubble_sort(char **p, int n){ + for(int end = n - 1; end > 0; end--){ + for(int i = 0; i < end; i++){ + // if(strcmp(p[i], p[i+1]) > 0){ + if(strcmp(*(p+i), *(p+(i+1))) > 0){ + char *temp = p[i]; + p[i] = p[i+1]; + p[i+1] = temp; + } + } + } +} + +// void print_array(char *p[], int n){ +void print_array(char **p, int n){ + for(int i = 0; i < n; i++){ + // printf("%s ", p[i]); + printf("%s ", *(p+i)); + } + printf("\n"); +} + + +int main(int argc, char const *argv[]) +{ + char *p[] = {"cba", "abd", "abc", "bac", "bab"}; + // char **p = {"cba", "abd", "abc", "bac", "bab"}; // err 注意这里不能用 + int n = sizeof(p)/sizeof(*p); + print_array(p, n); + bubble_sort(p, n); + print_array(p, n); + return 0; +} + +``` +输出: + +```cpp +-----------排序前------------ +cba abd abc bac bab +-----------排序后------------ +abc abd bab bac cba +``` + +### 5.3、二级指针第二种输入模型(二维数组) + +```cpp +#include + +int main(int argc, char const *argv[]) +{ + //定义二维数组,不写第一个[]值有条件, 必须要初始化 + //a代表首行地址,首行地址和首行首元素地址有区别,但是他们的值是一样 + //区别:步长不一样 + char a[][30] = { "22222222222", "11111111111", "bbbbbbbbbbb", "aaaaaaaaaaaa" }; + printf("a:%d, a+1:%d\n", a, a+1); // a+1 比 a 大30 所以a代表的是首行的地址, 而不是整个数组的地址 + printf("a[0]: %d, a[0]+1: %d\n", a[0], a[0]+1); // a[0]+1 比 a[0] 大 1 所以a[0]是第0行的首元素地址 + printf("a[0]: %d, a[1]: %d\n", a[0], a[1]); + + char b[30]; + printf("&b:%d, &b+1:%d\n", &b, &b+1); // &b+1 比 &b 大30 说明&b是整个数组的地址 + printf("b:%d, b+1:%d\n", b, b+1); // b+1 比 b 大 1 说明b是首元素的地址 + + int n = sizeof(a) / sizeof(a[0]); // 30*4/30 + printf("n = %d\n", n); + + for (int i = 0; i < 4; i++){ + printf("%s\n",a[i]); + // printf("%s\n",*(a+i)); + //首行地址,和首行首元素地址的值是一样 ,所以下面的打印也可以,但是正常是上面的打印 + // printf("%s\n",a+i); + } + return 0; +} +``` +输出: (从输出可以看到二维数组各个地址的特性) +```cpp +a:-375886720, a+1:-375886690 +a[0]: -375886720, a[0]+1: -375886719 +a[0]: -375886720, a[1]: -375886690 +&b:-375886752, &b+1:-375886722 +b:-375886752, b+1:-375886751 +n = 4 +22222222222 +11111111111 +bbbbbbbbbbb +aaaaaaaaaaaa +``` +二维数组`a`(`char a[4][30]`): +> * 二维数组的数组名代表首行地址(第一行一维数组的地址); +> * 首行地址和首行首元素地址的值是一样的,但是它们步长不一样; +> * 首行地址`+1`,跳过一行,一行`30`个字节,`+30`; +> * 首行首元素地址`+1`,跳过一个字符,一个字符为`1`个字节,`+1` +> * `sizeof(a)`: 有`4`个一维数组,每个数组长度为`30`,`4 * 30 = 120`; +> * `sizeof(a[0])`: 第`0`个一维数组首元素地址,相当于测第`0`个一维数组的长度:为`30`; + +一维数组`b`(`char b[30]`): +> * `&b`代表整个一维数组的地址,相当于二维数组首行地址; +> * `b`代表一维数组首元素地址,相当于二维数组首行首元素地址; +> * `&b` 和 `b` 的值虽然是一样,但是,它们的步长不一样; +> * `&b + 1`: 跳过整个数组,`+30`; +> * `b+1`: 跳过`1`个字符,`+1`; + +**案例: 二维字符数组排序** + +```cpp +#include +#include + +void print_array(char a[][30], int n){ + //printf("a: %d, a+1:%d\n", a, a + 1); + for (int i = 0; i < n; i++) + printf("%s, ", a[i]); //首行地址,和首行首元素地址的值是一样 + printf("\n"); +} + +// 二维数组: 蠢办法: 暂时按照原来的样子写 +void sort_array(char a[][30], int n){ + char tmp[30]; + for(int end = n - 1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if (strcmp(a[i], a[i+1])> 0){ + //交换内存块 这里要注意 + strcpy(tmp, a[i]); + strcpy(a[i], a[i+1]); + strcpy(a[i+1], tmp); + } + } + } +} + +int main(int argc, char const **argv) +{ + char a[][30] = {"cba", "abd", "abc", "bac", "bab"}; + int n = sizeof(a) / sizeof(a[0]); + printf("before sort:\n"); + print_array(a, n); + + sort_array(a, n); + + printf("after sort:\n"); + print_array(a, n); + return 0; +} +``` +输出: +``` +before sort: +cba, abd, abc, bac, bab, +after sort: +abc, abd, bab, bac, cba, +``` +总结: +* 二维数组传参的时候,直接和实参类型写成一样是可以的,但是也可以用数组指针来写,后面看。 + +### 5.4、二级指针第三种输入模型(二级指针分配空间) + + +```cpp +#include +#include +#include + +char **getMem(int n){ + char **buf = (char **)malloc(n * sizeof(char *)); + + for(int i = 0; i < n; i++){ + buf[i] = (char *)malloc(sizeof(char) * 10); //为里面的里面每一个char *分配空间 + char temp[10]; + sprintf(temp, "%d-temp-%d", i, i); + strcpy(buf[i], temp); + } + return buf; +} + +void print_buf(char **buf, int n){ + for(int i = 0 ; i < n; i++) + printf("%s ", buf[i]); + printf("\n"); +} + +void free_buf(char **buf, int n){ + for(int i = 0; i < n; i++){ + free(buf[i]); + buf[i] = NULL; + } + if(buf != NULL){ + free(buf); + // buf = NULL; 这个没有实际的作用,因为是值传递s + } +} + +void free_buf2(char ***tmp, int n){ + char **buf = *tmp; //定义一个二级指针指向*tmp --> 操作*tmp + for(int i = 0; i < n; i++){ + free(buf[i]); + buf[i] = NULL; + } + if(buf != NULL){ + free(buf); + buf = NULL; + } + *tmp = NULL; +} + +int main(int argc, char const *argv[]) +{ + char **buf = NULL; + int n = 3; + buf = getMem(n); + print_buf(buf,n); + + //因为free_buf是值传递,所以自己赋值为NULL,如果要在函数中赋值,就要使用三级指针 + // free_buf(buf, n); + // buf = NULL; + + free_buf2(&buf, n); + if(buf == NULL){ // 不能执行 + free(buf); + buf = NULL; + } + return 0; +} +``` +输出: +```cpp +0-temp-0 1-temp-1 2-temp-2 +``` +关于`getMem`函数的内存分析: + +![1563678854129](assets/1563678854129.png) + +### 5.5、总结三种输入模型的内存分配 +```cpp +#include +#include + +int main(int argc, char const *argv[]) +{ + // 1. 指针数组 + char *p1[] = {"123", "456", "789"}; + + // 2. 二维数组 + char p2[3][4] = {"123", "456", "789"}; + + // 3. 手工二维内存 + char **p3 = (char **)malloc(3 * sizeof(char *)); + int num = 0; + for(int i = 0; i < 3; i++){ + //里面的每一个一维分配 4*sizeof(char) + p3[i] = (char *)malloc(4 * sizeof(char)); + sprintf(p3[i], "%d%d%d", num+1, num+2, num+3); + num += 3; + } + + //打印测试 + for(int i = 0; i < 3; i++) + printf("%s %s %s\n", p1[i], p2[i], p3[i]); + return 0; +} +``` +输出: + +```c +123 123 123 +456 456 456 +789 789 789 +``` +总结: + +![1563678870202](assets/1563678870202.png) + + +### 5.6、案例一:替换字符串 +```cpp +#include +#include +#include + +void replaceStr(char *src/*in*/, char **dst/*out*/, char *sub/*in*/, char *newsub/*in*/){ + if(src == NULL || dst == NULL || sub == NULL || newsub == NULL) + return ; + char *start = src; + char *p = NULL; + char temp[512]; //存储中间值 + + while(*start != '\0'){ + p = strstr(start, sub); + if(p == NULL){ + strcat(temp, p); //记得把最后一段加进去 + break; + } + int len = p - start; + if(len > 0) + strncat(temp, start, len); //拷贝原来的 + strncat(temp, newsub, strlen(newsub));//拷贝替换的 + start = p + strlen(sub); + } + char *buf = (char *)malloc(strlen(temp) + 1); + strcpy(buf, temp); + *dst = buf;// 间接赋值是指针存在的最大意义 +} + +void free_buf(char **buf){ + char *temp = *buf; + if(NULL != temp) + free(temp); + *buf = NULL; +} + +int main(int argc, char const *argv[]) +{ + char *p = "11111111aaaaa2222222aaaaa333333aaaaa";//将字符串中的aaaaa替换成了bbbbb + char *buf = NULL; //在被调函数中分配空间 + replaceStr(p, &buf, "aaaaa", "bbbbb"); + printf("buf = %s\n", buf); + free_buf(&buf); + return 0; +} +``` +输出:  + +``` +buf = 11111111bbbbb2222222bbbbb333333bbbbb +``` +### 5.7、案例二: 分割字符串 + +```cpp +#include +#include +#include + +//void splitStr(const char *src, char c, char buf[10][30], int *count){ +void splitStr(const char *src, char c, char **buf, int *count){ + if(src == NULL) + return; + const char *start = src; + char *p = NULL; + int i = 0; + while(*start != '\0'){ + p = strchr(start, c);// strstr是找字符串 + if(p != NULL){ + int len = p - start; + strncpy(buf[i], start, len); + buf[i][len] = 0; //结束符 + i++; + start = p + 1; + }else{ + strcpy(buf[i],start); + //buf[i][strlen(p)] = 0; + i++; + break; + } + } + *count = i; +} + +char **getMem(int n){ + char **buf = (char **)malloc(n * sizeof(char *)); + for(int i = 0; i < n; i++){ + buf[i] = (char *)malloc(sizeof(char) * 20); + memset(buf[i], 0, 20); + } + return buf; +} + +void getMem2(char ***temp, int n){ + char **buf = (char **)malloc(n * sizeof(char *)); + for(int i = 0; i < n; i++){ + buf[i] = (char *)malloc(sizeof(char) * 20); + memset(buf[i], 0, 20); + } + *temp = buf;//间接赋值是指针存在的最大意义 +} + +void free_buf(char ***temp, int n){ + char **buf = *temp; + for (int i = 0; i < n; i++){ + free(buf[i]); + buf[i] = NULL; + } + if (buf != NULL){ + free(buf); + buf = NULL; + } + *temp = NULL; +} + +int main(int argc,char const **argv) +{ + const char *src = "aaa,bbb,abcd,dcba,ccc"; + char **buf = NULL; + //buf = getMem(10); + getMem2(&buf, 10); + int n = 0; + splitStr(src, ',', buf, &n); + printf("n = %d\n", n); + + + for(int i = 0; i < n; i++) + printf("%s ", buf[i]); + free_buf(&buf, n); + printf("\n"); + return 0; +} +``` +输出: + +```cpp +n = 5 +aaa bbb abcd dcba ccc +``` +*** +## 6、多维数组 + +### 6.1、数组名 +* 数组首元素的地址(数组名`arr`)和数组地址(`&arr`)是两个不同的概念; +* 数组名代表数组首元素的地址,它是个常量。 变量本质是内存空间的别名,一定义数组,就分配内存,内存就固定了。所以数组名起名以后就不能被修改了。 +* 数组的类型由元素类型和数组大小共同决定,例如:`int arr[5]` 的类型为 ` int[5]`; + +> * 有`typedef`就是类型,没有就是变量。 + +定义数组指针的三种方法: + +* 先定义数组类型,根据数据类型定义指针变量; +* 直接定义**指针类型**; +* 直接定义**指针变量**(没有`typedef`); + +测试: + +```cpp +#include + +int main(int argc, char const **argv) +{ + //数组指针,它是指针,指向一个数组的指针(指向整体) + //定义一个数组指针变量 + + // typedef定义的是数组类型,不是变量 + typedef int(A)[10]; + //typedef int A[10]; 和上面的写法一样 + A a = { 0 }; //等同于 int a[10] + printf("len(a) = %d\n", sizeof(a)/sizeof(*a)); + + // 1. 先定义数组类型,根据数据类型定义指针变量 + A *p = NULL; //p是数组指针 + p = &a; //注意不能写成p = a(a是首元素地址) + printf("p: %d, p+1: %d\n", p, p+1);//跨10*4个字节 + + //test 通过数组指针访问数组 + for(int i = 0; i < 10; i++) + (*p)[i] = i*10; + for(int i = 0; i < 10; i++) + printf("%d ", (*p)[i]); + printf("\n"); + + // 2. 直接定义指针类型 + typedef int(*P)[10];//数组指针类型 + P p2; + p2 = &a; // ok + + // 3. 直接定义指针变量(没有typedef) + int (*p3)[10]; + p3 = &a; //ok + return 0; +} +``` +输出:(`p+1`比`p`大40) +```cpp +len(a) = 10 +p: 1102712240, p+1: 1102712280 +0 10 20 30 40 50 60 70 80 90 +``` + +### 6.2、二维数组的本质也是一维数组 + +```cpp +#include + +void print_arr(int *arr, int n){ //注意传入的是int *类型的指针 + for(int i = 0; i < n; i++) + printf("%d ", arr[i]); + printf("\n"); +} + +int main(int argc, char const **argv) +{ + int arr[][4] = { + 1,2,3,4, + 5,6,7,8, + 9,10,11,12 + }; + //强制将int (*)[4](数组指针)类型转换成int*类型 + print_arr((int *)arr,sizeof(arr)/sizeof(arr[0][0])); + return 0; +} +``` +输出:  +```cpp +1 2 3 4 5 6 7 8 9 10 11 12 +``` +### 6.3、二维数组一些值的等价(重要) +重点是下面的一些值的等价: + +* 数组地址: `&arr`; +* `0`行地址 : `arr`,`&arr[0]`; +* `1`行地址 : `arr+1`,`&arr[1]`; +* `arr[0][0]`地址: `arr[0]`, `*(arr+0)`, `*arr`, `&arr[0][0]`; +* `arr[1][0]`地址: `arr[1]`, `*(arr+1)`, `&arr[1][0]`; +* `arr[0][1]`地址: `arr[0]+1`, `*(arr+0)+1`, `&arr[0][1]`; +* `arr[1][2]`值: `arr[1][2]`, `*(arr[1]+2)`, `*(*(arr+1)+2)`, `*(&arr[1][2])`; +```cpp +#include + +int main(int argc, char const **argv) +{ + int arr[][4] = { + 1,2,3,4, + 5,6,7,8, + 9,10,11,12 + }; + //1.二维数组名代表第0行的首地址(区别第0行首元素的地址,虽然值一样,步长不一样) + printf("%d, %d\n",arr,arr+1); // 跳过了 4*4 + printf("%d, %d\n",&arr[0],&arr[1]); + + printf("-----------------------------------------\n"); + + //2.第0行首元素地址、第0行第二个元素地址,相差 4*1 步长 + printf("%d, %d\n",*(arr+0),*(arr+0)+1); + printf("%d, %d\n",arr[0], arr[0]+1); + printf("%d, %d\n",*arr, &arr[0][1]); + + printf("-----------------------------------------\n"); + + //3.以下每一行等价 + printf("%d\n",&arr); //整个数组地址 + printf("%d, %d\n", arr, &arr[0]); //都是0行首地址 + printf("%d, %d\n", arr+1, &arr[1]); //都是第一行首地址 + printf("%d, %d, %d, %d\n", arr[0], *(arr+0), *arr, &arr[0][0]); //都是arr[0][0]的地址 //虽然这些值和0行首地址相等 + printf("%d, %d, %d\n", arr[1], *(arr+1), &arr[1][0]); //都是arr[1][0]的地址 + printf("%d, %d, %d\n", arr[0]+1, *(arr+0)+1, &arr[0][1]); //都是arr[0][1]的地址 + printf("%d, %d, %d, %d\n", arr[1][2], *(arr[1]+2), *(*(arr+1)+2), *(&arr[1][2])); //都是arr[1][2]的值 + + printf("-----------------------------------------\n"); + + //4.遍历一下数组 + int row = sizeof(arr)/sizeof(arr[0]); + int col = sizeof(arr[0])/sizeof(arr[0][0]); + for(int i = 0; i < row; i++){ + for(int j = 0; j < col; j++){ + //printf("%d ", *(arr[i]+j)); + printf("%d ", *(*(arr+i)+j)); + //printf("%d ", arr[i][j]); + } + printf("\n"); + } + return 0; +} +``` +输出结果: + +![1563678894466](assets/1563678894466.png) + + +### 6.4、二维数组形参(数组指针做形参) +先看一个数组指针来指向二维数组中的`"某一维数组"`: +```cpp +#include + +int main(int argc, char const ** argv) +{ + int arr[][4] = { + 1,2,3,4, + 5,6,7,8, + 9,10,11,12 + }; + + int (*p)[4]; //定义指针数组 + //指向二维数组的 "第一个数组" arr[0] + p = arr; //a本来就是第0个一维数组的地址 所以无需加& + + for(int i = 0; i < 3; i++){ + for(int j = 0; j < 4; j++){ + //printf("%d ",p[i][j]); + //printf("%d ", *(*(p+i)+j)); + printf("%d ", *(p[i]+j)); + } + printf("\n"); + } + return 0; +} + +``` +输出: +```cpp +1 2 3 4 +5 6 7 8 +9 10 11 12 +``` +> 注意 +> * `int (*p)[4]`指向`int a1[4]`的时候加`&`,即`p = &a1`; +> * `int (*p)[4]`指向`int a2[3][4]`的时候不加`&`,即`p = a2`。因为`a2`本来就是第`0`个一维数组的地址所以无需加`&`; +> * 因为这个是数组指针,即指向一维数组的指针; + +所以函数中的二维数组本质上就是一个数组指针: (和一维数组的`int *arr`类比) +```cpp +#include + +// 指针数组 以下三种写法是一样的 +//void print_arr(int arr[3][4], int n, int m){ +//void print_arr(int arr[][4], int n, int m){ +void print_arr(int (*arr)[4], int n, int m){ + for(int i = 0; i < n; i++){ + for(int j = 0; j < m; j++){ + printf("%d ", *(arr[i]+j)); + } + printf("\n"); + } +} + +int main(int argc, char const **argv) +{ + int arr[][4] = { + 1,2,3,4, + 5,6,7,8, + 9,10,11,12 + }; + int n = sizeof(arr)/sizeof(arr[0]); + int m = sizeof(arr[0])/sizeof(arr[0][0]); + print_arr(arr, n, m); + + printf("----------------------------\n"); + + //测试sizeof() + //一维数组 + int a[10]; + printf("%d\n", sizeof(a)); + printf("%d, %d\n", sizeof(a[0]),sizeof(*a)); + + printf("----------------------------\n"); + + //二维数组 + printf("%d\n", sizeof(arr)); + printf("%d, %d\n", sizeof(arr[0]), sizeof(*arr)); + printf("%d\n", sizeof(arr[0][0])); + return 0; +} +``` +输出: +```cpp +1 2 3 4 +5 6 7 8 +9 10 11 12 +---------------------------- +40 +4, 4 +---------------------------- +48 +16, 16 +4 +``` + +### 6.5、案例: 合并字符串数组并排序(综合) +> 题目: +> * 将`p1`和`p2`合并到`p3`(`p3`在函数中分配空间(堆)), 然后对`p3`进行排序。 +> * 使用函数`combine_sort`实现; +> * 这题综合了指针数组、数组指针、多级指针的使用。 +```cpp +#include +#include +#include + +void combine_sort(const char **p1, int len1, char (*p2)[10], int len2, char ***p3, int *len3){ + if(NULL == p1 || NULL == p2 || NULL == p3) + return ; + + // 打造一个指针数组 char *temp[len1 + len2] + char **temp = (char **)malloc( (len1+len2) * sizeof(char*)); + for(int i = 0; i < len1; i++){ + temp[i] = (char*)malloc( (strlen(p1[i])+1) * sizeof(char)); + strcpy(temp[i], p1[i]); + } + for(int i = len1; i < len1 + len2; i++){ + temp[i] = (char*)malloc( (strlen(p2[i-len1]) + 1) * sizeof(char) ); + strcpy(temp[i], p2[i-len1]); + } + + //bubble_sort + int n = len1 + len2; + char *p = NULL; //辅助 + for(int end = n-1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if(strcmp(temp[i], temp[i+1]) > 0){ + //交换的是指针指向 + p = temp[i]; + temp[i] = temp[i+1]; + temp[i+1] = p; + } + } + } + + //间接赋值是指针存在的最大意义 + *p3 = temp; + *len3 = n; +} + +void free_mem(char ***temp, int n){ + char **buf = *temp; + for(int i = 0; i < n; i++){ + if(buf[i] != NULL){ + free(buf[i]); + buf[i] = NULL; + } + } + if(buf != NULL){ + free(buf); + *temp = NULL;//注意这里 + } +} + +int main(int argc, char const **argv) +{ + const char *p1[] = {"ddd","aaa","yyy","bbb"}; + char p2[][10] = {"zzz","eee", "xxx","ccc"}; + char **p3 = NULL; // out 在被调函数中分配 + + int len1 = sizeof(p1)/sizeof(*p1); + int len2 = sizeof(p2)/sizeof(p2[0]); + int len3; + + combine_sort(p1, len1, p2, len2, &p3, &len3); + + for(int i = 0; i < len3; i++) + printf("%s ", p3[i]); + printf("\n"); + + free_mem(&p3, len3); + return 0; +} +``` +输出: +```cpp +aaa bbb ccc ddd eee xxx yyy zzz +``` +*** +## 7、结构体 + +### 7.1、结构体套一级指针 +```cpp +#include +#include +#include + +typedef struct Teacher{ + char *name; + int age; +}Teacher; + +Teacher* getMem(int n){ + Teacher *p = (Teacher *)malloc(n * sizeof(Teacher)); + char tmp[20]; + for(int i = 0; i < n; i++){ + p[i].name = (char *)malloc(20 * sizeof(char)); + sprintf(tmp, "teacher%d", i+1); + strcpy(p[i].name, tmp); + p[i].age = 20 + i*2; + } + return p; +} + +void getMem2(Teacher **temp, int n){ + Teacher *p = (Teacher *)malloc(n * sizeof(Teacher)); + char tmp[20]; + for(int i = 0; i < n; i++){ + p[i].name = (char *)malloc(20 * sizeof(char)); + sprintf(tmp, "teacher%d", i+1); + strcpy(p[i].name, tmp); + p[i].age = 20 + i*2; + } + //间接赋值即可 + *temp = p; +} + +void free_teacher(Teacher *p, int n){ + for(int i = 0; i < n; i++){ + if(p[i].name != NULL){ + free(p[i].name); + p[i].name = NULL; + } + } + if(p != NULL) + free(p); +} + +int main(int argc, char const **argv) +{ + Teacher *p = NULL; + getMem2(&p, 3); + for(int i = 0; i < 3; i++) + printf("%s, %d\n", p[i].name, p[i].age); + printf("\n"); + + free_teacher(p, 3); + p = NULL; + return 0; +} +``` +输出: +```cpp +teacher1, 20 +teacher2, 22 +teacher3, 24 +``` + +### 7.2、结构体套二级指针 + +> 题目: 一个老师可以有多个学生,这里学生在老师的结构体中是二维内存,且这里有多个老师,要求对老师数组按照年龄排序。 + +```cpp +#include +#include +#include +#include + +typedef struct Teacher{ + int age; + char **stu; // 一个老师有多个学生 +}Teacher; + + //多个老师,每个老师有多个学生 + //n代表老师个数, m代表学生个数 +void createTeacher(Teacher **temp, int n, int m){ + Teacher *p = (Teacher *)malloc(n * sizeof(Teacher)); + srand((unsigned int)time(NULL));//设置随机种子 + for(int i = 0; i < n; i++){ + p[i].stu = (char **)malloc(m * sizeof(char*)); + for(int j = 0; j < m; j++){ + p[i].stu[j] = (char *)malloc(20 * sizeof(char)); + char buf[20]; + sprintf(buf, "teacher%d'stu:%d", i, j); + strcpy(p[i].stu[j], buf); + } + p[i].age = 20 + rand()%10; + } + //间接复制 + *temp = p; +} + +//打印结构体 +void showTeacher(Teacher *p, int n ,int m){ + for(int i = 0; i < n; i++){ + printf("Teacher[%d]: [age = %d]\t", i, p[i].age); + printf("[Stu = "); + for(int j = 0; j < m; j++){ + printf("%s, ", p[i].stu[j]); + } + printf("]\n"); + } + printf("\n"); +} + +//按照年龄排序 +void sortTeacher(Teacher *p, int n){ + Teacher tmp; + for(int end = n-1; end > 0; end--){ + for(int i = 0; i < end; i++){ + if(p[i].age > p[i+1].age){ + tmp = p[i]; + p[i] = p[i+1]; + p[i+1] = tmp; + } + } + } +} + +// 释放内存 +void freeTeacher(Teacher **temp, int n, int m){ + Teacher *p = *temp; + //对p操作即可 + for(int i = 0; i < n; i++){ + for(int j = 0; j < m; j++){ + if(p[i].stu[j] != NULL){ + free(p[i].stu[j]); + p[i].stu[j] = NULL; + } + } + if(p[i].stu != NULL){ + free(p[i].stu); + p[i].stu = NULL; + } + } + if(p != NULL){ + free(p); + p = NULL; + *temp = NULL; + } +} + +int main(int argc, const char **argv) +{ + Teacher *p = NULL; + int n = 5, m = 3; //5个老师,每个老师3个学生 + createTeacher(&p, n, m); + printf("---------------排序前------------\n"); + showTeacher(p, n, m); + sortTeacher(p, n); + printf("---------------排序后------------\n"); + showTeacher(p, n, m); + freeTeacher(&p, n, m); + return 0; +} + +``` +输出: + +![1563678911447](assets/1563678911447.png) + +大概的内存分析: + +![1563678923223](assets/1563678923223.png) + +总结: + +* 主要通过这题加强对内存分配的顺序以及结构体嵌套指针的使用; +* 还有释放内存要注意的顺序; + + +### 7.3、结构体的浅拷贝和深拷贝 + +* 所谓浅拷贝就是编译器机械的拷贝变量1中的内容到变量2中,如果是指针变量只会拷贝指针变量中存放的地址并不会拷贝指针所指向的内存空间的内容(只是拷贝值); +* 深拷贝需要自己实现拷贝指针所指向的内存空间的内容; +* 如果使用浅拷贝,不能同时释放两次内存; + +![1563678934897](assets/1563678934897.png) + +看个例子: + +```cpp +#include +#include +#include + +typedef struct Student{ + int age; + char *name; +}Student; + +void showStudent(Student *p){ + printf("%s, %d\n", (*p).name, (*p).age); +} + +int main(int argc, char const **argv) +{ + Student s1; + s1.name = (char *)malloc(20 * sizeof(char)); + strcpy(s1.name, "zhangsan"); + s1.age = 13; + + //浅拷贝 + Student s2; + s2 = s1; + showStudent(&s2); + + //深拷贝 + Student s3; + s3 = s1; + s3.name = (char *)malloc(20 * sizeof(char)); + strcpy(s3.name, s1.name); + showStudent(&s3); + + + //最主要的区别就是 浅拷贝堆内存不能释放两次,但是深拷贝可以(不能再次释放s2所指向的内存) + if(s1.name != NULL){ + free(s1.name); + s1.name = NULL; + } + if(s3.name != NULL){ + free(s3.name); + s3.name = NULL; + } + return 0; +} +``` +输出: +```cpp +zhangsan, 13 +zhangsan, 13 +``` +内存分析: + +![1563678946606](assets/1563678946606.png) + +### 7.4、结构体字节对齐 +一些原则: + +> * 数据成员的对齐规则(以最大的类型字节为单位)。 +> > * 结构体(`struct`)的数据成员,第一个数据成员放在`offset`为`0`的地方,以后每个数据成员存放在`offset`为该数据成员大小的整数倍的地方(比如`int`在`32`位机为`4`字节,则要从`4`的整数倍地址开始存储); +> * 结构体作为成员的对齐规则。 +> >* 如果一个结构体`B`里嵌套另一个结构体`A`,则结构体`A`应从`offset`为`A`内部最大成员的整数倍的地方开始存储。(`struct B`里存有`struct A`,`A`里有`char`,`int`,`double`等成员,那`A`应该从`8`的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。 +> > * 注意:①. 结构体`A`所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。②. 不是直接将结构体`A`的成员直接移动到结构体`B`中 +> +> * 收尾工作: 结构体的总大小,也就是`sizeof`的结果,必须是其内部最大成员的整数倍,不足的要补齐。 + + diff --git a/CPlus/C/assets/1563676358953.png b/CPlus/C/assets/1563676358953.png new file mode 100644 index 00000000..9684b61a Binary files /dev/null and b/CPlus/C/assets/1563676358953.png differ diff --git a/CPlus/C/assets/1563676374043.png b/CPlus/C/assets/1563676374043.png new file mode 100644 index 00000000..14670601 Binary files /dev/null and b/CPlus/C/assets/1563676374043.png differ diff --git a/CPlus/C/assets/1563676388437.png b/CPlus/C/assets/1563676388437.png new file mode 100644 index 00000000..c2e4cfac Binary files /dev/null and b/CPlus/C/assets/1563676388437.png differ diff --git a/CPlus/C/assets/1563676408651.png b/CPlus/C/assets/1563676408651.png new file mode 100644 index 00000000..65cba7f8 Binary files /dev/null and b/CPlus/C/assets/1563676408651.png differ diff --git a/CPlus/C/assets/1563676424030.png b/CPlus/C/assets/1563676424030.png new file mode 100644 index 00000000..6fce4cca Binary files /dev/null and b/CPlus/C/assets/1563676424030.png differ diff --git a/CPlus/C/assets/1563676435095.png b/CPlus/C/assets/1563676435095.png new file mode 100644 index 00000000..08acad7a Binary files /dev/null and b/CPlus/C/assets/1563676435095.png differ diff --git a/CPlus/C/assets/1563676593021.png b/CPlus/C/assets/1563676593021.png new file mode 100644 index 00000000..dfd4761c Binary files /dev/null and b/CPlus/C/assets/1563676593021.png differ diff --git a/CPlus/C/assets/1563676610872.png b/CPlus/C/assets/1563676610872.png new file mode 100644 index 00000000..1115194d Binary files /dev/null and b/CPlus/C/assets/1563676610872.png differ diff --git a/CPlus/C/assets/1563676624900.png b/CPlus/C/assets/1563676624900.png new file mode 100644 index 00000000..c0df2d55 Binary files /dev/null and b/CPlus/C/assets/1563676624900.png differ diff --git a/CPlus/C/assets/1563676643955.png b/CPlus/C/assets/1563676643955.png new file mode 100644 index 00000000..ee0165b6 Binary files /dev/null and b/CPlus/C/assets/1563676643955.png differ diff --git a/CPlus/C/assets/1563676653222.png b/CPlus/C/assets/1563676653222.png new file mode 100644 index 00000000..3659eabd Binary files /dev/null and b/CPlus/C/assets/1563676653222.png differ diff --git a/CPlus/C/assets/1563676670688.png b/CPlus/C/assets/1563676670688.png new file mode 100644 index 00000000..60aae10e Binary files /dev/null and b/CPlus/C/assets/1563676670688.png differ diff --git a/CPlus/C/assets/1563676697144.png b/CPlus/C/assets/1563676697144.png new file mode 100644 index 00000000..99e4b563 Binary files /dev/null and b/CPlus/C/assets/1563676697144.png differ diff --git a/CPlus/C/assets/1563676717889.png b/CPlus/C/assets/1563676717889.png new file mode 100644 index 00000000..6e482251 Binary files /dev/null and b/CPlus/C/assets/1563676717889.png differ diff --git a/CPlus/C/assets/1563676734004.png b/CPlus/C/assets/1563676734004.png new file mode 100644 index 00000000..dd1ab725 Binary files /dev/null and b/CPlus/C/assets/1563676734004.png differ diff --git a/CPlus/C/assets/1563676746208.png b/CPlus/C/assets/1563676746208.png new file mode 100644 index 00000000..74e8bbe9 Binary files /dev/null and b/CPlus/C/assets/1563676746208.png differ diff --git a/CPlus/C/assets/1563676762623.png b/CPlus/C/assets/1563676762623.png new file mode 100644 index 00000000..c2d5248b Binary files /dev/null and b/CPlus/C/assets/1563676762623.png differ diff --git a/CPlus/C/assets/1563676775722.png b/CPlus/C/assets/1563676775722.png new file mode 100644 index 00000000..17ef8cfb Binary files /dev/null and b/CPlus/C/assets/1563676775722.png differ diff --git a/CPlus/C/assets/1563676790063.png b/CPlus/C/assets/1563676790063.png new file mode 100644 index 00000000..24b3ac6b Binary files /dev/null and b/CPlus/C/assets/1563676790063.png differ diff --git a/CPlus/C/assets/1563676803049.png b/CPlus/C/assets/1563676803049.png new file mode 100644 index 00000000..d318f328 Binary files /dev/null and b/CPlus/C/assets/1563676803049.png differ diff --git a/CPlus/C/assets/1563676817117.png b/CPlus/C/assets/1563676817117.png new file mode 100644 index 00000000..ed5a1e58 Binary files /dev/null and b/CPlus/C/assets/1563676817117.png differ diff --git a/CPlus/C/assets/1563676830225.png b/CPlus/C/assets/1563676830225.png new file mode 100644 index 00000000..b4e427b6 Binary files /dev/null and b/CPlus/C/assets/1563676830225.png differ diff --git a/CPlus/C/assets/1563676853582.png b/CPlus/C/assets/1563676853582.png new file mode 100644 index 00000000..6ae549d4 Binary files /dev/null and b/CPlus/C/assets/1563676853582.png differ diff --git a/CPlus/C/assets/1563676863442.png b/CPlus/C/assets/1563676863442.png new file mode 100644 index 00000000..be5d6826 Binary files /dev/null and b/CPlus/C/assets/1563676863442.png differ diff --git a/CPlus/C/assets/1563676877818.png b/CPlus/C/assets/1563676877818.png new file mode 100644 index 00000000..047c5b7c Binary files /dev/null and b/CPlus/C/assets/1563676877818.png differ diff --git a/CPlus/C/assets/1563676892955.png b/CPlus/C/assets/1563676892955.png new file mode 100644 index 00000000..7062e7ed Binary files /dev/null and b/CPlus/C/assets/1563676892955.png differ diff --git a/CPlus/C/assets/1563676911604.png b/CPlus/C/assets/1563676911604.png new file mode 100644 index 00000000..af719659 Binary files /dev/null and b/CPlus/C/assets/1563676911604.png differ diff --git a/CPlus/C/assets/1563676920269.png b/CPlus/C/assets/1563676920269.png new file mode 100644 index 00000000..90a75018 Binary files /dev/null and b/CPlus/C/assets/1563676920269.png differ diff --git a/CPlus/C/assets/1563676935545.png b/CPlus/C/assets/1563676935545.png new file mode 100644 index 00000000..3f24b9f9 Binary files /dev/null and b/CPlus/C/assets/1563676935545.png differ diff --git a/CPlus/C/assets/1563676956880.png b/CPlus/C/assets/1563676956880.png new file mode 100644 index 00000000..0b95f7b4 Binary files /dev/null and b/CPlus/C/assets/1563676956880.png differ diff --git a/CPlus/C/assets/1563676980786.png b/CPlus/C/assets/1563676980786.png new file mode 100644 index 00000000..21f84a15 Binary files /dev/null and b/CPlus/C/assets/1563676980786.png differ diff --git a/CPlus/C/assets/1563677002327.png b/CPlus/C/assets/1563677002327.png new file mode 100644 index 00000000..3951c1c1 Binary files /dev/null and b/CPlus/C/assets/1563677002327.png differ diff --git a/CPlus/C/assets/1563677026375.png b/CPlus/C/assets/1563677026375.png new file mode 100644 index 00000000..e034934b Binary files /dev/null and b/CPlus/C/assets/1563677026375.png differ diff --git a/CPlus/C/assets/1563677038950.png b/CPlus/C/assets/1563677038950.png new file mode 100644 index 00000000..22db14c7 Binary files /dev/null and b/CPlus/C/assets/1563677038950.png differ diff --git a/CPlus/C/assets/1563677052177.png b/CPlus/C/assets/1563677052177.png new file mode 100644 index 00000000..a4c670b3 Binary files /dev/null and b/CPlus/C/assets/1563677052177.png differ diff --git a/CPlus/C/assets/1563678465719.png b/CPlus/C/assets/1563678465719.png new file mode 100644 index 00000000..cb58e623 Binary files /dev/null and b/CPlus/C/assets/1563678465719.png differ diff --git a/CPlus/C/assets/1563678484021.png b/CPlus/C/assets/1563678484021.png new file mode 100644 index 00000000..71e6e444 Binary files /dev/null and b/CPlus/C/assets/1563678484021.png differ diff --git a/CPlus/C/assets/1563678501109.png b/CPlus/C/assets/1563678501109.png new file mode 100644 index 00000000..89527d0b Binary files /dev/null and b/CPlus/C/assets/1563678501109.png differ diff --git a/CPlus/C/assets/1563678609815.png b/CPlus/C/assets/1563678609815.png new file mode 100644 index 00000000..60ecd0b9 Binary files /dev/null and b/CPlus/C/assets/1563678609815.png differ diff --git a/CPlus/C/assets/1563678668687.png b/CPlus/C/assets/1563678668687.png new file mode 100644 index 00000000..096cd62b Binary files /dev/null and b/CPlus/C/assets/1563678668687.png differ diff --git a/CPlus/C/assets/1563678683569.png b/CPlus/C/assets/1563678683569.png new file mode 100644 index 00000000..249b0a41 Binary files /dev/null and b/CPlus/C/assets/1563678683569.png differ diff --git a/CPlus/C/assets/1563678722598.png b/CPlus/C/assets/1563678722598.png new file mode 100644 index 00000000..0a699365 Binary files /dev/null and b/CPlus/C/assets/1563678722598.png differ diff --git a/CPlus/C/assets/1563678738192.png b/CPlus/C/assets/1563678738192.png new file mode 100644 index 00000000..4691e757 Binary files /dev/null and b/CPlus/C/assets/1563678738192.png differ diff --git a/CPlus/C/assets/1563678749399.png b/CPlus/C/assets/1563678749399.png new file mode 100644 index 00000000..78f849c5 Binary files /dev/null and b/CPlus/C/assets/1563678749399.png differ diff --git a/CPlus/C/assets/1563678768829.png b/CPlus/C/assets/1563678768829.png new file mode 100644 index 00000000..805cf118 Binary files /dev/null and b/CPlus/C/assets/1563678768829.png differ diff --git a/CPlus/C/assets/1563678781957.png b/CPlus/C/assets/1563678781957.png new file mode 100644 index 00000000..2f6df58a Binary files /dev/null and b/CPlus/C/assets/1563678781957.png differ diff --git a/CPlus/C/assets/1563678806538.png b/CPlus/C/assets/1563678806538.png new file mode 100644 index 00000000..ba396dcb Binary files /dev/null and b/CPlus/C/assets/1563678806538.png differ diff --git a/CPlus/C/assets/1563678825779.png b/CPlus/C/assets/1563678825779.png new file mode 100644 index 00000000..f2c754e3 Binary files /dev/null and b/CPlus/C/assets/1563678825779.png differ diff --git a/CPlus/C/assets/1563678837574.png b/CPlus/C/assets/1563678837574.png new file mode 100644 index 00000000..64885550 Binary files /dev/null and b/CPlus/C/assets/1563678837574.png differ diff --git a/CPlus/C/assets/1563678854129.png b/CPlus/C/assets/1563678854129.png new file mode 100644 index 00000000..bed13432 Binary files /dev/null and b/CPlus/C/assets/1563678854129.png differ diff --git a/CPlus/C/assets/1563678870202.png b/CPlus/C/assets/1563678870202.png new file mode 100644 index 00000000..719d0423 Binary files /dev/null and b/CPlus/C/assets/1563678870202.png differ diff --git a/CPlus/C/assets/1563678894466.png b/CPlus/C/assets/1563678894466.png new file mode 100644 index 00000000..fb3ef768 Binary files /dev/null and b/CPlus/C/assets/1563678894466.png differ diff --git a/CPlus/C/assets/1563678911447.png b/CPlus/C/assets/1563678911447.png new file mode 100644 index 00000000..1accdf09 Binary files /dev/null and b/CPlus/C/assets/1563678911447.png differ diff --git a/CPlus/C/assets/1563678923223.png b/CPlus/C/assets/1563678923223.png new file mode 100644 index 00000000..ead6ea1a Binary files /dev/null and b/CPlus/C/assets/1563678923223.png differ diff --git a/CPlus/C/assets/1563678934897.png b/CPlus/C/assets/1563678934897.png new file mode 100644 index 00000000..4037f8ef Binary files /dev/null and b/CPlus/C/assets/1563678934897.png differ diff --git a/CPlus/C/assets/1563678946606.png b/CPlus/C/assets/1563678946606.png new file mode 100644 index 00000000..f90648eb Binary files /dev/null and b/CPlus/C/assets/1563678946606.png differ diff --git a/CPlus/C/assets/1570423375986.png b/CPlus/C/assets/1570423375986.png new file mode 100644 index 00000000..1e069e8f Binary files /dev/null and b/CPlus/C/assets/1570423375986.png differ diff --git a/C++/C/images/c1.png b/CPlus/C/images/c1.png similarity index 100% rename from C++/C/images/c1.png rename to CPlus/C/images/c1.png diff --git a/C++/C/images/c10.png b/CPlus/C/images/c10.png similarity index 100% rename from C++/C/images/c10.png rename to CPlus/C/images/c10.png diff --git a/C++/C/images/c11.png b/CPlus/C/images/c11.png similarity index 100% rename from C++/C/images/c11.png rename to CPlus/C/images/c11.png diff --git a/C++/C/images/c12.png b/CPlus/C/images/c12.png similarity index 100% rename from C++/C/images/c12.png rename to CPlus/C/images/c12.png diff --git a/C++/C/images/c13.png b/CPlus/C/images/c13.png similarity index 100% rename from C++/C/images/c13.png rename to CPlus/C/images/c13.png diff --git a/C++/C/images/c14.png b/CPlus/C/images/c14.png similarity index 100% rename from C++/C/images/c14.png rename to CPlus/C/images/c14.png diff --git a/C++/C/images/c15.png b/CPlus/C/images/c15.png similarity index 100% rename from C++/C/images/c15.png rename to CPlus/C/images/c15.png diff --git a/C++/C/images/c16.png b/CPlus/C/images/c16.png similarity index 100% rename from C++/C/images/c16.png rename to CPlus/C/images/c16.png diff --git a/C++/C/images/c17.png b/CPlus/C/images/c17.png similarity index 100% rename from C++/C/images/c17.png rename to CPlus/C/images/c17.png diff --git a/C++/C/images/c18.png b/CPlus/C/images/c18.png similarity index 100% rename from C++/C/images/c18.png rename to CPlus/C/images/c18.png diff --git a/C++/C/images/c19.png b/CPlus/C/images/c19.png similarity index 100% rename from C++/C/images/c19.png rename to CPlus/C/images/c19.png diff --git a/C++/C/images/c2.png b/CPlus/C/images/c2.png similarity index 100% rename from C++/C/images/c2.png rename to CPlus/C/images/c2.png diff --git a/C++/C/images/c20.png b/CPlus/C/images/c20.png similarity index 100% rename from C++/C/images/c20.png rename to CPlus/C/images/c20.png diff --git a/C++/C/images/c3.png b/CPlus/C/images/c3.png similarity index 100% rename from C++/C/images/c3.png rename to CPlus/C/images/c3.png diff --git a/C++/C/images/c4.png b/CPlus/C/images/c4.png similarity index 100% rename from C++/C/images/c4.png rename to CPlus/C/images/c4.png diff --git a/C++/C/images/c5.png b/CPlus/C/images/c5.png similarity index 100% rename from C++/C/images/c5.png rename to CPlus/C/images/c5.png diff --git a/C++/C/images/c6.png b/CPlus/C/images/c6.png similarity index 100% rename from C++/C/images/c6.png rename to CPlus/C/images/c6.png diff --git a/C++/C/images/c7.png b/CPlus/C/images/c7.png similarity index 100% rename from C++/C/images/c7.png rename to CPlus/C/images/c7.png diff --git a/C++/C/images/c8.png b/CPlus/C/images/c8.png similarity index 100% rename from C++/C/images/c8.png rename to CPlus/C/images/c8.png diff --git a/C++/C/images/c9.png b/CPlus/C/images/c9.png similarity index 100% rename from C++/C/images/c9.png rename to CPlus/C/images/c9.png diff --git a/CPlus/Cplus/1_Cplus.md b/CPlus/Cplus/1_Cplus.md new file mode 100644 index 00000000..e2b01ffe --- /dev/null +++ b/CPlus/Cplus/1_Cplus.md @@ -0,0 +1,1614 @@ +# C++基础知识总结(一) + +* [1、C++对C语言的提高](#1c对c语言的提高) + * [1.1、命令空间简单使用](#11命令空间简单使用) + * [1.2、`const`关键字的加强](#12const关键字的加强) + * [1.3、引用-重点](#13引用-重点) + * [1.4、指针引用](#14指针引用) + * [1.5、没有引用指针](#15没有引用指针) + * [1.6、`const`引用](#16const引用) + * [1.7、默认参数函数重载作用域运算符](#17默认参数函数重载作用域运算符) + * [1.8、`new`、`delete`的使用](#18newdelete的使用) +* [2、C++面向对象基础](#2c面向对象基础) + * [2.1、一个简单案例](#21一个简单案例) + * [2.2、构造函数和析构函数](#22构造函数和析构函数) + * [2.3、深拷贝和浅拷贝](#23深拷贝和浅拷贝) + * [2.4、指向对象成员函数的指针](#24指向对象成员函数的指针) + * [2.5、常对象](#25常对象) + * [2.6、常对象成员-常数据成员&常成员函数](#26常对象成员-常数据成员常成员函数) + * [2.7、指向对象的常指针](#27指向对象的常指针) + * [2.8、指向常变量、对象的指针](#28指向常变量对象的指针) + * [2.9、静态成员](#29静态成员) + * [2.10、友元](#210友元) +* [3、C++重载运算符](#3c重载运算符) + * [3.1、重载基本运算符](#31重载基本运算符) + * [3.2、重载`=`号操作符](#32重载号操作符) + * [3.3、重载流插入运算符和流提取运算符](#33重载流插入运算符和流提取运算符) + * [3.4、综合案例-矩阵加法以及输入输出](#34综合案例-矩阵加法以及输入输出) +* [4、函数模板、类模板](#4函数模板类模板) + * [4.1、案例-简单实现的Vector类模板中的`push_back`](#41案例-简单实现的vector类模板中的push-back) +* [5、文件操作](#5文件操作) + * [5.1、输入输出流](#51输入输出流) + * [5.2、文件读写](#52文件读写) + +*** +## 1、C++对C语言的提高 +### 1.1、命令空间简单使用 +引用命令空间的三种方式: + +* 直接指定标识符。例如`std::cout<<"hello"< + +//定义一个命令空间 +namespace space1{ + int a = 100; + int b = 200; +} + +//using namespace std; +//只导入其中的cout和endl +using std::cout; +using std::endl; + +int main(int argc, char const **argv) +{ + cout<<"hello"<真正的不能改变); + +测试: + +```cpp +#include + +int main(int argc, char const **argv) +{ + //a此时是一个真正的常量 : 不能通过指针修改(C语言中的const是一个冒牌货) + const int a = 100; + //如果对一个常量取地址,编译器会临时开辟一个空间temp,让这个指针指向temp + int *p = (int *)&a; //注意要强制转换一下 + *p = 200; + printf("a = %d, *p = %d\n", a, *p); + + //可以使用const定义的"常量"来定义数组(C语言中不能这样) + int arr[a]; //ok + return 0; +} +``` + +输出(可以看到`a`没有改变(`C`语言中会变)): +``` +a = 100, *p = 200 +``` +### 1.3、引用-重点 + +* 变量名,本身是一段内存的引用,即别名(`alias`). 引用可以看作一个已定义变量的别名; +* 对变量声明一个引用,并不另外开辟内存单元,例如`int &b = a`,则`b`和`a`都代表同一个内存单元(使用`sizeof()`测`a`、`b`大小是相同的)。引用与被引用的变量有相同的地址; +* 在声明一个引用时,必须同时初始化(和常量有点类似),即声明它代表哪一个变量; +* 当声明一个变量的引用后,改引用一直与其代表的变量相联系,不能再作为其他变量的别名。 +* `&`符号前有数据类型时,是引用。其他都是代表取地址; +* 引用所占用的大小跟指针是相等的,引用可能是一个"常指针"(`int *const p`); +* 对引用的初始化,可以是一个变量名,也可以是另一个引用。如`int a = 3; int &b = a; int &c = b;`此时,整形变量`a`有两个别名`b`、`c`; +* 不能建立`void`类型的引用(但是有`void *`类型指针(万能指针))。不能建立引用的数组(可以有指针数组); + + +使用经典的`swap`问题来看引用作为形参的简单使用: + +```cpp +#include + +//值传递(不行) +int swap1(int a, int b){ + int t = a; + a = b; + b = t; +} + +//指针(也是值传递) +int swap2(int *a, int *b){ + int t = *a; + *a = *b; + *b = t; +} + +//引用(引用传递) +int swap3(int &a, int &b){ + int t = a; + a = b; + b = t; +} + +int main(int argc, char const **argv) +{ + int a = 100, b = 200; + swap1(a, b); + printf("a = %d, b = %d\n", a , b); + swap2(&a, &b); + printf("a = %d, b = %d\n", a , b); + swap3(a, b); + printf("a = %d, b = %d\n", a , b); + return 0; +} +``` +输出: + +```cpp +a = 100, b = 200 +a = 200, b = 100 +a = 100, b = 200 +``` + +### 1.4、指针引用 +可以建立指针变量的引用,如: + +```cpp +int a = 5; +int *p = &a; +int* &ref = p; //ref是一个指向 “整形变量的指针变量” 的引用,初始化为p +``` +下面看一个使用指针引用的例子,对比使用二级指针和使用指针引用的区别: +```cpp +#include +#include +#include + +struct Student{ + int age; + char name[20]; +}; + +//通过二级指针 +void getMem(Student **temp){ + Student *p = (Student *)malloc(sizeof(Student)); + p->age = 13; + strcpy(p->name, "zhangsan"); + *temp = p; +} +//通过引用 +void getMem2(Student* &p){ //将Student*看成一个类型 + p = (Student *)malloc(sizeof(Student)); + p->age = 14; + strcpy(p->name, "lisi"); +} + +//通过指针 +void free1(Student **temp){ + Student *p = *temp; + if(p != NULL){ + free(p); + *temp = NULL; + } +} +//通过指向指针的引用 +void free2(Student* &p){ //指向指针的引用 + if(p != NULL){ + free(p); + p = NULL; + } +} + +int main(int argc, char const **argv) +{ + Student *p = NULL; + getMem(&p); + printf("age = %d, name = %s\n", p->age, p->name); + free1(&p); + + printf("------------------\n"); + + getMem2(p); + printf("age = %d, name = %s\n", p->age, p->name); + free2(p); + return 0; +} + +``` +输出: + +```cpp +age = 13, name = zhangsan +------------------ +age = 14, name = lisi +``` + +### 1.5、没有引用指针 +* 由于引用不是一种独立的数据类型,所以不能建立指向引用类型的指针变量。 +* 但是,可以将变量的引用的地址赋值给一个指针,此时指针指向的是原来的变量。 + +例如: + +```cpp +int a = 3; +int &b = a; +int *p = &b; //指针变量p指向变量a的引用b,相当于指向a,合法 +``` +上面代码和下面一行代码相同: + +```cpp +int *p = &a; +``` +输出`*p`的值,就是`b`的值,即`a`的值。 +不能定义指向引用类型的指针变量,不能写成: + +```cpp +int& *p = &a; //企图定义指向引用类型的指针变量,错误 +``` + +### 1.6、`const`引用 + +* 如果想对一个常量进行引用,必须是一个`const`引用; +* 可以对一个变量进行 常引用(此时引用不可修改,但是原变量可以修改)。这个特征一般是用在函数形参修饰上,不希望改变原来的实参的值; +* 可以用常量或表达式对引用进行初始化,但此时必须用`const`作声明(内部是使用一个`temp`临时变量转换); + + +测试: + +```cpp +#include +#include +using namespace std; + +int main(int argc, char const **argv) +{ + //对一个常量进行引用 + const int a = 10; + //int& ref1 = a; //err + const int &ref1 = a; //ok + + //可以对一个变量进行 常引用(此时引用不可修改,但是原变量可以修改) + //这个特征一般是用在函数形参修饰上,有时候不希望改变原来的实参的值 + int b = 10; + const int& ref2 = b; + //ref2 = 20; //err + b = 20; // ok + printf("b = %d, ref2 = %d\n", b, ref2); + + //对表达式做引用 + // 内部系统处理 int temp = c+10; const int& ref3 = temp; + int c = 30; + const int& ref3 = c + 10; //合法 + + //也可以对不同类型进行转换 + double d = 3.14; + const int& ref4 = d; // int temp = d; const int& ref4 = temp + cout< 如果默认参数出现,那么右边的都必须有默认参数,也就是只有参数列表后面部分的参数才可以提供默认参数值; +* 函数重载规则: ①函数名相同。②参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。 ③返回值类型不同则不可以构成重载。 +* 一个函数,不能既作重载,又作默认参数的函数。 + +简单使用: + +```cpp +#include + +int fun(int a, int b = 20, int c = 30){ + return a + b + c; +} + +int a = 100; + +int main(int argc, char const **argv) +{ + // 作用域运算符 :: + int a = 200; + printf("a = %d\n", a); + printf("a2 = %d\n", ::a); //全局的a + + printf("%d\n",fun(10)); + printf("%d\n",fun(10, 10)); + printf("%d\n",fun(10, 10, 10)); + return 0; +} +``` +### 1.8、`new`、`delete`的使用 + +* `C`语言中使用`malloc`函数必须指定开辟空间的大小,即`malloc(size)`,且`malloc`函数只能从用户处知道应开辟空间的大小而不知道数据的类型,因此无法使其返回的指针指向具体的数据类型。其返回值一律为`void *`,使用时必须强制转换; +* `C++`中提供了`new`和`delete`运算符来替代`malloc`和`free`函数; +* 差别: `malloc`不会调用类的构造函数,而`new`会调用类的构造函数。②`free`不会调用类的析构函数,而`delete`会调用类的析构函数;(析构函数释放的是对象内部的内存,而`delete`释放的是对象,而`delete`也出发析构,所以都可以释放) + + +例如 + +```cpp +new int; //开辟一个存放整数的空间,返回一个指向整形数据的指针 +new int(100); //开辟一个存放整数的空间,并指定初始值为100 +new char[10]; // 开辟一个存放字符数组的空间,size = 10 +new int[5][4]; +float *p = new float(3.14); //将返回的指向实型数据的指针赋给指针变量p +delete p; +delete []p; //释放对数组空间的操作(加方括号) +``` +简单测试: + +```cpp +#include + +int main(int argc, char const **argv) +{ + // new 和 delete 使用 + int *p1 = new int(10); + printf("*p1 = %d\n", *p1); + delete p1; + + int *p2 = new int[10]; + for(int i = 0;i < 10; i++) + p2[i] = i; + for(int i = 0;i < 10; i++) + printf("%d ",p2[i]); + printf("\n"); + + delete []p2; + return 0; +} +``` +输出: +```cpp +*p1 = 10 +0 1 2 3 4 5 6 7 8 9 +``` + +*** +## 2、C++面向对象基础 + +### 2.1、一个简单案例 +> 题目,判断两个圆是否相交。 +```cpp +#include +#include +#include +using namespace std; + +//点(坐标类) +class Point{ + public: + void setXY(int x, int y); + double getPointDis(Point &thr); //计算两点距离 + private: + int x; + int y; +}; + +//圆类 +class Circle{ + public: + void setR(int r); + void setXY(int x, int y); + bool isInterSection(Circle &thr); + private: + int r; + Point p0; //圆心 +}; + +void Point::setXY(int x, int y){ + this->x = x; + this->y = y; +} +double Point::getPointDis(Point &thr){ + int dx = x - thr.x; + int dy = y - thr.y; + double dis = sqrt(dx*dx + dy*dy); + return dis; +} + +void Circle::setR(int r){ + this->r = r; //注意不能写成this.r (this是一个指针,每个对象都可以通过this指针来访问自己的地址) +} +void Circle::setXY(int x,int y){ + p0.setXY(x, y); +} +bool Circle::isInterSection(Circle &thr){ + int R = r + thr.r; //两个圆的半径之和 + double dis = p0.getPointDis(thr.p0); //两个圆心的距离 + if(dis <= R) + return true; + else + return false; +} + +int main(int argc, char const **argv) +{ + Circle c1,c2; + c1.setR(1); + c1.setXY(0,0); + c2.setR(3); + c2.setXY(2,2); + + Circle *pc1 = &c1; //定义指向对象的指针 + + if(pc1->isInterSection(c2))//通过指针的方式调用 + printf("两圆相交!\n"); + else + printf("两圆不相交!\n"); + return 0; +} + +``` +输出: +```cpp +两圆相交! +``` +### 2.2、构造函数和析构函数 +* 如果用户没有定义默认的,系统会提供一个默认构造函数,但是如果用户已经定义了构造函数,系统就不会提供默认的构造函数; +* 系统也会提供一个默认的拷贝构造函数,默认是浅拷贝,形如`Clazz c1(c2)`的使用,将`c1`拷贝给`c2`; +* `C++`有一个参数初始化列表的特性,注意不能用在数组上; +* 构造函数也可以是带有默认参数; +* 析构函数被调用的情况:① 如果用`new`运算符动态的建立了一个对象,当用`delete`运算符释放该对象时,先调用该对象的析构函数。②`static`局部对象在函数调用结束之后对象并不是放,因此也不调用析构函数。只有在调用`exit`或者`main`函数结束的时候才会调用`static`的析构函数。 +* 构造函数和析构函数的顺序: ①先构造的后析构,后构造的先析构; + + +拷贝构造函数 +```cpp +#include + +class Clazz{ + public: + Clazz(){ + x = 0; + y = 0; + } + Clazz(int x, int y){ + this->x = x; + this->y = y; + } + void printXY(){ + printf("%d, %d\n", x, y); + } + + //显示的拷贝构造函数 如果不写,默认也有这个 + Clazz(const Clazz &thr){ + x = thr.x; + y = thr.y; + } + private: + int x; + int y; +}; + +int main(int argc, char const **argv) +{ + Clazz c1(100, 200); + + Clazz c2(c1); //拷贝 + c2.printXY(); + + //构造函数是对象初始化的时候调用 + Clazz c3 = c1; // 调用的依然是c3的拷贝构造函数 + c3.printXY(); + + + Clazz c4; //调用的是无参构造器 + c4 = c1; //不是调用拷贝构造函数,而是重载操作符 + c4.printXY(); + + return 0; +} + +``` + +构造函数中的参数初始化列表: + +* 注意构造对象成员的 顺序跟初始化列表的顺序无关; +* 而是跟成员对象的定义顺序有关; +```cpp +#include +#include + +class Box{ + public: + Box(); //默认的无参 + //参数初始化表 + Box(int l, int w, int h):l(l),w(w),h(h) + { + strcpy(this->name, "zhangsan"); //默认 + } + Box(int l, int w, int h, const char *name):l(l),w(w),h(h) + { + strcpy(this->name,name); //字符串不能那样初始化 + } + int volume(); + private: + char name[10]; + int l,w,h; +}; + +Box::Box() +{ + l = 10; + w = 10; + h = 10; + strcpy(this->name, "zhangsan"); +} + +int Box::volume() +{ + return l*w*h; +} + +int main(int argc, char const **argv) +{ + Box b1; + printf("%d\n", b1.volume()); + Box b2(20, 20, 20); + printf("%d\n", b2.volume()); + Box b3(30, 30, 30, "lisi"); + printf("%d\n", b3.volume()); + return 0; +} +``` +输出: + +```cpp +1000 +8000 +27000 +``` + +### 2.3、深拷贝和浅拷贝 +* 深拷贝是有必要的,因为析构函数释放内存的时候,如果使用浅拷贝拷贝了一个对象,释放两个对象指向得到同一个内存两次,就会产生错误。 +```cpp +#include +#include +#include + +class Teacher{ + public: + Teacher(int id, const char* name){ + this->id = id; + int len = strlen(name); + this->name = (char*)malloc(len+1); + strcpy(this->name, name); + } + + //显式的提供一个拷贝构造函数,完成深拷贝动作 + // 防止在析构函数中 释放堆区空间两次 + Teacher(const Teacher &thr){ + id = thr.id; + //深拷贝 + int len = strlen(thr.name); + name = (char*)malloc(len + 1); + strcpy(name, thr.name); + } + + //必须要显式的提供深拷贝构造函数,不然会释放两次 + ~Teacher(){ + if(name != NULL){ + free(name); + name = NULL; + } + } + + void print(){ + printf("id: %d, name = %s\n", id, name); + } + private: + int id; + char *name; +}; + +int main(int argc, char const **argv) +{ + Teacher t1(1, "zhangsan"); + t1.print(); + Teacher t2(t1); + t2.print(); + return 0; +} +``` + + +### 2.4、指向对象成员函数的指针 +* 注意: 定义指向对象成员函数的指针变量的方法和定义[**指向普通函数的指针变量**](https://blog.csdn.net/zxzxzx0119/article/details/84001021#t3)不同,定义指向成员函数的指针变量的方法: `void (Time:: *p)();`(对比普通的: `void (*p)();`) : 定义`p`为指向`Time`类中公共成员函数的指针变量; +* 可以使用上面的`p`指针指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个公用成员函数的指针即可,如`p = &Time::get_time`; + +简单测试: +```cpp +#include + +class Time{ + public: + Time(int h, int m, int s):hour(h),minute(m),sec(s){ } + void get_time(); + private: + int hour; + int minute; + int sec; +}; + +void Time::get_time() +{ + printf("%d:%d:%d\n", hour, minute, sec); +} + +int main(int argc, char const **argv) +{ + Time t1(10,10,30); //10:10:30 + Time *p = &t1; //定义指向对象的指针 + p->get_time(); + + void (Time::*p2)(); //定义指向Time类公共成员函数的指针变量 + p2 = &Time::get_time; + (t1.*p2)(); // 调用对象t1中p2所指向的成员函数(即t1.get_time()) + + return 0; +} +``` +输出: +```2cpp +10:10:30 +10:10:30 +``` +### 2.5、常对象 +* 可以在定义对象时加上关键字`const`,指定对象为常对象。常对象必须要有初值,凡是希望保护数据成员不被改变的对象,都可以声明为常对象。 +* 基本语法: `类名 const 对象名 [(实参表)]` 或者`const 类名 对象名 [(实参表)]`,举例: `Time const t(10, 10, 30)`和`const Time t(10, 10, 30)`; +* 如果一个对象被声明为常对象,则通过改对象只能调用它的常成员函数,不能调用对象的普通成员函数;例如`void get_time() const` (常成员函数); +* 常成员函数可以访问常对象中的数据成员,但不允许修改常对象中数据成员的值; + +### 2.6、常对象成员-常数据成员&常成员函数 +①常数据成员 +* 只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据赋值; +* 因为常数据成员不能被赋值; + +②常成员函数 +* 常成员函数声明: 例如`void get_time() const`,常成员函数只能引用本类中的数据成员,不能修改它们; +* 注意: 如果定义了一个常对象,只能调用其中的`const`成员函数,不能调用非`const`成员函数(不论这些函数是否会修改对象中的数据); +* 常成员函数不能调用另一个非`const`成员函数; + +|数据成员|非`const`成员函数|`const`成员函数| +|-|-|-| +|非`const`数据成员|可以引用,可以改变值|可以引用,不可改变值| +|`const`数据成员|可以引用,不可改变值|可以引用,不可改变值| +|`const`对象|不允许|可以引用,不可改变值| + +### 2.7、指向对象的常指针 +* 基本语法`Time * const p = &t1`和基本的常指针差不多; +* 只能指向同一个对象不能改变指针的指向; +* 意义是作为函数的形参,不允许在函数执行过程中改变指针变量,使其始终指向原来的对象; +### 2.8、指向常变量、对象的指针 +①指向常变量的指针 +* 如果一个变量已经被声明为常变量,只能用指向常变量的指针变量指向它,而不能用非`const`指针指向它; + +```cpp +const char c[] = "boy"; +const char *p1 = c; // ok +char *p2 = c; // err +``` +* `const`指针除了可以指向`const`变量,还可以指向非`const`变量,此时不能通过指针来改变变量的值; +* 如果函数的形参是指向非`const`变量的指针变量,实参只能使用指向非`const`变量的指针;更多形参和实参指针变量关系看下表; + +|形参|实参|合法否|改变指针所指向的变量的值| +|-|-|-|-| +|指向非`const`变量的指针|非`const`变量的地址|合法|可以| +|指向非`const`变量的指针|`const`变量的地址|不合法|| +|指向`const`变量的指针|`const`变量的地址|合法|不可以| +|指向`const`变量的指针|非`const`变量的地址|合法|不可以| + +②指向常对象的指针 +* 和指向常变量的指针类似,只能用指向常对象的指针变量指向它,不能用指向非`const`对象的指针指向它; +* 如果定义了一个指向常对象的指针变量,并指向了一个非`const`对象,则不能通过改指针变量来改变变量的值; + +```cpp +Time t1(10, 10, 30); +const Time *p = &t1; +(*p).hour = 20; //err +``` +* 使用意义也是在形参上,希望调用函数的时候对象的值不被改变,就把形参指定为指向常对象的指针变量; + +### 2.9、静态成员 +①静态数据成员 +* 静态数据成员在内存中只占一份空间(不是每一个对象都为它保留一份空间),静态数据成员的值对所有对象都是一样的,如果改变它的值,在其他对象中也会改变; +* 静态数据成员只能在类体外进行初始化。如`int Box::height = 10;`,只在类体中声明静态数据成员时加上`static`,不必在初始化语句中加`static`。 +* 不能在参数初始化列表中对静态数据成员进行初始化; + +②静态成员函数 +* 静态成员函数和普通函数最大的区别就在静态成员函数没有`this`指针,决定了静态成员函数与不能访问本类中的非静态成员。所以静态成员函数的作用就是用来访问静态数据成员; +* 但是普通成员函数(非静态)可以引用类中的静态数据成员; +### 2.10、友元 +① 将普通函数声明为友元函数 + +* 这样这个普通函数就可以访问声明了友元函数的那个类的私有成员; + +```cpp +#include + +class Time{ + public: + Time(int, int, int); + friend void display(Time&); + private: + int hour; + int minute; + int sec; +}; + +Time::Time(int h, int m, int s){ + hour = h; + minute = m; + sec = s; +} + +//普通的友元函数 +void display(Time& t){ + printf("%d:%d:%d\n", t.hour, t.minute, t.sec); +} + +int main(int argc, const char **argv) +{ + Time t(10, 10, 30); + display(t); + return 0; +} +``` +输出: +```cpp +10:10:30 +``` + +② 将别的类中的成员函数声明为友元函数 + +```cpp +#include + +class Date; //对Date类声明 + +class Time{ + public: + Time(int, int, int); + void display(Date &); //要访问Date类的成员 + private: + int hour; + int minute; + int sec; +}; + +Time::Time(int h, int m, int s){ + hour = h; + minute = m; + sec = s; +} + +class Date{ + public: + Date(int, int, int); + friend void Time::display(Date &); //声明Time类中的display函数为本类的友元函数 + private: + int month; + int day; + int year; +}; + +Date::Date(int m, int d, int y){ + month = m; + day = d; + year = y; +} + +void Time::display(Date &d){ + printf("%d/%d/%d\n",d.month, d.day, d.year); + printf("%d:%d:%d\n", hour, minute, sec); +} + +int main(int argc, const char **argv) +{ + Time t(10, 10, 30); + Date d(12,25,2018); + t.display(d); + return 0; +} + +``` + +③ 友元类 + +* 在`A`类中声明`B`类是自己的友元类,这样`B`类就可以访问`A`的所有私有成员了。 +* 但是要注意友元的关系是单向的而不是双向的。且友元的关系不能传递; + +## 3、C++重载运算符 + +### 3.1、重载基本运算符 + +**案例一: 重载`+`号来计算复数** + +```cpp +#include + + +class Complex{ + public: + Complex(int a, int b):a(a),b(b){ } + + // 方法一.使用成员函数 + //不能写成返回& (引用)因为temp是局部的 ,函数调用完毕就会释放 + Complex operator+ (const Complex &thr){ + Complex temp(a + thr.a, b + thr.b); + return temp; + } + + // 方法二.使用友元函数 + //friend Complex operator+ (const Complex &c1, const Complex &c2); + + // 打印 + void printC() + { + printf("(%d,%di)\n",a,b); + } + private: + int a; //实部 + int b; //虚部 +}; + +//Complex operator+ (const Complex &c1, const Complex &c2){ //注意也不能返回局部的引用 +// return Complex(c1.a + c2.a, c1.b + c2.b); +//} + +int main(int argc, char const **argv) +{ + Complex c1(1,2); + Complex c2(3,4); + Complex c3 = c1 + c2; + + //下面的显示调用也是和上面等同的,但是一般不会这么写 + //Complex c3 = c1.operator+(c2); //成员函数 + //Complex c3 = operator+(c1, c2); //友元函数 + c3.printC(); +} + +``` +输出: +```cpp +(4,6i) +``` + +**案例二: 重载双目运算符** + +* 注意这个要返回的是引用,因为运算符支持连续的相加操作; +* 在成员函数中返回 `this`指针指向的内容,在友元函数中可以返回第一个参数的引用(不是局部变量的,所以可以返回); + +```cpp +#include + +//重载 += 运算符 +class Complex{ + public: + Complex(int a, int b):a(a), b(b){ } + + // 方法一:使用成员函数 + // 注意这里和上一个不同,这里要返回引用 + //Complex& operator+= (const Complex &thr){ + // this->a += thr.a; + // this->b += thr.b; + // return *this; //注意这里返回this指针指向的内容 + //} + + // 方法二: 使用友元函数 + friend Complex& operator+= (Complex& c1, const Complex& c2); + + void printC() + { + printf("(%d,%di)\n", a, b); + } + private: + int a; + int b; +}; + +//也要返回引用 因为要支持连续操作 类似(c1 += c2) += c3 +Complex& operator+= (Complex& c1, const Complex& c2){ + c1.a += c2.a; + c1.b += c2.b; + return c1; +} + +int main(int argc, char const **argv) +{ + Complex c1(1,2); + Complex c2(3,4); + (c1 += c2) += c2; + c1.printC(); + + return 0; +} + +``` +**案例三: 重载单目运算符** + +```cpp +#include + + +class Complex{ + public: + Complex(int a, int b):a(a),b(b){ } + + //重载 前置++ + Complex operator++ (){ + a++; + b++; + return *this; + } + + // 重载后置++ 需要使用一个占位符 + friend const Complex operator++ (Complex& c1, int); + void printC() + { + printf("(%d,%di)\n", a, b); + } + private: + int a; + int b; + +}; + +//后置++ +const Complex operator++ (Complex& c1, int) +{ + Complex temp(c1.a, c1.b); + c1.a++; + c1.b++; + return temp; +} + +int main(int argc, char const **argv) +{ + Complex c(1,2); + c++; + c.printC(); + ++c; + c.printC(); + return 0; +} + +``` +输出: + +```cpp +(2,3i) +(3,4i) +``` + + +### 4.2、重载`=`号操作符 +* 这个重点是在当类中有指针的时候,就要注意堆中分配空间的问题,如果不是在初始化的时候使用的`=`操作符,就是代表的赋值,其中的指针不能使用浅拷贝; +* 需要我们重写`=`操作符,实现深拷贝。(就是不能让两个对象同时指向堆中的同一块内存,因为释放内存的时候不能释放两次); +```cpp +#include +#include + +class Student{ + public: + Student() { + this->id = 0; + this->name = NULL; + } + Student(int id, const char *name){ + this->id = id; + + int len = strlen(name); + this->name = new char[len+1]; + strcpy(this->name, name); + } + Student(const Student& thr){ // 拷贝构造 + this->id = thr.id; + //深拷贝 + int len = strlen(thr.name); + this->name = new char[len+1]; + strcpy(this->name, thr.name); + } + + //重载=号,防止 s2 = s1的时候内部的name直接指向堆中的同一个内容,析构时发生错误 + Student& operator= (const Student& thr){ + //1. 防止自身赋值 + if(this == &thr) //&是取地址 + return *this; + //2. 将自身的空间回收 + if(this->name != NULL){ + delete[] this->name; + this->name = NULL; + this->id = 0; + } + + //3. 执行深拷贝 + this->id = thr.id; + int len = strlen(thr.name); + this->name = new char[len + 1]; + strcpy(this->name, thr.name); + + //4. 返回本身的对象 + return *this; + } + ~Student(){ + if(this->name != NULL){ + delete[] this->name; + this->name = NULL; + this->id = 0; + } + } + void printS(){ + printf("%s\n", name); + } + private: + int id; + char *name; +}; + + +int main(int argc,const char **argv) +{ + Student s1(1, "zhangsan"); + Student s2(s1); //拷贝构造 + Student s3 = s1; //这个调用的还是拷贝构造函数,不是=操作符,初始化的都是拷贝构造函数 + s1.printS(); + s2.printS(); + s3.printS(); + + + //但是这样就是调用的等号操作符 + Student s4; + s4 = s1; //不是初始化的时候 + s4.printS(); + return 0; +} +``` + +### 4.3、重载流插入运算符和流提取运算符 + +* 重载流插入运算符的基本格式: `istream& operator>> (istream&, 自定义类&);` ,也就是返回值和第一个参数必须是`istream&`类型; +* 重载流提取运算符的基本格式: `ostream& operator<< (istream&, 自定义类&);`,也就是返回值和第一个参数必须是`ostream&`类型; +* 注意: 重载这两个运算符的时候,只能将他们作为友元函数,不能作为成员函数。避免修改 `c++`的标准库。 + +案例: +```cpp +#include +#include +using namespace std; + +class Complex{ + public: + friend ostream& operator<< (ostream&, Complex&); + friend istream& operator>> (istream&, Complex&); + private: + int a; + int b; +}; + +ostream& operator<< (ostream& os, Complex& c) +{ + os<<"("<> (istream& is, Complex& c) +{ + cout<<"a:"; + is>>c.a; + cout<<"b:"; + is>>c.b; + return is; +} + +int main(int argc, char const **) +{ + Complex c; + cin>>c; + cout< +#include +using namespace std; + +const int N = 2; +const int M = 3; + +class Matrix{ + public: + Matrix(); + friend Matrix operator+(Matrix&, Matrix&); + //千万注意: 只能将"<<"和">>"定义为友元函数,不能作为成员函数 + friend ostream& operator<<(ostream&, Matrix&); + friend istream& operator>>(istream&, Matrix&); + private: + int mat[N][M]; +}; + +Matrix::Matrix(){ + for(int i = 0; i < N; i++){ + for(int j = 0; j < M; j++){ + mat[i][j] = 0; + } + } +} + +//注意返回局部变量对象,不能返回引用 +Matrix operator+ (Matrix& a, Matrix& b){ + Matrix c; + for(int i = 0; i < N; i++){ + for(int j = 0; j < M; j++){ + c.mat[i][j] = a.mat[i][j] + b.mat[i][j]; + } + } + return c; +} + +ostream& operator<<(ostream& os, Matrix& m){ + for(int i = 0; i < N; i++){ + for(int j = 0; j < M; j++){ + os<>(istream& is, Matrix& m){ + for(int i = 0; i < N; i++){ + for(int j = 0; j < M; j++){ + is>>m.mat[i][j]; + } + } + return is; +} + +int main(int argc, const char **argv) +{ + Matrix matrix1,matrix2; + cin>>matrix1; + cin>>matrix2; + Matrix matrix3 = matrix1 + matrix2; + cout<`; +* 类模板基本语法: `template `,注意函数声明在外面的时候也要在函数的上面写上这个语句; +* 函数模板实际上是建立一个通用函数, 其函数类型和形参类型不具体制定, 用 一个虚拟的类型来代表。 这 个通用函数就成为函数模板; +* 注意: ①函数模板不允许自动类型转化;②普通函数能够自动进行类型转化; +* <函数模板和普通函数在一起调用规则:① 函数模板可以想普通函数那样可以被重载;②`C++`编 译器优先考虑普通函数; +* 类模板中的 `static`关 键字: ①从 类模板实例化的每一个模板类有自己的类模板数据成员, 该 模板的所有对象共享一 个 `statci`数 据成员②每 个模板类有自己类模板的`static`数据成员的副本; + + +测试程序: +```cpp +#include +#include +using namespace std; + +template +void swap1(T& a, T& b){ + T t = a; + a = b; + b = t; +} + +template +class Complex{ + public: + Complex(T a, R b):a(a),b(b){ } + //方法一: 下面的printC函数是在类内实现 + //void printC() + //{ + // cout<<"("<& c){ + os<<"("< +void Complex::printC() +{ + cout<<"("<(a,b);//显示的调用 + //自动类型推导 + //swap1(a, b); + printf("a = %d,b = %d\n", a, b); + + printf("-------类模板-------\n"); + Complexc1(2.2, 3.3); + c1.printC(); + + Complexc2(4, 5.5); + cout< +#include +#include +using namespace std; + +template +class MyArray{ + public: + MyArray(int capacity){ //构造函数 + this->capacity = capacity; + this->size = 0; + this->p = new T[this->capacity]; + } + MyArray(const MyArray& thr){ //拷贝构造 + this->size = thr.size; + this->capacity = thr.capacity; + this->p = new T[this->capacity]; + for(int i = 0; i < this->size; i++){ + this->p[i] = thr.p[i]; + } + } + + //重载=号 + MyArray& operator= (const MyArray& thr){ + //1. 防止自身赋值 + if(this == &thr){ + return *this; + } + //2. 删除原来的 + if(p != NULL){ + delete[] this->p; + this->p = NULL; + } + + this->size = thr.size; + this->capacity = thr.capacity; + this->p = new T[this->capacity]; + for(int i = 0; i < this->size; i++){ + this->p[i] = thr.p[i]; + } + return *this; + } + + + //重载[]运算符: 提取元素 + T& operator[](int index){ + return this->p[index]; + } + + void pushBack(T& ele){ + if(size == capacity){ + return; + } + // 调用的是 = 号运算符,如果成员是指针,要注意深拷贝问题 + p[size++] = ele; + } + //搞定插入常量(右值)的引用问题(C++11特性) + void pushBack(T&& ele){ + if(size == capacity){ + return; + } + // 调用的是 = 号运算符,如果成员是指针,要注意深拷贝问题 + p[size++] = ele; + } + + ~MyArray(){ + if(this->p != NULL){ + delete[] this->p; + } + } + + int getSize(){ + return this->size; + } + private: + int capacity;//容量 + int size; //当前数组元素 + T *p; //数组首地址 +}; + + +int main(int argc, const char **argv) +{ + MyArrayarray(20); + int a = 10, b = 20; + array.pushBack(a); + array.pushBack(b); + + array.pushBack(30); + + for(int i = 0; i < array.getSize(); i++){ + cout< +#include +using namespace std; + +int main(int argc, char const **argv) +{ + char ch; + //while( (ch = cin.get()) != EOF) + // cout << ch << endl; + //char ch; cin.get(ch); //same as above + char buf[256] = { 0 }; + cin.get(buf, 256); // 两个参数,从缓冲区读取一个字符串指定长度 + cout << buf; + cin.get(buf, 256, '\n'); //三个参数,指定终止的字符 + cout << buf; + cin.getline(buf, 256); //读取一行数据,不读换行符 + cout << buf; + return 0; +} +``` +测试`cin.ignore()`使用: + +```cpp +#include + +using namespace std; + +int main(int argc, char const **argv) +{ + char ch; + cin.get(ch); //从缓冲区要数据 阻塞 + cout << ch << endl; + cin.ignore(1); //忽略当前字符 从缓冲区取走了 + cin.get(ch); + cout << ch << endl; + return 0; +} +``` +运行效果: + +![在这里插入图片描述](images/cpp3.png) + +测试`cin.peek()`函数的使用: +```cpp +#include + +using namespace std; + +int main(int argc, char const** argv) +{ + cout << "请输入数组或者字符串:" << endl; + char ch; + ch = cin.peek(); //偷窥一下缓冲区,并不会取走, 返回第一个字符 + if (ch >= '0' && ch <= '9'){ + int number; + cin >> number; // 从缓冲区读取这个数字 + cout << "您输入的是数字:" << number << endl; + } + else{ + char buf[256] = { 0 }; + cin >> buf; // 从缓冲区读取这个字符串 + cout << "您输入的是字符串:" << buf << endl; + } + return 0; +} + +``` +运行结果:  + +![在这里插入图片描述](images/cpp4.png) + + + +测试`cin.putback()`函数的使用: + +```cpp +#include + +using namespace std; + +int main(int argc, char const **argv) +{ + cout << "请输入字符串或者数字:" << endl; + char ch; + cin.get(ch); //从缓冲区取走一个字符 + if (ch >= '0' && ch <= '9'){ + cin.putback(ch); //ch放回到缓冲区 + int number; + cin >> number; + cout << "您输入的是数字:" << number << endl; + }else{ + cin.putback(ch);// 将字符放回缓冲区 + char buf[256] = { 0 }; + cin >> buf; + cout << "您输入的是字符串: " << buf << endl; + } + return 0; +} +``` +效果和测试`cin.peek()`函数的一样。 + +标准输出流对象 `cout`,重点函数: +```cpp +cout.flush() // 刷新缓冲区 +cout.put() // 向缓冲区写字符 +cout.write() // 二进制流的输出 +cout.width() // 输出格式控制 +cout.fill() +cout.setf(标记) +``` +`cout`比较简单不做案例演示。 + + +### 6.2、文件读写 +输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。 在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。 和 文件有关系的输入输出类主要在 `fstream.h` 这 个头文件中被定义,在这个头文件中主要被定义了三个类, 由这三个类控制对文件的各种输入输出操作 , 他们分别是 `ifstream`、 `ofstream`。 + +![在这里插入图片描述](images/cpp5.png) + +由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在 `fstream.h` 头 文件中 +是没有像 `cout` 那 样预先定义的全局对象,所以我们必须自己定义一个该类的对象。 `ifstream` 类 ,它是从 `istream` 类 派生的,用来支持从磁盘文件的输入。`ofstream` 类 ,它是从`ostream`类 派生的,用来支持向磁盘文件的输出。 +`fstream` 类 ,它是从` iostream `类 派生的,用来支持对磁盘文件的输入输出。 + +所谓打开( `open`)文 件是一种形象的说法,如同打开房门就可以进入房间活动一样。 打 +开 文件是指在文件读写之前做必要的准备工作,包括: +* 为文件流对象和指定的磁盘文件建立关联,以便使文件流 向指定的磁盘文件。 +* 指定文件的工作方式,如,该文件是作为输入文件还是输出文件,是 `ASCII` 文件还是二进制文件等。 + +以上工作可以通过两种不同的方法实现。 + +```cpp +#include +#include //文件读写的头文件 + +using namespace std; + +int main(int argc, char const **argv) +{ + const char* srcName = "src.txt"; + const char* destName = "dest.txt"; + ifstream is(srcName, ios::in); //只读方式打开 + ofstream os(destName, ios::out | ios::app); // app表示是追加 + if(!is){ + cout << "打开文件失败!" << endl; + return -1; + } + char ch; + while(is.get(ch)){ + cout << ch; + os.put(ch); // 输出到os指向的文件 + } + is.close(); + os.close(); + return 0; +} +``` + +演示结果: + +![在这里插入图片描述](images/cpp6.png) + + +测试按照二进制方式写入和读取: +```cpp +#include +#include +using namespace std; + +class Person{ +public: + int age; + int id; + Person(){ } + Person(int age, int id):age(age), id(id){ } + void show() + { + cout << "age: " << age << ", id: " << id << endl; + } +}; + +#if 0 +int main(int argc, char const **argv) +{ + Person p1(10, 20), p2(30, 40); + const char *destFile = "dest.txt"; + ofstream os(destFile, ios::out | ios::binary); // 二进制方式写入 + os.write((char*)&p1, sizeof(Person)); + os.write((char*)&p2, sizeof(Person)); + os.close(); + return 0; +} +#endif + +int main(int argc, char const** argv) +{ + const char *destFile = "dest.txt"; + ifstream is(destFile, ios::in | ios::binary); + Person p1, p2; + is.read((char*)&p1, sizeof(Person)); + is.read((char*)&p2, sizeof(Person)); + p1.show(); + p2.show(); + return 0; +} + +``` + diff --git a/C++/C++/images/cpp1.png b/CPlus/Cplus/images/cpp1.png similarity index 100% rename from C++/C++/images/cpp1.png rename to CPlus/Cplus/images/cpp1.png diff --git a/C++/C++/images/cpp2.png b/CPlus/Cplus/images/cpp2.png similarity index 100% rename from C++/C++/images/cpp2.png rename to CPlus/Cplus/images/cpp2.png diff --git a/C++/C++/images/cpp3.png b/CPlus/Cplus/images/cpp3.png similarity index 100% rename from C++/C++/images/cpp3.png rename to CPlus/Cplus/images/cpp3.png diff --git a/C++/C++/images/cpp4.png b/CPlus/Cplus/images/cpp4.png similarity index 100% rename from C++/C++/images/cpp4.png rename to CPlus/Cplus/images/cpp4.png diff --git a/C++/C++/images/cpp5.png b/CPlus/Cplus/images/cpp5.png similarity index 100% rename from C++/C++/images/cpp5.png rename to CPlus/Cplus/images/cpp5.png diff --git a/C++/C++/images/cpp6.png b/CPlus/Cplus/images/cpp6.png similarity index 100% rename from C++/C++/images/cpp6.png rename to CPlus/Cplus/images/cpp6.png diff --git a/CPlus/readme.md b/CPlus/readme.md new file mode 100644 index 00000000..e69de29b diff --git "a/DB/MySQL/advance/MYSQL\344\270\273\344\273\216\345\244\215\345\210\266.md" "b/DB/MySQL/advance/MYSQL\344\270\273\344\273\216\345\244\215\345\210\266.md" new file mode 100644 index 00000000..6cf3e0ae --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\344\270\273\344\273\216\345\244\215\345\210\266.md" @@ -0,0 +1,125 @@ +# MYSQL主从复制 + +## 一、基本原理 + +MySQL复制过程分成三步: + +* 1)、master将改变记录到二进制日志(`binary log`)。这些记录过程叫做二进制日志事件,`binary log events`; +* 2)、slave将master的`binary log events`拷贝到它的中继日志(relay log); +* 3)、slave重做中继日志中的事件,将改变应用到自己的数据库中。 MySQL复制是异步的且串行化的。 + +简单来说: **slave会从master读取binlog来进行数据同步** + +![zhucong_1.png](images/zhucong_1.png) + +![1557017250485](assets/1557017250485.png) + +Mysql的复制(replication)是一个**异步的复制**。 + +实现整个复制操作主要由三个进程完成的,其中两个进程在Slave(Sql进程和IO进程),另外一个进程在 Master(IO进程)上。 + +要实施复制,首先必须打开Master端的`binary log(bin-log)`功能,否则无法实现。 + +因为整个复制过程实际上就是Slave从Master端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。 + +> 复制的详细过程: +> +> (1)Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容; +> +> (2)Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请求信息读取制定日志指定位置之后的日志信息,返回给Slave 的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的`bin-log`文件的名称以及`bin-log`的位置; +> +> (3)Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的`relay-log`文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到`master-info`文件中,以便在下一次读取的时候能够清楚的高速Master“我需要从某个bin-log的哪个位置开始往后的日志内容,请发给我”; +> +> (4)Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。 + +原则: + +* 每个slave只有一个master; +* 每个slave只能有一个唯一的服务器ID; +* 每个master可以有多个salve; + +## 二、一主一从相关配置 + +演示主机为`Windows` (配置文件为`my.ini`文件),从机为`Linux`(配置文件为`my.cnf`) + +### 1、主机配置(windows的my.ini) + +* 1)、[必须]主服务器唯一ID; +* 2)、[必须]启用二进制日志; + * `log-bin=自己本地的路径/data/mysqlbin`。 + * `log-bin=D:/devSoft/MySQLServer5.5/data/mysqlbin`。 +* 3)、[可选]启用错误日志 + * `log-err=自己本地的路径/data/mysqlerr`。 + * `log-err=D:/devSoft/MySQLServer5.5/data/mysqlerr`。 +* 4)、[可选]根目录 + * `basedir="自己本地路径"`。 + * `basedir="D:/devSoft/MySQLServer5.5/"`。 +* 5)、[可选]临时目录 + * `tmpdir="自己本地路径"`。 + * `tmpdir="D:/devSoft/MySQLServer5.5/"`。 +* 6)、[可选]数据目录 + * `datadir="自己本地路径/Data/"`。 + * `datadir="D:/devSoft/MySQLServer5.5/Data/"`。 +* 7)、[可选]设置不要复制的数据库 + * `binlog-ignore-db=mysql`。 +* 8)、[可选]设置需要复制的数据库 + * `binlog-do-db=需要复制的主数据库名字`。 + +### 2、从机配置(linux的my.cnf) + +* [必须]从服务器唯一ID; +* [可选]启用二进制日志; + +![zhucong_2.png](images/zhucong_2.png) + +### 3、因修改过配置文件,请主机+从机都重启后台mysql服务 + +### 4、主从机都关闭linux防火墙 + +* windows手动关闭; +* 关闭虚拟机linux防火墙 `service iptables stop`; + +### 5、在Windows主机上建立帐户并授权slave + +* `GRANT REPLICATION SLAVE ON *.* TO 'zhangsan'@'从机器数据库IP' IDENTIFIED BY '123456';`。 +* 刷新一下配置`flush privileges;`。 +* 查询master的状态。 + * `show master status;` + * **记录下File和Position的值**; +* 执行完此步骤后不要再操作主服务器MYSQL,防止主服务器状态值变化。 + +![zhucong_3.png](images/zhucong_3.png) + +![zhucong_4.png](images/zhucong_4.png) + +### 6、在Linux从机上配置需要复制的主机 + +* 配置 + +```mysql +CHANGE MASTER TO MASTER_HOST='主机IP',MASTER_USER='zhangsan',MASTER_PASSWORD='123456',MASTER_LOG_FILE='File名字',MASTER_LOG_POS=Position数字; +``` + +* 启动从服务器复制功能,`start slave;`。 +* 查看配置 + * 下面两个参数都是Yes,则说明主从配置成功! + * `Slave_IO_Running: Yes`。 + * `Slave_SQL_Running: Yes`。 + +```mysql +CHANGE MASTER TO MASTER_HOST='192.168.124.3', +MASTER_USER='zhangsan', +MASTER_PASSWORD='123456', +MASTER_LOG_FILE='mysqlbin.具体数字',MASTER_LOG_POS=具体值; +``` + +![zhucong_5.png](images/zhucong_5.png) + +

+### 7、主机键表,看从机有没有 + + + +### 8、如何停止主从服务复制功能 + +在`linux`下面输入`stop slave;`。 \ No newline at end of file diff --git "a/DB/MySQL/advance/MYSQL\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" "b/DB/MySQL/advance/MYSQL\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" new file mode 100644 index 00000000..29bbbac7 --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\344\271\220\350\247\202\351\224\201\345\222\214\346\202\262\350\247\202\351\224\201.md" @@ -0,0 +1,38 @@ +# MYSQL乐观锁和悲观锁 + +转载: + +#### 悲观锁(Pessimistic Lock) + +------ + +悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。**通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁**。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。 + +这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题**是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引(Index),而不是全表扫描(ALL)**。 + +#### 乐观锁(Optimistic Lock) + +------ + +乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。 + +乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现: + +```mysql +1. SELECT data AS old_data, version AS old_version FROM …; +2. 根据获取的数据进行业务操作,得到new_data和new_version +3. UPDATE SET data = new_data, version = new_version WHERE version = old_version +if (updated row > 0) { + // 乐观锁获取成功,操作完成 +} else { + // 乐观锁获取失败,回滚并重试 +} +``` + +乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。**因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程**。 + +#### 总结 + +- 乐观锁在不发生取锁失败的情况下开销比悲观锁小,**但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能** +- 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方 + diff --git "a/DB/MySQL/advance/MYSQL\344\272\213\345\212\241\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" "b/DB/MySQL/advance/MYSQL\344\272\213\345\212\241\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" new file mode 100644 index 00000000..e0d1bfea --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\344\272\213\345\212\241\345\222\214\351\232\224\347\246\273\347\272\247\345\210\253.md" @@ -0,0 +1,106 @@ +# MYSQL事务和隔离级别 + +## 一、事务 + +事务是由一组SQL语句组成的逻辑处理单元,是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。事务具有以下4个属性,通常简称为事务的ACID属性: + +* 原子性(Atomicity):**事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行**。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 +* 一致性(Consistent):**在事务开始和完成时,数据都必须保持一致状态**。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。 以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。 +* 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。 **隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离**。即要达到这么一种效果:对于任意两个并发的事务 T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始之前就已经结束,要么在 T1 结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。 +* 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。  可以通过数据库备份和恢复来实现,在系统发生奔溃时,使用备份的数据库进行数据恢复。 + +> MySQL 默认采用**自动提交模式**。也就是说,如果不显式使用 `START TRANSACTION` 语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。 + +

+> 这几个特性不是一种平级关系: +> +> - 只有满足一致性,事务的执行结果才是正确的。 +> - 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时要只要能满足原子性,就一定能满足一致性。 +> - 在并发的情况下,多个事务并发执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 +> - 事务满足持久化是为了能应对数据库奔溃的情况。 + +## 二、并发一致性问题 + +### 1、更新丢失(Lost Update) + +**T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改**。 + +例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。 + +**如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题**。 + +

+### 2、脏读 + +一句话:事务B读取到了事务A已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果A事务回滚`Rollback`,B读取的数据无效,不符合一致性要求。 + +解决办法: 把数据库的事务隔离级别调整到 `READ_COMMITTED` + +**T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据**。 + +

+### 3、不可重复读(Non-Repeatable Reads) + + 在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。 + + **一句话:一个事务范围内两个相同的查询却返回了不同数据**。 + +同时操作,事务1分别读取事务2操作时和提交后的数据,读取的记录内容不一致。**不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果**。 + +解决办法: 如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。把数据库的事务隔离级别调整到`REPEATABLE_READ` + +**T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同**。 + +

+### 4、幻读 + +一个事务T1按相同的查询条件重新读取以前检索过的数据,却发现其他事务T2插入了满足其查询条件的新数据,这种现象就称为“幻读”。(和可重复读类似,但是事务 T2 的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致) + +一句话:事务A 读取到了事务B提交的新增数据,不符合隔离性。 + +解决办法: 如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题。把数据库的事务隔离级别调整到 `SERIALIZABLE_READ`。 + +T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 + +

+## 三、事务隔离级别 + +"脏读"、"不可重复读"和"幻读",其实都是数据库读一致性问题,**必须由数据库提供一定的事务隔离机制来解决**。 + + 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。 + +MYSQL常看当前数据库的事务隔离级别:`show variables like 'tx_isolation';` + +### 1、读未提交 (Read Uncommitted) + +最低的隔离等级,**允许其他事务看到没有提交的数据**,会导致脏读。 + +### 2、读已提交 (Read Committed) + +**被读取的数据可以被其他事务修改,这样可能导致不可重复读**。**也就是说,事务读取的时候获取读锁,但是在读完之后立即释放(不需要等事务结束),而写锁则是事务提交之后才释放**,释放读锁之后,就可能被其他事务修改数据。该等级也是 SQL Server 默认的隔离等级。 + +### 3、可重复读(Repeatable Read) + +**所有被 Select 获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况**。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,即前一个事务有读锁但是没有范围锁,为什么叫做可重复读等级呢?那是因为该等级解决了下面的不可重复读问题。(引申:现在主流数据库都使用 MVCC 并发控制,使用之后`RR`(可重复读)隔离级别下是不会出现幻读的现象。) + +MYSQL默认是`REPEATABLE-READ `。 + +### 4、串行化(Serializable) + +所有事务一个接着一个的执行,这样可以避免幻读 (phantom read),对于基于锁来实现并发控制的数据库来说,串行化要求在执行范围查询的时候,需要获取范围锁,如果不是基于锁实现并发控制的数据库,则检查到有违反串行操作的事务时,需回滚该事务。 + +### 5、总结 + +- 读未提交: **一个事务还没提交时,它做的变更就能被别的事务看到**。 +- 读提交: 一个事务提交**之后**,它做的变更**才**会被其他事务看到。 +- 可重复读 : **一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的**。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 +- 串行化: 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 + +四个级别逐渐增强,每个级别解决一个问题,事务级别越高,性能越差,大多数环境(`Read committed` 就可以用了) + +| 隔离级别 | 读数据一致性|脏读 | 不可重复读 | 幻影读 | +| -------- | -------- |---- | ---------- | ------ | +| 未提交读 | 最低级别| √ | √ | √ | +| 提交读 | 语句级 |× | √ | √ | +| 可重复读 | 事务级 |× | × | √ | +| 可串行化 | 最高级别,事务级|× | × | × | \ No newline at end of file diff --git "a/DB/MySQL/advance/MYSQL\345\237\272\346\234\254\346\236\266\346\236\204.md" "b/DB/MySQL/advance/MYSQL\345\237\272\346\234\254\346\236\266\346\236\204.md" new file mode 100644 index 00000000..ea076719 --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\345\237\272\346\234\254\346\236\266\346\236\204.md" @@ -0,0 +1,153 @@ +# Mysql基本架构 + + * [一、Mysql在Linux下的基本安装配置](#1mysql在linux下的基本安装配置) + * [二、主要配置文件](#2主要配置文件) + * [三、Mysql逻辑架构介绍](#3mysql逻辑架构介绍) + +## 一、Mysql在Linux下的基本安装配置 + +### 1、查看是否已经安装好 + +![pic.png](images/ad1_.png) + +### 2、Mysql的启动和停止 + +```shell +service mysql start # 启动 +service mysql stop # 停止 +# 另外一种启动停止的方式 +/etc/init.d/mysql start +/etc/init.d/mysql stop +``` + +### 3、在Linux下配置Mysql的密码 + +方式一(在mysql系统之外): +在mysql系统外: + +```shell +mysqladmin -u root -p password "test123" +Enter password: 【输入原来的密码】 +``` + +方式二(登录mysql): + +```shell +mysql -u root -p +Enter password: 【输入原来的密码】 +mysql>use mysql; +mysql> update user set password=password("test") where user='root'; +mysql> flush privileges; +mysql> exit; +``` + +### 4、Mysql在linux下的安装位置 + +| 路径 | 解释 | 备注 | +| ----------------- | ------------------------- | ---------------------------- | +| /var/lib/mysql/ | mysql数据库文件的存放路径 | /var/lib/mysql/ | +| /usr/share/mysql | 配置文件目录 | mysql.server命令以及配置文件 | +| /usr/bin | 相关命令目录 | mysqladmin mysqldump等命令 | +| /etc/init.d/mysql | 启停相关脚本 | | + +### 5、中文乱码问题 + +一个很重要的点: **乱码问题解决是你修改完配置之后再建的新的表,之前的表还是会是乱码**。 + +给一个博客讲解。 + +> https://www.2cto.com/database/201305/215563.html + +## 二、主要配置文件 + +* 二进制日志`log-bin` : 主从复制; +* 错误日志`log-error` : **默认是关闭的**,记录严重的警告和错误信息,每次启动和关闭的详细信息等; +* 查询日志`log` : 默认关闭,记录查询的`sql`语句,如果开启会减低`mysql`的整体性能,因为记录日志也是需要消耗系统资源的; +* 数据文件 + +![images/ad2_数据文件.png](images/ad2_数据文件.png) + +* 如何配置: ①`windows`: `my.ini`文件;②`linux`: `/etc/my.cnf`文件; + +数据库存放目录 + +`ps -ef|grep mysql` 可以看到: + +* 数据库目录: ` datadir=/var/lib/mysql ` +* pid文件目录: `--pid-file=/var/lib/mysql/bigdata01.pid` + +MySQL核心目录: + +```shell +/var/lib/mysql :mysql 安装目录 +/usr/share/mysql: 配置文件 +/usr/bin:命令目录(mysqladmin、mysqldump等) +/etc/init.d/mysql启停脚本 +``` + + MySQL配置文件 + +```shell +my-huge.cnf 高端服务器 1-2G内存 +my-large.cnf 中等规模 +my-medium.cnf 一般 +my-small.cnf 较小 +但是,以上配置文件mysql默认不能识别,默认只能识别 /etc/my.cnf +如果需要采用 my-huge.cnf : +cp /usr/share/mysql/my-huge.cnf /etc/my.cnf +注意:mysql5.5默认配置文件/etc/my.cnf;Mysql5.6 默认配置文件/etc/mysql-default.cnf +``` + +Mysql字符编码 + +```mysql +sql : show variables like '%char%' ; +可以发现部分编码是 latin,需要统一设置为utf-8 +设置编码: +vi /etc/my.cnf: +[mysql] +default-character-set=utf8 +[client] +default-character-set=utf8 + +[mysqld] +character_set_server=utf8 +character_set_client=utf8 +collation_server=utf8_general_ci + +重启Mysql: service mysql restart +sql : show variables like '%char%' ; +注意事项:修改编码 只对“之后”创建的数据库生效,因此 我们建议 在mysql安装完毕后,第一时间 统一编码。 +``` +## 三、Mysql逻辑架构介绍 + +Mysql逻辑架构图如下: + +

+ +下面是mysql官网的体系图: + +

+ + + +![ad7_a.png](images/ad7_a.png) + +各层的基本概述 + +> 1.连接层 +> +> 最上层是一些**客户端和连接服务**,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于`tcp/ip`的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了**线程池**的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。 +> +> 2.服务层 +> +> 第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成**缓存的查询,SQL的分析和优化及部分内置函数的执行**。所有跨存储引擎的功能也在这一层实现,如**过程、函数**等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序,是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。 +> +> 3.引擎层 +> +> 存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过APl与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。 +> +> 4.存储层 +> +> 数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。 + diff --git "a/DB/MySQL/advance/MYSQL\345\255\230\345\202\250\345\274\225\346\223\216.md" "b/DB/MySQL/advance/MYSQL\345\255\230\345\202\250\345\274\225\346\223\216.md" new file mode 100644 index 00000000..13b7798b --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\345\255\230\345\202\250\345\274\225\346\223\216.md" @@ -0,0 +1,33 @@ +# Mysql存储引擎 + +查看数据库存储引擎两个命令: + +```mysql +show engines; +show variables like '%storage engine%';// 查看默认的存储引擎 +``` + +演示: + +![ad6_查看存储引擎.png](images/ad6_查看存储引擎.png) + +关于MyISAM和InnoDB的简单对比: + +![ad5_存储引擎对比.png](images/ad5_存储引擎对比.png) + + 问:MyISAM和InnoDB引擎的区别 + + - MyISAM 不支持外键,而 InnoDB 支持 + - MyISAM 是非事务安全型的,而 InnoDB 是事务安全型的。 + - **MyISAM 锁的粒度是表级,而 InnoDB 支持行级锁定**。 + - MyISAM 支持全文类型索引,而 InnoDB 不支持全文索引。 + - MyISAM 相对简单,所以在效率上要优于 InnoDB,小型应用可以考虑使用 MyISAM。 + - MyISAM 表是保存成文件的形式,在跨平台的数据转移中使用 MyISAM 存储会省去不少的麻烦。 + - InnoDB 表比 MyISAM 表更安全,可以在保证数据不会丢失的情况下,切换非事务表到事务表(`alter table tablename type=innodb`)。 + - 锁的区别: + * InnoDB(默认) :事务优先 (适合高并发操作;行锁) + * MyISAM :性能优先 (表锁) + + 应用场景: + * MyISAM 管理非事务表。它提供高速存储和检索,以及全文搜索能力。如果应用中需要执行大量的 SELECT 查询,那么 MyISAM 是更好的选择。 + * InnoDB 用于事务处理应用程序,具有众多特性,包括 ACID 事务支持。如果应用中需要执行大量的 INSERT 或 UPDATE 操作,则应该使用 InnoDB,这样可以提高多用户并发操作的性能。 diff --git "a/DB/MySQL/advance/MYSQL\347\264\242\345\274\225.md" "b/DB/MySQL/advance/MYSQL\347\264\242\345\274\225.md" new file mode 100644 index 00000000..3fe54dd0 --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\347\264\242\345\274\225.md" @@ -0,0 +1,954 @@ +# MYSQL索引部分 + +### 1、性能下降分析 + +需要优化的原因:性能低、执行时间太长、等待时间太长、SQL语句欠佳(连接查询)、索引失效、服务器参数设置不合理(缓冲、线程数) 。 + +

+ +先看SQL执行的顺序: + +```mysql +编写过程: +select dinstinct ..from ..join ..on ..where ..group by ...having ..order by ..limit .. + +解析过程: +from .. on.. join ..where ..group by ....having ...select dinstinct ..order by limit ... +``` + +解析图: + +![ad9_索引.png](images/ad9_索引.png) +> 详细参考这篇博客: https://www.cnblogs.com/annsshadow/p/5037667.html + +SQL优化, 主要就是在优化索引 + +* 相当于书的目录; +* index是帮助MYSQL高效获取数据的数据结构。索引是数据结构(树:B树(默认)、Hash树...); + +### 2、索引优缺点 + +索引的弊端: + + * 索引本身很大, 实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的; + * 索引不是所有情况均适用: a. 少量数据,b.频繁更新的字段,c.很少使用的字段 + * 索引会降低增删改的效率;MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段, 都会调整因为更新所带来的键值变化后的索引信息。 + + 优势: + + * 提高查询效率(降低IO使用率) + * 降低CPU使用率 (...order by age desc,因为 B树索引 本身就是一个 好排序的结构,因此在排序时 可以直接使用) + +### 3、索引分类 + +* 主键索引: 不能重复。**id 不能是null** (设定为主键后数据库会**自动建立索引**,innodb为聚簇索引); +* 唯一索引 :不能重复。**id 可以是null** +* 单值索引 : 单列, 一个表可以多个单值索引。 +* 复合索引 :多个列构成的索引 (相当于二级目录 : z: zhao) (name,age) (a,b,c,d,...,n) + +创建索引的两种方式: + +```mysql +创建索引: + 方式一(创建): + create 索引类型 索引名 on 表(字段) + 单值(普通索引): + create index dept_index on tb(dept); + 唯一: + create unique index name_index on tb(name) ; + 复合索引 + create index dept_name_index on tb(dept,name); + + 方式二(添加): + alter table 表名 索引类型 索引名(字段) + 主键索引: + ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) + 单值: + alter table tb add index dept_index(dept) ; + 唯一: + alter table tb add unique index name_index(name); + 复合索引 + alter table tb add index dept_name_index(dept,name); + 全文索引 + ALTER TABLE `table_name` ADD FULLTEXT ( `column`) + + 注意:如果一个字段是primary key,则改字段默认就是 主键索引 +``` + +删除索引 + +```mysql +删除索引: +drop index 索引名 on 表名 ; +drop index name_index on tb ; +``` +查询索引 +```mysql +查询索引: +show index from 表名 ; +show index from 表名 \G +``` + +### 4、哪些情况需要建立索引,哪些不需要 + +需要建立索引的情况: + +* 主键自动建立唯一索引(`primary key`); +* 频繁作为查询条件的字段应该创建索引(`where` 后面的语句); +* 查询中与其它表关联的字段,**外键关系建立索引**; +* 单键/组合索引的选择问题,`who?`(在高并发下倾向创建组合索引); +* 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度; +* 查询中统计或者分组字段;(`group by....`) + +不需要建立索引的情况: + +* 表记录太少; +* 经常增删改的表; +* **Where条件里用不到的字段不创建索引**; +* 数据重复且**分布平均**的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。 注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果(有一个比值,不同的个数和总个数的比值越大越好); + +### 5、Explain + +具体可以参考这篇博客: [https://blog.csdn.net/drdongshiye/article/details/84546264](#https://blog.csdn.net/drdongshiye/article/details/84546264)。 + +#### 1)、概念和作用 + +概念: 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是 如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈; + +作用: + +* 表的读取顺序; +* 哪些索引可以使用; +* 哪些索引被实际使用; +* 数据读取操作的**操作类型**; +* 表之间的引用; +* 每张表有多少行被优化器查询; + +#### 2)、id + +表示:**`select`查询的序列号**,包含一组数字,表示**查询中执行select子句或操作表的顺序**。 + +分为三种情况: + +a)、第一种情况: **id相同,执行顺序由上至下**。 + +此例中 先执行where 后的第一条语句 `t1.id = t2.id` 通过 `t1.id` 关联 `t2.id` 。 而 t2.id 的结果建立在 `t2.id=t3.id` 的基础之上。 + +b)、**id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行**。 + +c)、id相同不同,同时存在。 + +id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行。 + +衍生表 = `derived2 --> derived + 2` (2 表示由 id =2 的查询衍生出来的表。type 肯定是 all ,因为衍生的表没有建立索引) + +#### 3)、select_type + +![ad14_.png](images/ad14_.png) + +#### 4)、type + +![ad15_.png](images/ad15_.png) + +#### 5)、possible_keys和key + +possible_keys : 显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。 + +key: + +* 实际使用的索引。如果为NULL,则没有使用索引; +* 查询中若使用了**覆盖索引**,则该索引和查询的select字段重叠; + +> 覆盖索引: +> +> 如果一个索引包含 (或者说覆盖) 所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在InnoDB存情引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要"回表",也就是要通过主键再查找一次。这样就会比较慢。 +> +> 覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! +> +> 现在我创建了索引(username,age),在查询数据的时候: `select username , age fromuser where username = Java' and age = 22`。要查词出的列在叶子节点都存在! 所以就不要回表。 + +#### 6)、key_len、ref、rows + +key_len + +* 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。 +* **在不损失精确性的情况下,长度越短越好**。 +* **key_len字段能够帮你检查是否充分的利用上了索引**。 +* **具体使用到了多少个列的索引,这里就会计算进去**,没有使用到的列,这里不会计算进去。留意下这个列的值,算一下你的多列索引总长度就知道有没有使用到所有的列了。 + +ref: + +* **显示索引的哪一列被使用了,如果可能的话,是一个常数**。哪些列或常量被用于查找索引列上的值; + +rows: + +* **rows列显示MySQL认为它执行查询时必须检查的行数**。 +* 越少越好; + +#### 7)、Extra + +![ad16_5104.png](images/ad16_5104.png) + +#### 8)、检测 + +![ad17_.png](images/ad17_.png) + +答案: + +![ad18_.png](images/ad18_.png) + +### 6、SQL优化实战 + +#### 1)、实战一-单表 + +建表SQL: + +```mysql +CREATE TABLE IF NOT EXISTS `article`( + +`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, +`author_id` INT(10) UNSIGNED NOT NULL, +`category_id` INT(10) UNSIGNED NOT NULL, +`views` INT(10) UNSIGNED NOT NULL, +`comments` INT(10) UNSIGNED NOT NULL, +`title` VARBINARY(255) NOT NULL, +`content` TEXT NOT NULL +); + +INSERT INTO `article` (author_id,category_id,views,comments,title,content) VALUES +(1,1,1,1,1,1), +(2,2,2,2,2,2), +(1,1,3,3,3,3); +``` + +表中内容: + +

+ +实战一: + +查询 `categoryid` 为1 且 `comments` 大于 1 的情况下,views 最多的文章。 + +![ad_20.png](images/ad_20.png) + +完整代码: + +```mysql +mysql> select id, author_id from article where category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1; ++----+-----------+ +| id | author_id | ++----+-----------+ +| 3 | 1 | ++----+-----------+ +1 row in set (0.01 sec) + +mysql> explain select id, author_id from article where category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G +^[[A*************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: article + partitions: NULL + type: ALL +possible_keys: NULL + key: NULL + key_len: NULL + ref: NULL + rows: 3 + filtered: 33.33 + Extra: Using where; Using filesort +1 row in set, 1 warning (0.00 sec) + +``` + +第一版优化,建立索引: + +![ad20_.png](images/ad20_.png) + +代码: + +```mysql +mysql> create index idx_article_ccv on article(category_id, comments, views); +Query OK, 0 rows affected (0.20 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> explain select id, author_id from article where category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G +*************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: article + partitions: NULL + type: range +possible_keys: idx_article_ccv + key: idx_article_ccv + key_len: 8 + ref: NULL + rows: 1 + filtered: 100.00 + Extra: Using index condition; Using filesort +1 row in set, 1 warning (0.02 sec) + +``` + +结论: +type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。 +但是我们已经建立了索引为啥没用呢? 这是因为按照 BTree 索引的工作原理: +先排序 category_id, 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。当 comments 字段在联合素引里处于中间位置时,因comments > 1 条件是一个范围值(所谓 range), +MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。 + +第二版: 先删除上面那个不是很好的索引,然后只建立`(category_id, views)`之间的索引,而没有`comments`: + +```mysql +mysql> drop index idx_article_ccv on article; +Query OK, 0 rows affected (0.09 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> create index article_cv on article(category_id, views); +Query OK, 0 rows affected (0.08 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> explain select id, author_id from article where category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1\G +*************************** 1. row *************************** + id: 1 + select_type: SIMPLE + table: article + partitions: NULL + type: ref +possible_keys: article_cv + key: article_cv + key_len: 4 + ref: const + rows: 2 + filtered: 33.33 + Extra: Using where +1 row in set, 1 warning (0.00 sec) + +``` + +结论: 可以看到type变成了`ref`,Extra中的`Using fileSort`也消失了,结果非常理想。 + +#### 2)、实战二-双表 + +两个表: + +

+ +使用 + +```mysql +mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card; ++----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL | +| 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | Using where; Using join buffer (Block Nested Loop) | ++----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +2 rows in set, 1 warning (0.04 sec) + +``` + +结论:type 有All,不是很好。 + +可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。 + +这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,**所以右边是我们的关键点,一定需要建立索引**。(如果将索引建立在左边,不会有这么好)。 + +```mysql +mysql> ALTER TABLE `book` ADD INDEX Y ( `card`); +Query OK, 0 rows affected (0.12 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card; ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL | +| 1 | SIMPLE | book | NULL | ref | Y | Y | 4 | mysqlad.class.card | 1 | 100.00 | Using index | ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +2 rows in set, 1 warning (0.00 sec) + +``` + +上面的索引建立在右边的表(`book`)。下面如果我们建立在`class`表,并使用左连接,就不会有这么好的效果,如下: + +```mysql +mysql> DROP INDEX Y ON book; +Query OK, 0 rows affected (0.05 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> ALTER TABLE class ADD INDEX X (card); +Query OK, 0 rows affected (0.07 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> +mysql> EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card; ++----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +| 1 | SIMPLE | class | NULL | index | NULL | X | 4 | NULL | 20 | 100.00 | Using index | +| 1 | SIMPLE | book | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | Using where; Using join buffer (Block Nested Loop) | ++----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+----------------------------------------------------+ +2 rows in set, 1 warning (0.00 sec) + + +``` + +所以总结: + +* 1、保证**被驱动表的join字段已经被索引**。被驱动表  join 后的表为被驱动表  (需要被查询); +* 2、left join 时,选择小表作为驱动表,大表作为被驱动表(建立索引的表)。但是 left join 时一定是左边是驱动表,右边是被驱动表。 +* 3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。 +* 4、**子查询尽量不要放在被驱动表**,有可能使用不到索引。 + +#### 3)、实战三-三表 + +![ad_22.png](images/ad_22.png) + +建立索引后的查询: + +```mysql +mysql> alter table phone add index z(card); +Query OK, 0 rows affected (0.09 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> alter table book add index y(card); +Query OK, 0 rows affected (0.06 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +mysql> explain select * from class left join book on class.card=book.card left join phone on book.card=phone.card; ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +| 1 | SIMPLE | class | NULL | ALL | NULL | NULL | NULL | NULL | 20 | 100.00 | NULL | +| 1 | SIMPLE | book | NULL | ref | y | y | 4 | mysqlad.class.card | 1 | 100.00 | Using index | +| 1 | SIMPLE | phone | NULL | ref | z | z | 4 | mysqlad.book.card | 1 | 100.00 | Using index | ++----+-------------+-------+------------+------+---------------+------+---------+--------------------+------+----------+-------------+ +3 rows in set, 1 warning (0.00 sec) + +``` + +结论: 后2行的`type`都是`ref`且总`rows`优化很好,效果不错,因此索引最好设置在需要经常查询的字段中。 + +相关建索引建议: + +* 1、保证被驱动表的join字段已经被索引; +* 2、left join 时,选择小表作为驱动表,大表作为被驱动表; +* 3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表; +* 4、子查询尽量不要放在被驱动表,有可能使用不到索引; + +### 7、索引失效(应该避免) + +表: + +

+ +建表语句: + +```mysql +CREATE TABLE staffs ( + id INT PRIMARY KEY AUTO_INCREMENT, + NAME VARCHAR (24) NULL DEFAULT '' COMMENT '姓名', + age INT NOT NULL DEFAULT 0 COMMENT '年龄', + pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位', + add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间' +) CHARSET utf8 COMMENT '员工记录表' ; + +INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW()); +INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW()); +INSERT INTO staffs(NAME,age,pos,add_time) VALUES('2000',23,'dev',NOW()); +INSERT INTO staffs(NAME,age,pos,add_time) VALUES(null,23,'dev',NOW()); + +ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos); +``` + +#### 1)、全值匹配我以及最佳前缀匹配 + +索引 idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的 + +```mysql +EXPLAIN SELECT * FROM staffs WHERE NAME = 'July'; + +EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25; + +EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev'; + +``` + +结果: + +![ad23_.png](images/ad23_.png) + +但是如果我们只有`age和pos`或者只有`pos`, 查询结果就会很差,所以这就是**最佳左前缀法则**。 + +```mysql +mysql> explain select * from staffs where age=25 and pos='dev'; ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where | ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +1 row in set, 1 warning (0.00 sec) + +mysql> explain select * from staffs where pos='dev'; ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 25.00 | Using where | ++----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+ +1 row in set, 1 warning (0.00 sec) + +``` + + **如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列**。 + +再看中间断的情况: + +```mysql +mysql> explain select * from staffs where name='July' and pos='dev'; ++----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+ +| 1 | SIMPLE | staffs | NULL | ref | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 75 | const | 1 | 25.00 | Using index condition | ++----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+ +1 row in set, 1 warning (0.00 sec) + +``` + +结论: 只用到了第一个。中间断了。 + +#### 2)、不在索引列上做任何操作 + +**不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描**。 + +下面在`name`使用了`left()`函数,就会失效。 + +![ad_24.png](images/ad_24.png) + +#### 3)、存储引擎不能使用索引中范围条件右边的列 + +如果中间出现了范围的,就会变成`range`。后面就会失效: + +![ad24_.png](images/ad24_.png) + +#### 4)、尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select * + +不用`select * `,而是`select `具体的字段。 + +![ad25_.png](images/ad25_.png) + +#### 5)、使用不等于(!=或者<>)的时候无法使用索引 + +![ad26_.png](images/ad26_.png) + +但是如果业务需要必须要写的话,那也没办法。 + +#### 6)、like以通配符开头('%abc...')mysql索引失效会变成全表扫描(最好在右边写%) + +![ad27_.png](images/ad27_.png) + + **问题:解决like '%字符串%'时索引不被使用的方法??** + +答: **使用覆盖索引**。 + +```mysql +#before index + +# 第一批 (建最下面的索引后可以被优化) +EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT NAME FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT age FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT id,NAME FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%'; + +# 第二批: 搅屎棍 + +EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%'; +EXPLAIN SELECT id,NAME,age,email FROM tbl_user WHERE NAME LIKE '%aa%'; + +#create index (上面的字符第一批在键了下面的索引后会优化,但是第二批搅屎棍不会,因为覆盖不了) +# 为啥第一批的id也能优化,因为Extra中的 Using Index (主键本身也是索引) + +CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age); +``` + +#### 7)、字符串不加单引号索引失效(发生了类型转换) + +![ad28_.png](images/ad28_.png) + +#### 8)、总结和练习 + +![ad_29.png](images/ad_29.png) + +索引建议总结: + +* 1、对于单键索引,尽量选择针对当前query过滤性更好的索引; +* 2、在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。(避免索引过滤性好的索引失效); +* 3、在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引; +* 4、尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的; + +再来一波练习: + +```mysql +mysql> select * from test03; # 表 ++----+------+------+------+------+------+ +| id | c1 | c2 | c3 | c4 | c5 | ++----+------+------+------+------+------+ +| 1 | a1 | a2 | a3 | a4 | a5 | +| 2 | b1 | b2 | b3 | b4 | b5 | +| 3 | c1 | c2 | c3 | c4 | c5 | +| 4 | d1 | d2 | d3 | d4 | d5 | +| 5 | e1 | e2 | e3 | e4 | e5 | ++----+------+------+------+------+------+ +5 rows in set (0.02 sec) + +mysql> create index idx_test03_c1234 on test03(c1,c2,c3,c4); # 创建索引 +Query OK, 0 rows affected (0.12 sec) +Records: 0 Duplicates: 0 Warnings: 0 + +# 1、全值匹配我最爱 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4'; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | 100.00 | NULL | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +1 row in set, 1 warning (0.31 sec) + +# 2、这种情况Mysql会底层会帮我们自动优化 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3'; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 124 | const,const,const,const | 1 | 100.00 | NULL | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------------------+------+----------+-------+ +1 row in set, 1 warning (0.00 sec) + +# 3、 中间阶段 -> range +mysql> explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4'; ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +| 1 | SIMPLE | test03 | NULL | range | idx_test03_c1234 | idx_test03_c1234 | 93 | NULL | 1 | 20.00 | Using index condition | ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +1 row in set, 1 warning (0.00 sec) + +# 4、这个比上面那个好,多用了一个(key_len会大一点),因为Mysql底层会调优将c4>'a4'放在后面 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3'; ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +| 1 | SIMPLE | test03 | NULL | range | idx_test03_c1234 | idx_test03_c1234 | 124 | NULL | 1 | 100.00 | Using index condition | ++----+-------------+--------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------+ +1 row in set, 1 warning (0.00 sec) + +# 5、注意 : c3作用在排序而不是查找 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 20.00 | Using index condition | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +1 row in set, 1 warning (0.01 sec) + +# 6、 和5一模一样 +mysql> explain select * from test03 where c1='a1' and c2='a2' order by c3; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 100.00 | Using index condition | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +1 row in set, 1 warning (0.00 sec) + +# 6、出现了Using filesort +mysql> explain select * from test03 where c1='a1' and c2='a2' order by c4; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+---------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+---------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 100.00 | Using index condition; Using filesort | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+---------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 8.1、 只用c1一个字段索引,但是c2、c3用于排序,所有没有 filesort +mysql> explain select * from test03 where c1='a1' and c5='a5' order by c2,c3; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | 20.00 | Using index condition; Using where | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 8.2、 出现了filesort,我们建的索引是1234,它没有按照顺序来,3,2 颠倒了 +mysql> explain select * from test03 where c1='a1' and c5='a5' order by c3,c2; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | 20.00 | Using index condition; Using where; Using filesort | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 9、 +mysql> explain select * from test03 where c1='a1' and c2='a2' order by c2,c3; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 100.00 | Using index condition | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+-----------------------+ +1 row in set, 1 warning (0.00 sec) + +# 10.1、 和c5这个坑爹货没关系 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 20.00 | Using index condition; Using where | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 10.2、 这里排序字段已经是一个常量 和8.2不同 +mysql> explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 62 | const,const | 1 | 20.00 | Using index condition; Using where | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------------+------+----------+------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 本例有常量c2的情况,和8.2对比 filesort (下面是8.2的) +mysql> explain select * from test03 where c1='a1' and c5='a5' order by c3,c2; ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +| 1 | SIMPLE | test03 | NULL | ref | idx_test03_c1234 | idx_test03_c1234 | 31 | const | 1 | 20.00 | Using index condition; Using where; Using filesort | ++----+-------------+--------+------------+------+------------------+------------------+---------+-------+------+----------+----------------------------------------------------+ +1 row in set, 1 warning (0.00 sec) + + +# 11、group 虽然是分组,但是分组之前必排序 (可能导致临时表) + explain select * from test03 where c1='a1' and c4='a4' group by c2,c3; # +# 12、灭绝师太!!! Using temporary; Using filesort + explain select * from test03 where c1='a1' and c4='a4' group by c3,c2; +``` + +### 8、order by关键字排序优化 + +**主要讨论order by**会不会产生`fileSort`。 + +**MySQL支持二种方式的排序,FileSort和Index,Index效率高。它指MySQL扫描索引本身完成排序。FileSort方式效率较低**。 + +ORDER BY满足两情况,会使用Index方式排序: + +* ORDER BY 语句使用索引最左前列; +* 使用Where子句与Order BY子句条件列组合满足索引最左前列; +* where子句中如果出**现索引的范围查询(即explain中出现range)会导致order by 索引失效**。 + +ORDER BY子句,**尽量使用Index方式排序,避免使用FileSort方式排序** + +测试: 键表语句: + +```mysql +CREATE TABLE tblA( + id int primary key not null auto_increment, + age INT, + birth TIMESTAMP NOT NULL, + name varchar(200) +); + +INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc'); +INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd'); +INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def'); + +CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name); + +表的内容 +mysql> select * from tblA; ++----+------+---------------------+------+ +| id | age | birth | name | ++----+------+---------------------+------+ +| 1 | 22 | 2019-03-21 19:10:29 | abc | +| 2 | 23 | 2019-03-21 19:10:29 | bcd | +| 3 | 24 | 2019-03-21 19:10:29 | def | ++----+------+---------------------+------+ +3 rows in set (0.00 sec) +``` + +注意我们建立的索引是`(age, birth, name)`。 + +然后看下面的查询,当我们`order by birth`或者`order by birth,age`的时候,就会出现`Using fileSort`: + +```mysql +# 1、没有产生Using FileSort +mysql> explain select * from tblA where age>20 order by age; ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +| 1 | SIMPLE | tblA | NULL | index | idx_A_ageBirth | idx_A_ageBirth | 612 | NULL | 3 | 100.00 | Using where; Using index | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 2、没有产生Using FileSort +mysql> explain select * from tblA where age>20 order by age, birth; ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +| 1 | SIMPLE | tblA | NULL | index | idx_A_ageBirth | idx_A_ageBirth | 612 | NULL | 3 | 100.00 | Using where; Using index | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+--------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 3、产生了Using FileSort +mysql> explain select * from tblA where age>20 order by birth; ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +| 1 | SIMPLE | tblA | NULL | index | idx_A_ageBirth | idx_A_ageBirth | 612 | NULL | 3 | 100.00 | Using where; Using index; Using filesort | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +# 4、产生了Using FileSort +mysql> explain select * from tblA where age>20 order by birth, age; ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +| 1 | SIMPLE | tblA | NULL | index | idx_A_ageBirth | idx_A_ageBirth | 612 | NULL | 3 | 100.00 | Using where; Using index; Using filesort | ++----+-------------+-------+------------+-------+----------------+----------------+---------+------+------+----------+------------------------------------------+ +1 row in set, 1 warning (0.00 sec) + +``` + +还要注意一个`ASC和DESC`的问题: + +![ad29_.png](images/ad29_.png) + +提高Order by速度: + +1)、Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是: + +* 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。 + +* 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。 + +2)、 尝试提高` sort_buffer_size` + +不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 + +3)、尝试提高 `max_length_for_sort_data` + +提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率. + +![ad_31.png](images/ad_31.png) + +总结: **尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀**。 + +

+ +第二种中,`where a = const and b > const order by b , c` 不会出现 using filesort b , c 两个衔接上了 + +但是:`where a = const and b > const order by c `将会出现 using filesort 。因为 b 用了范围索引,断了。而上一个 order by 后的b 用到了索引,所以能衔接上 c 。 + +### 9、B+Tree与B-Tree 的区别 + +结论在内存有限的情况下,B+TREE 永远比 B-TREE好。无限内存则后者方便。 + +* 1)、B-树的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;**B+树叶子节点中只有关键字和指向下一个节点的索引**,记录只放在叶子节点中。(一次查询可能进行两次i/o操作) + +* 2)、在B-树中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而B+树中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。从这个角度看B-树的性能好像要比B+树好,而在实际应用中却是B+树的性能要好些。因为B+树的非叶子节点不存放实际的数据,**这样每个节点可容纳的元素个数比B-树多**,树高比B-树小,这样带来的好处是减少磁盘访问次数。尽管B+树找到一个记录所需的比较次数要比B-树多,但是一次磁盘访问的时间相当于成百上千次内存比较的时间,因此实际中B+树的性能可能还会好些,**而且B+树的叶子节点使用指针连接在一起,方便顺序遍历**(例如查看一个目录下的所有文件,一个表中的所有记录等),这也是很多数据库和文件系统使用B+树的缘故。 + +思考:为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引? + +1) B+树的磁盘读写代价更低 + +  **B+树的内部结点并没有指向关键字具体信息的指针**。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 + +2) B+树的查询效率更加稳定 + +  由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。 + +> 索引建立成哪种索引类型? +> +> 根据数据引擎类型自动选择的索引类型 +> +> * 除开 innodb 引擎主键默认为聚簇索引 外。 Innodb的索引都采用的 B+TREE。 +> * MyIsam 则都采用的 **B-TREE**索引。 + +### 10、聚簇索引和非聚簇索引 + +聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。 + +**术语‘聚簇’表示数据行和相邻的键值进错的存储在一起**。 + + 如下图,左侧的索引就是聚簇索引,因为**数据行在磁盘的排列和索引排序保持一致。** + +

+ +聚簇索引优点 : 按照聚簇索引排列顺序,查询显示一定范围数据的时候,**由于数据都是紧密相连,数据库不用从多个数据块中提取数据**,所以节省了大量的io操作。 + +聚簇索引限制 : + +- 对于mysql数据库目前只有innodb数据引擎支持聚簇索引,而MyIsam并不支持聚簇索引。 +- 由于*数据物理存储排序方式只能有一种*,所以每个Mysql的表只能有一个聚簇索引。一般情况下就是该表的**主键**。 +- **为了充分利用聚簇索引的聚簇的特性,所以innodb表的主键列尽量选用有序的顺序id,而不建议用无序的id,比如uuid这种。(参考聚簇索引优点。)** + +### 11、全文索引、Hash索引 + +**全文索引** + +* MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。 +* 查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 +* 全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。 + +InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 + +```mysql +不同于like方式的的查询: +SELECT * FROM article WHERE content LIKE ‘%查询字符串%’; + +全文索引用match+against方式查询:(明显的提高查询效率。) +SELECT * FROM article WHERE MATCH(title,content) AGAINST (‘查询字符串’); + +``` + +**Hash索引** + +哈希索引能以 O(1) 时间进行查找,但是失去了有序性: + +- **无法用于排序与分组**; +- 只支持精确查找,无法用于部分查找和范围查找。 + +InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 + +### 12、查询截取分析 + +优化SQL步骤: + +* 1)、观察,至少跑一天,看看生产的慢SQL情况; +* 2)、开启慢查询日志,设置阙值,比如超过5秒钟的就是慢SQL,并将它抓取出来; +* 3)、explain+ 慢SQL分析; +* 4)、`show profile`; + +即: + +* 1)、慢查询的开启并捕获; +* 2)、explain+慢SQL分析; +* 3)、show profile查询SQL在MYSQL服务器里面的执行细节和生命周期情况; +* 4)、SQL数据库服务器的参数调优; + +原则: **小表驱动大表**。 + +GROUP BY关键字优化: + +* group by实质是先排序后进行分组,遵照索引建的最佳左前缀; +* 当无法使用索引列,增大`max_length_for_sort_data`参数的设置+增大`sort_buffer_size`参数的设置; +* where高于having,能写在where限定的条件就不要去having限定了; + + + + + + + + + + + + + diff --git "a/DB/MySQL/advance/MYSQL\351\224\201\346\234\272\345\210\266.md" "b/DB/MySQL/advance/MYSQL\351\224\201\346\234\272\345\210\266.md" new file mode 100644 index 00000000..c3667870 --- /dev/null +++ "b/DB/MySQL/advance/MYSQL\351\224\201\346\234\272\345\210\266.md" @@ -0,0 +1,260 @@ +# MYSQL锁机制 + +* [一、锁概述和分类](#一锁概述和分类) +* [二、表锁](#二表锁) +* [三、行锁](#三行锁) +* [四、优化建议](#四优化建议) + +## 一、锁概述和分类 + +![ad42_.png](images/ad42_.png) + +## 二、表锁 + +偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 + +【手动增加表锁】 + +```mysql +lock table 表名字1 read(write),表名字2 read(write),其它; +``` + +【查看表上加过的锁】 + +```mysql +show open tables; +``` + +【释放表锁】 + +```mysql +unlock tables; +``` + +演示: + +```mysql +mysql> select * from mylock; ++----+------+ +| id | name | ++----+------+ +| 1 | a | +| 2 | b | +| 3 | c | +| 4 | d | +| 5 | e | ++----+------+ +5 rows in set (0.00 sec) + +# 给mylock表加读锁,给t1表加写锁 +mysql> lock table mylock read, t1 write; +Query OK, 0 rows affected (0.02 sec) + +# 查看已经加锁的表, 下面的结果省略了很多行 +mysql> show open tables; ++--------------------+------------------------------------------------------+--------+-------------+ +| Database | Table | In_use | Name_locked | ++--------------------+------------------------------------------------------+--------+-------------+ +| mysqlad | t1 | 1 | 0 | +| performance_schema | events_transactions_current | 0 | 0 | +| performance_schema | events_statements_summary_by_program | 0 | 0 | +| performance_schema | events_waits_summary_by_host_by_event_name | 0 | 0 | +| mysqlad | mylock | 1 | 0 | +| performance_schema | file_sum +121 rows in set (0.00 sec) + +# 释放表锁 +mysql> unlock tables; +Query OK, 0 rows affected (0.00 sec) + +``` +读锁案例:下面通过两个会话窗口来演示对`mylock`表加读锁之后的效果: + +| session_1 | session_2 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 获得表mylock的READ锁定
![ad31.png](images/ad31.png) | 连接终端 | +| 当前session_1可以查询该表记录
![ad32_.png](images/ad32_.png) | 其他session(session_2)也可以查询该表
![ad33_.png](images/ad33_.png) | +| 当前session_1不能查询其它没有锁定的表。
![ad34_.png](images/ad34_.png) | 其他session_2可以查询或者更新未锁定的表
![ad35_.png](images/ad35_.png) | +| 当前session_1中插入或者更新锁定的表都会提示错误:
![ad36_.png](images/ad36_.png) | 其他session_2插入或者更新锁定表**会一直等待**获得锁:(阻塞)
![ad37_.png](images/ad37_.png) | +| 释放锁。
mysql> unlock tables; | **session_2**立即释放阻塞,马上获得锁。
![ad38_.png](images/ad38_.png) | + +演示对`mylock`加写锁: + +| seession_1 | session_2 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 获得表mylock的WRITE锁定, 当前session对锁定表的查询+更新+插入操作都可以执行:
![ad39_.png](images/ad39_.png) | 其他session对锁定表的查询被阻塞,
需要等待锁被释放:

在锁表前,如果session2有数据缓存,
锁表以后,在锁住的表不发生改变的情况下
session2可以读出缓存数据,一旦数据发生改变,缓存将失效,操作将被阻塞住。 | +| 释放锁
mysql> unlock tables; | session_2立即被释放得到锁
![ad41_.png](images/ad41_.png) | + +通过上面的实验,可以发现: + +MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。 + +MySQL的表级锁有两种模式: + +- 表共享读锁(Table Read Lock) +- 表独占写锁(Table Write Lock) + +| 锁类型 | 他人可读 | 他人可写 | +| ------ | -------- | -------- | +| 读锁 | 是 | 否 | +| 写锁 | 否 | 否 | + + 结合上表,所以对MyISAM表进行操作,会有以下情况: + +* 1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。 +* 2、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。 + +简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。 + +总结: + +* 可以通过`show open tables`来查看哪些表被枷锁了; +* 如何分析表锁定,可以通过检查`table_locks_waited`和`table_locks_immediate`状态变量来分析系统上的表锁定; + +

+ +这里有两个状态变手记录MySQL内部表级锁定的情况,两个变量说明如下: + +`Table_locks_immediate`: 产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1 ; + +`Table_locks_waited`: 出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况; + +总结: MyISAM的读写锁调度是**写**优先,这也是MyISAM不适合做写为主表的引擎。 因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。 + +## 三、行锁 + +特点: + +* 偏向InnoDB存储引擎,开销大,加锁慢; +* 会出现死锁; +* 锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 + +> InnoDB与MyISAM的最大不同有两点:**一是支持事务(TRANSACTION);二是采用了行级锁**。 +> +> **Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了**。 +> +> 但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。 + +演示案例,建表SQL: + +```mysql +create table test_innodb_lock (a int(11),b varchar(16))engine=innodb; + +insert into test_innodb_lock values(1,'b2'); +insert into test_innodb_lock values(3,'3'); +insert into test_innodb_lock values(4,'4000'); +insert into test_innodb_lock values(5,'5000'); +insert into test_innodb_lock values(6,'6000'); +insert into test_innodb_lock values(7,'7000'); +insert into test_innodb_lock values(8,'8000'); +insert into test_innodb_lock values(9,'9000'); +insert into test_innodb_lock values(1,'b1'); + +# 创建两个索引 +create index test_innodb_a_ind on test_innodb_lock(a); +create index test_innodb_lock_b_ind on test_innodb_lock(b); + +# 查询结果 +mysql> select * from test_innodb_lock; ++------+------+ +| a | b | ++------+------+ +| 1 | b2 | +| 3 | 3 | +| 4 | 4000 | +| 5 | 5000 | +| 6 | 6000 | +| 7 | 7000 | +| 8 | 8000 | +| 9 | 9000 | +| 1 | b1 | ++------+------+ +9 rows in set (0.00 sec) +``` + +测试: **读己之所写**。 + +![ad44_.png](images/ad44_.png) + +然后看`session_1`和`session_2`同时更新`a = 4`的情况: + +| 更新但是不提交,即没有commit
![ad45_.png](images/ad45_.png) | session_2被阻塞,只能等待

| +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 提交更新
`mysql> commit;` | 解除阻塞

| + +但是如果两个会话**不是更新同一行**呢? + +如果不是更新同一行,则就算在`session_1`没有`commit`的时候,`session_2`也不会阻塞。 + +![ad48_.png](images/ad48_.png) + +**尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁**。 + +举个例子: 因为我们的`b`是`varchar`类型的,更新的时候我故意将`b`的单引号去掉,此时`MYSQL`底层自动类型转换,但是此时就会导致索引失效,然后我们看下面,就会导致我们的行锁变成了表锁,从而导致阻塞等待。 + +

+ +**间隙锁带来的插入问题**: + +![ad50_.png](images/ad50_.png) + +【什么是间隙锁】 + +**当我们用范围条件而不是相等条件检索数据**,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”, + +**InnoDB也会对这个“间隙”加锁(不放过一个),这种锁机制就是所谓的间隙锁**(GAP Lock)。 + +【危害】 + +因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。 + +间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害。 + +> 面试题:常考如何锁定一行。 +> +> 使用`for update`。 +> +> ![ad51_.png](images/ad51_.png) + + + +**【如何分析行锁定】** + +通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况 + +`mysql>show status like 'innodb_row_lock%';` + +

+ +对各个状态量的说明如下: + +`Innodb_row_lock_current_waits`:当前正在等待锁定的数量; + +`Innodb_row_lock_time`:从系统启动到现在锁定总时间长度; + +`Innodb_row_lock_time_avg`:每次等待所花平均时间; + +`Innodb_row_lock_time_max`:从系统启动到现在等待最常的一次所花的时间; + +`Innodb_row_lock_waits`:**系统启动后到现在总共等待的次数**; + +对于这5个状态变量,比较重要的主要是 + + `Innodb_row_lock_time_avg`(等待平均时长), + +` Innodb_row_lock_waits`(等待总次数) + +` Innodb_row_lock_time`(等待总时长)这三项。 + +尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。 + +最后可以通过`SELECT * FROM information_schema.INNODB_TRX\G;`来查询正在被锁阻塞的sql语句。 + +## 四、优化建议 + +* **尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁**; +* **尽可能较少检索条件,避免间隙锁**; +* 尽量控制事务大小,减少锁定资源量和时间长度; +* 锁住某行后,尽量不要去调别的行或表,赶紧处理被锁住的行然后释放掉锁; +* 涉及相同表的事务,对于调用表的顺序尽量保持一致; +* 在业务环境允许的情况下,尽可能低级别事务隔离; \ No newline at end of file diff --git a/DB/MySQL/advance/assets/1555897430321.png b/DB/MySQL/advance/assets/1555897430321.png new file mode 100644 index 00000000..fd64ee63 Binary files /dev/null and b/DB/MySQL/advance/assets/1555897430321.png differ diff --git a/DB/MySQL/advance/assets/1555897441333.png b/DB/MySQL/advance/assets/1555897441333.png new file mode 100644 index 00000000..57daef05 Binary files /dev/null and b/DB/MySQL/advance/assets/1555897441333.png differ diff --git a/DB/MySQL/advance/assets/1555897474077.png b/DB/MySQL/advance/assets/1555897474077.png new file mode 100644 index 00000000..5333b196 Binary files /dev/null and b/DB/MySQL/advance/assets/1555897474077.png differ diff --git a/DB/MySQL/advance/assets/1555897481241.png b/DB/MySQL/advance/assets/1555897481241.png new file mode 100644 index 00000000..2aa3d436 Binary files /dev/null and b/DB/MySQL/advance/assets/1555897481241.png differ diff --git a/DB/MySQL/advance/assets/1557017250485.png b/DB/MySQL/advance/assets/1557017250485.png new file mode 100644 index 00000000..b28308f5 Binary files /dev/null and b/DB/MySQL/advance/assets/1557017250485.png differ diff --git a/DB/MySQL/advance/assets/copycode.gif b/DB/MySQL/advance/assets/copycode.gif new file mode 100644 index 00000000..dc146865 Binary files /dev/null and b/DB/MySQL/advance/assets/copycode.gif differ diff --git "a/DB/MySQL/advance/images/ad10_\350\201\232\347\260\207\347\264\242\345\274\225.png" "b/DB/MySQL/advance/images/ad10_\350\201\232\347\260\207\347\264\242\345\274\225.png" new file mode 100644 index 00000000..a82d88e8 Binary files /dev/null and "b/DB/MySQL/advance/images/ad10_\350\201\232\347\260\207\347\264\242\345\274\225.png" differ diff --git a/DB/MySQL/advance/images/ad11_.png b/DB/MySQL/advance/images/ad11_.png new file mode 100644 index 00000000..4f0c4134 Binary files /dev/null and b/DB/MySQL/advance/images/ad11_.png differ diff --git a/DB/MySQL/advance/images/ad12_.png b/DB/MySQL/advance/images/ad12_.png new file mode 100644 index 00000000..0fe65cd4 Binary files /dev/null and b/DB/MySQL/advance/images/ad12_.png differ diff --git a/DB/MySQL/advance/images/ad13_.png b/DB/MySQL/advance/images/ad13_.png new file mode 100644 index 00000000..9d95c00a Binary files /dev/null and b/DB/MySQL/advance/images/ad13_.png differ diff --git a/DB/MySQL/advance/images/ad14_.png b/DB/MySQL/advance/images/ad14_.png new file mode 100644 index 00000000..2c2363fa Binary files /dev/null and b/DB/MySQL/advance/images/ad14_.png differ diff --git a/DB/MySQL/advance/images/ad15_.png b/DB/MySQL/advance/images/ad15_.png new file mode 100644 index 00000000..b41e3a03 Binary files /dev/null and b/DB/MySQL/advance/images/ad15_.png differ diff --git a/DB/MySQL/advance/images/ad16_5104.png b/DB/MySQL/advance/images/ad16_5104.png new file mode 100644 index 00000000..de152810 Binary files /dev/null and b/DB/MySQL/advance/images/ad16_5104.png differ diff --git a/DB/MySQL/advance/images/ad17_.png b/DB/MySQL/advance/images/ad17_.png new file mode 100644 index 00000000..613bef06 Binary files /dev/null and b/DB/MySQL/advance/images/ad17_.png differ diff --git a/DB/MySQL/advance/images/ad18_.png b/DB/MySQL/advance/images/ad18_.png new file mode 100644 index 00000000..259fdb79 Binary files /dev/null and b/DB/MySQL/advance/images/ad18_.png differ diff --git a/DB/MySQL/advance/images/ad19_.png b/DB/MySQL/advance/images/ad19_.png new file mode 100644 index 00000000..f2d29385 Binary files /dev/null and b/DB/MySQL/advance/images/ad19_.png differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad1_.png" b/DB/MySQL/advance/images/ad1_.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad1_.png" rename to DB/MySQL/advance/images/ad1_.png diff --git a/DB/MySQL/advance/images/ad20_.png b/DB/MySQL/advance/images/ad20_.png new file mode 100644 index 00000000..a9466abb Binary files /dev/null and b/DB/MySQL/advance/images/ad20_.png differ diff --git a/DB/MySQL/advance/images/ad21_.png b/DB/MySQL/advance/images/ad21_.png new file mode 100644 index 00000000..fbb2e96e Binary files /dev/null and b/DB/MySQL/advance/images/ad21_.png differ diff --git a/DB/MySQL/advance/images/ad22_.png b/DB/MySQL/advance/images/ad22_.png new file mode 100644 index 00000000..930a2881 Binary files /dev/null and b/DB/MySQL/advance/images/ad22_.png differ diff --git a/DB/MySQL/advance/images/ad23_.png b/DB/MySQL/advance/images/ad23_.png new file mode 100644 index 00000000..def35ab0 Binary files /dev/null and b/DB/MySQL/advance/images/ad23_.png differ diff --git a/DB/MySQL/advance/images/ad24_.png b/DB/MySQL/advance/images/ad24_.png new file mode 100644 index 00000000..e1639986 Binary files /dev/null and b/DB/MySQL/advance/images/ad24_.png differ diff --git a/DB/MySQL/advance/images/ad25_.png b/DB/MySQL/advance/images/ad25_.png new file mode 100644 index 00000000..0bb943b8 Binary files /dev/null and b/DB/MySQL/advance/images/ad25_.png differ diff --git a/DB/MySQL/advance/images/ad26_.png b/DB/MySQL/advance/images/ad26_.png new file mode 100644 index 00000000..34bb4501 Binary files /dev/null and b/DB/MySQL/advance/images/ad26_.png differ diff --git a/DB/MySQL/advance/images/ad27_.png b/DB/MySQL/advance/images/ad27_.png new file mode 100644 index 00000000..b65dd100 Binary files /dev/null and b/DB/MySQL/advance/images/ad27_.png differ diff --git a/DB/MySQL/advance/images/ad28_.png b/DB/MySQL/advance/images/ad28_.png new file mode 100644 index 00000000..44a7384a Binary files /dev/null and b/DB/MySQL/advance/images/ad28_.png differ diff --git a/DB/MySQL/advance/images/ad29_.png b/DB/MySQL/advance/images/ad29_.png new file mode 100644 index 00000000..274eef7e Binary files /dev/null and b/DB/MySQL/advance/images/ad29_.png differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad2_\346\225\260\346\215\256\346\226\207\344\273\266.png" "b/DB/MySQL/advance/images/ad2_\346\225\260\346\215\256\346\226\207\344\273\266.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad2_\346\225\260\346\215\256\346\226\207\344\273\266.png" rename to "DB/MySQL/advance/images/ad2_\346\225\260\346\215\256\346\226\207\344\273\266.png" diff --git a/DB/MySQL/advance/images/ad30_.png b/DB/MySQL/advance/images/ad30_.png new file mode 100644 index 00000000..e1070e68 Binary files /dev/null and b/DB/MySQL/advance/images/ad30_.png differ diff --git a/DB/MySQL/advance/images/ad31.png b/DB/MySQL/advance/images/ad31.png new file mode 100644 index 00000000..635c3b99 Binary files /dev/null and b/DB/MySQL/advance/images/ad31.png differ diff --git a/DB/MySQL/advance/images/ad32_.png b/DB/MySQL/advance/images/ad32_.png new file mode 100644 index 00000000..8338a192 Binary files /dev/null and b/DB/MySQL/advance/images/ad32_.png differ diff --git a/DB/MySQL/advance/images/ad33_.png b/DB/MySQL/advance/images/ad33_.png new file mode 100644 index 00000000..4d2c5a30 Binary files /dev/null and b/DB/MySQL/advance/images/ad33_.png differ diff --git a/DB/MySQL/advance/images/ad34_.png b/DB/MySQL/advance/images/ad34_.png new file mode 100644 index 00000000..c01b5802 Binary files /dev/null and b/DB/MySQL/advance/images/ad34_.png differ diff --git a/DB/MySQL/advance/images/ad35_.png b/DB/MySQL/advance/images/ad35_.png new file mode 100644 index 00000000..9744c6a7 Binary files /dev/null and b/DB/MySQL/advance/images/ad35_.png differ diff --git a/DB/MySQL/advance/images/ad36_.png b/DB/MySQL/advance/images/ad36_.png new file mode 100644 index 00000000..466e015c Binary files /dev/null and b/DB/MySQL/advance/images/ad36_.png differ diff --git a/DB/MySQL/advance/images/ad37_.png b/DB/MySQL/advance/images/ad37_.png new file mode 100644 index 00000000..6bf18220 Binary files /dev/null and b/DB/MySQL/advance/images/ad37_.png differ diff --git a/DB/MySQL/advance/images/ad38_.png b/DB/MySQL/advance/images/ad38_.png new file mode 100644 index 00000000..85e284b9 Binary files /dev/null and b/DB/MySQL/advance/images/ad38_.png differ diff --git a/DB/MySQL/advance/images/ad39_.png b/DB/MySQL/advance/images/ad39_.png new file mode 100644 index 00000000..5fe36ece Binary files /dev/null and b/DB/MySQL/advance/images/ad39_.png differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad3_mysql\346\234\215\345\212\241\345\231\250\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" "b/DB/MySQL/advance/images/ad3_mysql\346\234\215\345\212\241\345\231\250\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/advance/images/ad3_mysql\346\234\215\345\212\241\345\231\250\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" rename to "DB/MySQL/advance/images/ad3_mysql\346\234\215\345\212\241\345\231\250\351\200\273\350\276\221\346\236\266\346\236\204\345\233\276.png" diff --git a/DB/MySQL/advance/images/ad40_.png b/DB/MySQL/advance/images/ad40_.png new file mode 100644 index 00000000..f83b78f8 Binary files /dev/null and b/DB/MySQL/advance/images/ad40_.png differ diff --git a/DB/MySQL/advance/images/ad41_.png b/DB/MySQL/advance/images/ad41_.png new file mode 100644 index 00000000..e787fa23 Binary files /dev/null and b/DB/MySQL/advance/images/ad41_.png differ diff --git a/DB/MySQL/advance/images/ad42_.png b/DB/MySQL/advance/images/ad42_.png new file mode 100644 index 00000000..da451bdd Binary files /dev/null and b/DB/MySQL/advance/images/ad42_.png differ diff --git a/DB/MySQL/advance/images/ad43_.png b/DB/MySQL/advance/images/ad43_.png new file mode 100644 index 00000000..89704c24 Binary files /dev/null and b/DB/MySQL/advance/images/ad43_.png differ diff --git a/DB/MySQL/advance/images/ad44_.png b/DB/MySQL/advance/images/ad44_.png new file mode 100644 index 00000000..3ca1e280 Binary files /dev/null and b/DB/MySQL/advance/images/ad44_.png differ diff --git a/DB/MySQL/advance/images/ad45_.png b/DB/MySQL/advance/images/ad45_.png new file mode 100644 index 00000000..637aa9fa Binary files /dev/null and b/DB/MySQL/advance/images/ad45_.png differ diff --git a/DB/MySQL/advance/images/ad46_.png b/DB/MySQL/advance/images/ad46_.png new file mode 100644 index 00000000..fcf69527 Binary files /dev/null and b/DB/MySQL/advance/images/ad46_.png differ diff --git a/DB/MySQL/advance/images/ad47_.png b/DB/MySQL/advance/images/ad47_.png new file mode 100644 index 00000000..5cb7c107 Binary files /dev/null and b/DB/MySQL/advance/images/ad47_.png differ diff --git a/DB/MySQL/advance/images/ad48_.png b/DB/MySQL/advance/images/ad48_.png new file mode 100644 index 00000000..aa3b78ba Binary files /dev/null and b/DB/MySQL/advance/images/ad48_.png differ diff --git a/DB/MySQL/advance/images/ad49_.png b/DB/MySQL/advance/images/ad49_.png new file mode 100644 index 00000000..79ee7bd2 Binary files /dev/null and b/DB/MySQL/advance/images/ad49_.png differ diff --git "a/DB/MySQL/advance/images/ad4_\351\200\273\350\276\221\346\236\266\346\236\204.png" "b/DB/MySQL/advance/images/ad4_\351\200\273\350\276\221\346\236\266\346\236\204.png" new file mode 100644 index 00000000..a95c593a Binary files /dev/null and "b/DB/MySQL/advance/images/ad4_\351\200\273\350\276\221\346\236\266\346\236\204.png" differ diff --git a/DB/MySQL/advance/images/ad50_.png b/DB/MySQL/advance/images/ad50_.png new file mode 100644 index 00000000..f8369430 Binary files /dev/null and b/DB/MySQL/advance/images/ad50_.png differ diff --git a/DB/MySQL/advance/images/ad51_.png b/DB/MySQL/advance/images/ad51_.png new file mode 100644 index 00000000..94b0f18b Binary files /dev/null and b/DB/MySQL/advance/images/ad51_.png differ diff --git a/DB/MySQL/advance/images/ad52_.png b/DB/MySQL/advance/images/ad52_.png new file mode 100644 index 00000000..77d99536 Binary files /dev/null and b/DB/MySQL/advance/images/ad52_.png differ diff --git "a/DB/MySQL/advance/images/ad5_\345\255\230\345\202\250\345\274\225\346\223\216\345\257\271\346\257\224.png" "b/DB/MySQL/advance/images/ad5_\345\255\230\345\202\250\345\274\225\346\223\216\345\257\271\346\257\224.png" new file mode 100644 index 00000000..3efd4b2c Binary files /dev/null and "b/DB/MySQL/advance/images/ad5_\345\255\230\345\202\250\345\274\225\346\223\216\345\257\271\346\257\224.png" differ diff --git "a/DB/MySQL/advance/images/ad6_\346\237\245\347\234\213\345\255\230\345\202\250\345\274\225\346\223\216.png" "b/DB/MySQL/advance/images/ad6_\346\237\245\347\234\213\345\255\230\345\202\250\345\274\225\346\223\216.png" new file mode 100644 index 00000000..73b9865e Binary files /dev/null and "b/DB/MySQL/advance/images/ad6_\346\237\245\347\234\213\345\255\230\345\202\250\345\274\225\346\223\216.png" differ diff --git a/DB/MySQL/advance/images/ad7_a.png b/DB/MySQL/advance/images/ad7_a.png new file mode 100644 index 00000000..16b20807 Binary files /dev/null and b/DB/MySQL/advance/images/ad7_a.png differ diff --git "a/DB/MySQL/advance/images/ad8_\346\200\247\350\203\275\344\270\213\351\231\215\345\216\237\345\233\240.png" "b/DB/MySQL/advance/images/ad8_\346\200\247\350\203\275\344\270\213\351\231\215\345\216\237\345\233\240.png" new file mode 100644 index 00000000..3ac0acfa Binary files /dev/null and "b/DB/MySQL/advance/images/ad8_\346\200\247\350\203\275\344\270\213\351\231\215\345\216\237\345\233\240.png" differ diff --git "a/DB/MySQL/advance/images/ad9_\347\264\242\345\274\225.png" "b/DB/MySQL/advance/images/ad9_\347\264\242\345\274\225.png" new file mode 100644 index 00000000..72813230 Binary files /dev/null and "b/DB/MySQL/advance/images/ad9_\347\264\242\345\274\225.png" differ diff --git a/DB/MySQL/advance/images/ad_20.png b/DB/MySQL/advance/images/ad_20.png new file mode 100644 index 00000000..fee1d9da Binary files /dev/null and b/DB/MySQL/advance/images/ad_20.png differ diff --git a/DB/MySQL/advance/images/ad_22.png b/DB/MySQL/advance/images/ad_22.png new file mode 100644 index 00000000..b04711ce Binary files /dev/null and b/DB/MySQL/advance/images/ad_22.png differ diff --git a/DB/MySQL/advance/images/ad_24.png b/DB/MySQL/advance/images/ad_24.png new file mode 100644 index 00000000..2fcfefd8 Binary files /dev/null and b/DB/MySQL/advance/images/ad_24.png differ diff --git a/DB/MySQL/advance/images/ad_29.png b/DB/MySQL/advance/images/ad_29.png new file mode 100644 index 00000000..79f5e5b0 Binary files /dev/null and b/DB/MySQL/advance/images/ad_29.png differ diff --git a/DB/MySQL/advance/images/ad_31.png b/DB/MySQL/advance/images/ad_31.png new file mode 100644 index 00000000..907e938c Binary files /dev/null and b/DB/MySQL/advance/images/ad_31.png differ diff --git a/DB/MySQL/advance/images/shiwu_1.png b/DB/MySQL/advance/images/shiwu_1.png new file mode 100644 index 00000000..4969a447 Binary files /dev/null and b/DB/MySQL/advance/images/shiwu_1.png differ diff --git a/DB/MySQL/advance/images/shiwu_2.png b/DB/MySQL/advance/images/shiwu_2.png new file mode 100644 index 00000000..4cf1fdde Binary files /dev/null and b/DB/MySQL/advance/images/shiwu_2.png differ diff --git a/DB/MySQL/advance/images/shiwu_3.png b/DB/MySQL/advance/images/shiwu_3.png new file mode 100644 index 00000000..b401df44 Binary files /dev/null and b/DB/MySQL/advance/images/shiwu_3.png differ diff --git a/DB/MySQL/advance/images/shiwu_4.png b/DB/MySQL/advance/images/shiwu_4.png new file mode 100644 index 00000000..6d5f4815 Binary files /dev/null and b/DB/MySQL/advance/images/shiwu_4.png differ diff --git a/DB/MySQL/advance/images/shiwu_5.png b/DB/MySQL/advance/images/shiwu_5.png new file mode 100644 index 00000000..5101f9ef Binary files /dev/null and b/DB/MySQL/advance/images/shiwu_5.png differ diff --git a/DB/MySQL/advance/images/zhucong_1.png b/DB/MySQL/advance/images/zhucong_1.png new file mode 100644 index 00000000..47f877a5 Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_1.png differ diff --git a/DB/MySQL/advance/images/zhucong_2.png b/DB/MySQL/advance/images/zhucong_2.png new file mode 100644 index 00000000..99ed71a7 Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_2.png differ diff --git a/DB/MySQL/advance/images/zhucong_3.png b/DB/MySQL/advance/images/zhucong_3.png new file mode 100644 index 00000000..a4017591 Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_3.png differ diff --git a/DB/MySQL/advance/images/zhucong_4.png b/DB/MySQL/advance/images/zhucong_4.png new file mode 100644 index 00000000..b82b4d6b Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_4.png differ diff --git a/DB/MySQL/advance/images/zhucong_5.png b/DB/MySQL/advance/images/zhucong_5.png new file mode 100644 index 00000000..8f261ba9 Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_5.png differ diff --git a/DB/MySQL/advance/images/zhucong_6.png b/DB/MySQL/advance/images/zhucong_6.png new file mode 100644 index 00000000..14758d4d Binary files /dev/null and b/DB/MySQL/advance/images/zhucong_6.png differ diff --git "a/DB/MySQL/primary/MySQL\345\237\272\347\241\200.md" "b/DB/MySQL/primary/MySQL\345\237\272\347\241\200.md" new file mode 100644 index 00000000..e3a31df2 --- /dev/null +++ "b/DB/MySQL/primary/MySQL\345\237\272\347\241\200.md" @@ -0,0 +1,1480 @@ +# MySQL基础 + - [一、数据库的基本操作](#一数据库的基本操作) + - [1、基本命令](#1基本命令) + - [2、数据库储存引擎](#2数据库储存引擎) + - [二、数据表的基本操作](#二数据表的基本操作) + - [1、创建数据表](#1创建数据表) + - [2、修改数据表](#2修改数据表) + - [3、删除数据表](#3删除数据表) + - [4、综合案例小结](#4综合案例小结) + - [三、数据类型和运算符](#三数据类型和运算符) + - [1、MYSQL数据类型介绍](#1mysql数据类型介绍) + - [2、如何选择数据类型](#2如何选择数据类型) + - [3、常见运算符介绍](#3常见运算符介绍) + - [四、Mysql函数](#四mysql函数) + - [1、数学函数](#1数学函数) + - [2、字符串函数](#2字符串函数) + - [3、日期和时间函数](#3日期和时间函数) + - [4、条件判断函数](#4条件判断函数) + - [5、系统信息函数](#5系统信息函数) + - [6、加/解密函数](#6加解密函数) + - [7、其他函数](#7其他函数) + - [8、综合案列-Mysql函数的使用](#8综合案列-mysql函数的使用) +*** +## 一、数据库的基本操作 +### 1、基本命令 +**登陆数据库命令:** +```mysql +mysql -h localhost -u root -p +``` +**创建数据库命令:** +```sql +create database test_db; +``` +**查看已经创建的数据库的定义** +```mysql +show create database test_db; +``` +**查看已经存在的所有数据库:** +```mysql +show databases; +``` +**删除数据库** + +```sql +drop database test_db; +``` +注意删除数据库时要小心,不会给出提示,数据和数据表会一同删除。 + + +### 2、数据库储存引擎 + +#### 1)、查看引擎命令 +使用如下命令查看系统所支持的引擎类型: + +```mysql +show engines; +``` + +#### 2)、InnoDB引擎 + +InnoDB 是事务型数据库的首选引擎,**支持事务安全表 (ACID ) ,支持行锁定和外键。** + +InnoDB 作为**默认存储引擎**,特性有: + +* InnoDB 给 MySQL 提供了**具有提交、回滚和崩溃恢复能力的事务安全 (ACID 兼容)存储引擎**。InnoDB 锁定在**行级**并且也在 SELECT 语句中提供一个类似 Oracle 的**非锁定读**。这些功能增加了多用户部署和性能。在 SQL 查询中,可以自由地将 **InnoDB 类型的表与其他MySQL 的表的类型混合起来**,甚至在同一个查询中也可以混合。 +* InnoDB 是**为处理巨大数据量的最大性能设计**。它的 CPU 效率可能是任何其他基于磁盘的关系数据库引擎所不能匹敌的。 +* InnoDB 存储引擎完全与 MySQL 服务器整合,I**nnoDB 存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池**。InnoDB **将它的表和索引存在一个逻辑表空间中,表空间可以包含数个文件〈或原始磁盘分区) 。**这与 MyISAM 表不同,比如在 `MyISAM` 表中每个表被存在分离的文件中。InnoDB 表可以是任何尺寸,,即使在文件尺寸被限制为 2GB 的操作系统上。 +* InnoDB **支持外键完整性约束 (FOREIGN KEY)** 。存储表中的数据时, 每张表的存储都按主键顺序存放, 如果没有显示在表定义时指定主键,InnoDB 会为每一行生成一个 6B 的ROWID,并以此作为主键。 +* InnoDB 被用在众多需要高性能的大型数据库站点上。 +* InnoDB 不创建目录,使用 InnoDB 时,MySQL 将在 MySQL 数据目录下创建一个名为`ibdata1` 的 10MB 大小的自动扩展数据文件,以及两个名为` ib_logfile0` 和` ib_logfilel `的 `5MB`大小的日志文件。 + +InnoDB 不创建目录,使用 InnoDB 时,MySQL 将在 MySQL 数据目录下创建一个名为 +ibdatal 的 10MB 大小的自动扩展数据文件,以及两个名为 ib_logfile0 和 ib_logfilel 的 SMB +大小的日志文件。 + +#### 3)、MyISAM引擎 + +MyISAM 基于 ISAM 的存储引擎,并对其进行扩展。它是在 **Web、数据存储**和其他应用 +环境下最常使用的存储引擎之一。MyISAM 拥有较高的插入、查询速度,**但不支持事务**。在 +MyISAM 主要特性有: + +* **大文件** (达 63 位文件长度) 在支持大文件的文件系统和操作系统上被支持。 +* 当把删除、更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块来自动完成。 +* 每个 MyISAM 表最大索引数是 64,这可以通过重新编译来改变。每个索引最大的列数是 16 个。 +* 最大的键长度是 1000B,这也可以通过编译来改变。对于键长度超过 250B 的情况,一个超过 1024B 的键将被用上。 +* **BLOB 和TEXT 列可以被索引**。 +* **NULL 值被允许在索引的列中。这个值占每个键的 0~1 个字节**。 +* 所有数字键值以高字节优先被存储以允许一个更高的索引压缩。 +* 每表一个`AUTO_INCREMENT` 列的内部处理。MyISAM 为 `INSERT` 和 `UPDATE` 操作自动更新这一列。这使得 `AUTO_INCREMENT `列更快〈至少 10%) 。在序列顶的值被删除之后就不能再利用。 +* 可以把**数据文件和索引文件**放在不同目录。 +* 每个字符列可以有不同的字符集。 +* 有VARCHAR 的表可以固定或动态记录长度。 +* VARCHAR 和CHAR 列可以多达 64KB。 + +> 使用 MyISAM 引擎创建数据库,将生产 3 个文件。文件的名字以**表的名字**开始,扩展名指出文件类型, `frm`文件存储表定义,数据文件的扩展名为`.MYD (MYData)`,索引文件的扩展名是`.MYI MYIndex)` 。 + +#### 4)、MEMORY引擎 + +MEMORY 存储引擎**将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问**。MEMORY 主要特性有: + +* MEMORY 表的每个表可以有多达 32 个索引,每个索引 16 列,以及 500B 的最大键长度。 +* MEMORY 存储引擎执行 **HASH 和 BTREE** 索引。 +* 可以在一个MEMORY 表中有非唯一键。 +* MEMORY 表使用一个固定的记录长度格式。 +* MEMORY 不支持BLOB 或TEXT 列。 +* MEMORY 支持 `AUTO_INCREMENT` 列和**对可包含NULL 值的列的索引**。 +* MEMORY 表在所有客户端之间共享 (就像其他任何非 TEMPORARY 表) 。 +* **MEMORY 表内容被存在内存中,内存是 MEMORY 表和服务器在查询处理时的空闲中创建的内部表共享**。 +* 当不再需要 MEMORY 表的内容时,**要释放被 MEMORY 表使用的内存**,应该执行`DELETE FROM` 或TRUNCATE TABLE,或者删除整个表 〈使用DROP TABLE) 。 + +#### 5)、存储引擎的选择
+ +不同存储引擎都有各自的特点,以适应不同的需求。下面是各种引擎的不同的功能: + +![](images/1_存储引擎选择.png) + +* 如果要提供提交、回滚和崩溃恢复能力的**事务安全** (ACID 兼容) 能力,并要求实现**并发控制**,InnoDB 是个很好的选择; + +* 如果数据表主要用来**插入和查询记录**,则 MyISAM 引擎能提供较**高的处理效率**; + +* 如果只是**临时存放数据**,数据量不大,并且**不需要较高的数据安全性**,可以选择将**数据保存在内存中**的 Memory 引擎,MySQL 中使用该引擎作为临时表,存放查询的中间结果; + +* 如果只有 **INSERT 和 SELECT 操作**,可以选择 Archive 引擎,Archive 存储引擎支持高并发的插入操作,但是本身**并不是事务安全**的。Archive 存储引擎非常适合**存储归档数据**,如记录日志信息可以使用 Archive 引擎。 + +使用哪一种引擎要根据需要灵活选择, 一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求。使用合适的存储引擎,将会提高整个数据库的性能。 + +> 顺便说一下`Mysql`中单行注释是`#`,而不是`--`。 + +*** + +## 二、数据表的基本操作 + +### 1、创建数据表 + +```mysql +use test_db; +create table tb_emp1 +( + id int(11), + name varchar(15), + deptID int(11), + salary float +); +``` +使用下面语句查看此数据库存在的表 +```mysql +show tables; +``` +#### 1)、主键约束 + +> 主键,又称主码,是表中**一列或多列的组合**。主键约束〈Primary Key Constraint) 要求**主键列的数据唯一,并且不允许为空`!= null`**。主键能够唯一地标识表中的一条记录,可以结合外键**来定义不同数据表之间的关系**, 并且可以加快数据库查询的速度。主键和记录之间的关系如同身份证和人之间的关系,它们之间是一一对应的。主键分为两种类型: **单字段主键和多字段联合主键。** + + - 单字段主键; + - 在定义完所有列之后定义主键; + - 多字段联合主键; + +单字段约束: +```mysql +create table tb_emp2 +( + id int(11) primary key, + name varchar(15), + deptID int(11), + salary float +); +``` +后面约束: +```mysql +create table tb_emp3 +( + id int(11), + name varchar(15), + deptID int(11), + salary float, + primary key(id) +); +``` +联合约束:假设没有主键`id`,可以通过`name`和`deptID`来确定一个唯一的员工。 + +```mysql +create table tb_emp4 +( + id int(11), + name varchar(15), + deptID int(11), + salary float, + primary key(name,deptID) +); +``` +#### 2)、外键约束 + +* 外键用来在两个表的数据之间建立链接, 它可以是一列或者多列。一个表可以有一个或多个外键。**外键对应的是参照完整性**,一个表的外键可以为空值,**若不为空值,则每一个外键值必须等于另一个表中主键的某个值。** +* 外键 : 首先它是表中的一个字段,**它可以不是本表的主键,但对应另外一个表的主键。外键主要作用是保证数据引用的完整性, 定义外键后**,不允许删除在另一个表中具有关联关系的行。外键的作用是保持数据的一致性、完整性。例如,部门表 `tb_dept `的主键是`id`,在员工表`tb_emp5`中有一个键 `deptId` 与这个` id` 关联。 + +有关主表和从表: + +* 主表(父表) : 对于两个具有关联关系的表而言,相关联字段中**主键所在的那个表**即是主表。 + +* 从表(子表) : 对于两个具有关联关系的表而言,相关联字段中**外键所在的那个表**即是从表。 + +需要注意: + + - 子表的外键必须要关联父表的**主键**; + - **相关联的数据类型必须匹配**; + - **先删子表,再删父表**; + +下面的例子**tb_emp5(员工表)中的deptID关联部门表中的ID(主键)**: +```mysql +//父表 +create table tb_dept1 +( + id int(11)primary key, + name varchar(22) not null, + location varchar(50) +) +``` + +```mysql +//子表 +create table tb_emp5 +( + id int(11) primary key, + name varchar(25), + deptID int(11), + salary float, + constraint fk_emp5_dept foreign key(deptID) references tb_dept1(id) +) +``` +#### 3)、非空约束 + +非空约束指定的字段不能为空,如果添加数据的时候没有指定值,会报错。 + +```mysql +create table tb_emp6 +( + id int(11) primary key, + name varchar(15) not null, + deptID int(11), + salary float +); +``` +#### 4)、唯一性约束 + +* 唯一性要求该列唯一; +* **允许为空,但只能出现一个空值;** +* 唯一性可以确保一列或几列不出现重复值; + +```mysql +create table tb_dept2 +( + id int(11)primary key, + name varchar(22) unique, + location varchar(50) +); +``` +```mysql +create table tb_dept3 +( + id int(11)primary key, + name varchar(22), + location varchar(50), + constraint N_uq unique(name) #N_uq是约束名 +); +``` +**注意`UNIQUE`和主键约束(`PRIMARY KEY `)的区别:** + +* 一个表中可以有多个字段声明为`UNIQUE`,但只能有一个`PRIMARY KEY` 声明; +* 声明为 `PRIMAY KEY` 的列不允许有空值,但是声明为 `UNIQUE`的字段允许空值 (NULL) 的存在。 + +#### 5)、默认约束 + +指定了默认约束之后,如果没有指定值,就用默认的。 + +```mysql +create table tb_emp7 +( + id int(11) primary key, + name varchar(15) not null, + deptID int(11) default 111, + salary float +); +``` +#### 6)、设置表的属性自加 + +* 在数据库应用中,经常希望在每次插入新记录时,系统自动生成字段的主键值。可以通过为表主键添加`AUTO_INCREMENT` 关键字来实现。 +* 默认的,在MySQL 中 `AUTO _INCREMENT`的初始值是 1,每新增一条记录,字段值自动加 1。 +* **一个表只能有一个字段使用AUTO_INCREMENT 约束,且该字段必须为主键的一部分。** +* `AUTO_INCREMENT `约束的字段可以是任何整数类型 (TINYINT、SMALLIN、INT、BIGINT 等) 。 + +```mysql +create table tb_emp8 +( + id int(11) primary key auto_increment, + name varchar(15) not null, + deptID int(11), + salary float +); +``` +#### 7)、查看表的结构 + +`desc`可以查看表的字段名,数据类型,是否为主键,是否默认值。 + +```mysql +desc tb_emp8; +``` +效果如图 + +![这里写图片描述](images/2_desc查看结果.png) + +查看表的详细结构,可以看储存引擎,和字符编码 + +```mysql +show create table tb_emp8; +``` +### 2、修改数据表 + +#### 1)、修改表名 + +将表`tb_dept3`改为`tb_deptment3` + +```mysql +alter table tb_dept3 rename tb_deptment3; +``` +查看数据库中的表 +```mysql +show tables; +``` +修改表名不会改变结构,`desc`前后结果一样。 + +#### 2)、修改字段的数据类型 + +```mysql +# 修改表字段的数据类型,把name列的数据类型改为varchar(33) +alter table tb_dept1 modify name varchar(33); +``` +#### 3)、修改字段名 + +```mysql +# 修改表的字段名,不改数据类型 将tb_dept1中的location字段改成loc +alter table tb_dept1 change location loc varchar(50); +``` + +```mysql +# 修改表的字段名,并且改变数据类型, 同时改变数据类型 +alter table tb_dept1 change loc location varchar(60); +``` +`change`也可以只改变数据类型,但是一般不要**轻易改变数据类型**。 + +#### 4)、添加字段 + +有三种添加方式: + +* ①默认在最后面添加; +* ②在第一个位置添加`first`; +* ③和指定的位置添加`after`; + +```mysql +# 添加字段(默认在最后面添加) +alter table tb_dept1 add managerID int(10); +``` +```mysql +# 添加字段(默认在最后面添加)(非空约束) +alter table tb_dept1 add column1 int(10) not null; +``` + +```mysql +# 添加字段(在第一个位置添加) +alter table tb_dept1 add column2 int(10) first; +``` + +```mysql +# 添加字段(在指定位置后面添加) +alter table tb_dept1 add column3 int(10) after name; +``` +#### 5)、删除字段 + +```mysql +# 删除字段, 删除tb_dept1的column3字段 +alter table tb_dept1 drop column3; +``` +#### 6)、修改字段的排列位置 + +```mysql +# 修改字段的排列位置(改到第一个位置) +alter table tb_dept1 modify column1 int(10) first; +# 修改字段的位置为指定的位置 +alter table tb_dept1 modify column2 int(10) after name; +``` +#### 7)、更改表的储存引擎 + +```mysql +# 查看数据表的定义 +show create table tb_deptment3; +# 更改数据表的引擎 +alter table tb_deptment3 engine = MyISAM; +``` +#### 8)、删除表的外键约束 + +```mysql +create table tb_emp9 +( + id int(11)primary key, + deptID int(11), + name varchar(25), + salary float, + constraint fk_emp9_dept foreign key(deptID) references tb_dept1(id) +) + +# 删除外键约束 +alter table tb_emp9 drop foreign key fk_emp9_dept; +``` +### 3、删除数据表 + +```mysql +# 删除表 +drop table if exists tb_emp9; +``` +注意注意: **删除有关联的数据表的父表的时候,先删除外键再删除父表** + +### 4、综合案例小结 + +```mysql +create database company; +use company; +create table offices +( + officeCode int(10) primary key not null unique, + city varchar(50) not null, + address varchar(50), + country varchar(50) not null, + postalCode varchar(15) unique +) + +create table employees +( + employeeNumber int(11) primary key not null unique auto_increment, + lastName varchar(50) not null, + firstName varchar(50) not null, + mobile varchar(25) unique, + officeCode int(10) not null, + jobTitle varchar(50) not null, + birth datetime not null, + note varchar(255), + sex varchar(5) +) + +show tables; +desc employees; + +#将mobile字段修改到officeCode后面 +alter table employees modify mobile varchar(25) after officeCode; +#将birth的字段名改为employee_birth +alter table employees change birth employee_birth datetime; +#修改sex字段为char(1)类型,非空约束 +alter table employees modify sex char(1) not null; +#删除字段note +alter table employees drop note; +#增加字段名 +alter table employees add favoriate_activity varchar(100); + +#为employee增加一个外键 +alter table employees add constraint fk_em_off foreign key(officeCode) references offices(officeCode); + +#删除表的外键约束 +alter table employees drop foreign key fk_em_off; + +#更改employee的数据引擎 +alter table employees engine = MyISAM; + +#更改employee的表名 +alter table employees rename employees_info; +``` +*** + +## 三、数据类型和运算符 + +### 1、MYSQL数据类型介绍 + +数据类型主要有下面几种 + +MySQL 支持多种数据类型,主要有**数值类型、日期/时间类型和字符串类型**。 + +* 数值数据类型: 包括整数类型 TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT;浮点小数数据类型 FLOAT 和 DOUBLE;定点小数类型 DECIMAL 。 +* 日期/时间类型: 包括 YEAR、TIME、DATE、DATETIME 和TIMESTAMP。 +* 字符串类型: 包括 CHAR、VARCHAR、BINARY、VARBINARY、BLOB、TEXT、ENUM 和 SET 等。字符串类型又分为**文本字符串和二进制字符串**。 + +#### 1)、整数类型 + +整数数据类型主要有一下几种: + +![](images/3_整形.png) + +不同的数据类型取值范围如下: + +![在这里插入图片描述](images/4_整形取值范围.png) + +注意`INT`(`num`)中的数和取值范围无关。 + +举例 + +```mysql +create table tb_emp1{ + id INT(11), + name VARCHAR(25), + deptId INT(11), + salary FLOAT +}; +``` + +`id `字段的数据类型为 INT(11),注意到后面的数字 11,这表示的是该数据类型指定的显示宽度,指定能够显示的数值中数字的个数。例如,假设声明一个 INT 类型的字段:`year INT(4)`该声明指明,在 year 字段中的数据一般只显示 4 位数字的宽度。在这里要注意: **显示宽度和数据类型的取值范围是无关的**。显示宽度只是指明 MySQL最大可能显示的数字个数,数值的位数小鱼指定的宽度时会由空格填充。 + +#### 2)、浮点数类型和定点数类型 + +小数类型: + +| 类型名称 | 说明 | 存储需求 | +| ------------------ | ---------------------- | --------- | +| FLOAT | 单精度浮点数 | 4个字节 | +| DOUBLE | 双精度浮点数 | 8个字节 | +| DECIMAL(M, D), DEC | 压缩的"严格"**定点数** | M+2个字节 | + +DECIMAL 类型不同于 FLOAT 和 DOUBLE,DECIMAL 实际是以**串**存放的,DECIMAL可能的最大取值范围与 DOUBLE 一样,**但是其有效的取值范围由M 和 D 的值决定**。如果改变 M 而固定D,则其取值范围将随 M 的变大而变大。从表中可以看到,DECIMAL 的存储空间并不是固定的,而由**其精度值 M** 决定,占用 M+2 个字节。 + +FLOAT 类型的取值范围如下: + +* 有符号的取值范围: `-3.402823466E+38 ~ -1.175494351E-38`。 +* 无符号的取值范围: `0` 和 `1.175494351E-38 ~ 3.402823466E+38`。 + +DOUBLE 类型的取值范围如下: + +* 有符号的取值范围: `-1.7976931348623157E+308 ~ -2.2250738585072014E-308`。 +* 无符号的取值范围: `0` 和 `2.2250738585072014E-308 ~ 1.7976931348623157E+308`。 + +注意: **不论定点还是浮点类型,如果用户指定的精度超出精度范围,则会四舍五入进行处理。** + +注意浮点数和定点数的使用场合: + +* 在MySQL 中,**定点数以字符串形式存储**,在对精度要求比较高的时候〈如货币,科学数据等) 使用 DECIMAL 的类型比较好; +* 另外两个浮点数进行减法和比较运算时也容易出问题,**所以在使用浮点型时需要注意,并尽量避免做浮点数比较。** + +#### 3)、时间和日期类型 +![在这里插入图片描述](images/5_时间日期.png) + +##### a)、Year + +![在这里插入图片描述](images/6_日期Date.png) + +举几个例子: + +* **`0`表示`0000`,`‘0’`和`‘00’`表示`2000`;** +* **`‘78’`和`78`表示`1978`,`‘68’`和`68`表示`2068`**; + +##### b)、Time + +![在这里插入图片描述](images/7_时间Time.png) + +案例: + +```mysql +create table tmp4(t Time); +delete from tmp4; +insert into tmp4 values('10:05:05'),('23:23'),('2 10:10'),('3 02'),('10'),(now()),(current_time); +``` +效果 + +![这里写图片描述](images/8_演示插入结果.png) + +##### c)、Date + +![在这里插入图片描述](images/9_Date日期.png) + +```mysql +create table tmp5(d Date); +insert into tmp5 values('1998-09-01'),('19980902'),('980903'),(19980904),(980905),(100906),(000907),(current_date); +``` +效果 +![这里写图片描述](images/10_演示结果.png) +##### d)、DateTime + +![在这里插入图片描述](images/11_DateTime.png) + +举例: + +```sql +create table tmp6(dt DateTime); +insert into tmp6 values('1998-08-08 08:08:08'),('19980809080808'),('98-08-08 08:08:08'),('980808080808'),(19980808080808),(980808080808); +``` +效果 + +![这里写图片描述](images/12_演示效果.png) + +##### e)、TimeStamp +![](images/13_TimeStamp.png) + +`TimeStamp`把时区修改之后查询结果就会不同,但是`DateTime`不会。 + +#### 4)、文本字符串类型 +![在这里插入图片描述](images/14_文本字符串类型.png) + +##### a)、`char`和`varchar`类型 + +`char`数据类型长度不可变,`varchar`长度可变 + +![在这里插入图片描述](images/15_char和varchar.png) + +举例: + +```sql +create table tmp8(ch char(4),vch varchar(4)); +insert into tmp8 values('ab ','ab ');-- 注意这里有空格 +select concat('(',ch,')'),concat('(',vch,')') from tmp8; +``` +看效果`vch`中的空格没有被截取(即`ch`末尾的两个空格被删除了,而`vch`的没有删除) +![这里写图片描述](images/16_char和varchar2.png) + +##### b)、Text类型 + +TEXT 列保存**非二进制字符串**,如文章内容、评论等。当保存或查询 TEXT 列的值时,不删除尾部空格。Text 类型分为 4 种: TINYTEXT、TEXT、MEDIUMTEXT 和 LONGTEXT。不同的 TEXT 类型的存储空间和数据长度不同。 + +* TINYTEXT 最大长度为 255(28 - 1)字符的 TEXT 列; + +* TEXT 最大长度为 65535(216 - 1)字符的TEXT 列; + +* MEDIUMTEXT 最大长度为 16777215(224 - 1)字符的TEXT列; +* LONGTEXT 最大长度为 4294967295 或 4GB(232 - 1)字符的TEXT 列; + +##### c)、`Enum`类型 + +ENUM 是一个字符串对象,其值为表创建时在列规定中枚举的一列值。语法格式如下:'' + +```mysql +字段名 ENUM('值1', '值2', ..., '值n') +``` + +字段名指将**要定义的字段**,值n指枚举列表中的第 n 个值。 ENUM 类型的字段在取值时,只能在指定的枚举列表中取,而且一次只能取一个。如果创建的成员中有空格时,其尾部的空格将自动被删除。ENUM 值在内部用整数表示,每个枚举值均有一个索引值: **列表值所允许的成员值从 1 开始编号**,MySQL 存储的就是这个索引编号。枚举最多可以有 65 535 个元素。 + +例如定义 ENUM 类型的列(first,'second','third),该列可以取的值和每个值的索引如表所示: + +![](images/17_emum.png) + +举例: + +```sql +create table tmp9(enm Enum('first','second','third')); +insert into tmp9 values('first'),('second'),('third'),(null); +select enm,enm+0 from tmp9; +``` +![这里写图片描述](images/18_测试结果.png) +再看一个实例 + +```sql +create table tmp10(soc int ,level enum('excellent','good','bad')); +insert into tmp10 values(70,'good'),(90,1),(75,2),(50,3); #'excellent','good','bad'-->对应 1,2,3 +select soc,level,level+0 from tmp10; +insert into tmp10 values(100,4); #没有4这个选项 +``` +效果 +![这里写图片描述](images/19_测试结果2.png) + +##### d)、Set类型 + +SET 是一个字符串对象,可以有零或多个值,SET 列最多可以有 64 个成员,其值为表创建时规定的一列值。指定包括多个 SET 成员的 SET 列值时,各成员之间用逗号`(, )`间隔开。语法格式如下: + +```mysql +SET('值1', '值2', ... '值n') +``` + +与 ENUM 类型相同,SET 值在内部用整数表示,**列表中每一个值都有一个索引编号**。当创建表时,SET 成员值的尾部空格将自动被删除。但与 ENUM 类型不同的是,ENUM 类型的字段只能从定义的列值中选择一个值插入,而 SET 类型的列可从定义的列值中选择多个字符的联合。 + +如果插入 SET 字段中列值有重复,则 **MySQL 自动删除重复的值**,**插入 SET 字段的值的顺序并不重要**,MySQL 会在存入数据库时,按照定义的顺序显示;如果插入了不正确的值,默认情况下,MySQL 将忽视这些值,并给出警告。 + +```mysql +-- 自动排序去重 +create table tmp11(s set('a','b','c','d')); # 只能插入a,b,c,d这四个值 +insert into tmp11 values('a'),('a,b,a'),('c,a,d'); +select *from tmp11; +``` +效果 + +![这里写图片描述](images/20_SET测试.png) + +*** +#### 5)、二进制字符串类型 +![这里写图片描述](images/21_二进制字符串类型.png) + +##### a)、Bit类型 +保存的是数的二进制表示: + +BIT 类型是位字段类型。M 表示每个值的位数,范围为 1-64。如果 M 被省略,默认为 1。如果为 BIT(M)列分配的值的长度小于 M 位,在值的左边用 0填充。例如,为 BIT(6)列分配一个值`b'101'`,其效果与分配 `b'000101'`相同。**BIT 数据类型用来保存位字段值**,例如: 以二进制的形式保存数据 13,13 的二进制形式为 1101,在这里需要位数至少为`4`位的 BIT 类型,即可以定义列类型为 BIT(4)。大于二进制 1111 的数据是不能插入 BIT(4)类型的字段中的。 + +```mysql +#bit +create table tmp12(b bit(4)); +insert into tmp12 values(2),(9),(15); +insert into tmp12 values(16);#报错,只能存到0-15 +select b,b+0 from tmp12; +``` +效果 + +![这里写图片描述](images/22_Bit类型.png) + + + +##### b)、Binary和varBinary + +BINARY 类型的长度是固定的 指定长度之后 不足最大长度的 将在它们右边填充 `"\0"`补齐以达到指定长度。例如: 指定列数据类型为 BINARY(3),当插入“a”时,存储的内容实际为`“a\0\0”`,当插入`“ab”`时,实际存储的内容为`“ab\0”`,不管存储的内容是否达到指定的长度,其存储空间均为指定的值 M。 + +VARBINARY 类型的长度是可变的,指定好长度之后,其长度可以在 0 到最大值之间。例如: 指定列数据类型为 VARBINARY(20),如果插入的值的长度只有 10,则实际存储空间为 10 加 1,即其实际占用的空间为字符串的实际长度加 1。 + +```mysql +#binary和varbinary +create table tmp13(b binary(3),vb varbinary(30)); +insert into tmp13 values(5,5); +select length(b),length(vb) from tmp13; +``` +效果如图: + +![这里写图片描述](images/23_效果.png) + +##### c)、Blob类型 + +BLOB是一个二进制大对象,用来存储可变数量的数据。有四种类型: TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB。 + +**BLOB列存储的是二进制字符串(字节字符串),TEXT存储的是非二进制字符串(字符字符串)**。 + +*** +### 2、如何选择数据类型 + +#### 1)、整数和浮点数 + +如果不需要小数部分,则使用整数来保存数据; + +如果需要表示小数部分,则使用浮点数类型。对于浮点数据列,存入的数值会对该列定义的小数位**进行四舍五入**。 例如,如果列的值的范围为 1-99999, 若使用整数,则MEDIUMINT UNSIGNED 是最好的类型,若需要存储小数,则使用 FLOAT 类型。 + +浮点类型包括 FLOAT 和 DOUBLE 类型。DOUBLE 类型精度比 FLOAT 类型高,因此,如要求存储精度较高时,应选择 DOUBLE 类型。 + +#### 2)、浮点数和定点数 + +浮点数FLOAT、DOUBLE 相对于定点数 DECIMAL 的优势是: **在长度一定的情况下, 浮点数能表示更大的数据范围**,但是由于浮点数容易产生误差。 + +**因此对精确度要求比较高时,建议使用DECIMAL 来存储**。DECIMAL 在 MySQL 中是以**字符串存储**的,用于定义货币等对精确度要求较高的数据。另外**两个浮点数进行减法和比较运算时也容易出问**题,因此在进行计算的时候,一定要小心。如果进行**数值比较,最好使用 DECIMAL 类型**。 + +#### 3)、日期和时间类型 + +MySQL 对于不同种类的日期和时间有很多的数据类型,比如 YEAR 和 TIME。如果只需要记录年份,则使用YEAR 类型即可; 如果只记录时间,只需使用TIME 类型。 + +**如果同时需要记录日期和时间,则可以使用 TIMESTAMP 或者 DATETIME 类型**。由于TIMESTAMP 列的取值范围小于 DATETIME 的取值范围,**因此存储范围较大的日期最好使用DATETIME**。 + +TIMESTAMP 也有一个DATETIME 不具备的属性。默认的情况下,当插入一条记录但并没有指定 TIMESTAMP 这个列值时,MySQL 会把 TIMESTAMP 列设为当前的时间。**因此当需要插入记录同时插入当前时间**时,使用TIMESTAMP 是方便的,另外 TIMESTAMP 在空间上比 DATETIME 更有效。 + +#### 4)、char和varchar + +char和varchar的区别: + +* char是固定长度字符,varchar是可变长度字符; + +* CHAR 会自动删除插入数据的尾部空格,VARCHAR 不会删除尾部空格。 + +CHAR 是固定长度,**所以它的处理速度比 VARCHAR 的速度要快**,但是它的缺点就是浪费存储空间。所以对存储不大,但在速度上有要求的可以使用 CHAR 类型,反之可以使用VARCHAR 类型来实现。 + +存储引擎对于选择 CHAR 和 VARCHAR 的影响: + +* 对于MYyISAM 存储引擎: 最好使用固定长度(`char`)的数据列代替可变长度的数据列。这样可以使整个表静态化,从而使数据检索更快,用空间换时间。 +* 对于 InnoDB 存储引擎: 使用可变长度(`varchar`)的数据列,因为 InnoDB 数据表的存储格式不分固定长度和可变长度,因此使用 CHAR 不一定比使用 VARCHAR 更好,但由于VARCHAR 是按照实际的长度存储,比较节省空间,所以对磁盘 IO 和数据存储总量比较好。 + +#### 5)、ENUM和SET + +**ENUM 只能取单值**, 它的数据列表是一个**枚举集合**。它的合法取值列表最多允许有 65 535个成员。因此,在需要从**多个值中选取一个**时,可以使用 ENUM。比如: 性别字段适合定义为 ENUM 类型,每次只能从“男”或“女”中取一个值。 + +**SET 可取多值**。它的合法取值列表最多允许有 64 个成员。空字符串也是一个合法的 SET值。在需要**取多个值的时候**,适合使用 SET 类型,比如: 要存储一个人兴趣爱好,最好使用SET 类型 。 + +**ENUM 和 SET 的值是以字符串形式出现的,但在内部,MySQL 以数值的形式存储它们**。 + +#### 6)、BLOB和TEXT + +**BLOB 是二进制字符串,TEXT 是非二进制字符串**,两者均可存放大容量的信息。BLOB主要存储图片、音频信息等,而 TEXT 只能存储纯文本文件。 + +*** +### 3、常见运算符介绍 + +#### 1)、运算符概述 + +总共有四大类: + +1. 算术运算符 + +算术运算符用于各类数值运算,包括加 (+) 、减 (-) 、乘 (+) 、除 (/) 、求余(或称模运算,%) 。 + +2. 比较运算符 + +比较运算符用于比较运算。包括大于 (>) 、小于 (<) 、等于 (=) 、大于等于 (>=) 、小于等于 (<=) 、不等于 (!=) ,以及`IN、BETWEEN AND、IS NULL、GREATEST、LEAST、LIKE、REGEXP `等。 + +3. 逻辑运算符 + +逻辑运算符的求值所得结果均为1 (TRUE) 、0 (FALSE) ,这类运算符有逻辑非 (NOT或者!) 、逻辑与 (AND 或者&&) 、逻辑或 (OR 或者|) 、逻辑异或 C(XOR) 。 + +4. 位操作运算符 + +位操作运算符参与运算的操作数按二进制位进行运算。包括位与(&) 、位或 (|) 、位非(~) 、位异或 (^) 、左移 (<<) 、右移 (>>) 6种。 + +#### 2)、算数运算符 + +没啥好说的就是`+、-、*、/、%`。 + +#### 3)、比较运算符 + +注意一下比较运算符 + +![这里写图片描述](images/24_比较运算符.png) + +数值比较有如下规则: + +* 若有一个或两个参数为NULL,则比较运算的结果为NULL; +* 若同一个比较运算中的两个参数都是字符串,则按照字符串进行比较; +* 若两个参数均为整数,则按照整数进行比较; +* **若一个字符串和数字进行相等判断,则 MySQL 可以自动将字符串转换为数字**; + +**安全等于运算符** + +这个操作符和=操作符执行相同的比较操作,不过`<=>`可以用来判断 NULL 值。在两个操作数均为NULL 时,其返回值为 1 而不为NULL;而当一个操作数为 NULL 时,其返回值为0而不为NULL。 + +`<=>`在执行比较操作时和`"="`的作用是相似的,唯一的区别是`<=>`可以来对NULL进行判断,两者都为NULL时返回`1`。 + +**不等于运算符`<>`或者`!=`**: + +`"<>"`或者`"!="`用于判断数字、字符串、表达式不相等的判断。如果不相等,返回值为 1; 否则返回值为 0。这两个运算符不能用于判断空值 NULL。 + +**LEAST运算符** + +语法格式为: + +```mysql +LEAST(值 1,值 2…,值m) +``` + +其中值 `n` 表示参数列表中有`n`个值。在有两个或多个参数的情况下, 返回最小值。假如任意一个自变量为NULL,则`LEAST()`的返回值为`NULL`。 + +**GREATEST** + +语法格式:`GREATEST(值1, 值2, 值3)` ,其中`n`表示参数列表中有`n`个值。当有`2`个或多个参数时,返回为最大值,假如任意一个自变量为NULL,则`GREATEST()`的返回值为NULL。 + +**LIKE** + +![这里写图片描述](images/26_LIKE.png) + +**正则表达式REGEXP** + +![这里写图片描述](images/27_REGEXP.png) + +看一个例子 + +```mysql +select 'ssky' regexp '^s','ssky' regexp 'y$', 'ssky' regexp '.sky', 'ssky' regexp '[ab]'; +``` +效果 + +![这里写图片描述](images/25_正则结果.png)s + +#### 4)、逻辑运算符 + +和高级语言差不多,不赘述。 + +#### 5)、位运算 + +和高级语言差不多,不赘述。 + +#### 6)、运算符优先级 + +![](images/28_优先级.png) + +### 4、综合案例-运算符的使用 + +```mysql +create table tmp15(note varchar(100),price int); +insert into tmp15 values("Thisisgood",50); +#算术运算符 +select price,price+10,price-10,price*2,price/2,price%3 from tmp15; +#比较运算符 +select price,price>10,price<10,price != 10,price = 10,price <=>10,price <>10 from tmp15; +# in, greatest等 +select price,price between 30 and 80,greatest(price,70,30),price in(10,20,50,35) from tmp15; +# 正则等 +select note,note is null,note like 't%',note regexp '$y',note regexp '[gm]' from tmp15; +# 逻辑运算 +select price,price&2,price|4, ~price from tmp15; +# 位运算 +select price,price<<2,price>>2 from tmp15; +``` + +*** +## 四、Mysql函数 + +### 1、数学函数 + +#### 1)、绝对值,π,平方根,去余函数(适用小数) + +```mysql +#绝对值,π,平方根,去余函数(适用小数) +mysql> select abs(-1),pi(),sqrt(9),Mod(31,8),Mod(45.5,6); + +效果: + ++---------+----------+---------+-----------+-------------+ +| abs(-1) | pi() | sqrt(9) | Mod(31,8) | Mod(45.5,6) | ++---------+----------+---------+-----------+-------------+ +| 1 | 3.141593 | 3 | 7 | 3.5 | ++---------+----------+---------+-----------+-------------+ + +``` +#### 2)、获取整数的函数 + +```mysql +#获取整数的函数 +mysql> select ceil(-3.5),ceiling(3.5),floor(-3.5),floor(3.5); + +效果: + ++------------+--------------+-------------+------------+ +| ceil(-3.5) | ceiling(3.5) | floor(-3.5) | floor(3.5) | ++------------+--------------+-------------+------------+ +| -3 | 4 | -4 | 3 | ++------------+--------------+-------------+------------+ + +``` +#### 3)、获取随机数的函数 + +```mysql +#获取随机数的函数 +mysql> select rand(),rand(),rand(10),rand(10); + +效果: + ++--------------------+---------------------+--------------------+--------------------+ +| rand() | rand() | rand(10) | rand(10) | ++--------------------+---------------------+--------------------+--------------------+ +| 0.9031498375378082 | 0.46329259729319494 | 0.6570515219653505 | 0.6570515219653505 | ++--------------------+---------------------+--------------------+--------------------+ + +可以看到前面两个不同,后面两个指定了种子所以相同。 +``` +#### 4)、Round函数(四舍五入函数),truncate()函数 + +```mysql +#Round函数(四舍五入函数),truncate()函数 +mysql> select round(3.4),(3.6),round(3.16,1),round(3.16,0),round(232.28,-1),truncate(1.31,1),truncate(1.99,1),truncate(19.99,-1); + +效果: + ++------------+-----+---------------+---------------+------------------+------------------+------------------+--------------------+ +| round(3.4) | 3.6 | round(3.16,1) | round(3.16,0) | round(232.28,-1) | truncate(1.31,1) | truncate(1.99,1) | truncate(19.99,-1) | ++------------+-----+---------------+---------------+------------------+------------------+------------------+--------------------+ +| 3 | 3.6 | 3.2 | 3 | 230 | 1.3 | 1.9 | 10 | ++------------+-----+---------------+---------------+------------------+------------------+------------------+--------------------+ + +``` +#### 5)、符号函数,幂运算函数pow,power,exp() + +```mysql +#符号函数,幂运算函数pow,power,exp()//e的x乘方 +mysql> select sign(-21),sign(0),sign(21),pow(2,2),power(2,-2),exp(2); + +效果: + ++-----------+---------+----------+----------+-------------+------------------+ +| sign(-21) | sign(0) | sign(21) | pow(2,2) | power(2,-2) | exp(2) | ++-----------+---------+----------+----------+-------------+------------------+ +| -1 | 0 | 1 | 4 | 0.25 | 7.38905609893065 | ++-----------+---------+----------+----------+-------------+------------------+ + +``` +#### 6)、自然对数运算和以10为底的对数运算,弧度,角度 radians角度转弧度,弧度转角度 + +```mysql +#自然对数运算和以10为底的对数运算,弧度,角度 radians角度转弧度,弧度转角度 +mysql> select log(3),log(-3),log10(100),log10(-100),radians(180),degrees(pi()/2); + +效果: + ++--------------------+---------+------------+-------------+-------------------+-----------------+ +| log(3) | log(-3) | log10(100) | log10(-100) | radians(180) | degrees(pi()/2) | ++--------------------+---------+------------+-------------+-------------------+-----------------+ +| 1.0986122886681098 | NULL | 2 | NULL | 3.141592653589793 | 90 | ++--------------------+---------+------------+-------------+-------------------+-----------------+ + +``` +#### 7)、正弦函数余弦函数 + +```mysql +#正弦函数余弦函数 +mysql> select sin(pi()/2),degrees(asin(1)),cos(pi()),degrees(acos(-1)),round(tan(pi()/4)),degrees(atan(1)),cot(pi()/4); + +效果: + ++-------------+------------------+-----------+-------------------+--------------------+------------------+--------------------+ +| sin(pi()/2) | degrees(asin(1)) | cos(pi()) | degrees(acos(-1)) | round(tan(pi()/4)) | degrees(atan(1)) | cot(pi()/4) | ++-------------+------------------+-----------+-------------------+--------------------+------------------+--------------------+ +| 1 | 90 | -1 | 180 | 1 | 45 | 1.0000000000000002 | ++-------------+------------------+-----------+-------------------+--------------------+------------------+--------------------+ + +``` +### 2、字符串函数 + +#### 1)、字符串函数、concat_ws忽略空值null + +```mysql +#字符串函数,concat_ws忽略空值null +mysql> select char_length('aab'),length('aabb'),concat('My sql ','5.7'),concat('My',null,'sql'),concat_ws('-','a','b','c'),concat_ws('*','aa',null,'bb'); + +效果: + ++--------------------+----------------+-------------------------+-------------------------+----------------------------+-------------------------------+ +| char_length('aab') | length('aabb') | concat('My sql ','5.7') | concat('My',null,'sql') | concat_ws('-','a','b','c') | concat_ws('*','aa',null,'bb') | ++--------------------+----------------+-------------------------+-------------------------+----------------------------+-------------------------------+ +| 3 | 4 | My sql 5.7 | NULL | a-b-c | aa*bb | ++--------------------+----------------+-------------------------+-------------------------+----------------------------+-------------------------------+ + +``` +#### 2)、替换字符串的函数 + +```mysql +#替换字符串的函数 +mysql> select insert('Quest',2,4,'What') as Coll,insert('Quest',-1,4,'What') as Coll2,insert('Quest',3,100,'Wh') as Coll3; + +效果: + ++-------+-------+-------+ +| Coll | Coll2 | Coll3 | ++-------+-------+-------+ +| QWhat | Quest | QuWh | ++-------+-------+-------+ + +``` +#### 3)、大小写转换、获取指定长度字符串的函数left,right + +```mysql +#大小写转换,获取指定长度字符串的函数left,right; +mysql> select lower('ZHENGXIN'),lcase('ZHENGXIN'),upper('zhengxin'),ucase('zhengxin'),left('football',5),right('football',5); + +效果: + ++-------------------+-------------------+-------------------+-------------------+--------------------+---------------------+ +| lower('ZHENGXIN') | lcase('ZHENGXIN') | upper('zhengxin') | ucase('zhengxin') | left('football',5) | right('football',5) | ++-------------------+-------------------+-------------------+-------------------+--------------------+---------------------+ +| zhengxin | zhengxin | ZHENGXIN | ZHENGXIN | footb | tball | ++-------------------+-------------------+-------------------+-------------------+--------------------+---------------------+ + +``` +#### 4)、填充字符串的函数,删除空格的函数 + +```mysql +#填充字符串的函数,删除空格的函数 +mysql> select lpad('hello',4,'*'),lpad('hello',10,'*'), + -> rpad('hello',10,'*'),concat('(',ltrim(' book '),')'), + -> concat('(',rtrim(' book '),')'), + -> concat('(',trim(' book '),')'), + -> trim('xy' from 'xyxyabababxyxy'); + +效果: + ++---------------------+----------------------+----------------------+-------------------------------------+-------------------------------------+------------------------------------+----------------------------------+ +| lpad('hello',4,'*') | lpad('hello',10,'*') | rpad('hello',10,'*') | concat('(',ltrim(' book '),')') | concat('(',rtrim(' book '),')') | concat('(',trim(' book '),')') | trim('xy' from 'xyxyabababxyxy') | ++---------------------+----------------------+----------------------+-------------------------------------+-------------------------------------+------------------------------------+----------------------------------+ +| hell | *****hello | hello***** | (book ) | ( book) | (book) | ababab | ++---------------------+----------------------+----------------------+-------------------------------------+-------------------------------------+------------------------------------+----------------------------------+ + +``` +#### 5)、重复生成,空格函数,替换函数,比较大小的函数 + +```mysql +#重复生成,空格函数,替换函数,比较大小的函数 +mysql> select repeat('mysql',3),concat('(',space(6),')'), + -> replace('xxx.baidu.com','x','w'),strcmp('abc','abd'); + +效果: + ++-------------------+--------------------------+----------------------------------+---------------------+ +| repeat('mysql',3) | concat('(',space(6),')') | replace('xxx.baidu.com','x','w') | strcmp('abc','abd') | ++-------------------+--------------------------+----------------------------------+---------------------+ +| mysqlmysqlmysql | ( ) | www.baidu.com | -1 | ++-------------------+--------------------------+----------------------------------+---------------------+ + +``` +#### 6)、获取子串的函数 + +```mysql +#获取子串的函数 +mysql> select substring('breakfast',5) as coll, + -> substring('breakfast',3,5) as coll2, + -> substring('breakfast',-3) as coll3, #从后面开始截取3个 + -> substring('breakfast',-1,4) as coll4; #从结尾开始第一个位置截取四个 + +效果: + ++-------+-------+-------+-------+ +| coll | coll2 | coll3 | coll4 | ++-------+-------+-------+-------+ +| kfast | eakfa | ast | t | ++-------+-------+-------+-------+ + +``` +注意还有一个MID函数和substring作用是一样的 + +```mysql +#匹配字串开始的位置,字符串逆序 +mysql> select locate('ball','football'),position('ball'in'football'), + -> instr('football','ball'),reverse('abc'); + +效果: + ++---------------------------+------------------------------+--------------------------+----------------+ +| locate('ball','football') | position('ball'in'football') | instr('football','ball') | reverse('abc') | ++---------------------------+------------------------------+--------------------------+----------------+ +| 5 | 5 | 5 | cba | ++---------------------------+------------------------------+--------------------------+----------------+ + +``` +#### 7)、返回指定位置的值,返回指定字符串的位置的函数 + +```mysql +#返回指定位置的值,返回指定字符串的位置的函数 +mysql> select elt(3,'a','b','c'),elt(2,'a'), + -> field('Hi','hihi','Hey','Hi','bas') as coll, + -> field('Hi','hihi','a','b') as coll2, + -> find_in_set('Hi','hihi,Hey,Hi,bas'); #返回字串位置的函数 + +效果: + ++--------------------+------------+------+-------+-------------------------------------+ +| elt(3,'a','b','c') | elt(2,'a') | coll | coll2 | find_in_set('Hi','hihi,Hey,Hi,bas') | ++--------------------+------------+------+-------+-------------------------------------+ +| c | NULL | 3 | 0 | 3 | ++--------------------+------------+------+-------+-------------------------------------+ + +``` +#### 8)、make_set()函数的使用 + +```mysql +#make_set()函数的使用 +mysql> select make_set(1,'a','b','c') as coll,#0001选第一个 + -> make_set(1|4, 'hello','nice','word') as coll2, #0001 0100-->0101 -->选第一和第三 + -> make_set(1|4,'hello','nice',null,'word') as coll3,#0001 0100-->0101 -->选第一和第三 + -> make_set(0,'a','b','c') as coll4; + +效果: + ++------+------------+-------+-------+ +| coll | coll2 | coll3 | coll4 | ++------+------------+-------+-------+ +| a | hello,word | hello | | ++------+------------+-------+-------+ + +``` +*** +### 3、日期和时间函数 + +#### 1)、获取日期时间函数 + +```mysql +#获取日期时间函数 +mysql> select current_date(),curdate(),curdate()+0, + -> current_time(),curtime(),curtime()+0, + -> current_timestamp(),localtime(),now(),sysdate(); + +效果: + ++----------------+------------+-------------+----------------+-----------+-------------+---------------------+---------------------+---------------------+---------------------+ +| current_date() | curdate() | curdate()+0 | current_time() | curtime() | curtime()+0 | current_timestamp() | localtime() | now() | sysdate() | ++----------------+------------+-------------+----------------+-----------+-------------+---------------------+---------------------+---------------------+---------------------+ +| 2019-02-25 | 2019-02-25 | 20190225 | 10:40:22 | 10:40:22 | 104022 | 2019-02-25 10:40:22 | 2019-02-25 10:40:22 | 2019-02-25 10:40:22 | 2019-02-25 10:40:22 | ++----------------+------------+-------------+----------------+-----------+-------------+---------------------+---------------------+---------------------+---------------------+s +``` +#### 2)、获取时间的数字,根据时间获取日期(互为反函数) + +```mysql +#获取时间的数字,根据时间获取日期(互为反函数) +mysql> select unix_timestamp(),unix_timestamp(now()),now(), + -> from_unixtime(1523689758); + +效果: + ++------------------+-----------------------+---------------------+---------------------------+ +| unix_timestamp() | unix_timestamp(now()) | now() | from_unixtime(1523689758) | ++------------------+-----------------------+---------------------+---------------------------+ +| 1551062468 | 1551062468 | 2019-02-25 10:41:08 | 2018-04-14 15:09:18 | ++------------------+-----------------------+---------------------+---------------------------+ + +``` +#### 3)、返回当前时区日期和时间的函数,日期月份时间函数 + +```mysql +#返回当前时区日期和时间的函数,日期月份时间函数 +mysql> select utc_time(),utc_time()+0, + -> utc_date(),utc_date()+0, + -> month('2016-03-04'),monthname('2016-03-04'), + -> dayname('2018-04-14'),dayofweek('2018-04-14'), + -> weekday('2018-04-14'); + +效果: + ++------------+--------------+------------+--------------+---------------------+-------------------------+-----------------------+-------------------------+-----------------------+ +| utc_time() | utc_time()+0 | utc_date() | utc_date()+0 | month('2016-03-04') | monthname('2016-03-04') | dayname('2018-04-14') | dayofweek('2018-04-14') | weekday('2018-04-14') | ++------------+--------------+------------+--------------+---------------------+-------------------------+-----------------------+-------------------------+-----------------------+ +| 02:41:56 | 24156 | 2019-02-25 | 20190225 | 3 | March | Saturday | 7 | 5 | ++------------+--------------+------------+--------------+---------------------+-------------------------+-----------------------+-------------------------+-----------------------+ + +``` +注意dayofweek和weekday的差别: + +* `DAYOFWEEK(d)`函数返回d对应的一周中的索引(位置)。1表示周日,2表示周一,....,7表示周六; +* `WEEKDAY(d)`返回d对应的工作日索引。0表示周一,1表示周二,...,6表示周日; + +```mysql +#返回是这一年的第几周 +mysql> select week('2018-4-16'),#默认0表示第一天从周末开始 + -> week('2018-04-16',1), #周一#返回是这一年的第几周 + -> dayofyear('2018-4-16'),dayofmonth('2018-4-14'), #返回一年中的第几天 + -> year('2018-4-14'),quarter('2018-4-14'), + -> minute('10:10:02'),second("10:10:02"); + +效果: + ++-------------------+----------------------+------------------------+-------------------------+-------------------+----------------------+--------------------+--------------------+ +| week('2018-4-16') | week('2018-04-16',1) | dayofyear('2018-4-16') | dayofmonth('2018-4-14') | year('2018-4-14') | quarter('2018-4-14') | minute('10:10:02') | second("10:10:02") | ++-------------------+----------------------+------------------------+-------------------------+-------------------+----------------------+--------------------+--------------------+ +| 15 | 16 | 106 | 14 | 2018 | 2 | 10 | 2 | ++-------------------+----------------------+------------------------+-------------------------+-------------------+----------------------+--------------------+--------------------+ + +``` +#### 4)、获取指定日期的指定值的函数 + +```mysql +#获取指定日期的指定值的函数 +mysql> select extract(year from '2018-07-06') as coll, + -> extract(year_month from '2018-08-06') as coll2, + -> extract(day_minute from '2018-07-06 10:11:05') as coll3; + +效果: + ++------+--------+-------+ +| coll | coll2 | coll3 | ++------+--------+-------+ +| 2018 | 201808 | 61011 | ++------+--------+-------+ + +``` +#### 5)、时间和秒钟转换的函数 + +```mysql +#时间和秒钟转换的函数 +mysql> select time_to_sec('01:00:40'), + -> sec_to_time(3600); + +效果: + ++-------------------------+-------------------+ +| time_to_sec('01:00:40') | sec_to_time(3600) | ++-------------------------+-------------------+ +| 3640 | 01:00:00 | ++-------------------------+-------------------+ + +``` +#### 6)、计算日期和时间的函数 + +```mysql +#计算日期和时间的函数 +mysql> select date_add('2010-12-31 23:59:59',interval 1 second) as coll, + -> adddate('2010-12-31 23:59:59',interval 1 second) as coll2, + -> date_add('2010-12-31 23:59:59',interval '0:0:1' hour_second) as coll3, #后面的hour_second要看表决定 + -> date_sub('2011-01-02',interval 31 day) as coll4, + -> subdate('2011-01-02',interval 31 day) as coll5, + -> date_sub('2011-01-02 00:01:00',interval '0 0:1:1' day_second) as coll6; #对应位置的相减 + +效果: + ++---------------------+---------------------+---------------------+------------+------------+---------------------+ +| coll | coll2 | coll3 | coll4 | coll5 | coll6 | ++---------------------+---------------------+---------------------+------------+------------+---------------------+ +| 2011-01-01 00:00:00 | 2011-01-01 00:00:00 | 2011-01-01 00:00:00 | 2010-12-02 | 2010-12-02 | 2011-01-01 23:59:59 | ++---------------------+---------------------+---------------------+------------+------------+---------------------+ + +``` +#### 7)、直接输入两个时间,计算 + +```mysql +#直接输入两个时间,计算 +mysql> select addtime('2000-12-31 23:59:59','1:1:1') as coll, + -> subtime('2000-12-31 23:59:59','1:1:1')as coll2, + -> datediff('2000-12-28','2001-01-03') as coll3; #前面的减后面的 ++---------------------+---------------------+-------+ +| coll | coll2 | coll3 | ++---------------------+---------------------+-------+ +| 2001-01-01 01:01:00 | 2000-12-31 22:58:58 | -6 | ++---------------------+---------------------+-------+ + +``` +注意日期的一些区别: + +![1563756502079](assets/1563756502079.png) + +日期和时间格式化的函数 + +![1563756518657](assets/1563756518657.png) + +![1563756595193](assets/1563756595193.png) + +![1563756606926](assets/1563756606926.png) + +#### 8)、时间日期格式化函数 + +```mysql +#时间日期格式化函数 +mysql> select date_format('1997-10-04 22:23:00','%W %M %Y') as coll, + -> date_format('1997-10-04 22:23:00','%D %y %a %d %m %b %j'), + -> time_format('16:00:00','%H %k %h %I %l'), + -> date_format('2000-10-05 22:23:00',get_format(date,'USA')); + +效果: + ++-----------------------+-----------------------------------------------------------+------------------------------------------+-----------------------------------------------------------+ +| coll | date_format('1997-10-04 22:23:00','%D %y %a %d %m %b %j') | time_format('16:00:00','%H %k %h %I %l') | date_format('2000-10-05 22:23:00',get_format(date,'USA')) | ++-----------------------+-----------------------------------------------------------+------------------------------------------+-----------------------------------------------------------+ +| Saturday October 1997 | 4th 97 Sat 04 10 Oct 277 | 16 16 04 04 4 | 10.05.2000 | ++-----------------------+-----------------------------------------------------------+------------------------------------------+-----------------------------------------------------------+ + +``` +### 4、条件判断函数 + +```mysql +#条件约束函数 +mysql> select if(1>2,2,3), + -> ifNull(null,10),ifNull(1/0,100), + -> case 2 when 1 then 'one' when 2 then 'two' when 3 then 'three' else 'more' end, #2等于后面的2返回后面的then + -> case when 1>2 then 'a' else 'b' end; + +效果: + ++-------------+-----------------+-----------------+--------------------------------------------------------------------------------+-------------------------------------+ +| if(1>2,2,3) | ifNull(null,10) | ifNull(1/0,100) | case 2 when 1 then 'one' when 2 then 'two' when 3 then 'three' else 'more' end | case when 1>2 then 'a' else 'b' end | ++-------------+-----------------+-----------------+--------------------------------------------------------------------------------+-------------------------------------+ +| 3 | 10 | 100.0000 | two | b | ++-------------+-----------------+-----------------+--------------------------------------------------------------------------------+-------------------------------------+ + +``` +### 5、系统信息函数 + +```mysql +#系统信息函数 +mysql> show processlist;#输出当前用户的连接信息 + +效果: + ++----+------+-----------+------------+---------+------+----------+------------------+ +| Id | User | Host | db | Command | Time | State | Info | ++----+------+-----------+------------+---------+------+----------+------------------+ +| 2 | root | localhost | learnmysql | Query | 0 | starting | show processlist | ++----+------+-----------+------------+---------+------+----------+------------------+ +1 row in set (0.00 sec) + +``` +```mysql +#获取字符串的字符集和排列方式的函数 +mysql> select charset('abc'),charset(convert('abc' using latin1)), + -> charset(version()), #获取字符集 + -> collation('abc'),collation(convert('abc' using utf8));#获取排列方式 + +效果: + ++----------------+--------------------------------------+--------------------+------------------+--------------------------------------+ +| charset('abc') | charset(convert('abc' using latin1)) | charset(version()) | collation('abc') | collation(convert('abc' using utf8)) | ++----------------+--------------------------------------+--------------------+------------------+--------------------------------------+ +| utf8 | latin1 | utf8 | utf8_general_ci | utf8_general_ci | ++----------------+--------------------------------------+--------------------+------------------+--------------------------------------+ + +``` +还要注意Last_insert_id最后自动生成的ID值。 + +### 6、加/解密函数 + +```mysql +mysql> select password('newpwd'),MD5('mypwd'), + -> encode('secret','cry'),length(encode('secret','cry')), + -> decode(encode('secret','cry'),'cry');#加密后解密 + +效果: + ++-------------------------------------------+----------------------------------+------------------------+--------------------------------+--------------------------------------+ +| password('newpwd') | MD5('mypwd') | encode('secret','cry') | length(encode('secret','cry')) | decode(encode('secret','cry'),'cry') | ++-------------------------------------------+----------------------------------+------------------------+--------------------------------+--------------------------------------+ +| *1FA85AA204CC12B39B20E8F1E839D11B3F9E6AA4 | 318bcb4be908d0da6448a0db76908d78 | �h�� | 6 | secret | ++-------------------------------------------+----------------------------------+------------------------+--------------------------------+--------------------------------------+ + +``` +### 7、其他函数 + +```mysql +mysql> select format(123.1234,2),format(123.1,3),format(123.123,0),#格式化函数 + -> #不同进制数之间的转换 + -> conv('a',16,2),conv(15,10,2),conv(15,10,8),conv(15,10,16); + +效果: + ++--------------------+-----------------+-------------------+----------------+---------------+---------------+----------------+ +| format(123.1234,2) | format(123.1,3) | format(123.123,0) | conv('a',16,2) | conv(15,10,2) | conv(15,10,8) | conv(15,10,16) | ++--------------------+-----------------+-------------------+----------------+---------------+---------------+----------------+ +| 123.12 | 123.100 | 123 | 1010 | 1111 | 17 | F | ++--------------------+-----------------+-------------------+----------------+---------------+---------------+----------------+ + +``` + +```mysql +#IP地址与数字相互转换的函数 +mysql> select inet_aton('209.207.224.40'),inet_ntoa(3520061480), + -> #枷锁函数和解锁函数 + -> get_lock('lock1',10),#这个锁持续10秒 + -> is_used_lock('lock1'), #返回当前连接ID + -> is_free_lock('lock1'), #是否是可用的 + -> release_lock('lock1'); + +效果: + ++-----------------------------+-----------------------+----------------------+-----------------------+-----------------------+-----------------------+ +| inet_aton('209.207.224.40') | inet_ntoa(3520061480) | get_lock('lock1',10) | is_used_lock('lock1') | is_free_lock('lock1') | release_lock('lock1') | ++-----------------------------+-----------------------+----------------------+-----------------------+-----------------------+-----------------------+ +| 3520061480 | 209.207.224.40 | 1 | 2 | 0 | 1 | ++-----------------------------+-----------------------+----------------------+-----------------------+-----------------------+-----------------------+ + +``` +```mysql +#重复执行指定操作的函数 +mysql> select benchmark(5000,password('newpad')), + -> charset('abc'),charset(convert('abc' using latin1)),#改变字符集的函数 + -> cast(100 as char(2)),convert('2010-10-11 12:12:12',time);#改变数据类型的函数 + +效果: + ++------------------------------------+----------------+--------------------------------------+----------------------+-------------------------------------+ +| benchmark(5000,password('newpad')) | charset('abc') | charset(convert('abc' using latin1)) | cast(100 as char(2)) | convert('2010-10-11 12:12:12',time) | ++------------------------------------+----------------+--------------------------------------+----------------------+-------------------------------------+ +| 0 | utf8 | latin1 | 10 | 12:12:12 | ++------------------------------------+----------------+--------------------------------------+----------------------+-------------------------------------+ + +``` +### 8、综合案列-Mysql函数的使用 + +```mysql +select round(rand() * 10),round(rand() * 10),round(rand() * 10);#产生三个1-10之间的随机数 +select pi(),sin(pi()),cos(0),round(tan(pi()/4)),floor(cot(pi()/4)); + +create database test_db3; +use test_db3; +show tables; +create table member +( + m_id int(11) primary key auto_increment, + m_FN varchar(15), + m_LN varchar(15), + m_brith datetime, + m_info varchar(15) null +); + +insert into member values(null,'Halen','Park','1970-06-29','GoodMan'); + +select length(m_FN),#返回m_FN的长度 +concat(m_FN,m_LN),#返回第一条记录中的全名 +lower(m_info),#将m_info转换成小写 +reverse(m_info) from member; + +select year(curdate())-year(m_brith) as age,#计算年龄 +dayofyear(m_brith) as days, +date_format(m_brith,'%W %D %M %Y') as birthDate from member; + +insert into member values(null,'Samuel','Green',now(),null); + +select last_insert_id(); #输出最后插入的自增的编号 + +select m_brith,case when year(m_brith) < 2000 then 'old' +when year(m_brith) > 2000 then 'young' +else 'not born' end as status from member; +``` + diff --git a/DB/MySQL/primary/assets/1563756502079.png b/DB/MySQL/primary/assets/1563756502079.png new file mode 100644 index 00000000..8895f373 Binary files /dev/null and b/DB/MySQL/primary/assets/1563756502079.png differ diff --git a/DB/MySQL/primary/assets/1563756518657.png b/DB/MySQL/primary/assets/1563756518657.png new file mode 100644 index 00000000..fd747f02 Binary files /dev/null and b/DB/MySQL/primary/assets/1563756518657.png differ diff --git a/DB/MySQL/primary/assets/1563756595193.png b/DB/MySQL/primary/assets/1563756595193.png new file mode 100644 index 00000000..2c52483e Binary files /dev/null and b/DB/MySQL/primary/assets/1563756595193.png differ diff --git a/DB/MySQL/primary/assets/1563756606926.png b/DB/MySQL/primary/assets/1563756606926.png new file mode 100644 index 00000000..ad476e89 Binary files /dev/null and b/DB/MySQL/primary/assets/1563756606926.png differ diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/10_\346\274\224\347\244\272\347\273\223\346\236\234.png" "b/DB/MySQL/primary/images/10_\346\274\224\347\244\272\347\273\223\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/10_\346\274\224\347\244\272\347\273\223\346\236\234.png" rename to "DB/MySQL/primary/images/10_\346\274\224\347\244\272\347\273\223\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/11_DateTime.png" b/DB/MySQL/primary/images/11_DateTime.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/11_DateTime.png" rename to DB/MySQL/primary/images/11_DateTime.png diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/12_\346\274\224\347\244\272\346\225\210\346\236\234.png" "b/DB/MySQL/primary/images/12_\346\274\224\347\244\272\346\225\210\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/12_\346\274\224\347\244\272\346\225\210\346\236\234.png" rename to "DB/MySQL/primary/images/12_\346\274\224\347\244\272\346\225\210\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/13_TimeStamp.png" b/DB/MySQL/primary/images/13_TimeStamp.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/13_TimeStamp.png" rename to DB/MySQL/primary/images/13_TimeStamp.png diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/14_\346\226\207\346\234\254\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" "b/DB/MySQL/primary/images/14_\346\226\207\346\234\254\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/14_\346\226\207\346\234\254\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" rename to "DB/MySQL/primary/images/14_\346\226\207\346\234\254\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/15_char\345\222\214varchar.png" "b/DB/MySQL/primary/images/15_char\345\222\214varchar.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/15_char\345\222\214varchar.png" rename to "DB/MySQL/primary/images/15_char\345\222\214varchar.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/16_char\345\222\214varchar2.png" "b/DB/MySQL/primary/images/16_char\345\222\214varchar2.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/16_char\345\222\214varchar2.png" rename to "DB/MySQL/primary/images/16_char\345\222\214varchar2.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/17_emum.png" b/DB/MySQL/primary/images/17_emum.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/17_emum.png" rename to DB/MySQL/primary/images/17_emum.png diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/18_\346\265\213\350\257\225\347\273\223\346\236\234.png" "b/DB/MySQL/primary/images/18_\346\265\213\350\257\225\347\273\223\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/18_\346\265\213\350\257\225\347\273\223\346\236\234.png" rename to "DB/MySQL/primary/images/18_\346\265\213\350\257\225\347\273\223\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/19_\346\265\213\350\257\225\347\273\223\346\236\2342.png" "b/DB/MySQL/primary/images/19_\346\265\213\350\257\225\347\273\223\346\236\2342.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/19_\346\265\213\350\257\225\347\273\223\346\236\2342.png" rename to "DB/MySQL/primary/images/19_\346\265\213\350\257\225\347\273\223\346\236\2342.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/1_\345\255\230\345\202\250\345\274\225\346\223\216\351\200\211\346\213\251.png" "b/DB/MySQL/primary/images/1_\345\255\230\345\202\250\345\274\225\346\223\216\351\200\211\346\213\251.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/1_\345\255\230\345\202\250\345\274\225\346\223\216\351\200\211\346\213\251.png" rename to "DB/MySQL/primary/images/1_\345\255\230\345\202\250\345\274\225\346\223\216\351\200\211\346\213\251.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/20_SET\346\265\213\350\257\225.png" "b/DB/MySQL/primary/images/20_SET\346\265\213\350\257\225.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/20_SET\346\265\213\350\257\225.png" rename to "DB/MySQL/primary/images/20_SET\346\265\213\350\257\225.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/21_\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" "b/DB/MySQL/primary/images/21_\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/21_\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" rename to "DB/MySQL/primary/images/21_\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\347\261\273\345\236\213.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/22_Bit\347\261\273\345\236\213.png" "b/DB/MySQL/primary/images/22_Bit\347\261\273\345\236\213.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/22_Bit\347\261\273\345\236\213.png" rename to "DB/MySQL/primary/images/22_Bit\347\261\273\345\236\213.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/23_\346\225\210\346\236\234.png" "b/DB/MySQL/primary/images/23_\346\225\210\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/23_\346\225\210\346\236\234.png" rename to "DB/MySQL/primary/images/23_\346\225\210\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/24_\346\257\224\350\276\203\350\277\220\347\256\227\347\254\246.png" "b/DB/MySQL/primary/images/24_\346\257\224\350\276\203\350\277\220\347\256\227\347\254\246.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/24_\346\257\224\350\276\203\350\277\220\347\256\227\347\254\246.png" rename to "DB/MySQL/primary/images/24_\346\257\224\350\276\203\350\277\220\347\256\227\347\254\246.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/25_\346\255\243\345\210\231\347\273\223\346\236\234.png" "b/DB/MySQL/primary/images/25_\346\255\243\345\210\231\347\273\223\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/25_\346\255\243\345\210\231\347\273\223\346\236\234.png" rename to "DB/MySQL/primary/images/25_\346\255\243\345\210\231\347\273\223\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/26_LIKE.png" b/DB/MySQL/primary/images/26_LIKE.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/26_LIKE.png" rename to DB/MySQL/primary/images/26_LIKE.png diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/27_REGEXP.png" b/DB/MySQL/primary/images/27_REGEXP.png similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/27_REGEXP.png" rename to DB/MySQL/primary/images/27_REGEXP.png diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/28_\344\274\230\345\205\210\347\272\247.png" "b/DB/MySQL/primary/images/28_\344\274\230\345\205\210\347\272\247.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/28_\344\274\230\345\205\210\347\272\247.png" rename to "DB/MySQL/primary/images/28_\344\274\230\345\205\210\347\272\247.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/2_desc\346\237\245\347\234\213\347\273\223\346\236\234.png" "b/DB/MySQL/primary/images/2_desc\346\237\245\347\234\213\347\273\223\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/2_desc\346\237\245\347\234\213\347\273\223\346\236\234.png" rename to "DB/MySQL/primary/images/2_desc\346\237\245\347\234\213\347\273\223\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/3_\346\225\264\345\275\242.png" "b/DB/MySQL/primary/images/3_\346\225\264\345\275\242.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/3_\346\225\264\345\275\242.png" rename to "DB/MySQL/primary/images/3_\346\225\264\345\275\242.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/4_\346\225\264\345\275\242\345\217\226\345\200\274\350\214\203\345\233\264.png" "b/DB/MySQL/primary/images/4_\346\225\264\345\275\242\345\217\226\345\200\274\350\214\203\345\233\264.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/4_\346\225\264\345\275\242\345\217\226\345\200\274\350\214\203\345\233\264.png" rename to "DB/MySQL/primary/images/4_\346\225\264\345\275\242\345\217\226\345\200\274\350\214\203\345\233\264.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/5_\346\227\266\351\227\264\346\227\245\346\234\237.png" "b/DB/MySQL/primary/images/5_\346\227\266\351\227\264\346\227\245\346\234\237.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/5_\346\227\266\351\227\264\346\227\245\346\234\237.png" rename to "DB/MySQL/primary/images/5_\346\227\266\351\227\264\346\227\245\346\234\237.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/6_\346\227\245\346\234\237Date.png" "b/DB/MySQL/primary/images/6_\346\227\245\346\234\237Date.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/6_\346\227\245\346\234\237Date.png" rename to "DB/MySQL/primary/images/6_\346\227\245\346\234\237Date.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/7_\346\227\266\351\227\264Time.png" "b/DB/MySQL/primary/images/7_\346\227\266\351\227\264Time.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/7_\346\227\266\351\227\264Time.png" rename to "DB/MySQL/primary/images/7_\346\227\266\351\227\264Time.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/8_\346\274\224\347\244\272\346\217\222\345\205\245\347\273\223\346\236\234.png" "b/DB/MySQL/primary/images/8_\346\274\224\347\244\272\346\217\222\345\205\245\347\273\223\346\236\234.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/8_\346\274\224\347\244\272\346\217\222\345\205\245\347\273\223\346\236\234.png" rename to "DB/MySQL/primary/images/8_\346\274\224\347\244\272\346\217\222\345\205\245\347\273\223\346\236\234.png" diff --git "a/\346\225\260\346\215\256\345\272\223/MySQL/primary/images/9_Date\346\227\245\346\234\237.png" "b/DB/MySQL/primary/images/9_Date\346\227\245\346\234\237.png" similarity index 100% rename from "\346\225\260\346\215\256\345\272\223/MySQL/primary/images/9_Date\346\227\245\346\234\237.png" rename to "DB/MySQL/primary/images/9_Date\346\227\245\346\234\237.png" diff --git "a/DB/MySQL/primary/\345\255\220\346\237\245\350\257\242.md" "b/DB/MySQL/primary/\345\255\220\346\237\245\350\257\242.md" new file mode 100644 index 00000000..0d5c6a74 --- /dev/null +++ "b/DB/MySQL/primary/\345\255\220\346\237\245\350\257\242.md" @@ -0,0 +1,185 @@ +# 子查询 + +### 1、ANY、SOME、ALL + +一个查询语句嵌套在另一个查询语句内部的查询。 + +常见操作符 : `ANY(SOME)、ALL、IN、EXISTS`。 + +ANY和SOME关键字是同义词,表示满足其中任一条件,它们允许创建一个表达式对子查询的返回值列表进行比较,只要满足内层子查询中的任何一个比较条件,就返回一个结果作为外层查询的条件。 +下面定义两个表tb1和tb2: + +```mysql +CREATE table tbl1 ( num1 INT NOT NULL); + +CREATE table tbl2 ( num2 INT NOT NULL); +``` + +分别向两个表中插入数据: + +```mysql +INSERT INTO tbl1 values(1), (5), (13), (27); + +INSERT INTO tbl2 values(6), (14), (11), (20); + +``` + +**ANY关键字接在一个比较操作符的后面,表示若与子查询返回的任何值比较为TRUE**,则返回TRUE。 + +【例7.53】返回tbl2表的所有num2列,然后将tbl1中的num1的值与之进行比较,只要大于num2的任何1个值,即为符合查询条件的结果。 + + ```mysql +SELECT num1 FROM tbl1 WHERE num1 > ANY (SELECT num2 FROM tbl2); + ``` + +【例7.54】返回tbl1表中比tbl2表num2 列所有值都大的值,SQL语句如下: + +```mysql + SELECT num1 FROM tbl1 WHERE num1 > ALL (SELECT num2 FROM tbl2); +``` + +### 2、EXIST + +**Exits关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么EXISTS的结果为true,此时外层查询语句将进行查询;如果子查询没有返回任何行,那么EXISTS的返回结果是false,此时外层语句不会进行查询**; + +【例7.55】查询suppliers表中是否存在`s_id=107`的供应商,如果存在,则查询fruits表中的记录,SQL语句如下: + +```java +mysql> SELECT * FROM fruits + -> WHERE EXISTS + -> (SELECT s_name FROM suppliers WHERE s_id = 107); ++------+------+------------+---------+ +| f_id | s_id | f_name | f_price | ++------+------+------------+---------+ +| a1 | 101 | apple | 5.20 | +| a2 | 103 | apricot | 2.20 | +| b1 | 101 | blackberry | 10.20 | +| b2 | 104 | berry | 7.60 | +| b5 | 107 | xxxx | 3.60 | +| bs1 | 102 | orange | 11.20 | +| bs2 | 105 | melon | 8.20 | +| c0 | 101 | cherry | 3.20 | +| l2 | 104 | lemon | 6.40 | +| m1 | 106 | mango | 15.60 | +| m2 | 105 | xbabay | 2.60 | +| m3 | 105 | xxtt | 11.60 | +| o2 | 103 | coconut | 9.20 | +| t1 | 102 | banana | 10.30 | +| t2 | 102 | grape | 5.30 | +| t4 | 107 | xbababa | 3.60 | ++------+------+------------+---------+ +16 rows in set (0.00 sec) + +``` + +【例7.56】查询suppliers表中是否存在s_id=107的供应商,如果存在,则查询fruits表中的f_price大于10.20的记录,SQL语句如下: + +```mysql +mysql> SELECT * FROM fruits + -> WHERE f_price>10.20 AND EXISTS + -> (SELECT s_name FROM suppliers WHERE s_id = 107); ++------+------+--------+---------+ +| f_id | s_id | f_name | f_price | ++------+------+--------+---------+ +| bs1 | 102 | orange | 11.20 | +| m1 | 106 | mango | 15.60 | +| m3 | 105 | xxtt | 11.60 | +| t1 | 102 | banana | 10.30 | ++------+------+--------+---------+ +4 rows in set (0.00 sec) + +``` + +【例7.57】查询suppliers表中是否存在s_id=107的供应商,如果**不存在**则查询fruits表中的记录,SQL语句如下 (`NOT EXISTS`): + +```mysql +mysql> SELECT * FROM fruits + -> WHERE NOT EXISTS + -> (SELECT s_name FROM suppliers WHERE s_id = 107); +Empty set (0.00 sec) + +``` + +【例7.58】在`orderitems`表中查询f_id为c0的订单号,并根据订单号查询具有订单号的客户c_id,SQL语句如下: + +```mysql +mysql> SELECT c_id FROM orders WHERE o_num IN + (SELECT o_num FROM orderitems WHERE f_id = 'c0'); ++-------+ +| c_id | ++-------+ +| 10004 | +| 10001 | ++-------+ +2 rows in set (0.01 sec) + +``` + +内层:` SELECT o_num FROM orderitems WHERE f_id = 'c0';` + +可以看到,符合条件的o_num列的值有两个:30003和30005,然后执行外层查询,在orders表中查询订单号等于30003或30005的客户c_id。嵌套子查询语句还可以写为如下形式,实现相同的效果: + `SELECT c_id FROM orders WHERE o_num IN (30003, 30005);` + +【例7.61】在suppliers表中查询s_city等于“Tianjin”的供应商s_id,然后在fruits表中查询所有非该供应商提供的水果的种类,SQL语句如下: + +```mysql +mysql> SELECT s_id, f_name FROM fruits + -> WHERE s_id <> + -> (SELECT s1.s_id FROM suppliers AS s1 WHERE s1.s_city = 'Tianjin'); ++------+---------+ +| s_id | f_name | ++------+---------+ +| 103 | apricot | +| 104 | berry | +| 107 | xxxx | +| 102 | orange | +| 105 | melon | +| 104 | lemon | +| 106 | mango | +| 105 | xbabay | +| 105 | xxtt | +| 103 | coconut | +| 102 | banana | +| 102 | grape | +| 107 | xbababa | ++------+---------+ +13 rows in set (0.27 sec) + +``` + +### 3、合并查询结果 + +【例7.62】查询所有价格小于9的水果的信息,查询s_id等于101和103所有的水果的信息,使用UNION连接查询结果,SQL语句如下: + +```mysql +mysql> SELECT s_id, f_name, f_price + -> FROM fruits + -> WHERE f_price < 9.0 + -> UNION ALL + -> SELECT s_id, f_name, f_price + -> FROM fruits + -> WHERE s_id IN(101,103); ++------+------------+---------+ +| s_id | f_name | f_price | ++------+------------+---------+ +| 101 | apple | 5.20 | +| 103 | apricot | 2.20 | +| 104 | berry | 7.60 | +| 107 | xxxx | 3.60 | +| 105 | melon | 8.20 | +| 101 | cherry | 3.20 | +| 104 | lemon | 6.40 | +| 105 | xbabay | 2.60 | +| 102 | grape | 5.30 | +| 107 | xbababa | 3.60 | +| 101 | apple | 5.20 | +| 103 | apricot | 2.20 | +| 101 | blackberry | 10.20 | +| 101 | cherry | 3.20 | +| 103 | coconut | 9.20 | ++------+------------+---------+ +15 rows in set (0.02 sec) +``` + + + diff --git "a/DB/MySQL/primary/\346\237\245\350\257\242-\345\210\206\347\273\204\345\222\214\350\201\232\345\220\210\345\207\275\346\225\260.md" "b/DB/MySQL/primary/\346\237\245\350\257\242-\345\210\206\347\273\204\345\222\214\350\201\232\345\220\210\345\207\275\346\225\260.md" new file mode 100644 index 00000000..43abf43e --- /dev/null +++ "b/DB/MySQL/primary/\346\237\245\350\257\242-\345\210\206\347\273\204\345\222\214\350\201\232\345\220\210\345\207\275\346\225\260.md" @@ -0,0 +1,446 @@ +# 分组和聚合函数 + +演示的表和插入语句 + +```mysql +# 建立表 +mysql> CREATE TABLE fruits + -> ( + -> f_id char(10) NOT NULL, # 水果id + -> s_id INT NOT NULL, # 供应商id + -> f_name char(255) NOT NULL, # 水果名字 + -> f_price decimal(8,2) NOT NULL, # 水果价格 + -> PRIMARY KEY(f_id) + -> ); +Query OK, 0 rows affected (0.06 sec) + +# 插入 +mysql> INSERT INTO fruits (f_id, s_id, f_name, f_price) + -> VALUES('a1', 101,'apple',5.2), + -> ('b1',101,'blackberry', 10.2), + -> ('bs1',102,'orange', 11.2), + -> ('bs2',105,'melon',8.2), + -> ('t1',102,'banana', 10.3), + -> ('t2',102,'grape', 5.3), + -> ('o2',103,'coconut', 9.2), + -> ('c0',101,'cherry', 3.2), + -> ('a2',103, 'apricot',2.2), + -> ('l2',104,'lemon', 6.4), + -> ('b2',104,'berry', 7.6), + -> ('m1',106,'mango', 15.6), + -> ('m2',105,'xbabay', 2.6), + -> ('t4',107,'xbababa', 3.6), + -> ('m3',105,'xxtt', 11.6), + -> ('b5',107,'xxxx', 3.6); +Query OK, 16 rows affected (0.01 sec) +Records: 16 Duplicates: 0 Warnings: 0 + +# 查询所有 +mysql> select * from fruits; ++------+------+------------+---------+ +| f_id | s_id | f_name | f_price | ++------+------+------------+---------+ +| a1 | 101 | apple | 5.20 | +| a2 | 103 | apricot | 2.20 | +| b1 | 101 | blackberry | 10.20 | +| b2 | 104 | berry | 7.60 | +| b5 | 107 | xxxx | 3.60 | +| bs1 | 102 | orange | 11.20 | +| bs2 | 105 | melon | 8.20 | +| c0 | 101 | cherry | 3.20 | +| l2 | 104 | lemon | 6.40 | +| m1 | 106 | mango | 15.60 | +| m2 | 105 | xbabay | 2.60 | +| m3 | 105 | xxtt | 11.60 | +| o2 | 103 | coconut | 9.20 | +| t1 | 102 | banana | 10.30 | +| t2 | 102 | grape | 5.30 | +| t4 | 107 | xbababa | 3.60 | ++------+------+------------+---------+ +16 rows in set (0.00 sec) + +mysql> +``` + +## 一、分组查询 + +### 1、基本分组操作 + +对数据按照某个或多个字段进行分组,MYSQL中使用`group by `关键字对数据进行分组。基本形式为: + +```mysql +group by 字段 having <条件表达式> +``` + +`group by `关键字通常和集合函数一起使用,例如`MAX()、MIN()、COUNT()、SUM()、AVG()`。 + +例如: 根据`si_d`对`fruits`表中的数据进行分组: + +```mysql +mysql> select s_id, count(*) as Total from fruits group by s_id; ++------+-------+ +| s_id | Total | ++------+-------+ +| 101 | 3 | +| 102 | 3 | +| 103 | 2 | +| 104 | 2 | +| 105 | 3 | +| 106 | 1 | +| 107 | 2 | ++------+-------+ +7 rows in set (0.00 sec) + +``` + +`s_id`表示供应商的`ID`。`Total`字段使用`COUNT()`函数计算得出。 + +`GROUP BY`字句按照`s_id`先**排序**并对数据进行分组。 + +如果要查询所有种类的名称,可以使用`GROUP_CONCAT()`函数。 + +```mysql +mysql> select s_id, GROUP_CONCAT(f_name) AS Names FROM fruits GROUP BY s_id; ++------+-------------------------+ +| s_id | Names | ++------+-------------------------+ +| 101 | apple,blackberry,cherry | +| 102 | orange,banana,grape | +| 103 | apricot,coconut | +| 104 | berry,lemon | +| 105 | melon,xbabay,xxtt | +| 106 | mango | +| 107 | xxxx,xbababa | ++------+-------------------------+ +7 rows in set (0.00 sec) + +mysql> + +``` + +使用`having`过滤分组: + +**GROUP BY**可以和`HAVING`一起限定显示记录所需要满足的条件。只有满足条件的分组才会被显示。 + +例如,查询水果种类大于`>1`的分组信息。 + +```mysql +mysql> select s_id, GROUP_CONCAT(f_name) as Names from fruits GROUP BY s_id HAVING count(f_name) > 1; ++------+-------------------------+ +| s_id | Names | ++------+-------------------------+ +| 101 | apple,blackberry,cherry | +| 102 | orange,banana,grape | +| 103 | apricot,coconut | +| 104 | berry,lemon | +| 105 | melon,xbabay,xxtt | +| 107 | xxxx,xbababa | ++------+-------------------------+ +6 rows in set (0.01 sec) + +``` + +可以看到由于`s_id = 106`的**供应商**的水果种类只有一种。所以不在结果中。 + +### 2、 `GROUP BY` **关键字和`WHERE`关键字都是用来过滤数据,有什么区别呢**? + + 答: + +* `HAVING`在分组之后进行过滤。 +* `WHERE`在分组之前过滤。 + +看例子: + +①都where和having都可以使用的场景: + +```mysql +mysql> select f_id, f_price from fruits where f_price > 10.00; ++------+---------+ +| f_id | f_price | ++------+---------+ +| b1 | 10.20 | +| bs1 | 11.20 | +| m1 | 15.60 | +| m3 | 11.60 | +| t1 | 10.30 | ++------+---------+ +5 rows in set (0.00 sec) + +mysql> select f_id, f_price from fruits having f_price > 10.00; ++------+---------+ +| f_id | f_price | ++------+---------+ +| b1 | 10.20 | +| bs1 | 11.20 | +| m1 | 15.60 | +| m3 | 11.60 | +| t1 | 10.30 | ++------+---------+ +5 rows in set (0.00 sec) + +``` + +原因: `f_price`作为条件也出现在了查询字段中。 + +②只可以使用where,不可以使用having的情况: + +```mysql +mysql> select f_id, f_name from fruits where f_price > 10.00; ++------+------------+ +| f_id | f_name | ++------+------------+ +| b1 | blackberry | +| bs1 | orange | +| m1 | mango | +| m3 | xxtt | +| t1 | banana | ++------+------------+ +5 rows in set (0.01 sec) + +mysql> select f_id, f_name from fruits having f_price > 10.00; +ERROR 1054 (42S22): Unknown column 'f_price' in 'having clause' +mysql> + +``` + +原因: `f_price`没有在查询语句中出现,所以不能用`having`。 + +③只可以使用having,不可以使用where的情况: + +查询供应商供应的种类`>2`的情况。 + +```mysql +mysql> select s_id, GROUP_CONCAT(f_name), COUNT(f_name) as al from fruits GROUP BY s_id HAVING al > 2; ++------+-------------------------+----+ +| s_id | GROUP_CONCAT(f_name) | al | ++------+-------------------------+----+ +| 101 | apple,blackberry,cherry | 3 | +| 102 | orange,banana,grape | 3 | +| 105 | melon,xbabay,xxtt | 3 | ++------+-------------------------+----+ +3 rows in set (0.00 sec) + +mysql> select s_id, GROUP_CONCAT(f_name), COUNT(f_name) as al from fruits where al > 2 GROUP BY s_id; +ERROR 1054 (42S22): Unknown column 'al' in 'where clause' +mysql> + +``` + +原因: 因为`where`是在分组之前过滤,所以那个时候还没有`al`这个变量。 + +### 3、多字段分组,以及和order by一起使用 + +先按照`s_id`分组,然后按照`f_name`分组。这里在查询的时候,可能会报错。解决方案: + + + +查询代码: + +```mysql +mysql> select * from fruits GROUP BY s_id,f_name; +ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'lmysql.fruits.f_id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by +# 解决错误的语句 +mysql> SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')); +Query OK, 0 rows affected (0.01 sec) +mysql> select * from fruits GROUP BY s_id,f_name; ++------+------+------------+---------+ +| f_id | s_id | f_name | f_price | ++------+------+------------+---------+ +| a1 | 101 | apple | 5.20 | +| b1 | 101 | blackberry | 10.20 | +| c0 | 101 | cherry | 3.20 | +| t1 | 102 | banana | 10.30 | +| t2 | 102 | grape | 5.30 | +| bs1 | 102 | orange | 11.20 | +| a2 | 103 | apricot | 2.20 | +| o2 | 103 | coconut | 9.20 | +| b2 | 104 | berry | 7.60 | +| l2 | 104 | lemon | 6.40 | +| bs2 | 105 | melon | 8.20 | +| m2 | 105 | xbabay | 2.60 | +| m3 | 105 | xxtt | 11.60 | +| m1 | 106 | mango | 15.60 | +| t4 | 107 | xbababa | 3.60 | +| b5 | 107 | xxxx | 3.60 | ++------+------+------------+---------+ +16 rows in set (0.00 sec) + +``` + +使用`group by`和`order by `结合: + +要对求出来的`SUM(quantity * item_price) AS orderTotal`进行排序 + +```mysql +mysql> INSERT INTO orderitems(o_num, o_item, f_id, quantity, item_price) + -> VALUES(30001, 1, 'a1', 10, 5.2), + -> (30001, 2, 'b2', 3, 7.6), + -> (30001, 3, 'bs1', 5, 11.2), + -> (30001, 4, 'bs2', 15, 9.2), + -> (30002, 1, 'b3', 2, 20.0), + -> (30003, 1, 'c0', 100, 10), + -> (30004, 1, 'o2', 50, 2.50), + -> (30005, 1, 'c0', 5, 10), + -> (30005, 2, 'b1', 10, 8.99), + -> (30005, 3, 'a2', 10, 2.2), + -> (30005, 4, 'm1', 5, 14.99); +Query OK, 11 rows affected (0.01 sec) +Records: 11 Duplicates: 0 Warnings: 0 + +mysql> SELECT o_num, SUM(quantity * item_price) AS orderTotal + -> FROM orderitems + -> GROUP BY o_num + -> HAVING SUM(quantity*item_price) >= 100; ++-------+------------+ +| o_num | orderTotal | ++-------+------------+ +| 30001 | 268.80 | +| 30003 | 1000.00 | +| 30004 | 125.00 | +| 30005 | 236.85 | ++-------+------------+ +4 rows in set (0.01 sec) + +mysql> SELECT o_num, SUM(quantity * item_price) AS orderTotal + -> FROM orderitems + -> GROUP BY o_num + -> HAVING SUM(quantity*item_price) >= 100 + -> ORDER BY orderTotal; ++-------+------------+ +| o_num | orderTotal | ++-------+------------+ +| 30004 | 125.00 | +| 30005 | 236.85 | +| 30001 | 268.80 | +| 30003 | 1000.00 | ++-------+------------+ +4 rows in set (0.00 sec) + +``` + +## 二、聚合函数 + +### 1、count + +使用`count(*)`和`count(列)`的区别: `count(*)`是统计总的行数,而`count(列)`是统计这一列不为空的数目。 + +```mysql +mysql> select * from customers; ++-------+----------+---------------------+---------+--------+-----------+-------------------+ +| c_id | c_name | c_address | c_city | c_zip | c_contact | c_email | ++-------+----------+---------------------+---------+--------+-----------+-------------------+ +| 10001 | RedHook | 200 Street | Tianjin | 300000 | LiMing | LMing@163.com | +| 10002 | Stars | 333 Fromage Lane | Dalian | 116000 | Zhangbo | Jerry@hotmail.com | +| 10003 | Netbhood | 1 Sunny Place | Qingdao | 266000 | LuoCong | NULL | +| 10004 | JOTO | 829 Riverside Drive | Haikou | 570000 | YangShan | sam@hotmail.com | ++-------+----------+---------------------+---------+--------+-----------+-------------------+ +4 rows in set (0.00 sec) + +mysql> select count(c_email) from customers; ++----------------+ +| count(c_email) | ++----------------+ +| 3 | ++----------------+ +1 row in set (0.00 sec) + +mysql> select count(*) from customers; ++----------+ +| count(*) | ++----------+ +| 4 | ++----------+ +1 row in set (0.00 sec) + +mysql> + +``` + +### 2、sum + +不同订单号中订购的水果种类的数目。 + +```mysql +mysql> select * from orderitems; ++-------+--------+------+----------+------------+ +| o_num | o_item | f_id | quantity | item_price | ++-------+--------+------+----------+------------+ +| 30001 | 1 | a1 | 10 | 5.20 | +| 30001 | 2 | b2 | 3 | 7.60 | +| 30001 | 3 | bs1 | 5 | 11.20 | +| 30001 | 4 | bs2 | 15 | 9.20 | +| 30002 | 1 | b3 | 2 | 20.00 | +| 30003 | 1 | c0 | 100 | 10.00 | +| 30004 | 1 | o2 | 50 | 2.50 | +| 30005 | 1 | c0 | 5 | 10.00 | +| 30005 | 2 | b1 | 10 | 8.99 | +| 30005 | 3 | a2 | 10 | 2.20 | +| 30005 | 4 | m1 | 5 | 14.99 | ++-------+--------+------+----------+------------+ +11 rows in set (0.00 sec) + +mysql> select o_num, count(f_id) from orderitems group by o_num; ++-------+-------------+ +| o_num | count(f_id) | ++-------+-------------+ +| 30001 | 4 | +| 30002 | 1 | +| 30003 | 1 | +| 30004 | 1 | +| 30005 | 4 | ++-------+-------------+ +5 rows in set (0.00 sec) + +``` + +再看一个例子: 使用`sum()`函数求出这个订单号对应的总的数量 + +```mysql +mysql> select SUM(quantity) AS item_total from orderitems where o_num = 30005; ++------------+ +| item_total | ++------------+ +| 30 | ++------------+ +1 row in set (0.29 sec) + +# 求统计不同订单号的订购的水果的总数量 +mysql> select SUM(quantity) AS item_total from orderitems GROUP BY o_num; ++------------+ +| item_total | ++------------+ +| 33 | +| 2 | +| 100 | +| 50 | +| 30 | ++------------+ +5 rows in set (0.00 sec) + +``` + +### 3、avg + +查询每一个供应商的水果价格的平均值: + +```mysql +mysql> select s_id, AVG(f_price) AS avg_price from fruits GROUP BY s_id; ++------+-----------+ +| s_id | avg_price | ++------+-----------+ +| 101 | 6.200000 | +| 102 | 8.933333 | +| 103 | 5.700000 | +| 104 | 7.000000 | +| 105 | 7.466667 | +| 106 | 15.600000 | +| 107 | 3.600000 | ++------+-----------+ +7 rows in set (0.00 sec) + +mysql> + +``` + + + diff --git "a/DB/MySQL/primary/\347\224\250\345\210\260\347\232\204\350\241\250.md" "b/DB/MySQL/primary/\347\224\250\345\210\260\347\232\204\350\241\250.md" new file mode 100644 index 00000000..4bbc5760 --- /dev/null +++ "b/DB/MySQL/primary/\347\224\250\345\210\260\347\232\204\350\241\250.md" @@ -0,0 +1,104 @@ +案例中的表 + +```mysql +# fruits表 +mysql> CREATE TABLE fruits + -> ( + -> f_id char(10) NOT NULL, # 水果id + -> s_id INT NOT NULL, # 供应商id + -> f_name char(255) NOT NULL, # 水果名字 + -> f_price decimal(8,2) NOT NULL, # 水果价格 + -> PRIMARY KEY(f_id) + -> ); +Query OK, 0 rows affected (0.06 sec) + +mysql> INSERT INTO fruits (f_id, s_id, f_name, f_price) + -> VALUES('a1', 101,'apple',5.2), + -> ('b1',101,'blackberry', 10.2), + -> ('bs1',102,'orange', 11.2), + -> ('bs2',105,'melon',8.2), + -> ('t1',102,'banana', 10.3), + -> ('t2',102,'grape', 5.3), + -> ('o2',103,'coconut', 9.2), + -> ('c0',101,'cherry', 3.2), + -> ('a2',103, 'apricot',2.2), + -> ('l2',104,'lemon', 6.4), + -> ('b2',104,'berry', 7.6), + -> ('m1',106,'mango', 15.6), + -> ('m2',105,'xbabay', 2.6), + -> ('t4',107,'xbababa', 3.6), + -> ('m3',105,'xxtt', 11.6), + -> ('b5',107,'xxxx', 3.6); +Query OK, 16 rows affected (0.01 sec) +Records: 16 Duplicates: 0 Warnings: 0 + +# suppliers表 +mysql> CREATE TABLE suppliers + -> ( + -> s_id int NOT NULL AUTO_INCREMENT, + -> s_name char(50) NOT NULL, + -> s_city char(50) NULL, + -> s_zip char(10) NULL, + -> s_call CHAR(50) NOT NULL, + -> PRIMARY KEY (s_id) + -> ) ; +Query OK, 0 rows affected (0.06 sec) + +mysql> INSERT INTO suppliers(s_id, s_name,s_city, s_zip, s_call) + -> VALUES(101,'FastFruit Inc.','Tianjin','300000','48075'), + -> (102,'LT Supplies','Chongqing','400000','44333'), + -> (103,'ACME','Shanghai','200000','90046'), + -> (104,'FNK Inc.','Zhongshan','528437','11111'), + -> (105,'Good Set','Taiyuang','030000', '22222'), + -> (106,'Just Eat Ours','Beijing','010', '45678'), + -> (107,'DK Inc.','Zhengzhou','450000', '33332'); +Query OK, 7 rows affected (0.01 sec) +Records: 7 Duplicates: 0 Warnings: 0 + +# 订单表 +mysql> CREATE TABLE orders + -> ( + -> o_num int NOT NULL AUTO_INCREMENT, + -> o_date datetime NOT NULL, + -> c_id int NOT NULL, + -> PRIMARY KEY (o_num) + -> ) ; +Query OK, 0 rows affected (0.06 sec) + +mysql> INSERT INTO orders(o_num, o_date, c_id) + -> VALUES(30001, '2008-09-01', 10001), + -> (30002, '2008-09-12', 10003), + -> (30003, '2008-09-30', 10004), + -> (30004, '2008-10-03', 10005), + -> (30005, '2008-10-08', 10001); +Query OK, 5 rows affected (0.01 sec) +Records: 5 Duplicates: 0 Warnings: 0 + + +# 订单项 +CREATE TABLE orderitems +( + o_num int NOT NULL, + o_item int NOT NULL, + f_id char(10) NOT NULL, + quantity int NOT NULL, + item_price decimal(8,2) NOT NULL, + PRIMARY KEY (o_num,o_item) +) ; + +mysql> INSERT INTO orderitems(o_num, o_item, f_id, quantity, item_price) + -> VALUES(30001, 1, 'a1', 10, 5.2), + -> (30001, 2, 'b2', 3, 7.6), + -> (30001, 3, 'bs1', 5, 11.2), + -> (30001, 4, 'bs2', 15, 9.2), + -> (30002, 1, 'b3', 2, 20.0), + -> (30003, 1, 'c0', 100, 10), + -> (30004, 1, 'o2', 50, 2.50), + -> (30005, 1, 'c0', 5, 10), + -> (30005, 2, 'b1', 10, 8.99), + -> (30005, 3, 'a2', 10, 2.2), + -> (30005, 4, 'm1', 5, 14.99); +Query OK, 11 rows affected (0.01 sec) +Records: 11 Duplicates: 0 Warnings: 0 +``` + diff --git "a/DB/MySQL/question/1_MYSQL\346\236\266\346\236\204-\344\270\200\346\235\241MYSQL\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" "b/DB/MySQL/question/1_MYSQL\346\236\266\346\236\204-\344\270\200\346\235\241MYSQL\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" new file mode 100644 index 00000000..595e0396 --- /dev/null +++ "b/DB/MySQL/question/1_MYSQL\346\236\266\346\236\204-\344\270\200\346\235\241MYSQL\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" @@ -0,0 +1,119 @@ +# MYSQL架构 : 一条SQL查询语句是如何执行的 + +## 一、引入以及基础架构 + +你知道下面这条语句在MYSQL内部的执行过程吗? + +```mysql +mysql> select * from T where ID=10; +``` + +MySQL的基本架构示意图: + +![1555808577787](assets/1555808577787.png) + +大体来说,MySQL可以分为**Server层和存储引擎层**两部分。 + +* Server层包括**连接器、查询缓存、分析器、优化器、执行器**等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 +* 而存储引擎层负责**数据的存储和提取**。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。 + +## 二、连接器 + +第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。**连接器负责跟客户端建立连接、获取权限、维持和管理连接**。连接命令一般是这么写的: + +```shell +mysql -h$ip -P$port -u$user -p +``` + +`show processlist`命令可以看到链接的客户端。 + +客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数`wait_timeout`控制的,默认值是8小时。 + +如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: `Lost connection to MySQL server during query`。这时候如果你要继续,就需要重连,然后再执行请求了。 + +数据库里面,**长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个**。 + +建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。 + +但是全部使用长连接后,你可能会发现,**有些时候MySQL占用内存涨得特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的**。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MySQL异常重启了。 + +怎么解决这个问题呢?你可以考虑以下两种方案。 + +1、**定期断开长连接**。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。 + +2、如果你用的是MySQL 5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行`mysql_reset_connection`来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。 + +## 三、查询缓存 + +连接建立完成后,你就可以执行select语句了。执行逻辑就会来到第二步:查询缓存。 + +**MySQL拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value对的形式,被直接缓存在内存中**。key是查询的语句,value是查询的结果。如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。 + +如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。 + +**但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利。** + +查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。 + +好在MySQL也提供了这种“按需使用”的方式。你可以将参数query_cache_type设置成DEMAND,这样对于默认的SQL语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用SQL_CACHE显式指定,像下面这个语句一样: + +```mysql +mysql> select SQL_CACHE * from T where ID=10; +``` + +需要注意的是,**MySQL 8.0版本直接将查询缓存的整块功能删掉了**,也就是说8.0开始彻底没有这个功能了。 + +## 四、分析器 + +如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL需要知道你要做什么,因此需要对SQL语句做解析。 + +分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条SQL语句,MySQL需要识别出里面的字符串分别是什么,代表什么。 + +MySQL从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名T”,把字符串“ID”识别成“列ID”。 + +做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。 + +## 五、优化器 + +经过了分析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。 + +优化器是**在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序**。比如你执行下面这样的语句,这个语句是执行两个表的join: + +```mysql +mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20; +``` + +- 既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20。 +- 也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到t1,再判断t1里面c的值是否等于10。 + +这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。 + +优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。 + +## 六、执行器 + +MySQL通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。 + +开始执行的时候,要先判断一下你对这个表T有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示(在工程实现上,如果命中查询缓存,会在查询缓存放回结果的时候,做权限验证。查询也会在优化器之前调用precheck验证权限)。 + +```mysql +mysql> select * from T where ID=10; + +ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T' +``` + +如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。 + +比如我们这个例子中的表T中,ID字段没有索引,那么执行器的执行流程是这样的: + +1. 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中; +2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。 +3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。 + +至此,这个语句就执行完成了。 + +对于有索引的表,执行的逻辑也差不多。**第一次调用的是“取满足条件的第一行”这个接口**,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。 + +你会在数据库的慢查询日志中看到一个`rows_examined`的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。 + +在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此**引擎扫描行数跟rows_examined并不是完全相同的。** \ No newline at end of file diff --git "a/DB/MySQL/question/2_\346\227\245\345\277\227\347\263\273\347\273\237-\344\270\200\346\235\241SQL\346\233\264\346\226\260\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" "b/DB/MySQL/question/2_\346\227\245\345\277\227\347\263\273\347\273\237-\344\270\200\346\235\241SQL\346\233\264\346\226\260\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" new file mode 100644 index 00000000..a6c237b3 --- /dev/null +++ "b/DB/MySQL/question/2_\346\227\245\345\277\227\347\263\273\347\273\237-\344\270\200\346\235\241SQL\346\233\264\346\226\260\350\257\255\345\217\245\346\230\257\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" @@ -0,0 +1,138 @@ +# 日志系统 : 一条SQL更新语句是如何执行的 + +## 一、引入 + +一条更新语句的执行流程又是怎样的呢? + +之前你可能经常听DBA同事说,MySQL可以恢复到半个月内任意一秒的状态,这是怎样做到的呢? + +我们还是从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键ID和一个整型字段c: + +```mysql +mysql> create table T(ID int primary key, c int); +``` + +如果要将ID=2这一行的值加1,SQL语句就会这么写: + +```mysql +mysql> update T set c=c+1 where ID=2; +``` + +**在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表T上所有缓存结果都清空**。这也就是我们一般不建议使用查询缓存的原因。 + +接下来,**分析器会通过词法和语法解析知道这是一条更新语句**。**优化器决定要使用ID这个索引**。然后,执行器负责具体执行,找到这一行,然后更新。 + +与查询流程不一样的是,更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:`redo log`(重做日志)和 `binlog`(归档日志)。如果接触MySQL,那这两个词肯定是绕不过的。 + +## 二、重做日志(redo log) + +不知道你还记不记得《孔乙己》这篇文章,酒店掌柜有一个粉板,专门用来记录客人的赊账记录。如果赊账的人不多,那么他可以把顾客名和账目写在板上。但如果赊账的人多了,粉板总会有记不下的时候,这个时候掌柜一定还有一个专门记录赊账的账本。 + +如果有人要赊账或者还账的话,掌柜一般有两种做法: + +- 一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉; +- 另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。 + +在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。 + +这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受? + +同样,**在MySQL里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程IO成本、查找成本都很高**。为了解决这个问题,MySQL的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。 + +而粉板和账本配合的整个过程,其实就是MySQL里经常说到的WAL技术,WAL的全称是`Write-Ahead Logging`,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。 + +具体来说,**当有一条记录需要更新的时候,InnoDB引擎就会先把记录写到redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB引擎会在适当的时候,将这个操作记录更新到磁盘(账本)里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事**。 + +如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。 + +与此类似,**InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么这块“粉板”总共就可以记录4GB的操作**。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示: + +![1555812084299](assets/1555812084299.png) + +write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。 + +`write pos`和`checkpoint`之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果write pos追上checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把checkpoint推进一下。 + +有了redo log,InnoDB就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为**crash-safe**。 + +要理解crash-safe这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。 + +## 三、归档日志(binlog) + +MySQL整体来看,其实就有两块: + +* 一块是Server层,它主要做的是MySQL功能层面的事情; +* 还有一块是引擎层,负责存储相关的具体事宜。 + +**上面我们聊到的粉板redo log是InnoDB引擎特有的日志**,而Server层也有自己的日志,称为binlog(归档日志)。 + +你可能会问,为什么会有两份日志呢? + +因为最开始MySQL里并没有InnoDB引擎。MySQL自带的引擎是MyISAM,但是MyISAM没有crash-safe的能力,binlog日志只能用于归档。而InnoDB是另一个公司以插件形式引入MySQL的,既然只依靠binlog是没有crash-safe能力的,所以InnoDB使用另外一套日志系统——也就是redo log来实现crash-safe能力。 + +**这两种日志有以下三点不同**。 + +* redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。 +* **redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1** ”。 +* redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。 + +有了对这两个日志的概念性理解,我们再来看执行器和InnoDB引擎在执行这个简单的update语句时的内部流程。 + +* 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。 +* 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。 +* 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。 +* 执行器生成这个操作的binlog,并把binlog写入磁盘。 +* 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。 + +这里我给出这个update语句的执行流程图,图中浅色框表示是在InnoDB内部执行的,深色框表示是在执行器中执行的。 + +

+ +你可能注意到了,最后三步看上去有点“绕”,将redo log的写入拆成了两个步骤:prepare和commit,这就是"两阶段提交"。 + +**两阶段提交** + +为什么必须有“两阶段提交”呢?这是为了让两份日志之间的逻辑一致。要说明这个问题,我们得从文章开头的那个问题说起:**怎样让数据库恢复到半个月内任意一秒的状态**? + +前面我们说过了,**binlog会记录所有的逻辑操作,并且是采用“追加写”的形式。如果你的DBA承诺说半个月内可以恢复,那么备份系统中一定会保存最近半个月的所有binlog,同时系统会定期做整库备份**。这里的“定期”取决于系统的重要性,可以是一天一备,也可以是一周一备。 + +当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做: + +- 首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库; +- 然后,从备份的时间点开始,将备份的binlog依次取出来,重放到中午误删表之前的那个时刻。 + +这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。 + +好了,说完了数据恢复过程,我们回来说说,为什么日志需要“两阶段提交”。这里不妨用反证法来进行解释。 + +由于redo log和binlog是两个独立的逻辑,如果不用两阶段提交,要么就是先写完redo log再写binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。 + +仍然用前面的update语句来做例子。假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况呢? + +1. **先写redo log后写binlog**。假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。 + 但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。 + 然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。 +2. **先写binlog后写redo log**。如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。 + +可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 + +你可能会说,这个概率是不是很低,平时也没有什么动不动就需要恢复临时库的场景呀? + +其实不是的,不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用binlog来实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。 + +简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。 + +## 四、总结 + +物理日志redo log和逻辑日志binlog。 + +* `redo log`用于保证crash-safe能力。`innodb_flush_log_at_trx_commit`这个参数设置成1的时候,表示每次事务的redo log都直接持久化到磁盘。这个参数我建议你设置成1,这样可以保证MySQL异常重启之后数据不丢失。 +* `sync_binlog`这个参数设置成1的时候,表示每次事务的binlog都持久化到磁盘。这个参数我也建议你设置成1,这样可以保证MySQL异常重启之后binlog不丢失。 + + + +学习后的收获: + +* redo是物理的,binlog是逻辑的; +* 现在由于redo是属于InnoDB引擎,所以必须要有binlog,因为你可以使用别的引擎 保证数据库的一致性,必须要保证2份日志一致,使用的2阶段式提交;其实感觉像事务,不是成功就是失败,不能让中间环节出现,也就是一个成功,一个失败 如果有一天mysql只有InnoDB引擎了,有redo来实现复制,那么感觉oracle的DG就诞生了,物理的速度也将远超逻辑的,毕竟只记录了改动向量 binlog几大模式,一般采用row,因为遇到时间,从库可能会出现不一致的情况,但是row更新前后都有,会导致日志变大 最后2个参数,保证事务成功,日志必须落盘,这样,数据库crash后,就不会丢失某个事务的数据了。 +* 其次说一下,对问题的理解 备份时间周期的长短,感觉有2个方便 首先,是恢复数据丢失的时间,既然需要恢复,肯定是数据丢失了。如果一天一备份的话,只要找到这天的全备,加入这天某段时间的binlog来恢复,如果一周一备份,假设是周一,而你要恢复的数据是周日某个时间点,那就,需要全备+周一到周日某个时间点的全部binlog用来恢复,时间相比前者需要增加很多;看业务能忍受的程度 其次,是数据库丢失,如果一周一备份的话,需要确保整个一周的binlog都完好无损,否则将无法恢复;而一天一备,只要保证这天的binlog都完好无损;当然这个可以通过校验,或者冗余等技术来实现,相比之下,上面那点更重要 \ No newline at end of file diff --git "a/DB/MySQL/question/3_\344\272\213\345\212\241\351\232\224\347\246\273-\344\270\272\344\273\200\344\271\210\344\275\240\346\224\271\344\272\206\346\210\221\350\277\230\347\234\213\344\270\215\350\247\201.md" "b/DB/MySQL/question/3_\344\272\213\345\212\241\351\232\224\347\246\273-\344\270\272\344\273\200\344\271\210\344\275\240\346\224\271\344\272\206\346\210\221\350\277\230\347\234\213\344\270\215\350\247\201.md" new file mode 100644 index 00000000..ca3ef469 --- /dev/null +++ "b/DB/MySQL/question/3_\344\272\213\345\212\241\351\232\224\347\246\273-\344\270\272\344\273\200\344\271\210\344\275\240\346\224\271\344\272\206\346\210\221\350\277\230\347\234\213\344\270\215\350\247\201.md" @@ -0,0 +1,129 @@ +# 事务隔离: 为什么你改了我还看不见 + +事务最经典的例子就是转账,你要给朋友小王转100块钱,而此时你的银行卡只有100块钱。 + +转账过程具体到程序里会有一系列的操作,比如查询余额、做加减法、更新余额等,这些操作必须保证是一体的,不然等程序查完之后,还没做减法之前,你这100块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,不就乱了么? + +**简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败**。 + +## 一、隔离性与隔离级别 + +提到事务,你肯定会想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天我们就来说说其中I,也就是“隔离性”。 + +当数据库上有**多个事务同时执行**的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。 + +在谈隔离级别之前,你首先要知道,你**隔离得越严实,效率就会越低**。 + +因此很多时候,我们都要在二者之间寻找一个平衡点。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。下面逐一解释: + +- 读未提交是指,**一个事务还没提交时,它做的变更就能被别的事务看到**。 +- 读提交是指,一个事务提交**之后**,它做的变更**才**会被其他事务看到。 +- 可重复读是指,**一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的**。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 +- 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 + +看个实例: 假设数据表T中只有一列,其中一行的值为1,下面是按照时间顺序执行两个事务的行为。 + +```mysql +mysql> create table T(c int) engine=InnoDB; +insert into T(c) values(1); +``` + +按照时间顺序执行的事务: + +![1557033944906](assets/1557033944906.png) + +我们来看看在不同的隔离级别下,事务A会有哪些不同的返回结果,也就是图里面V1、V2、V3的返回值分别是什么。 + +* 若隔离级别是“读未提交”, 则V1的值就是2。这时候事务B虽然还没有提交,但是结果已经被A看到了。因此,V2、V3也都是2; +* 若隔离级别是“读提交”,则V1是1,V2的值是2。事务B的更新在提交后才能被A看到。所以, V3的值也是2。 +* 若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求:**事务在执行期间看到的数据前后必须是一致的**。 +* 若隔离级别是“串行化”,则在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后,事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。 + +在实现上,**数据库里面会创建一个视图**,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用**加锁**的方式来避免并行访问。 + +"读提交"的配置的方式是,将启动参数`transaction-isolation`的值设置成READ-COMMITTED。你可以用show variables来查看当前的值。 + +哪个隔离级别都有它自己的使用场景,你要根据自己的业务情况来定。我想**你可能会问那什么时候需要“可重复读”的场景呢**?我们来看一个数据校对逻辑的案例。 + +假设你在管理一个个人银行账户表。一个表存了每个月月底的余额,一个表存了账单明细。这时候你要做数据校对,也就是判断上个月的余额和当前余额的差额,是否与本月的账单明细一致。你一定希望在校对过程中,即使有用户发生了一笔新的交易,也不影响你的校对结果。 + +这时候使用“可重复读”隔离级别就很方便。事务启动时的视图可以认为是静态的,不受其他事务更新的影响。 + +## 二、事务隔离的实现 + +理解了事务的隔离级别,我们再来看看事务隔离具体是怎么实现的。这里我们展开说明“可重复读”。 + +在MySQL中,**实际上每条记录在更新的时候都会同时记录一条回滚操作**。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。 + +**假设一个值从1被按顺序改成了2、3、4**,在回滚日志里面就会有类似下面的记录。 + +![1557039557333](assets/1557039557333.png) + +当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于read-view A,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。 + +同时你会发现,即使现在有另外一个事务正在将4改成5,这个事务跟read-view A、B、C对应的事务是不会冲突的。 + +你一定会问,回滚日志总不能一直保留吧,什么时候删除呢?答案是,在不需要的时候才删除。也就是说,系统会判断,当没有事务再需要用到这些回滚日志时,回滚日志会被删除。 + +什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read-view的时候。 + +基于上面的说明,我们来讨论一下为什么**建议你尽量不要使用长事务**。 + +长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,**这就会导致大量占用存储空间**。 + +在MySQL 5.5及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有20GB,而回滚段有200GB的库。最终只好为了清理回滚段,重建整个库。 + +除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,这个我们会在后面讲锁的时候展开。 + +## 三、事务的启动方式 + +如前面所述,长事务有这些潜在风险,我当然是建议你尽量避免。其实很多时候业务开发同学并不是有意使用长事务,通常是由于误用所致。MySQL的事务启动方式有以下几种: + +* 1、**显式启动事务语句, begin 或 start transaction。配套的提交语句是commit,回滚语句是rollback**。 +* 2、`set autocommit=0`,这个命令会将这个线程的自动提交关掉。意味着如果你只执行一个select语句,这个事务就启动了,而且并不会自动提交。这个事务持续存在直到你主动执行commit 或 rollback 语句,或者断开连接。 + +有些客户端连接框架会默认连接成功后先执行一个set autocommit=0的命令。**这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务**。 + +因此,我会建议你总是使用`set autocommit=1`, 通过显式语句的方式来启动事务。 + +但是有的开发同学会纠结“多一次交互”的问题。对于一个需要频繁使用事务的业务,第二种方式每个事务在开始时都不需要主动执行一次 “begin”,减少了语句的交互次数。如果你也有这个顾虑,我建议你使用`commit work and chain`语法。 + +在autocommit为1的情况下,用begin显式启动的事务,如果执行commit则提交事务。如果执行 commit work and chain,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。 + +你可以在information_schema库的innodb_trx这个表中查询长事务,比如下面这个语句,用于查找持续时间超过60s的事务。 + +```mysql +select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60 +``` + +## 四、总结 + +1、务的特性:原子性、一致性、隔离性、持久性 + +2、多事务同时执行的时候,可能会出现的问题:脏读、不可重复读、幻读 + +3、事务隔离级别:读未提交、读提交、可重复读、串行化 + +4、不同事务隔离级别的区别: + +* 读未提交:一个事务还未提交,它所做的变更就可以被别的事务看到 +* 读提交:一个事务提交之后,它所做的变更才可以被别的事务看到 +* 可重复读:一个事务执行过程中看到的数据是一致的。未提交的更改对其他事务是不可见的 +* 串行化:对应一个记录会加读写锁,出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行 + +5、配置方法:启动参数transaction-isolation + +6、事务隔离的实现:每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。 + +7、回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。 + +8、什么时候不需要了?当系统里么有比这个回滚日志更早的read-view的时候。 + +9、为什么尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间。除此之外,长事务还占用锁资源,可能会拖垮库。 + +10、事务启动方式 + +* 一、显式启动事务语句,begin或者start transaction,提交commit,回滚rollback; +* 二、set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动提交,直到主动执行commit或rollback或断开连接。 + +11、建议使用方法一,如果考虑多一次交互问题,可以使用commit work and chain语法。在autocommit=1的情况下用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain则提交事务并自动启动下一个事务。 \ No newline at end of file diff --git "a/DB/MySQL/question/4_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\212).md" "b/DB/MySQL/question/4_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\212).md" new file mode 100644 index 00000000..c7fc3bee --- /dev/null +++ "b/DB/MySQL/question/4_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\212).md" @@ -0,0 +1,130 @@ +# 索引: 深入浅出索引(上) + +一句话简单来说,**索引的出现其实就是为了提高数据查询的效率**,就像书的目录一样。 + +## 一、索引常见的模型 + +索引的出现是为了提高查询效率,但是实现索引的方式却有很多种,所以这里也就引入了索引模型的概念。可以用于提高读写效率的数据结构很多,这里我先给你介绍三种常见、也比较简单的数据结构,它们分别是**哈希表、有序数组和搜索树**。 + +下面介绍三种模型区别: + +哈希表是一种以键-值(key-value)存储数据的结构,我们只要输入待查找的值即key,就可以找到其对应的值即Value,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置。 + +不可避免地,多个key值经过哈希函数的换算,会出现同一个值的情况。处理这种情况的一种方法是,拉出一个链表。 + +假设,你现在维护着一个身份证信息和姓名的表,需要根据身份证号查找对应的名字,这时对应的哈希索引的示意图如下所示: + +![1557041109708](assets/1557041109708.png) + +图中,User2和User4根据身份证号算出来的值都是N,但没关系,后面还跟了一个链表。假设,这时候你要查ID_card_n2对应的名字是什么,处理步骤就是:首先,将ID_card_n2通过哈希函数算出N;然后,按顺序遍历,找到User2。 + +需要注意的是,图中四个ID_card_n的值并不是递增的,这样做的好处是增加新的User时速度会很快,只需要往后追加。但缺点是,因为不是有序的,所以哈希索引做区间查询的速度是很慢的。 + +你可以设想下,如果你现在要找身份证号在`[ID_card_X, ID_card_Y]`这个区间的所有用户,**就必须全部扫描一遍了**。 + +所以,**哈希表这种结构适用于只有等值查询的场景**,比如Memcached及其他一些NoSQL引擎。 + +而**有序数组在等值查询和范围查询场景中的性能就都非常优秀**。还是上面这个根据身份证号查名字的例子,如果我们使用有序数组来实现的话,示意图如下所示: + +![1557041172422](assets/1557041172422.png) + +这里我们假设身份证号没有重复,这个数组就是按照身份证号递增的顺序保存的。这时候如果你要查`ID_card_n2`对应的名字,用二分法就可以快速得到,这个时间复杂度是O(log(N))。 + +同时很显然,这个索引结构支持范围查询。你要查身份证号在`[ID_card_X, ID_card_Y]`区间的User,可以先用二分法找到ID_card_X(如果不存在ID_card_X,就找到大于ID_card_X的第一个User),然后向右遍历,直到查到第一个大于ID_card_Y的身份证号,退出循环。 + +如果仅仅看查询效率,有序数组就是最好的数据结构了。但是,**在需要更新数据的时候就麻烦了,你往中间插入一个记录就必须得挪动后面所有的记录,成本太高**。 + +所以,**有序数组索引只适用于静态存储引擎**,比如你要保存的是2017年某个城市的所有人口信息,这类不会再修改的数据。 + +二叉搜索树也是课本里的经典数据结构了。还是上面根据身份证号查名字的例子,如果我们用二叉搜索树来实现的话,示意图如下所示: + +![1557041243597](assets/1557041243597.png) + +二叉搜索树的特点是:每个节点的左儿子小于父节点,父节点又小于右儿子。这样如果你要查ID_card_n2的话,按照图中的搜索顺序就是按照`UserA -> UserC -> UserF -> User2`这个路径得到。这个时间复杂度是O(log(N))。(更新时间复杂度也是`O(log(N))`) + +树可以有二叉,也可以有多叉。多叉树就是每个节点有多个儿子,儿子之间的大小保证从左到右递增。二叉树是搜索效率最高的,但是实际上大多数的数据库存储却并不使用二叉树。**其原因是,索引不止存在内存中,还要写到磁盘上**。 + +你可以想象一下一棵100万节点的平衡二叉树,树高20。一次查询可能需要访问20个数据块。在机械硬盘时代,从磁盘随机读一个数据块需要10 ms左右的寻址时间。也就是说,对于一个100万行的表,如果使用二叉树来存储,单独访问一个行可能需要20个10 ms的时间,这个查询可真够慢的。 + +为了让一个查询尽量少地读磁盘,**就必须让查询过程访问尽量少的数据块**。那么,我们就不应该使用二叉树,而是要使用“N叉”树。这里,“N叉”树中的“N”取决于数据块的大小。 + +以InnoDB的一个整数字段索引为例,这个N差不多是1200。这棵树高是4的时候,就可以存1200的3次方个值,这已经17亿了。考虑到树根的数据块总是在内存中的,一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。 + +N叉树由于在读写上的性能优点,以及适配磁盘的访问模式,已经被广泛应用在数据库引擎中了。 + +MySQL中,索引是在存储引擎层实现的,所以并没有统一的索引标准,**即不同存储引擎的索引的工作方式并不一样**。而即使多个存储引擎支持同一种类型的索引,其底层的实现也可能不同。由于InnoDB存储引擎在MySQL数据库中使用最为广泛,所以下面我就以InnoDB为例,和你分析一下其中的索引模型。 + +## 二、InnoDB索引模型 + +在InnoDB中,表都是根据主键顺序以索引的形式存放的,这种存储方式的表称为索引组织表。又因为前面我们提到的,InnoDB使用了B+树索引模型,所以数据都是存储在B+树中的。 + +每一个索引在InnoDB里面对应一棵B+树。 + +假设,我们有一个主键列为ID的表,表中有字段k,并且在k上有索引。 + +这个表的建表语句是: + +```mysql +mysql> create table T( +id int primary key, +k int not null, +name varchar(16), +index (k))engine=InnoDB; +``` + +表中R1~R5的`(ID,k)`值分别为`(100,1)、(200,2)、(300,3)、(500,5)和(600,6)`,两棵树的示例示意图如下。 + +![1557042219239](assets/1557042219239.png) + +从图中不难看出,根据叶子节点的内容,索引类型分为主键索引和非主键索引。 + +**主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引**(clustered index)。 + +**非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引**(secondary index)。 + +根据上面的索引结构说明,我们来讨论一个问题:**基于主键索引和普通索引的查询有什么区别?** + +- 如果语句是`select * from T where ID=500`,即主键查询方式,则只需要搜索ID这棵B+树; +- 如果语句是`select * from T where k=5`,即普通索引查询方式,**则需要先搜索k索引树,得到ID的值为500,再到ID索引树搜索一次。这个过程称为回表**。 + +## 三、索引维护 + +B+树为了维护索引有序性,在插入新值的时候需要做必要的维护。以上面这个图为例,如果插入新的行ID值为700,则只需要在R5的记录后面插入一个新记录。如果新插入的ID值为400,就相对麻烦了,需要逻辑上挪动后面的数据,空出位置。 + +而更糟的情况是,如果R5所在的数据页已经满了,根据B+树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。在这种情况下,性能自然会受影响。 + +除了性能外,页分裂操作还影响数据页的利用率。原本放在一个页的数据,现在分到两个页中,整体空间利用率降低大约50%。 + +当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。 + +基于上面的索引维护过程说明,我们来讨论一个案例: + +> 你可能在一些建表规范里面见到过类似的描述,要求建表语句里一定要有自增主键。当然事无绝对,我们来分析一下哪些场景下应该使用自增主键,而哪些场景下不应该。 + +自增主键是指自增列上定义的主键,在建表语句中一般是这么定义的: `NOT NULL PRIMARY KEY AUTO_INCREMENT`。 + +插入新记录的时候可以不指定ID的值,系统会获取当前ID最大值加1作为下一条记录的ID值。 + +也就是说,自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。 + +而有业务逻辑的字段做主键,则往往不容易保证有序插入,这样写数据成本相对较高。 + +除了考虑性能外,我们还可以从存储空间的角度来看。假设你的表中确实有一个唯一字段,比如字符串类型的身份证号,那应该用身份证号做主键,还是用自增字段做主键呢? + +由于每个非主键索引的叶子节点上都是主键的值。如果用身份证号做主键,那么每个二级索引的叶子节点占用约20个字节,而如果用整型做主键,则只要4个字节,如果是长整型(bigint)则是8个字节。 + +**显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。** + +所以,从性能和存储空间方面考量,自增主键往往是更合理的选择。 + +有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的: + +1、只有一个索引; + +2、该索引必须是唯一索引。 + +你一定看出来了,这就是典型的KV场景。 + +由于没有其他索引,所以也就不用考虑其他索引的叶子节点大小的问题。 + +这时候我们就要优先考虑上一段提到的“尽量使用主键查询”原则,直接将这个索引设置为主键,可以避免每次查询需要搜索两棵树。 \ No newline at end of file diff --git "a/DB/MySQL/question/5_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\213).md" "b/DB/MySQL/question/5_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\213).md" new file mode 100644 index 00000000..fffe08fc --- /dev/null +++ "b/DB/MySQL/question/5_\347\264\242\345\274\225-\346\267\261\345\205\245\346\265\205\345\207\272\347\264\242\345\274\225(\344\270\213).md" @@ -0,0 +1,129 @@ +# 索引: 深入浅出索引(下) + +在下面这个表T中,如果我执行` select * from T where k between 3 and 5`,需要执行几次树的搜索操作,会扫描多少行? + +下面是这个表的初始化语句。 + +```mysql +mysql> create table T ( +ID int primary key, +k int NOT NULL DEFAULT 0, +s varchar(16) NOT NULL DEFAULT '', +index k(k)) +engine=InnoDB; + +insert into T values(100,1, 'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg'); +``` + +![1557044048010](assets/1557044048010.png) + +现在,我们一起来看看这条SQL查询语句的执行流程: + +1. 在k索引树上找到k=3的记录,取得 ID = 300; +2. 再到ID索引树查到ID=300对应的R3; +3. 在k索引树取下一个值k=5,取得ID=500; +4. 再回到ID索引树查到ID=500对应的R4; +5. 在k索引树取下一个值k=6,不满足条件,循环结束。 + +在这个过程中,**回到主键索引树搜索的过程,我们称为回表**。可以看到,这个查询过程读了k索引树的3条记录(步骤1、3和5),回表了两次(步骤2和4)。 + +在这个例子中,由于查询结果所需要的数据只在主键索引上有,所以不得不回表。那么,有没有可能经过索引优化,避免回表过程呢? + +## 一、覆盖索引 + +如果执行的语句是`select ID from T where k between 3 and 5`,这时只需要查ID的值,**而ID的值已经在k索引树上了,因此可以直接提供查询结果,不需要回表。也就是说,在这个查询里面,索引k已经“覆盖了”我们的查询需求,我们称为覆盖索引**。 + +**由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段**。 + +基于上面覆盖索引的说明,我们来讨论一个问题:**在一个市民信息表上,是否有必要将身份证号和名字建立联合索引?** + +假设这个市民表的定义是这样的: + +```mysql +CREATE TABLE `tuser` ( + `id` int(11) NOT NULL, + `id_card` varchar(32) DEFAULT NULL, + `name` varchar(32) DEFAULT NULL, + `age` int(11) DEFAULT NULL, + `ismale` tinyint(1) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `id_card` (`id_card`), + KEY `name_age` (`name`,`age`) +) ENGINE=InnoDB +``` + +我们知道,身份证号是市民的唯一标识。也就是说,如果有根据身份证号查询市民信息的需求,我们只要在身份证号字段上建立索引就够了。而再建立一个(身份证号、姓名)的联合索引,是不是浪费空间? + +**如果现在有一个高频请求,要根据市民的身份证号查询他的姓名,这个联合索引就有意义了。它可以在这个高频请求上用到覆盖索引,不再需要回表查整行记录,减少语句的执行时间**。 + +当然,索引字段的维护总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。 + +## 二、最左前缀原则 + +看到这里你一定有一个疑问,如果为每一种查询都设计一个索引,索引是不是太多了。如果我现在要按照市民的身份证号去查他的家庭地址呢?虽然这个查询需求在业务中出现的概率不高,但总不能让它走全表扫描吧?反过来说,单独为一个不频繁的请求创建一个(身份证号,地址)的索引又感觉有点浪费。应该怎么做呢? + +这里,我先和你说结论吧。**B+树这种索引结构,可以利用索引的“最左前缀”,来定位记录。** + +为了直观地说明这个概念,我们用(name,age)这个联合索引来分析。 + +![1557044709421](assets/1557044709421.png) + +可以看到,索引项是按照索引定义里面出现的字段顺序排序的。 + +当你的逻辑需求是查到所有名字是“张三”的人时,可以快速定位到ID4,然后向后遍历得到所有需要的结果。 + +如果你要查的是所有名字第一个字是“张”的人,你的SQL语句的条件是"where name like ‘张%’"。这时,你也能够用上这个索引,查找到第一个符合条件的记录是ID3,然后向后遍历,直到不满足条件为止。 + +可以看到,不只是索引的全部定义,只要满足最左前缀,就可以利用索引来加速检索。这个最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符。 + +基于上面对最左前缀索引的说明,我们来讨论一个问题:**在建立联合索引的时候,如何安排索引内的字段顺序。** + +这里我们的评估标准是,索引的复用能力。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引了。因此,**第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。** + +所以现在你知道了,这段开头的问题里,我们要为高频请求创建(身份证号,姓名)这个联合索引,并用这个索引支持“根据身份证号查询地址”的需求。 + +那么,如果既有联合查询,又有基于a、b各自的查询呢?查询条件里面只有b的语句,是无法使用(a,b)这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护(a,b)、(b) 这两个索引。 + +这时候,我们要**考虑的原则就是空间**了。比如上面这个市民表的情况,name字段是比age字段大的 ,那我就建议你创建一个(name,age)的联合索引和一个(age)的单字段索引。 + +## 三、索引下推 + +上一段我们说到满足最左前缀原则的时候,最左前缀可以用于在索引中定位记录。这时,你可能要问,那些不符合最左前缀的部分,会怎么样呢? + +我们还是以市民表的联合索引(name, age)为例。如果现在有一个需求:检索出表中“名字第一个字是张,而且年龄是10岁的所有男孩”。那么,SQL语句是这么写的: + +```mysql +mysql> select * from tuser where name like '张%' and age=10 and ismale=1; +``` + +你已经知道了前缀索引规则,所以这个语句在搜索索引树的时候,只能用 “张”,找到第一个满足条件的记录ID3。当然,这还不错,总比全表扫描要好。 + +然后呢? + +当然是判断其他条件是否满足。 + +在MySQL 5.6之前,只能从ID3开始一个个回表。到主键索引上找出数据行,再对比字段值。 + +而MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 + +图3和图4,是这两个过程的执行流程图。 + +![1557048052916](assets/1557048052916.png) + +![1557048073808](assets/1557048073808.png) + +在图3和4这两个图里面,每一个虚线箭头表示回表一次。 + +图3中,在`(name,age)`索引里面我特意去掉了age的值,这个过程InnoDB并不会去看age的值,只是按顺序把“name第一个字是’张’”的记录一条条取出来回表。因此,需要回表4次。 + +图4跟图3的区别是,InnoDB在(name,age)索引内部就判断了age是否等于10,对于不等于10的记录,直接判断并跳过。在我们的这个例子中,只需要对ID4、ID5这两条记录回表取数据判断,就只需要回表2次。 + +## 四、总结 + +回表:回到主键索引树搜索的过程,称为回表 + +覆盖索引:某索引已经覆盖了查询需求,称为覆盖索引,例如:`select ID from T where k between 3 and 5` 在引擎内部使用覆盖索引在索引K上其实读了三个记录,R3~R5(对应的索引k上的记录项),但对于MySQL的Server层来说,它就是找引擎拿到了两条记录,因此MySQL认为扫描行数是2 + +最左前缀原则:B+Tree这种索引结构,可以利用索引的"最左前缀"来定位记录 只要满足最左前缀,就可以利用索引来加速检索。 最左前缀可以是联合索引的最左N个字段,也可以是字符串索引的最左M个字符 第一原则是:如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。 + +索引下推:在MySQL5.6之前,只能从根据最左前缀查询到ID开始一个个回表。到主键索引上找出数据行,再对比字段值。 MySQL5.6引入的索引下推优化,可以在索引遍历过程中,对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。 \ No newline at end of file diff --git a/DB/MySQL/question/assets/1555808577787.png b/DB/MySQL/question/assets/1555808577787.png new file mode 100644 index 00000000..3ce3eb49 Binary files /dev/null and b/DB/MySQL/question/assets/1555808577787.png differ diff --git a/DB/MySQL/question/assets/1555811614220.png b/DB/MySQL/question/assets/1555811614220.png new file mode 100644 index 00000000..6d6688be Binary files /dev/null and b/DB/MySQL/question/assets/1555811614220.png differ diff --git a/DB/MySQL/question/assets/1555812084299.png b/DB/MySQL/question/assets/1555812084299.png new file mode 100644 index 00000000..138d8bae Binary files /dev/null and b/DB/MySQL/question/assets/1555812084299.png differ diff --git a/DB/MySQL/question/assets/1557033944906.png b/DB/MySQL/question/assets/1557033944906.png new file mode 100644 index 00000000..14e9208b Binary files /dev/null and b/DB/MySQL/question/assets/1557033944906.png differ diff --git a/DB/MySQL/question/assets/1557039557333.png b/DB/MySQL/question/assets/1557039557333.png new file mode 100644 index 00000000..bbbe4d25 Binary files /dev/null and b/DB/MySQL/question/assets/1557039557333.png differ diff --git a/DB/MySQL/question/assets/1557041109708.png b/DB/MySQL/question/assets/1557041109708.png new file mode 100644 index 00000000..e531d848 Binary files /dev/null and b/DB/MySQL/question/assets/1557041109708.png differ diff --git a/DB/MySQL/question/assets/1557041172422.png b/DB/MySQL/question/assets/1557041172422.png new file mode 100644 index 00000000..342a3343 Binary files /dev/null and b/DB/MySQL/question/assets/1557041172422.png differ diff --git a/DB/MySQL/question/assets/1557041243597.png b/DB/MySQL/question/assets/1557041243597.png new file mode 100644 index 00000000..49fe530b Binary files /dev/null and b/DB/MySQL/question/assets/1557041243597.png differ diff --git a/DB/MySQL/question/assets/1557042219239.png b/DB/MySQL/question/assets/1557042219239.png new file mode 100644 index 00000000..54bee874 Binary files /dev/null and b/DB/MySQL/question/assets/1557042219239.png differ diff --git a/DB/MySQL/question/assets/1557044048010.png b/DB/MySQL/question/assets/1557044048010.png new file mode 100644 index 00000000..ba0c50d2 Binary files /dev/null and b/DB/MySQL/question/assets/1557044048010.png differ diff --git a/DB/MySQL/question/assets/1557044709421.png b/DB/MySQL/question/assets/1557044709421.png new file mode 100644 index 00000000..ec50ade4 Binary files /dev/null and b/DB/MySQL/question/assets/1557044709421.png differ diff --git a/DB/MySQL/question/assets/1557048052916.png b/DB/MySQL/question/assets/1557048052916.png new file mode 100644 index 00000000..b4c9c19a Binary files /dev/null and b/DB/MySQL/question/assets/1557048052916.png differ diff --git a/DB/MySQL/question/assets/1557048073808.png b/DB/MySQL/question/assets/1557048073808.png new file mode 100644 index 00000000..06ca3d70 Binary files /dev/null and b/DB/MySQL/question/assets/1557048073808.png differ diff --git a/DB/MySQL/question/readme.md b/DB/MySQL/question/readme.md new file mode 100644 index 00000000..48b0c233 --- /dev/null +++ b/DB/MySQL/question/readme.md @@ -0,0 +1,8 @@ +# 说明 + +挺好的一门课: `<<极客时间 - MYSQL实战45讲>>`。 + +* 大部分直接从`<<极客时间 - MYSQL实战45讲>>`拷贝过来; +* 减掉了一些无关紧要的描述; +* 整理了一下格式 (加粗重点部分); + diff --git "a/DB/Redis/Redis\344\270\255sorted set\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204\350\267\263\350\241\250.md" "b/DB/Redis/Redis\344\270\255sorted set\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204\350\267\263\350\241\250.md" new file mode 100644 index 00000000..4ad21ef8 --- /dev/null +++ "b/DB/Redis/Redis\344\270\255sorted set\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204\350\267\263\350\241\250.md" @@ -0,0 +1,100 @@ +# Redis的跳表数据结构 + +好文: + +ConcurrentSkipListMap有如下特点: + +- 没有使用锁,所有操作都是无阻塞的,所有操作都可以并行,包括写,多个线程可以同时写。 +- 与ConcurrentHashMap类似,迭代器不会抛出ConcurrentModificationException,是弱一致的,迭代可能反映最新修改也可能不反映,一些方法如putAll, clear不是原子的。 +- 与ConcurrentHashMap类似,同样实现了ConcurrentMap接口,直接支持一些原子复合操作。 +- 与TreeMap一样,可排序,默认按键自然有序,可以传递比较器自定义排序,实现了SortedMap和NavigableMap接口。 + +看段简单的使用代码: + +```java +public static void main(String[] args) { + Map map = new ConcurrentSkipListMap<>( + Collections.reverseOrder()); + map.put("a", "abstract"); + map.put("c", "call"); + map.put("b", "basic"); + System.out.println(map.toString()); +} +``` + +程序输出为: + +```java +{c=call, b=basic, a=abstract} +``` + +表示是有序的。 + +ConcurrentSkipListMap的大部分方法,我们之前都有介绍过,有序的方法,与TreeMap是类似的,原子复合操作,与ConcurrentHashMap是类似的,所以我们就不赘述了。 + +需要说明一下的是它的size方法,与大多数容器实现不同,这个方法不是常量操作,它需要遍历所有元素,复杂度为O(N),而且遍历结束后,元素个数可能已经变了,一般而言,在并发应用中,这个方法用处不大。 + +下面我们主要介绍下其基本实现原理。 + +**基本实现原理** + +我们先来介绍下跳表的结构,跳表是基于链表的,在链表的基础上加了多层索引结构。我们通过一个简单的例子来看下,假定容器中包含如下元素: + +```java +3, 6, 7, 9, 12, 17, 19, 21, 25, 26 +``` + +对Map来说,这些值可以视为键。ConcurrentSkipListMap会构造类似下图所示的跳表结构: + +![img](assets/924211-20170316221132260-1504932110.jpg) + +最下面一层,就是最基本的单向链表,这个链表是有序的。虽然是有序的,但我们知道,与数组不同,链表不能根据索引直接定位,不能进行二分查找。 + +为了快速查找,跳表有多层索引结构,这个例子中有两层,第一层有5个节点,第二层有2个节点。高层的索引节点一定同时是低层的索引节点,比如9和21。 + +高层的索引节点少,低层的多,统计概率上,第一层索引节点是实际元素数的1/2,第二层是第一层的1/2,逐层减半,但这不是绝对的,有随机性,只是大概如此。 + +对于每个索引节点,有两个指针,一个向右,指向下一个同层的索引节点,另一个向下,指向下一层的索引节点或基本链表节点。 + +有了这个结构,就可以实现类似二分查找了,查找元素总是从最高层开始,将待查值与下一个索引节点的值进行比较,如果大于索引节点,就向右移动,继续比较,如果小于,则向下移动到下一层进行比较。 + +下图两条线展示了查找值19和8的过程: + +![img](assets/924211-20170316221236448-149448813.jpg) + +对于19,查找过程是: + +1. 与9相比,大于9 +2. 向右与21相比,小于21 +3. 向下与17相比,大于17 +4. 向右与21相比,小于21 +5. 向下与19相比,找到 + +对于8,查找过程是: + +1. 与9相比,小于9 +2. 向下与6相比,大于6 +3. 向右与9相比,小于9 +4. 向下与7相比,大于7 +5. 向右与9相比,小于9,不能再向下,没找到 + +这个结构是有序的,查找的性能与二叉树类似,复杂度是O(log(N)),不过,这个结构是如何构建起来的呢? + +与二叉树类似,这个结构是在更新过程中进行保持的,保存元素的基本思路是: + +1. 先保存到基本链表,找到待插入的位置,找到位置后,先插入基本链表 +2. 更新索引层。 + +对于索引更新,随机计算一个数,表示为该元素最高建几层索引,一层的概率为1/2,二层为1/4,三层为1/8,依次类推。然后从最高层到最低层,在每一层,为该元素建立索引节点,建的过程也是先查找位置,再插入。 + +对于删除元素,ConcurrentSkipListMap不是一下子真的进行删除,为了避免并发冲突,有一个复杂的标记过程,在内部遍历元素的过程中会真正删除。 + +以上我们只是介绍了基本思路,为了实现并发安全、高效、无锁非阻塞,ConcurrentSkipListMap的实现非常复杂,具体我们就不探讨了,感兴趣的读者可以参考其源码,其中提到了多篇学术论文,论文中描述了它参考的一些算法。 + +对于常见的操作,如get/put/remove/containsKey,ConcurrentSkipListMap的复杂度都是O(log(N))。 + +上面介绍的SkipList结构是为了便于并发操作的,如果不需要并发,可以使用另一种更为高效的结构,数据和所有层的索引放到一个节点中,如下图所示: + +![img](assets/924211-20170316221603073-647542213.jpg) + +对于一个元素,只有一个节点,只是每个节点的索引个数可能不同,在新建一个节点时,使用随机算法决定它的索引个数,平均而言,1/2的元素有两个索引,1/4的元素有三个索引,依次类推。 \ No newline at end of file diff --git "a/DB/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266.md" "b/DB/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266.md" new file mode 100644 index 00000000..50145c49 --- /dev/null +++ "b/DB/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266.md" @@ -0,0 +1,182 @@ +# Redis主从复制 + +* [一、概念和作用](一概念和作用) +* [二、复制原理](#二复制原理) +* [三、修改配置文件细节操作](#三修改配置文件细节操作) +* [四、常见三种配置用法](#四常见三种配置用法) + * [1、一主二仆](#1一主二仆) + * [2、薪火相传](#2薪火相传) + * [3、反客为主](#3反客为主) + * [4、哨兵模式](#4哨兵模式) + +## 一、概念和作用 + +概念:主机数据更新后根据配置和策略, 自动同步到备机的`master/slaver`机制,**Master以写为主,Slave以读为主**。 + +![1_39.png](images/1_39.png) + +作用: + +* 读写分离; +* 容灾恢复; + +## 二、复制原理 + +* `slave`启动成功连接到`master`后会发送一个`sync`命令; +* Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, **在后台进程执行完毕之后,master将传送整个数据文件到slave**,以完成一次完全同步; +* 全量复制:而`slave`服务在接收到数据库文件数据后,将其存盘并加载到内存中; +* 增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步; +* 但是只要是重新连接master,**一次完全同步(全量复制)将被自动执行**; + +下面结合实战和理论解释。 + +> 缺点: 由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。。 + + +## 三、修改配置文件细节操作 + +配置原则: + +* 配从(库)不配主(库); +* 从库配置:`slaveof 主库IP 主库端口` + * **每次与master断开之后,都需要重新连接,除非你配置进`redis.conf`文件**;(下面有个例子,从机`down`了之后,再回来就没有了,需要重新`SLAVEOF`连接。) + * 使用`info replication`查看当前库的信息(是从库还是主库。以及其他信息); +* 修改配置文件细节操作。 + * 拷贝多个`redis.conf`文件,也就是每个库(在不同机器)有一个`redis.conf`; + * 开启`daemonize yes`; + * pid文件名字; + * 指定端口; + * log文件名字; + * `dump.rdb`名字; + +实操配置: + +①编写三个配置文件: + +![1_40.png](images/1_40.png) + +②修改配置 + +![1_41.png](images/1_41.png) + +③修改LOG等 + +![1_42.png](images/1_42.png) + +## 四、常见三种配置用法 + +搭建三台客户端。一开始都是主库: +![1_43.png](images/1_43.png) + +### 1、一主二仆 + +也就是: 一个Master两个Slave。 + +

+ +相关演示: + +![1_44.png](images/1_44.png) + +可以查看主机的日志,此时发现有两个`slave`挂在主机下面: + +

+ +以及备机的日志: + +![1_46.png](images/1_46.png) + +用`info replicatino`查看: + +![1_47.png](images/1_47.png) + +相关问题: + +(1)切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制? 比如从k4进来,那之前的123是否也可以复制? + +答: 可以。 + +(2 )从机是否可以写?set可否? + +答: 不可以,主要是读。 + +(3 )主机shutdown后情况如何?从机是上位还是原地待命 + +答: 从机待命。还是`slave`。 + +(4 )主机又回来了后,主机新增记录,从机还能否顺利复制? + +答: 可以,老领导回来了,我继续跟着你。 + +(5) 其中一台从机down后情况如何?依照原有它能跟上大部队吗? + +答: **不可以**。需要重新`SLAVEOF`连接。上面也说过了 + +**每次与master断开之后,都需要重新连接,除非你配置进`redis.conf`文件**;(从机`down`了之后,再回来就没有了,需要重新`SLAVEOF`连接。) + +![1_48.png](images/1_48.png) + +### 2、薪火相传 + +上一个Slave可以是下一个slave的Master,Slave同样可以接收其他 slaves的连接和同步请求,那么该slave作为了链条中下一个的master, **可以有效减轻master的写压力**。 + +如果中途变更转向:**会清除之前的数据,重新建立拷贝最新的**。 + +命令: `slaveof 新主库IP 新主库端口`。 + +![1_50.png](images/1_50.png) + +演示: + +`6379`作为`Master`,`6380`连接到`6379`,然后`6381`连接到`6380`。(注意此时`6380`也是`slave`) + +![1_51.png](images/1_51.png) + +### 3、反客为主 + +在一个`Master`两个`slave`的情况下,如果主机挂了,从库需要手动调用`SLAVEOF no one`命令,来使当前数据库停止与其他数据库的同步,转成主数据库。 + +演示: + +![1_52.png](images/1_52.png) + +### 4、哨兵模式 + +配置 + +* 调整结构,6379带着80、81。 +* 自定义的`/myredis`目录下新建`sentinel.conf`文件,名字绝不能错。 +* 配置哨兵,填写内容: + * `sentinel monitor 被监控数据库名字(自己起名字) 127.0.0.1 6379 1`。 + * 上面最后一个数字1,表示主机挂掉后salve投票看让谁接替成为主机,得票数多少后成为主机; + * 例如配置: `sentinel monitor host6379 127.0.0.1 6379 1`。如果`6379`挂了,谁的票数多余1票,就自动化成为主机; +* 启动哨兵: + * `redis-sentinel /myredis/sentinel.conf` + * 你的 + +> 一组sentinel能同时监控多个Master。 + +![1_53.png](images/1_53.png) + +演示: + +①一开始,`master`挂了。 + +![1_54.png](images/1_54.png) + +②查看`sentinel`文件内容变化。 + +![1_55.png](images/1_55.png) + +③重新看,`6780`已经自动成为了`master`。 + +![1_56.png](images/1_56.png) + +④如果之前的`master`即`6379`重启回来,会不会双`master`冲突? 答:不会,之前的`master`变成现在的`master`的奴隶。 + +![1_57.png](images/1_57.png) + + + +## + diff --git "a/DB/Redis/Redis\344\272\213\345\212\241\345\222\214\346\266\210\346\201\257\350\256\242\351\230\205.md" "b/DB/Redis/Redis\344\272\213\345\212\241\345\222\214\346\266\210\346\201\257\350\256\242\351\230\205.md" new file mode 100644 index 00000000..1682cc7f --- /dev/null +++ "b/DB/Redis/Redis\344\272\213\345\212\241\345\222\214\346\266\210\346\201\257\350\256\242\351\230\205.md" @@ -0,0 +1,101 @@ +# Redis事务和消息订阅 + +## 一、Redis事务 + +### 1、概念 + +可以一次执行多个命令,本质是一组命令的集合。一个事务中的 所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。 + +![1_31.png](images/1_31.png) + +事务能做的事: **一个队列中,一次性、顺序性、排他性的执行一系列命令**d + +常用命令: + +* DISCARD: 取消事务,放弃执行事务块内的所有命令; +* EXEC : 执行所有事务块内的命令; +* MULTI : 标记一个事务块的开始; +* WATCH key([key ....]) : 监视一个(或多个) key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断; +* UNWATCH : 取消WATCH命令对**所有key**的监控; + +### 2、正常执行和放弃事务 + +* 正常执行; +* 放弃事务; + +![1_32.png](images/1_32.png) + +### 3、全体连坐和冤头债主 + +下面的演示说明: **Redis是部分支持事务的**。不保证原子性。 + +* 全体连坐 (错误的命令,相当于编译错误,然后导致所有命令都挂了); +* 冤头债主(不支持的参数,或者类型问题,相当于运行时错误,不会导致所有命令都挂,只挂有问题的); + +![1_33.png](images/1_33.png) + +### 4、WATCH监控(重要) + +首先介绍了乐观锁和悲观锁: + +* 悲观锁(`Pessimistic Lock`):  顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,**所以每次在拿数据的时候都会上锁**,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。 +* 乐观锁(`Optimistic Lock`) : 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,**但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号(version)等机制**。乐观锁适用于多读的应用类型,这样可以提高吞吐量。 乐观锁策略:**提交版本必须大于记录当前版本才能执行更新**。 + +WATCH监控案例: (余额和消费),例如余额为`100`,消费为`0`,余额为`80`,消费为`20`..... + +①先看一波正常执行的: + +![1_34.png](images/1_34.png) + +②第二波,有另一个客户端修改了我们`WATCH`的key。 + +![1_35.png](images/1_35.png) + +③第三波,使用UNWATCH。 + +![1_36.png](images/1_36.png) + +总结: + +* 一旦执行了`exec`之前加的监控锁都会被取消掉了。 +* Watch指令,类似乐观锁,事务提交时,如果Key的值已被别的客户端改变, 比如某个list已被别的客户端`push/pop`过了,整个事务队列都不会被执行。 +* 通过WATCH命令在事务执行之前监控了多个Keys,**倘若在WATCH之后有任何Key的值发生了变化, EXEC命令执行的事务都将被放弃**,同时返回Nullmulti-bulk应答以通知调用者事务执行失败。 + +### 5、事务的阶段和特性 + +三个阶段: + +* 开启:**以MULTI开始一个事务**; +* 入队:**将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面**; +* 执行:**由EXEC命令触发事务**; + +三个特性: + +* **单独的隔离操作**:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断; +* **没有隔离级别的概念**:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行, 也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题; +* 不保证原子性:**redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行**,没有回滚; + +## 二、Redis消息订阅发布 + +概念: + +* 进程间的一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息; + +左边窗口开始订阅`c1、c2、c3`三个频道。右边还没有操作。 + +![1_37.png](images/1_37.png) + +然后右边开始发布消息。 + +![1_38.png](images/1_38.png) + +总结: + +先订阅后发布后才能收到消息, + +* 可以一次性订阅多个,`SUBSCRIBE c1 c2 c3`。 + +* 消息发布,`PUBLISH c2 hello-redis`。 + +* 订阅多个,通配符`*`,`PSUBSCRIBE new*`。 +* 收取消息, `PUBLISH new1 redis2015`。 diff --git "a/DB/Redis/Redis\345\237\272\347\241\200\345\222\214\345\205\245\351\227\250.md" "b/DB/Redis/Redis\345\237\272\347\241\200\345\222\214\345\205\245\351\227\250.md" new file mode 100644 index 00000000..acf6f113 --- /dev/null +++ "b/DB/Redis/Redis\345\237\272\347\241\200\345\222\214\345\205\245\351\227\250.md" @@ -0,0 +1,639 @@ +# Redis + +* [一、NOSQL基本简介](#nosql基本简介) +* [二、Redis入门与简介](#二redis入门与简介) +* [三、Redis数据类型](#三redis数据类型) +* [四、Redis.conf配置文件](#四redisconf配置文件) + +## 一、NOSQL基本简介 + +### 1、演变 + +①一开始,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。 + +![1_1.png](images/1_1.png) + +②后来,访问量上升,程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。在这个时候,Memcached就自然的成为一个非常时尚的技术产品。 + +![1_2.png](images/1_2.png) + +③由于数据库的写入压力增加,**Memcached只能缓解数据库的读取压力**。**读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离**,以提高读写性能和读库的可扩展性。Mysql的master-slave模式成为这个时候的网站标配了。 + +![1_3.png](images/1_3.png) + +④后来MySQL主库的写压力开始出现瓶颈,而数据量的持续猛增,由于MyISAM使用表锁,在高并发下会出现严重的锁问题,大量的高并发MySQL应用开始使用InnoDB引擎代替MyISAM。 + +同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题,分表分库成了一个热门技术。 + +![1_4.png](images/1_4.png) + +⑤现在的架构。 + +![1_5.png](images/1_5.png) + +### 2、RDBMS和NOSQL对比 + +RDBMS +* 高度组织化结构化数据,结构化查询语言(SQL) +* 数据和关系都存储在单独的表中 +* 数据操纵语言,数据定义语言 +* 严格的一致性 +* 基础事务 + +NoSQL +* 代表着不仅仅是SQL,没有声明性查询语言,没有预定义的模式 +* 键 - 值对存储,列存储,文档存储,图形数据库 +* **最终一致性**,而非ACID属性 +* 非结构化和不可预知的数据 +* CAP定理 +* 高性能,高可用性和可伸缩性 + + ### 3、在分布式数据库中CAP原理CAP+BASE + +传统数据库:ACID,即:原子性、一致性、隔离性、持久性。 + +CAP原理: + +* C:Consistency(强一致性); +* A:Availability(可用性); +* P:Partition tolerance(分区容错性); + +**CAP理论就是说在分布式存储系统中,最多只能实现上面的两点**。 + +

+ +**而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的**。 + +所以我们只能在一致性和可用性之间进行权衡,**没有NoSQL系统能同时保证这三点。** + +* `CA` : 传统Oracle数据库。单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。 +* `AP` : 大多数网站架构的选择。满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。 +* `CP` : Redis、Mongodb。满足一致性,分区容忍必的系统,通常性能不是特别高。 + + 注意:分布式架构的时候必须做出取舍。 + +一致性和可用性之间取一个平衡。多余大多数web应用,其实并不需要强一致性。因此牺牲C换取P,这是目前分布式数据库产品的方向。 + +> BASE就是为了解决关系数据库强一致性引起的问题而引起的可用性降低而提出的解决方案。 +> BASE其实是下面三个术语的缩写: +> * 基本可用(Basically Available) +> * 软状态(Soft state) +> * 最终一致(Eventually consistent) + +## 二、Redis入门与简介 + +简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是**存在内存中的**,**所以存写速度非常快,因此 redis 被广泛应用于缓存方向**。另外,redis 也经常用来做分布式锁。 + +### 1、安装 + +官网下载`redis.xxx.tar.gz`。 + +![1_7.png](images/1_7.png) + +![1_8.png](images/1_8.png) + +![1_9.png](images/1_9.png) + +修改: 配置文件: `sudo vi /usr/local/redis/etc/redis.conf`。 + +![1_10.png](images/1_10.png) + +启动`redis-server`和`redis-cli`。 + +![1_11.png](images/1_11.png) + +> 安装可参考[**这篇博客**](https://www.cnblogs.com/limit1/p/9045183.html)。 + +### 2、相关基础杂项 + +`redis`默认有16个数据库: + +![1_12.png](images/1_12.png) + +基本命令: + +* `select idx`: 选择第idx库。 +* `dbsize` : 查看当前数据库的key的数量。 +* `flushdb`:清空当前库。 +* `Flushall`: 通杀全部库。 +* 统一密码管理,16个库都是同样密码,要么都OK要么一个也连接不上。 +* Redis索引都是从零开始。 +* 默认端口`6379`。 + +Redis和其他`key-value`缓存产品都有如下特点: + +* Redis支持**数据的持久化**,可以将内存中的**数据保持在磁盘**中,重启的时候可以再次加载进行使用; +* Redis不仅仅支持简单的key-value类型的数据,同时还提供`list,set,zset,hash`等数据结构的存储; +* Redis**支持数据的备份**,即master-slave模式的数据备份; + +出现一个报错的问题解决方案: https://blog.csdn.net/u011627980/article/details/79891597?utm_source=blogxgwz9 + +## 三、Redis数据类型 + +| 结构类型 | 结构存储的值 | 结构的读写能力 | +| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| **STRING** | **可以是字符串、整数或者浮点数** | 对整个字符串或者字符串的其中一部分执行操作、对整数和浮点数执行自增或自减操作 | +| **LIST** | 一个链表,链表上的每个节点都包含了一个字符串 | 从**两端压入或者弹出元素** 对单个或者多个元素 进行修剪,只保留一个范围内的元素 | +| **SET** | 包含字符串的**无序**收集器(unordered collection),并且被包含的每个字符串都互不相同的。 | 添加、获取、移除单个元素,检查一个元素是否存在于集合中、 **计算交集、并集、差集** ,从集合里面随机获取元素 | +| **HAST** | 包含**键值对**的无序散列表 | 添加、获取、移除单个键值对 ,获取所有键值对 检查某个键是否存在 | +| **ZSET** | 字符串成员(member)与浮点数分值(score)之间的**有序映射**,元素的排列顺序由**分值的大小**决定 | 添加、获取、删除元素 ,根据分值范围或者成员来获取元素,计算一个键的排名 | + +

+ +### 1、基本命令 + +```shell +127.0.0.1:6379> select 0 +OK +127.0.0.1:6379> keys * +1) "k2" +2) "k3" +127.0.0.1:6379> move k2 2 # 将k2移动到2号数据库 +(integer) 1 +127.0.0.1:6379> select 2 +OK +127.0.0.1:6379[2]> keys * +1) "k2" +127.0.0.1:6379[2]> select 0 +OK +127.0.0.1:6379> keys * +1) "k3" +127.0.0.1:6379> exists k3 +(integer) 1 +127.0.0.1:6379> exists k1 +(integer) 0 +127.0.0.1:6379> del k3 +127.0.0.1:6379> set k1 v1 +OK +127.0.0.1:6379> set k2 v2 +OK +127.0.0.1:6379> set k1 v100 # 默认会覆盖 +OK +127.0.0.1:6379> keys * +1) "k2" +2) "k1" +127.0.0.1:6379> type k1 +string +127.0.0.1:6379> lpush mylist 1 2 3 4 5 +(integer) 5 +127.0.0.1:6379> type mylist +list +127.0.0.1:6379> ttl k1 # ttl查看还能存活的时间 -1表示永久, -2表示已经死了 +(integer) -1 +127.0.0.1:6379> expire k2 10 # 设置k2 的ttl为10秒钟 +(integer) 1 +127.0.0.1:6379> ttl k2 +(integer) 7 +127.0.0.1:6379> ttl k2 +(integer) 4 +127.0.0.1:6379> ttl k2 +(integer) -2 +127.0.0.1:6379> keys * +1) "mylist" +2) "k1" +127.0.0.1:6379> +``` + +### 2、STRING + +

+ +```shell +127.0.0.1:6379> del mylist +(integer) 1 +127.0.0.1:6379> append k1 12345 # 追加 +(integer) 9 +127.0.0.1:6379> get k1 +"v10012345" +127.0.0.1:6379> STRLEN k1 # 长度 +(integer) 9 +127.0.0.1:6379> set k2 2 +OK +127.0.0.1:6379> INCR k2 # 自增 +(integer) 3 +127.0.0.1:6379> INCR k2 +(integer) 4 +127.0.0.1:6379> get k2 +"4" +127.0.0.1:6379> DECR k2 +(integer) 3 +127.0.0.1:6379> get k2 +"3" +127.0.0.1:6379> INCRBY k2 10 # + num +(integer) 13 +127.0.0.1:6379> get k2 +"13" +127.0.0.1:6379> DECRBY k2 10 +(integer) 3 +127.0.0.1:6379> set k3 v3 +OK +127.0.0.1:6379> INCR k3 # incr, decr,incrby,decrby都只能对数字进行操作 +(error) ERR value is not an integer or out of range +127.0.0.1:6379> get k1 +"v10012345" +127.0.0.1:6379> GETRANGE k1 0 -1 # getrange 获取指定范围的值 +"v10012345" +127.0.0.1:6379> GETRANGE k1 0 3 +"v100" +127.0.0.1:6379> SETRANGE k1 0 xxx +(integer) 9 +127.0.0.1:6379> get k1 +"xxx012345" +127.0.0.1:6379> setex k4 10 v4 # set with expire +OK +127.0.0.1:6379> ttl k4 +(integer) 7 +127.0.0.1:6379> ttl k4 +(integer) 2 +127.0.0.1:6379> ttl k4 +(integer) -2 +127.0.0.1:6379> get k4 +(nil) +127.0.0.1:6379> keys * +1) "k2" +2) "k3" +3) "k1" +127.0.0.1:6379> setnx k1 v111 # 存在就设值不进去 +(integer) 0 +127.0.0.1:6379> get k1 +"xxx012345" +127.0.0.1:6379> setnx k5 v5 # 不存在就设值 +(integer) 1 +127.0.0.1:6379> get k5 +"v5" +127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 一次设值多个 +OK +127.0.0.1:6379> mget k1 k2 k3 +1) "v1" +2) "v2" +3) "v3" +127.0.0.1:6379> +``` + +### 3、List + +

+ +总结: + +* 它是一个字符串链表,left、right都可以插入添加; +* 如果键不存在,创建新的链表; +* 如果键已存在,新增内容; +* 如果值全移除,对应的键也就消失了。 +* 链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。 + +```shell +127.0.0.1:6379> LPUSH list01 1 2 3 4 5 # 从左边分别推入1、2、3、4、5 +(integer) 5 +127.0.0.1:6379> LRANGE list01 0 -1 +1) "5" +2) "4" +3) "3" +4) "2" +5) "1" +127.0.0.1:6379> RPUSH list02 1 2 3 4 5 +(integer) 5 +127.0.0.1:6379> LRANGE list02 0 -1 +1) "1" +2) "2" +3) "3" +4) "4" +5) "5" +127.0.0.1:6379> LPOP list01 # 弹掉最左边的,是5 +"5" +127.0.0.1:6379> LPOP list02 +"1" +127.0.0.1:6379> RPOP list01 +"1" +127.0.0.1:6379> RPOP list02 +"5" +127.0.0.1:6379> LINDEX list01 1 # 相当于 s.charAt() +"3" +127.0.0.1:6379> LINDEX list02 2 +"4" +127.0.0.1:6379> LLEN list01 # s.length() +(integer) 3 +127.0.0.1:6379> LRANGE list01 0 -1 +1) "4" +2) "3" +3) "2" +127.0.0.1:6379> RPUSH list03 1 1 1 2 2 2 3 3 3 4 4 4 +(integer) 12 +127.0.0.1:6379> LREM list03 2 3 # 删除N个value , 删除2个3 +(integer) 2 +127.0.0.1:6379> LRANGE list03 0 -1 + 1) "1" + 2) "1" + 3) "1" + 4) "2" + 5) "2" + 6) "2" + 7) "3" + 8) "4" + 9) "4" +10) "4" +127.0.0.1:6379> LTRIM list03 3 5 # s = s.substring(3, 5) +OK +127.0.0.1:6379> LRANGE list03 0 -1 +1) "2" +2) "2" +3) "2" +127.0.0.1:6379> LRANGE list02 0 -1 +1) "2" +2) "3" +3) "4" +127.0.0.1:6379> RPOPLPUSH list02 list03 # RPOPLPUSH,将第一个的最后一个放到第二个的头部 +"4" +127.0.0.1:6379> LRANGE list03 0 -1 +1) "4" +2) "2" +3) "2" +4) "2" +127.0.0.1:6379> LSET list03 1 x # s.set(idx, value); +OK +127.0.0.1:6379> LRANGE list03 0 -1 +1) "4" +2) "x" +3) "2" +4) "2" +127.0.0.1:6379> LINSERT list03 before x java +(integer) 5 +127.0.0.1:6379> LRANGE list03 0 -1 +1) "4" +2) "java" +3) "x" +4) "2" +5) "2" +127.0.0.1:6379> + + +``` + +### 4、Set + +

+ +```shell +127.0.0.1:6379> sadd set01 1 1 2 2 3 3 # 创建set01并加入元素 +(integer) 3 +127.0.0.1:6379> smembers set01 # 查看set中的所有元素,注意是去重的s +1) "1" +2) "2" +3) "3" +127.0.0.1:6379> SISMEMBER set01 1 +(integer) 1 +127.0.0.1:6379> SISMEMBER set01 x +(integer) 0 +127.0.0.1:6379> SCARD set01 # 元素个数 +(integer) 3 +127.0.0.1:6379> SREM set01 2 # 删除2 +(integer) 1 +127.0.0.1:6379> SMEMBERS set01 +1) "1" +2) "3" +127.0.0.1:6379> sadd set01 5 8 3 12 6 +(integer) 4 +127.0.0.1:6379> SMEMBERS set01 +1) "1" +2) "3" +3) "5" +4) "6" +5) "8" +6) "12" +127.0.0.1:6379> SRANDMEMBER set01 2 # 随机取两个 +1) "6" +2) "12" +127.0.0.1:6379> SRANDMEMBER set01 2 # 随机取两个 +1) "1" +2) "6" +127.0.0.1:6379> SPOP set01# 随机出栈 +"1" +127.0.0.1:6379> SPOP set01 +"12" +127.0.0.1:6379> SMEMBERS set01 +1) "3" +2) "5" +3) "6" +4) "8" +127.0.0.1:6379> flushdb # 清空了一下 +OK +127.0.0.1:6379> SADD set01 1 2 3 4 5 +(integer) 5 +127.0.0.1:6379> SADD set02 1 2 3 a b +(integer) 5 +127.0.0.1:6379> SDIFF set01 set02 # 差集 +1) "4" +2) "5" +127.0.0.1:6379> SINTER set01 set02 # 交集 +1) "1" +2) "2" +3) "3" +127.0.0.1:6379> SUNION set01 set02 # 并集 +1) "b" +2) "3" +3) "1" +4) "5" +5) "a" +6) "4" +7) "2" +127.0.0.1:6379> + + +``` + +注意上面差集: **在第一个set里面而不在后面任何一个set里面的项**。 + +### 5、Hash + +注意Redis里面的Hash和普通的`HashMap`有点小区别,Redis里面的Hash的`value`又是一个`key-value`集合。 + +![1_14.png](images/1_14.png) + +

+ +```shell +127.0.0.1:6379> flushdb +OK +127.0.0.1:6379> hset user name z3 +(integer) 1 +127.0.0.1:6379> hget user name +"z3" +127.0.0.1:6379> hmset customer id 11 name li4 age 24 # 批量设置 +OK +127.0.0.1:6379> hmget customer id name age +1) "11" +2) "li4" +3) "24" +127.0.0.1:6379> HGETALL customer# 获取所有 key、value、key、value..格式呈现 +1) "id" +2) "11" +3) "name" +4) "li4" +5) "age" +6) "24" +127.0.0.1:6379> HDEL customer name # 删除 +(integer) 1 +127.0.0.1:6379> HGETALL customer +1) "id" +2) "11" +3) "age" +4) "24" +127.0.0.1:6379> HLEN customer # 长度 +(integer) 2 +127.0.0.1:6379> HEXISTS customer id # 判断是否存在 +(integer) 1 +127.0.0.1:6379> HEXISTS customer email +(integer) 0 +127.0.0.1:6379> HKEYS customer # 获取所有key +1) "id" +2) "age" +127.0.0.1:6379> HVALS customer +1) "11" +2) "24" +127.0.0.1:6379> HINCRBY customer age 2 #增 +(integer) 26 +127.0.0.1:6379> HINCRBY customer age 2 +(integer) 28 +127.0.0.1:6379> HSET customer score 91.5 +(integer) 1 +127.0.0.1:6379> HINCRBYFLOAT customer score 0.5 +"92" +127.0.0.1:6379> HSETNX customer age 26 +(integer) 0 +127.0.0.1:6379> HSETNX customer email abc@163.com +(integer) 1 +127.0.0.1:6379> +``` + +### 6、ZSet(有序集合) + +ZSet就是在`set`的基础上,加一个`score`值。里面按照`score`值排序。也就是每两个是一个整体。 + +例如: + +* 之前是set01 : `v1 v2 v3`; +* 现在是set01 : `score1 v1 score2 v2 score3 v3`; + +

+ +```shell +127.0.0.1:6379> flushdb +OK +127.0.0.1:6379> ZADD zset01 60 v1 70 v2 80 v3 90 v4 100 v5 # 添加,每个整体有两个内容v,score +(integer) 5 +127.0.0.1:6379> ZRANGE zset01 0 -1 +1) "v1" +2) "v2" +3) "v3" +4) "v4" +5) "v5" +127.0.0.1:6379> +127.0.0.1:6379> ZRANGE zset01 0 -1 withscores + 1) "v1" + 2) "60" + 3) "v2" + 4) "70" + 5) "v3" + 6) "80" + 7) "v4" + 8) "90" + 9) "v5" +10) "100" +127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 +1) "v1" +2) "v2" +3) "v3" +4) "v4" +127.0.0.1:6379> ZRANGEBYSCORE zset01 60 (90 +1) "v1" +2) "v2" +3) "v3" +127.0.0.1:6379> ZRANGEBYSCORE zset01 (60 (90 +1) "v2" +2) "v3" +127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 limit 1 2 +1) "v2" +2) "v3" +127.0.0.1:6379> ZREM zset01 v5 # 删除v5 +(integer) 1 +127.0.0.1:6379> ZCARD zset01 # 个数 +(integer) 4 +127.0.0.1:6379> ZCOUNT zset01 60 80 # 一个范围的个数 +(integer) 3 +127.0.0.1:6379> ZRANK zset01 v3 # 排名 +(integer) 2 +127.0.0.1:6379> ZSCORE zset01 v2 +"70" +127.0.0.1:6379> ZREVRANK zset01 v3 +(integer) 1 +127.0.0.1:6379> ZREVRANGE zset01 0 -1 +1) "v4" +2) "v3" +3) "v2" +4) "v1" +127.0.0.1:6379> +``` + +## 四、Redis.conf配置文件 + +### 1、GENERAL通用 + +* daemonize; +* pidfile; +* port; +* tcp-backlog;设置tcp的`backlog`,`backlog`其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到`/proc/sys/net/core/somaxconn`的值,所以需要确认增大`somaxconn`和`tcp_max_syn_backlog`两个值。来达到想要的效果; +* timeout; +* `Tcp-keepalive`: 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60 。 +* `Syslog-enabled`: 是否把日志输出到syslog中。 + +### 2、SECURITY安全 + +设置密码: + +

+ +### 3、LIMITS限制 + +#### a、Maxclients + +**设置redis同时可以与多少个客户端进行连接**。默认情况下为10000个客户端。 + +#### b、Maxmemory + +**设置redis可以使用的内存量**。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过`maxmemory-policy`来指定。如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”, + +那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。 + +但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。 + +#### c、Maxmemory-policy + +缓存过期策略:主要有下面几种。 + +![1_21.png](images/1_21.png) + +缓存失效策略(FIFO 、LRU、LFU三种算法的区别) + +当缓存需要被清理时(比如空间占用已经接近临界值了),需要使用某种淘汰算法来决定清理掉哪些数据。常用的淘汰算法有下面几种: + +1. FIFO:`First In First Out`,先进先出。判断被存储的时间,**离目前最远的数据优先被淘汰**。 +2. LRU:`Least Recently Used`,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰(最久没有使用的)。 +3. LFU:`Least Frequently Used`,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰(最近一段时间使用的最少的)。 + +上面的策略: + +(1)volatile-lru:使用LRU算法移除key,只对设置了过期时间的键。 + +(2)allkeys-lru:使用LRU算法移除key。 + +(3)volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键。 + +(4)allkeys-random:移除随机的key。 + +(5)volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key。 + +(6)noeviction:不进行移除。针对写操作,只是返回错误信息。 + + + + diff --git "a/DB/Redis/Redis\345\256\236\347\216\260\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/DB/Redis/Redis\345\256\236\347\216\260\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 00000000..666a2394 --- /dev/null +++ "b/DB/Redis/Redis\345\256\236\347\216\260\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,115 @@ +# Redis实现分布式锁 + +为什么需要锁? + +* 多任务环境中才需要; +* 任务都需要对同一共享资源进行写操作; +* 对资源的访问是互斥的; + +过程: + +* 任务通过竞争锁资源才能对该资源进行操作(①竞争锁); +* 当有一个任务再对资源进行更新时(②占有锁),其他任务都不可以对这个资源进行操作(③任务阻塞); +* 直到任务完成(④释放锁); + +分布式锁解决方案: + +| | 实现思路 | 优点 | 缺点 | +| --------- | ------------------------------------------------------------ | ------------ | ---------------------------------------- | +| mysql | 利用数据库自身锁机制实现,要求
数据库支持行级锁 | 简单,稳定 | 性能差,无法适用高并发,容易死锁,不优雅 | +| redis | 基于redis的setnx命令实现,通过lua
脚本保证解锁时对缓存操作序列的原子性 | 性能好 | 复杂 | +| zookeeper | 基于zk的节点特性和watch机制 | 性能好,稳定 | 复杂 | + +Redis加解锁的正确姿势 + +* 通过`setnx`命令,必须给锁设置一个失效时间; (避免死锁) +* 加锁的时候,每个节点产生一个随机字符串(作为lockKey的value) (UUID);(避免误删锁,即自己线程加的锁,有可能被别的线程删掉) +* 写入随机值与设置失效时间必须是同时的; (保证加锁是原子的) + +> 加锁就一行代码:`jedis.set(String key, String value, String nxxx, String expx, int time)`,这个set()方法一共有五个形参: +>- 第一个为key,我们使用key来当锁,因为key是唯一的。 +>- 第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,分布式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用`UUID.randomUUID().toString()`方法生成。 +>- 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作; +>- 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定(防止死锁)。 +>- 第五个为time,与第四个参数相呼应,代表key的过期时间。 +>总的来说,执行上面的set()方法就只会导致两种结果:1、当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2、 已有锁存在,不做任何操作。 + +```java +@RestController +public class RedisLockController { + + @Autowired + StringRedisTemplate stringRedisTemplate; + + @RequestMapping("/testRedis") + public String testRedis(){ + String lockKey = "lockKey"; + String clientId = UUID.randomUUID().toString(); //防止 自己线程加的锁,总是有可能被别的线程删掉 + try { + //设值和设置过期必须是 原子性的操作 + Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS); + if(!res){ + return "error"; + } + int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); //jedis.get() + if(stock > 0){ + int newStock = stock-1; + stringRedisTemplate.opsForValue().set("stock", newStock + ""); + System.out.println("扣减成功, 剩余库存: " + newStock + ""); + }else{ + System.out.println("扣减失败, 库存不足"); + } + }finally { + //解锁 , 就是判断这把锁是不是自己加的 + if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){ + stringRedisTemplate.delete(lockKey); + } + } + return "success"; + } +} +``` + +使用Redisson实现分布式锁: + +![1565570699214](assets/1565570699214.png) + +简单来说: **就是另开一个定时任务延长锁的时间,防止两个线程同时进入**。 + +```java +@RestController +public class RedissonLockController { + + @Autowired + Redisson redisson; + + @Autowired + StringRedisTemplate stringRedisTemplate; + + @RequestMapping("/testRedisson") + public String testRedis() throws InterruptedException { + String lockKey = "lockKey"; + String clientId = UUID.randomUUID().toString(); + RLock lock = redisson.getLock(lockKey); // 得到锁 + try { + lock.tryLock(30, TimeUnit.SECONDS); // 超时时间 + int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); //jedis.get() + if (stock > 0) { + int newStock = stock - 1; + stringRedisTemplate.opsForValue().set("stock", newStock + ""); + System.out.println("扣减成功, 剩余库存: " + newStock + ""); + } else { + System.out.println("扣减失败, 库存不足"); + } + } finally { + lock.unlock(); + } + return "success"; + } +} +``` + + + + + diff --git "a/DB/Redis/Redis\346\214\201\344\271\205\345\214\226(\344\272\214).md" "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226(\344\272\214).md" new file mode 100644 index 00000000..a9c19223 --- /dev/null +++ "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226(\344\272\214).md" @@ -0,0 +1,156 @@ +# Redis持久化 + +转载: + +由于 RDB 的数据实时性问题,目前用 AOF 比较多了,而持久化恢复也是优先 AOF。 + +RDB 是旧的模式,现在基本上都使用 AOF。 + +**恢复的时候,优先加载 AOF,当没有 AOF 时才加载 RDB**。 + +## 一、RDB + +Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 + +![assets/1555842247398.png](assets/1555842247398.png) + +RDB特点: + +1、RDB 是一种快照模式,即——保存的是 key value 数据内容。 + +2、RDB 有 2 种持久方式,同步 save 模式和异步 bgsave 模式。由于 save 是同步的,所以可以保证数据一致性,而 bgsave 则不能。 + +3、save 可以在客户端显式触发,也可以在 shutdown 时自动触发;bgsave 可以在客户端显式触发,也可以通过配置由定时任务触发,也可以在 slave 节点触发。 + +4、save 导致 redis 同步阻塞,基本已经废弃。bgsave 则不会导致阻塞,但也有缺点:在 fork 时,需要增加内存服务器开销,因为当内存不够时,将使用虚拟内存,导致阻塞 Redis 运行。所以,需要保证空闲内存足够。 + +5、默认执行 shutdown 时,如果没有开启 AOF,则自动执行 bgsave。 + +6、每次的 RDB 文件都是替换的。 + +**关于优化:** + +Redis 会压缩 RDB 文件,使用 LZF 算法,让最终的 RDB 文件远小于内存大小,默认开启。但会消耗 CPU。 + +**RDB 缺点:** + +1、**无法秒级持久化**。 + +2、老版本 Redis 无法兼容新版本 RDB。 + +**RDB 优点:** + +1、文件紧凑,适合备份,全量复制场景。例如每 6 小时执行 bgsave,保存到文件系统之类的。 + +2、**Redis 加载 RDB 恢复数据远远快于 AOF**。 + +## 二、AOF + +由于 RDB 的数据实时性问题,AOF(append only file) 是目前 Redis 持久化的主流方式。 + +**AOF 特点:** + +1、默认文件名是 appendonly.aof。和 RDB 一样,保存在配置中 dir 目录下。 + +2、AOF 相比较于 RDB,每次都会保存写命令,数据实时性更高。 + +3、AOF 由于每次都会记录写命令,文件会很大,因此需要进行优化,称之为“重写机制”(下面详细说)。 + +4、AOF 每次保存的写命令都放在一个缓冲区,根据不同的策略(下面详细说)同步到磁盘。 + +**“重写机制” 细节:** + +1、fork 子进程(类似 bgsave) + +2、主进程会写到2个缓冲区,一个是原有的 “AOF 缓存区”,一个是专门为子进程准备的 “AOF 重写缓冲区”; + +3、子进程写到到新的 AOF 文件中,批量的,默认 32m;写完后通知主进程。 + +4、主进程把“AOF 重写缓冲区”的数据写到新 AOF 文件中。 + +5、将新的 AOF 文件替换老文件。 + +重写流程图(`rewrite`): + +![1555842861644](assets/1555842861644.png) + +**缓冲区同步策略,由参数 appendfsync 控制,一共3种:** + +1、always:调用系统 `fsync()` 函数,直到同步到硬盘返回;严重影响redis性能(同步性好)。 + +2、everysec:先调用 OS write 函数, 写到缓冲区,然后 redis 每秒执行一次 OS fsync 函数。推荐使用这种方式(默认配置)。 + +3、no: 只执行 write OS 函数,具体同步硬盘策略由 OS 决定;不推荐,数据不安全,容易丢失数据。 + +## 三、性能优化 + +**1、fork 操作** + +当 Redis 做 RDB 或者 AOF 重写时,必然要进行 fork 操作,对于 OS 来说,fork 都是一个重量级操作。而且,fork 还会拷贝一些数据,虽然不会拷贝主进程所有的物理空间,但会复制主进程的空间内存页表。对于 10GB 的 Redis 进程,需要复制大约 20MB 的内存页表,因此 fork 操作耗时跟进程总内存量息息相关,再加上,如果使用虚拟化技术,例如 Xen 虚拟机,fork 会更加耗时。 + +一个正常的 fork 耗时大概在 20毫秒左右。为什么呢,假设一个 Redis 实例的 OPS 在 5 万以上,如果 fork 操作耗时在秒级,那么僵拖慢几万条命令的执行,对生产环境影响明显。 + +我们可以在 Info stats 统计中查询 latestforkusec 指标获取最近一次 fork 操作耗时,单位微秒。 + +**如何优化:** + +1) 优先使用物理机或者高效支持 fork 的虚拟化技术,避免使用 Xen。 + +2) 控制 redis 实例最大内存,尽量控制在 10GB 以内。 + +3) 合理配置 Linux 内存分配策略,避免内存不足导致 fork 失败。 + +4) 降低 fork 的频率,如适度放宽 AOF 自动触发时机,避免不必要的全量复制。 + +**2、子进程开销** + +fork 完毕之后,会创建子进程,子进程负责 RDB 或者 AOF 重写,这部分过程主要涉及到 CPU,内存,硬盘三个地方的优化。 + +1) CPU 写入文件的过程是 CPU 密集的过程,通常子进程对单核 CPU 利用率接近 90%。如何优化呢?既然是 CPU 密集型操作,就不要绑定单核 CPU,因为这样会和父 CPU 进行竞争。同时,不要和其他 CPU 密集型服务不是在一个机器上。如果部署了多个 Redis 实例,尽力保证统一时刻只有一个子进程执行重写工作。 + +2) 内存子进程通过 fork 操作产生,占用内存大小等同于父进程,理论上需要两倍的内存完成持久化操作,但 Linux 有 copy on write 机制,父子进程会共享相同的物理内存页,当父进程处理写操作时,会把要修改的页创建对应的副本,而子进程在 fork 操作过程中,共享整个父进程内存快照。即——如果重写过程中存在内存修改操作,父进程负责创建所修改内存页的副本。这里就是内存消耗的地方。如何优化呢?尽量保证同一时刻只有一个子进程在工作;避免大量写入时做重写操作。 + +3) 硬盘 硬盘开销分析:子进程主要职责是将 RDB 或者 AOF 文件写入硬盘进行持久化,势必对硬盘造成压力,可通过工具例如 iostat,iotop 等,分析硬盘负载情况。 + +**如何优化:** + +1)、不要和其他高硬盘负载的服务放在一台机器上,例如 MQ,存储。 + +2)、AOF 重写时会消耗大量硬盘 IO,可以开启配置 `no-appendfsync-on-rewrite`,默认关闭。表示在 AOF 重写期间不做 fsync 操作。 + +3)、当开启 AOF 的 Redis 在高并发场景下,如果使用普通机械硬盘,每秒的写速率是 100MB左右,这时,Redis 的性能瓶颈在硬盘上,建议使用 SSD。 + +4)、对于单机配置多个 Redis 实例的情况,可以配置不同实例分盘存储 AOF 文件,分摊硬盘压力。 + +**3、AOF 追加阻塞** + +当开启 AOF 持久化时,常用的同步硬盘的策略是“每秒同步” everysec,用于平衡性能和数据安全性,对于这种方式,redis 使用另一条线程每秒执行 fsync 同步硬盘,当系统资源繁忙时,将造成 Redis 主线程阻塞。 + +流程图如下: + +![1555843318624](assets/1555843318624.png) + +通过上图可以发现:everysec 配置最多可能丢失 2 秒数据,不是 1 秒;如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率。 + +**问题定位:** + +1)、发生 AOF 阻塞时,会输入日志。用于记录 AOF fsync 阻塞导致拖慢 Redis 服务的行为。 + +2)、每当 AOF 追加阻塞事件发生时,在 info Persistence 统计中,aofdelayedfsync 指标会累加,查看这个指标方便定位 AOF 阻塞问题。 + +3)、AOF 同步最多运行 2 秒的延迟,当延迟发生时说明硬盘存在性能问题,可通过监控工具 iotop 查看,定位消耗 IO 的进程。 + +**4、单机多实例部署** + +Redis 单线程架构**无法充分利用多核CPU,通常的做法是一台机器上部署多个实例,当多个实例开启 AOF 后,彼此之间就会产生CPU 和 IO 的竞争**。 + +如何解决这个问题呢? + +让所有实例的 AOF 串行执行。 + +我们通过 info Persistence 中关于 AOF 的信息写出 Shell 脚本,然后串行执行实例的 AOF 持久化。 + +整个过程如图: + +

+通过不断判断 AOF 的状态,手动执行 AOF 重写,保证 AOF 不会存在竞争。 \ No newline at end of file diff --git "a/DB/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226.md" new file mode 100644 index 00000000..cbbbfa0d --- /dev/null +++ "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226.md" @@ -0,0 +1,176 @@ +## Redis持久化 + +### 1、RDB(Redis Database) + +#### a、基本概念 + +概念: **在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里**。 + +Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方 式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。 + +![1_23.png](images/1_23.png) + +**fork的作用是复制一个与当前进程一样的进程**。**新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程**。 + +rdb 保存的是`dump.rdb`文件 + +#### b、如何触发RDB快照 + +①配置文件中默认的快照配置,冷拷贝后重新使用,可以cp dump.rdb dump_new.rdb。然后要还原数据的时候就将`dump_new.rdb`还原成`dump.rdb`,然后重新启动的时候就会自动加载。 + +![1_22.png](images/1_22.png) + +②命令save或者是bgsave,这个会强制的备份。 + +* `Save`:save时**只管保存**,其它不管,全部阻塞。 +* `BGSAVE`:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。可以通过lastsave 命令获取最后一次成功执行快照的时间。 +* 执行flushall命令,也会产生`dump.rdb`文件,但里面是空的,无意义 (**当时还挖了一个坑**)。 + +> 即: 调用`save`也就是立刻、马上备份。`flushAll`也可以马上形成备份,但是没有意义。 + +`redis.conf`相关文件关于 `RDB`的配置: + +* `save`配置:RDB是整个内存的压缩过的Snapshot,RDB的数据结构,**可以配置复合的快照触发条件**。 + + 默认 + + 是1分钟内改了1万次, + + 或5分钟内改了10次, + + 或15分钟内改了1次。 + +* `stop-writes-on-bgsave-error` : 如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制。 + +* `rdbcompression` :rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。 + +* `dbfilename`: 默认是`dump.rdb`。 + +* `dir`: 生成`dump.rdb`的默认目录。 + +#### c、如何恢复 + +将备份文件 (`dump.rdb`) 移动到 redis 安装目录并启动服务即可。 + +CONFIG GET dir获取目录 + +#### d、优势和劣势 + +优势: + +* 适合大规模的数据恢复; +* 对数据完整性和一致性要求不高; + +劣势: + +* 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就 会丢失最后一次快照后的所有修改; +* fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑; + +#### e、如何停止 + +动态所有停止RDB保存规则的方法:`redis-cli config set save ""`。 + +**实战: 更换默认save,并使用dump.rdb恢复配置文件**(后面未做了) + +### 2、AOF(Append Only File) + +#### a、基本概念 + +以日志的形式来记录**每个写操作**。 + +**将Redis执行过的所有写指令记录下来(读操作不记录)**, 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。 + +Aof保存的是`appendonly.aof`文件。 + +![1_30.png](images/1_30.png) + +#### b、配置位置 + +* `appendonly`,默认是`no`,我们要改成`yes`才会有作用; +* `appendfilename`,默认是`appendonly.aof`; +* `appendfsync`: + * always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好; + * everysec:出厂默认推荐,异步操作,每秒记录 如果一秒内宕机,有数据丢失; + * no; +* `no-appendfsync-on-rewrite`:重写时是否可以运用Appendfsync,用默认no即可,保证数据安全性; +* `auto-aof-rewrite-min-size`:设置重写的基准值; +* `auto-aof-rewrite-percentage`:设置重写的基准值; + +![1_24.png](images/1_24.png) + +#### c、 AOF启动、恢复、修复 + +正常恢复: + +* 启动:设置Yes, --> 修改默认的`appendonly no`,改为yes。 +* 将有数据的aof文件复制一份保存到对应目录(`config get dir`); +* 恢复:重启redis然后重新加载。 + +异常恢复(就是下面的搞破坏) + +* 启备份被写坏的AOF文件; +* `redis-check-aof --fix`进行修复; + +第一步: 启动`redis-server`之前和之后的,启动之后就会生成`appendonly.aof`文件: + +![1_25.png](images/1_25.png) + +第二步: 进行相关操作并且查看更新之后的`appendonly.aof`文件: + +![1_26.png](images/1_26.png) + +但是上述文件不能还原我们之前的数据,因为最后一样有一个`flushAll`,还原的时候也会执行这个,所以不能用这个还原,但是我们可以手动的编辑`appendonly.aof`文件,从而还原我们的数据库。 + +![1_27.png](images/1_27.png) + +但是如果我们的`appendonly.aof`文件被破坏了,例如我随便加了一些乱码进去,这就会导致`redis`启动不了。 + +如下,虽然`appendonly.aof`和`dump.rdb`可以共存,但是会优先加载`appendonly.aof`,所以导致不能启动: + +![1_28.png](images/1_28.png) + +此时我们可以使用`redis-check-aof --fix appendonly.aof`来修复乱码文件: + +![1_29.png](images/1_29.png) + +#### d、重写rewrite + +概念: **AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩**, 只保留可以恢复数据的最小指令集.可以使用命令`bgrewriteaof`。 + +原理: + +**AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename), 遍历新进程的内存中数据,每条记录有一条的Set语句**。重写aof文件的操作,并没有读取旧的aof文件, 而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。 + +触发机制: + +Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。 + +#### e、优势和劣势 + +优势: + +* 每修改同步:`appendfsync always` 同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好。 +* 每秒同步:`appendfsync everysec` 异步操作,每秒记录 如果一秒内宕机,有数据丢失。 +* 不同步:`appendfsync no` 从不同步。 + +劣势: + +* **相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb**; +* **aof运行效率要慢于rdb,每秒同步策略效率较好**,不同步效率和rdb相同; + +### 3、RDB和AOF对比和选择 + +* RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。 +* AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些 命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾. Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。 +* 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。 +* 同时开启: + * 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。 + * RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。 + +> 性能建议: +> +> 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留`save 900 1`这条规则。 +> +> 如果Enalbe AOF,**好处是在最恶劣情况下也只会丢失不超过两秒数据**,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,**应该尽量减少AOF rewrite的频率**,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。 +> +> 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。 \ No newline at end of file diff --git "a/DB/Redis/Redis\346\214\201\344\271\205\345\214\226\343\200\201\351\233\206\347\276\244\346\236\266\346\236\204\343\200\201\351\253\230\345\217\257\347\224\250\343\200\201\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250\345\216\237\347\220\206\347\255\211\346\200\273\347\273\223.md" "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226\343\200\201\351\233\206\347\276\244\346\236\266\346\236\204\343\200\201\351\253\230\345\217\257\347\224\250\343\200\201\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250\345\216\237\347\220\206\347\255\211\346\200\273\347\273\223.md" new file mode 100644 index 00000000..e4e1b293 --- /dev/null +++ "b/DB/Redis/Redis\346\214\201\344\271\205\345\214\226\343\200\201\351\233\206\347\276\244\346\236\266\346\236\204\343\200\201\351\253\230\345\217\257\347\224\250\343\200\201\345\210\206\345\270\203\345\274\217\345\255\230\345\202\250\345\216\237\347\220\206\347\255\211\346\200\273\347\273\223.md" @@ -0,0 +1,375 @@ +# Redis + +## 一、Redis持久化 + +### 1、Redis持久化意义 + +redis持久化的意义,在于故障恢复,比如部署了重要的数据,如果没有持久化的话,redis遇到灾难性故障的时候,就会丢失所有的数据。如果通过持久化将数据搞一份儿在磁盘上去,然后定期比如说同步和备份到一些云存储服务上去,那么就可以保证数据不丢失全部,还是可以恢复一部分数据回来的。 + +![1567220619289](assets/1567220619289.png) + +### 2、RDB和AOF优缺点 + +**1、RDB和AOF两种持久化机制的介绍** + +RDB持久化机制,对redis中的数据执行周期性的持久化 + +AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集 + +如果我们想要redis仅仅作为纯内存的缓存来用,那么可以禁止RDB和AOF所有的持久化机制 + +通过RDB或AOF,都可以将redis内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云,云服务 + +如果redis挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动redis,redis就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务 + +**2、RDB持久化机制的优点** + +1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说Amazon的S3云服务上去,在国内可以是阿里云的ODPS分布式存储上,以预定好的备份策略来定期备份redis中的数据 + +2)RDB对redis对外提供的读写服务,影响非常小,**可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可** + +3)相对于AOF持久化机制来说,**直接基于RDB数据文件来重启和恢复redis进程,更加快速** + +**3、RDB持久化机制的缺点** + +1)如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,**那么会丢失最近5分钟的数据** + +2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒 + +**4、AOF持久化机制的优点** + +1)**AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据** + +2)**AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复** + +3)**AOF日志文件即使过大的时候,出现后台重写rewrite操作,也不会影响客户端的读写**。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。 + +4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据 + +![1567227276975](assets/1567227276975.png) + +**5、AOF持久化机制的缺点** + +1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大 + +2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的 + +3)以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。 + +**6、RDB和AOF到底该如何选择** + +1)不要仅仅使用RDB,因为那样会导致你丢失很多数据 + +2)也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug + +3)综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复 + +### 3、RDB持久化配置实战 + +**1、如何配置RDB持久化机制** + +redis.conf文件,也就是`/etc/redis/6379.conf`,去配置持久化 + +`save 60 1000` + +每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting,快照 + +也可以手动调用save或者bgsave命令,同步或异步执行rdb快照生成 + +save可以设置多个,就是多个snapshotting检查点,每到一个检查点,就会去check一下,是否有指定的key数量发生了变更,如果有,就生成一个新的dump.rdb文件 + +**2、RDB持久化机制的工作流程** + +1)redis根据配置自己尝试去生成rdb快照文件 + +2)fork一个子进程出来 + +3)子进程尝试将数据dump到临时的rdb快照文件中 + +4)完成rdb快照文件的生成之后,就替换之前的旧的快照文件 + +dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照 + +**3、基于RDB持久化机制的数据恢复实验** + +1)在redis中保存几条数据,立即停掉redis进程,然后重启redis,看看刚才插入的数据还在不在,数据还在,为什么? + +**带出来一个知识点,通过redis-cli SHUTDOWN这种方式去停掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据立即生成一份完整的rdb快照** + +`/var/redis/6379/dump.rdb` + +2)在redis中再保存几条新的数据,用kill -9粗暴杀死redis进程,模拟redis故障异常退出,导致内存数据丢失的场景,这次就发现,redis进程异常被杀掉,数据没有进dump文件,几条最新的数据就丢失了。 + +3)手动设置一个save检查点,save 5 1 + +4)写入几条数据,等待5秒钟,会发现自动进行了一次dump rdb快照,在dump.rdb中发现了数据 + +5)异常停掉redis进程,再重新启动redis,看刚才插入的数据还在 + +### 4、AOF持久化实战 + +**1、AOF持久化的配置** + +AOF持久化,默认是关闭的,默认是打开RDB持久化 + +`appendonly yes`,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,除非你说随便丢个几分钟的数据也无所谓 + +**打开AOF持久化机制之后,redis每次接收到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下** + +而且即使AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的,因为aof数据比较完整 + +可以配置AOF的fsync策略,有三种策略可以选择,一种是每次写入一条数据就执行一次fsync; 一种是每隔一秒执行一次fsync; 一种是不主动执行fsync + +* always: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,那就只能这样了 + + mysql -> 内存策略,大量磁盘,QPS到多少,一两k。QPS,每秒钟的请求数量 + redis -> 内存,磁盘持久化,QPS到多少,单机,一般来说,上万QPS没问题 + +* everysec: 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的 + +* no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控了 + +**2、AOF持久化的数据恢复实验** + +1)先仅仅打开RDB,写入一些数据,然后kill -9杀掉redis进程,接着重启redis,发现数据没了,因为RDB快照还没生成 + +2)打开AOF的开关,启用AOF持久化 + +3)写入一些数据,观察AOF文件中的日志内容 + +其实你在`appendonly.aof`文件中,可以看到刚写的日志,它们其实就是先写入os cache的,然后1秒后才fsync到磁盘中,只有fsync到磁盘中了,才是安全的,要不然光是在os cache中,机器只要重启,就什么都没了 + +4)kill -9杀掉redis进程,重新启动redis进程,发现数据被恢复回来了,就是从AOF文件中恢复回来的 + +redis进程启动的时候,直接就会从appendonly.aof中加载所有的日志,把内存中的数据恢复回来 + +**3、AOF rewrite** + +redis中的数据其实有限的,很多数据可能会自动过期,可能会被用户删除,可能会被redis用缓存清除的算法清理掉 + +redis中的数据会不断淘汰掉旧的,就一部分常用的数据会被自动保留在redis内存中 + +所以可能很多之前的已经被清理掉的数据,对应的写日志还停留在AOF中,AOF日志文件就一个,会不断的膨胀,到很大很大 + +所以AOF会自动在后台每隔一定时间做rewrite操作,比如日志里已经存放了针对100w数据的写日志了; redis内存只剩下10万; 基于内存中当前的10万数据构建一套最新的日志,到AOF中; 覆盖之前的老日志; 确保AOF日志文件不会过大,保持跟redis内存数据量一致 + +redis 2.4之前,还需要手动,开发一些脚本,crontab,通过BGREWRITEAOF命令去执行AOF rewrite,但是redis 2.4之后,会自动进行rewrite操作 + +在redis.conf中,可以配置rewrite策略 + + ```java +auto-aof-rewrite-percentage 100 +auto-aof-rewrite-min-size 64mb + ``` + +比如说上一次AOF rewrite之后,是128mb + +然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite + +但是此时还要去跟min-size,64mb去比较,256mb > 64mb,才会去触发rewrite + +1)redis fork一个子进程 + +2)子进程基于当前内存中的数据,构建日志,开始往一个新的临时的AOF文件中写入日志 + +3)redis主进程,接收到client新的写操作之后,在内存中写入日志,同时新的日志也继续写入旧的AOF文件 + +4)子进程写完新的日志文件之后,redis主进程将内存中的新日志再次追加到新的AOF文件中 + +5)用新的日志文件替换掉旧的日志文件 + +![1567229722195](assets/1567229722195.png) + +**4、AOF破损文件的修复** + +如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损 + +用`redis-check-aof --fix`命令来修复破损的AOF文件 + +**5、AOF和RDB同时工作** + +1)如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting + +2)如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite + +3)同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整 + +### 5、数据恢复实战 + +**1、企业级的持久化的配置策略** + +在企业中,RDB的生成策略,用默认的也差不多 + +save 60 10000:如果你希望尽可能确保说,RDB最多丢1分钟的数据,那么尽量就是每隔1分钟都生成一个快照,低峰期,数据量很少,也没必要 + +10000->生成RDB,1000->RDB,这个根据你自己的应用和业务的数据量,你自己去决定 + +AOF一定要打开,fsync,everysec + +`auto-aof-rewrite-percentage 100`: 就是当前AOF大小膨胀到超过上次100%,上次的两倍 +`auto-aof-rewrite-min-size 64mb`: 根据你的数据量来定,16mb,32mb + +2、企业级的数据备份方案 + +RDB非常适合做冷备,每次生成之后,就不会再有修改了 + +数据备份方案 + +(1)写crontab定时调度脚本去做数据备份 +(2)每小时都copy一份rdb的备份,到一个目录中去,仅仅保留最近48小时的备份 +(3)每天都保留一份当日的rdb的备份,到一个目录中去,仅仅保留最近1个月的备份 +(4)每次copy备份的时候,都把太旧的备份给删了 +(5)每天晚上将当前服务器上所有的数据备份,发送一份到远程的云服务上去 + +/usr/local/redis + +每小时copy一次备份,删除48小时前的数据 + +```shell +crontab -e + +0 * * * * sh /usr/local/redis/copy/redis_rdb_copy_hourly.sh + +redis_rdb_copy_hourly.sh + +#!/bin/sh + +cur_date=`date +%Y%m%d%k` +rm -rf /usr/local/redis/snapshotting/$cur_date +mkdir /usr/local/redis/snapshotting/$cur_date +cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date + +del_date=`date -d -48hour +%Y%m%d%k` +rm -rf /usr/local/redis/snapshotting/$del_date + + +每天copy一次备份 + +crontab -e + +0 0 * * * sh /usr/local/redis/copy/redis_rdb_copy_daily.sh + +redis_rdb_copy_daily.sh + +#!/bin/sh + +cur_date=`date +%Y%m%d` +rm -rf /usr/local/redis/snapshotting/$cur_date +mkdir /usr/local/redis/snapshotting/$cur_date +cp /var/redis/6379/dump.rdb /usr/local/redis/snapshotting/$cur_date + +del_date=`date -d -1month +%Y%m%d` +rm -rf /usr/local/redis/snapshotting/$del_date +``` + +每天一次将所有数据上传一次到远程的云服务器上去 + +3、数据恢复方案 + +1)如果是redis进程挂掉,那么重启redis进程即可,直接基于AOF日志文件恢复数据 + +不演示了,在AOF数据恢复那一块,演示了,fsync everysec,最多就丢一秒的数 + +2)如果是redis进程所在机器挂掉,那么重启机器后,尝试重启redis进程,尝试直接基于AOF日志文件进行数据恢复 + +AOF没有破损,也是可以直接基于AOF恢复的 + +AOF append-only,顺序写入,如果AOF文件破损,那么用redis-check-aof fix + +3)如果redis当前最新的AOF和RDB文件出现了丢失/损坏,那么可以尝试基于该机器上当前的某个最新的RDB数据副本进行数据恢复 + +当前最新的AOF和RDB文件都出现了丢失/损坏到无法恢复,一般不是机器的故障,人为 + +大数据系统,hadoop,有人不小心就把hadoop中存储的大量的数据文件对应的目录,rm -rf一下,我朋友的一个小公司,运维不太靠谱,权限也弄的不太好 + +/var/redis/6379下的文件给删除了 + +找到RDB最新的一份备份,小时级的备份可以了,小时级的肯定是最新的,copy到redis里面去,就可以恢复到某一个小时的数据 + +**容灾演练**(踩坑) + +appendonly.aof + dump.rdb,优先用appendonly.aof去恢复数据,但是我们发现redis自动生成的appendonly.aof是没有数据的 + +然后我们自己的dump.rdb是有数据的,但是明显没用我们的数据 + +redis启动的时候,自动重新基于内存的数据,生成了一份最新的rdb快照,直接用空的数据,覆盖掉了我们有数据的,拷贝过去的那份dump.rdb + +你停止redis之后,其实应该先删除appendonly.aof,然后将我们的dump.rdb拷贝过去,然后再重启redis + +很简单,就是虽然你删除了appendonly.aof,但是因为打开了aof持久化,redis就一定会优先基于aof去恢复,即使文件不在,那就创建一个新的空的aof文件 + +停止redis,暂时在配置中关闭aof,然后拷贝一份rdb过来,再重启redis,数据能不能恢复过来,可以恢复过来 + +脑子一热,再关掉redis,手动修改配置文件,打开aof,再重启redis,数据又没了,空的aof文件,所有数据又没了 + +在数据安全丢失的情况下,基于rdb冷备,如何完美的恢复数据,同时还保持aof和rdb的双开 + +停止redis,关闭aof,拷贝rdb备份,重启redis,确认数据恢复,直接在命令行热修改redis配置,打开aof,这个redis就会将内存中的数据对应的日志,写入aof文件中 + +此时aof和rdb两份数据文件的数据就同步了 + +**redis config set热修改配置参数,可能配置文件中的实际的参数没有被持久化的修改,再次停止redis,手动修改配置文件,打开aof的命令,再次重启redis** + +4)如果当前机器上的所有RDB文件全部损坏,那么从远程的云服务上拉取最新的RDB快照回来恢复数据 + +5)如果是发现有重大的数据错误,比如某个小时上线的程序一下子将数据全部污染了,数据全错了,那么可以选择某个更早的时间点,对数据进行恢复 + +举个例子,12点上线了代码,发现代码有bug,导致代码生成的所有的缓存数据,写入redis,全部错了 + +找到一份11点的rdb的冷备,然后按照上面的步骤,去恢复到11点的数据,不就可以了吗 + +## 二、主从复制 + +### 1、redis不能支撑高并发的瓶颈 + +单机。 + +如果redis要支撑超过10万+的并发,那应该怎么做? + +单机的redis几乎不太可能说QPS超过10万+,除非一些特殊情况,比如你的机器性能特别好,配置特别高,物理机,维护做的特别好,而且你的整体的操作不是太复杂 + +**读写分离**,一般来说,对缓存,一般都是用来支撑读高并发的,写的请求是比较少的,可能写请求也就一秒钟几千,一两千 + +大量的请求都是读,一秒钟二十万次读 + +redis主从架构 -> 读写分离架构 -> 可支持水平扩展的读高并发架构 + +![1567234090841](assets/1567234090841.png) + +复制: 异步的复制。 + +**1、redis replication的核心机制** + +1)redis采用异步方式复制数据到slave节点,不过redis 2.8开始,slave node会周期性地确认自己每次复制的数据量 + +2)一个master node是可以配置多个slave node的 + +3)slave node也可以连接其他的slave node + +4)slave node做复制的时候,是不会block master node的正常工作的 + +5)slave node在做复制的时候,也不会block对自己的查询操作,它会用旧的数据集来提供服务; 但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了 + +6)slave node主要用来进行横向扩容,做读写分离,扩容的slave node可以提高读的吞吐量 + +slave,高可用性,有很大的关系 + +**2、master持久化对于主从架构的安全保障的意义** + +如果采用了主从架构,那么建议必须开启master node的持久化! + +不建议用slave node作为master node的数据热备,因为那样的话,如果你关掉master的持久化,可能在master宕机重启的时候数据是空的,然后可能一经过复制,salve node数据也丢了 + +master -> RDB和AOF都关闭了 -> 全部在内存中 + +master宕机,重启,是没有本地数据可以恢复的,然后就会直接认为自己IDE数据是空的 + +master就会将空的数据集同步到slave上去,所有slave的数据全部清空 + +100%的数据丢失 + +master节点,必须要使用持久化机制 + +第二个,master的各种备份方案,要不要做,万一说本地的所有文件丢失了; 从备份中挑选一份rdb去恢复master; 这样才能确保master启动的时候,是有数据的 + +即使采用了后续讲解的高可用机制,slave node可以自动接管master node,但是也可能sentinal还没有检测到master failure,master node就自动重启了,还是可能导致上面的所有slave node数据清空故障 \ No newline at end of file diff --git a/DB/Redis/assets/1555842247398.png b/DB/Redis/assets/1555842247398.png new file mode 100644 index 00000000..44097390 Binary files /dev/null and b/DB/Redis/assets/1555842247398.png differ diff --git a/DB/Redis/assets/1555842861644.png b/DB/Redis/assets/1555842861644.png new file mode 100644 index 00000000..f3403f08 Binary files /dev/null and b/DB/Redis/assets/1555842861644.png differ diff --git a/DB/Redis/assets/1555843318624.png b/DB/Redis/assets/1555843318624.png new file mode 100644 index 00000000..316b93b6 Binary files /dev/null and b/DB/Redis/assets/1555843318624.png differ diff --git a/DB/Redis/assets/1555843337361.png b/DB/Redis/assets/1555843337361.png new file mode 100644 index 00000000..57b0a37b Binary files /dev/null and b/DB/Redis/assets/1555843337361.png differ diff --git a/DB/Redis/assets/1565570699214.png b/DB/Redis/assets/1565570699214.png new file mode 100644 index 00000000..c0027050 Binary files /dev/null and b/DB/Redis/assets/1565570699214.png differ diff --git a/DB/Redis/assets/1567220619289.png b/DB/Redis/assets/1567220619289.png new file mode 100644 index 00000000..66521533 Binary files /dev/null and b/DB/Redis/assets/1567220619289.png differ diff --git a/DB/Redis/assets/1567227276975.png b/DB/Redis/assets/1567227276975.png new file mode 100644 index 00000000..57038c2d Binary files /dev/null and b/DB/Redis/assets/1567227276975.png differ diff --git a/DB/Redis/assets/1567229722195.png b/DB/Redis/assets/1567229722195.png new file mode 100644 index 00000000..7448b7c0 Binary files /dev/null and b/DB/Redis/assets/1567229722195.png differ diff --git a/DB/Redis/assets/1567234090841.png b/DB/Redis/assets/1567234090841.png new file mode 100644 index 00000000..f41656eb Binary files /dev/null and b/DB/Redis/assets/1567234090841.png differ diff --git a/DB/Redis/assets/924211-20170316221132260-1504932110.jpg b/DB/Redis/assets/924211-20170316221132260-1504932110.jpg new file mode 100644 index 00000000..fc7e2395 Binary files /dev/null and b/DB/Redis/assets/924211-20170316221132260-1504932110.jpg differ diff --git a/DB/Redis/assets/924211-20170316221236448-149448813.jpg b/DB/Redis/assets/924211-20170316221236448-149448813.jpg new file mode 100644 index 00000000..40c2ee66 Binary files /dev/null and b/DB/Redis/assets/924211-20170316221236448-149448813.jpg differ diff --git a/DB/Redis/assets/924211-20170316221603073-647542213.jpg b/DB/Redis/assets/924211-20170316221603073-647542213.jpg new file mode 100644 index 00000000..f0fdd361 Binary files /dev/null and b/DB/Redis/assets/924211-20170316221603073-647542213.jpg differ diff --git a/DB/Redis/assets/copycode.gif b/DB/Redis/assets/copycode.gif new file mode 100644 index 00000000..dc146865 Binary files /dev/null and b/DB/Redis/assets/copycode.gif differ diff --git a/DB/Redis/images/1_1.png b/DB/Redis/images/1_1.png new file mode 100644 index 00000000..9df9d88c Binary files /dev/null and b/DB/Redis/images/1_1.png differ diff --git a/DB/Redis/images/1_10.png b/DB/Redis/images/1_10.png new file mode 100644 index 00000000..846eaec5 Binary files /dev/null and b/DB/Redis/images/1_10.png differ diff --git a/DB/Redis/images/1_11.png b/DB/Redis/images/1_11.png new file mode 100644 index 00000000..f12e5225 Binary files /dev/null and b/DB/Redis/images/1_11.png differ diff --git a/DB/Redis/images/1_12.png b/DB/Redis/images/1_12.png new file mode 100644 index 00000000..2bf3c0a3 Binary files /dev/null and b/DB/Redis/images/1_12.png differ diff --git a/DB/Redis/images/1_13.png b/DB/Redis/images/1_13.png new file mode 100644 index 00000000..6375489a Binary files /dev/null and b/DB/Redis/images/1_13.png differ diff --git a/DB/Redis/images/1_14.png b/DB/Redis/images/1_14.png new file mode 100644 index 00000000..00e75e56 Binary files /dev/null and b/DB/Redis/images/1_14.png differ diff --git a/DB/Redis/images/1_15.png b/DB/Redis/images/1_15.png new file mode 100644 index 00000000..e8328e92 Binary files /dev/null and b/DB/Redis/images/1_15.png differ diff --git a/DB/Redis/images/1_16.png b/DB/Redis/images/1_16.png new file mode 100644 index 00000000..888eb78f Binary files /dev/null and b/DB/Redis/images/1_16.png differ diff --git a/DB/Redis/images/1_17.png b/DB/Redis/images/1_17.png new file mode 100644 index 00000000..eabdef86 Binary files /dev/null and b/DB/Redis/images/1_17.png differ diff --git a/DB/Redis/images/1_18.png b/DB/Redis/images/1_18.png new file mode 100644 index 00000000..1f42294d Binary files /dev/null and b/DB/Redis/images/1_18.png differ diff --git a/DB/Redis/images/1_19.png b/DB/Redis/images/1_19.png new file mode 100644 index 00000000..44c08c4f Binary files /dev/null and b/DB/Redis/images/1_19.png differ diff --git a/DB/Redis/images/1_2.png b/DB/Redis/images/1_2.png new file mode 100644 index 00000000..c772ddd5 Binary files /dev/null and b/DB/Redis/images/1_2.png differ diff --git a/DB/Redis/images/1_20.png b/DB/Redis/images/1_20.png new file mode 100644 index 00000000..8cfaa7af Binary files /dev/null and b/DB/Redis/images/1_20.png differ diff --git a/DB/Redis/images/1_21.png b/DB/Redis/images/1_21.png new file mode 100644 index 00000000..1562d200 Binary files /dev/null and b/DB/Redis/images/1_21.png differ diff --git a/DB/Redis/images/1_22.png b/DB/Redis/images/1_22.png new file mode 100644 index 00000000..558721e0 Binary files /dev/null and b/DB/Redis/images/1_22.png differ diff --git a/DB/Redis/images/1_23.png b/DB/Redis/images/1_23.png new file mode 100644 index 00000000..d338432f Binary files /dev/null and b/DB/Redis/images/1_23.png differ diff --git a/DB/Redis/images/1_24.png b/DB/Redis/images/1_24.png new file mode 100644 index 00000000..c4097c61 Binary files /dev/null and b/DB/Redis/images/1_24.png differ diff --git a/DB/Redis/images/1_25.png b/DB/Redis/images/1_25.png new file mode 100644 index 00000000..4af75f93 Binary files /dev/null and b/DB/Redis/images/1_25.png differ diff --git a/DB/Redis/images/1_26.png b/DB/Redis/images/1_26.png new file mode 100644 index 00000000..7d66fd5c Binary files /dev/null and b/DB/Redis/images/1_26.png differ diff --git a/DB/Redis/images/1_27.png b/DB/Redis/images/1_27.png new file mode 100644 index 00000000..d10189ee Binary files /dev/null and b/DB/Redis/images/1_27.png differ diff --git a/DB/Redis/images/1_28.png b/DB/Redis/images/1_28.png new file mode 100644 index 00000000..4d550003 Binary files /dev/null and b/DB/Redis/images/1_28.png differ diff --git a/DB/Redis/images/1_29.png b/DB/Redis/images/1_29.png new file mode 100644 index 00000000..74305b94 Binary files /dev/null and b/DB/Redis/images/1_29.png differ diff --git a/DB/Redis/images/1_3.png b/DB/Redis/images/1_3.png new file mode 100644 index 00000000..9871ddc4 Binary files /dev/null and b/DB/Redis/images/1_3.png differ diff --git a/DB/Redis/images/1_30.png b/DB/Redis/images/1_30.png new file mode 100644 index 00000000..195d1d0a Binary files /dev/null and b/DB/Redis/images/1_30.png differ diff --git a/DB/Redis/images/1_31.png b/DB/Redis/images/1_31.png new file mode 100644 index 00000000..05d67183 Binary files /dev/null and b/DB/Redis/images/1_31.png differ diff --git a/DB/Redis/images/1_32.png b/DB/Redis/images/1_32.png new file mode 100644 index 00000000..29fa5b99 Binary files /dev/null and b/DB/Redis/images/1_32.png differ diff --git a/DB/Redis/images/1_33.png b/DB/Redis/images/1_33.png new file mode 100644 index 00000000..8b5e7c39 Binary files /dev/null and b/DB/Redis/images/1_33.png differ diff --git a/DB/Redis/images/1_34.png b/DB/Redis/images/1_34.png new file mode 100644 index 00000000..620a2850 Binary files /dev/null and b/DB/Redis/images/1_34.png differ diff --git a/DB/Redis/images/1_35.png b/DB/Redis/images/1_35.png new file mode 100644 index 00000000..1b93867b Binary files /dev/null and b/DB/Redis/images/1_35.png differ diff --git a/DB/Redis/images/1_36.png b/DB/Redis/images/1_36.png new file mode 100644 index 00000000..e28e48e9 Binary files /dev/null and b/DB/Redis/images/1_36.png differ diff --git a/DB/Redis/images/1_37.png b/DB/Redis/images/1_37.png new file mode 100644 index 00000000..f9a5527f Binary files /dev/null and b/DB/Redis/images/1_37.png differ diff --git a/DB/Redis/images/1_38.png b/DB/Redis/images/1_38.png new file mode 100644 index 00000000..dcfd1022 Binary files /dev/null and b/DB/Redis/images/1_38.png differ diff --git a/DB/Redis/images/1_39.png b/DB/Redis/images/1_39.png new file mode 100644 index 00000000..90b5493a Binary files /dev/null and b/DB/Redis/images/1_39.png differ diff --git a/DB/Redis/images/1_4.png b/DB/Redis/images/1_4.png new file mode 100644 index 00000000..b6f2e879 Binary files /dev/null and b/DB/Redis/images/1_4.png differ diff --git a/DB/Redis/images/1_40.png b/DB/Redis/images/1_40.png new file mode 100644 index 00000000..d1218637 Binary files /dev/null and b/DB/Redis/images/1_40.png differ diff --git a/DB/Redis/images/1_41.png b/DB/Redis/images/1_41.png new file mode 100644 index 00000000..59772ec0 Binary files /dev/null and b/DB/Redis/images/1_41.png differ diff --git a/DB/Redis/images/1_42.png b/DB/Redis/images/1_42.png new file mode 100644 index 00000000..bddb8294 Binary files /dev/null and b/DB/Redis/images/1_42.png differ diff --git a/DB/Redis/images/1_43.png b/DB/Redis/images/1_43.png new file mode 100644 index 00000000..8536b89d Binary files /dev/null and b/DB/Redis/images/1_43.png differ diff --git a/DB/Redis/images/1_44.png b/DB/Redis/images/1_44.png new file mode 100644 index 00000000..df0e340f Binary files /dev/null and b/DB/Redis/images/1_44.png differ diff --git a/DB/Redis/images/1_45.png b/DB/Redis/images/1_45.png new file mode 100644 index 00000000..7575143f Binary files /dev/null and b/DB/Redis/images/1_45.png differ diff --git a/DB/Redis/images/1_46.png b/DB/Redis/images/1_46.png new file mode 100644 index 00000000..cd86d31f Binary files /dev/null and b/DB/Redis/images/1_46.png differ diff --git a/DB/Redis/images/1_47.png b/DB/Redis/images/1_47.png new file mode 100644 index 00000000..03f3899d Binary files /dev/null and b/DB/Redis/images/1_47.png differ diff --git a/DB/Redis/images/1_48.png b/DB/Redis/images/1_48.png new file mode 100644 index 00000000..b24b5460 Binary files /dev/null and b/DB/Redis/images/1_48.png differ diff --git a/DB/Redis/images/1_49.png b/DB/Redis/images/1_49.png new file mode 100644 index 00000000..6a3c6d4c Binary files /dev/null and b/DB/Redis/images/1_49.png differ diff --git a/DB/Redis/images/1_5.png b/DB/Redis/images/1_5.png new file mode 100644 index 00000000..23d97db2 Binary files /dev/null and b/DB/Redis/images/1_5.png differ diff --git a/DB/Redis/images/1_50.png b/DB/Redis/images/1_50.png new file mode 100644 index 00000000..ae1e29d0 Binary files /dev/null and b/DB/Redis/images/1_50.png differ diff --git a/DB/Redis/images/1_51.png b/DB/Redis/images/1_51.png new file mode 100644 index 00000000..d3176ebe Binary files /dev/null and b/DB/Redis/images/1_51.png differ diff --git a/DB/Redis/images/1_52.png b/DB/Redis/images/1_52.png new file mode 100644 index 00000000..c86587a9 Binary files /dev/null and b/DB/Redis/images/1_52.png differ diff --git a/DB/Redis/images/1_53.png b/DB/Redis/images/1_53.png new file mode 100644 index 00000000..c841f1fb Binary files /dev/null and b/DB/Redis/images/1_53.png differ diff --git a/DB/Redis/images/1_54.png b/DB/Redis/images/1_54.png new file mode 100644 index 00000000..1da453f9 Binary files /dev/null and b/DB/Redis/images/1_54.png differ diff --git a/DB/Redis/images/1_55.png b/DB/Redis/images/1_55.png new file mode 100644 index 00000000..a4f9b679 Binary files /dev/null and b/DB/Redis/images/1_55.png differ diff --git a/DB/Redis/images/1_56.png b/DB/Redis/images/1_56.png new file mode 100644 index 00000000..e2e9d843 Binary files /dev/null and b/DB/Redis/images/1_56.png differ diff --git a/DB/Redis/images/1_57.png b/DB/Redis/images/1_57.png new file mode 100644 index 00000000..1c088fa9 Binary files /dev/null and b/DB/Redis/images/1_57.png differ diff --git a/DB/Redis/images/1_6.png b/DB/Redis/images/1_6.png new file mode 100644 index 00000000..34d87ab5 Binary files /dev/null and b/DB/Redis/images/1_6.png differ diff --git a/DB/Redis/images/1_7.png b/DB/Redis/images/1_7.png new file mode 100644 index 00000000..da7cbe04 Binary files /dev/null and b/DB/Redis/images/1_7.png differ diff --git a/DB/Redis/images/1_8.png b/DB/Redis/images/1_8.png new file mode 100644 index 00000000..479f01b9 Binary files /dev/null and b/DB/Redis/images/1_8.png differ diff --git a/DB/Redis/images/1_9.png b/DB/Redis/images/1_9.png new file mode 100644 index 00000000..097aaaef Binary files /dev/null and b/DB/Redis/images/1_9.png differ diff --git a/DB/Redis/images/DeepinScreenshot_select-area_20190324120948.png b/DB/Redis/images/DeepinScreenshot_select-area_20190324120948.png new file mode 100644 index 00000000..4ec68b2e Binary files /dev/null and b/DB/Redis/images/DeepinScreenshot_select-area_20190324120948.png differ diff --git "a/DB/Redis/\345\270\270\350\247\201Redis\351\205\215\347\275\256.md" "b/DB/Redis/\345\270\270\350\247\201Redis\351\205\215\347\275\256.md" new file mode 100644 index 00000000..04856135 --- /dev/null +++ "b/DB/Redis/\345\270\270\350\247\201Redis\351\205\215\347\275\256.md" @@ -0,0 +1,121 @@ +## 常见Redis.conf的配置 + +* 1)、**Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程`daemonize no`**; + +* 2)、当Redis以守护进程方式运行时,Redis默认会把pid写入`/var/run/redis.pid`文件,可以通过pidfile指定 + + `pidfile /var/run/redis.pid` + +* 3)、指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字 port 6379 + +* 4)、绑定的主机地址 ,bind 127.0.0.1 + +* 5)、**当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能`timeout 300`**。 + +* 6)、**指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose** + + **loglevel verbose**。 + +* 7)、日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null, logfile stdout + +* 8)、设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id,databases 16 + +* 9)、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 + + `save ` + + Redis默认配置文件中提供了三个条件: + + save 900 1 + + save 300 10 + + save 60 10000 + + 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。 + +* 10)、指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大 + + rdbcompression yes + +* 11)、**指定本地数据库文件名,默认值为dump.rdb,`dbfilename dump.rdb`**。 + +* 12)、**指定本地数据库存放目录 ,`dir ./`**。 + +* 13)、设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步 + + `slaveof ` + +* 14)、当master服务设置了密码保护时,slav服务连接master的密码 + + `masterauth ` + +* 15)、设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过`AUTH `命令提供密码,默认关闭 + + `requirepass foobared` + +* 16)、设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息 + + `maxclients 128` + +* 17)、指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区 + + `maxmemory ` + +* 18)、指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no + + `appendonly no` + +* 19)、指定更新日志文件名,默认为appendonly.aof + + `appendfilename appendonly.aof` + +* 20)、指定更新日志条件,共有3个可选值: + + no:表示等操作系统进行数据缓存同步到磁盘(快) + + always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全) + + everysec:表示每秒同步一次(折衷,默认值) + + appendfsync everysec + +* 21)、指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制) + + vm-enabled no + +* 22)、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享 + + `vm-swap-file /tmp/redis.swap` + +* 23)、将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0 + + `vm-max-memory 0` + +* 24)、Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值 + + vm-page-size 32 +* 25)、设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。 + + vm-pages 134217728 + +* 26)、设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4 + + vm-max-threads 4 + +* 27)、设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启 + + glueoutputbuf yes + +* 28)、指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法 + + hash-max-zipmap-entries 64 + + hash-max-zipmap-value 512 +* 29)、指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍) + + activerehashing yes + +* 30)、指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件 + + `include /path/to/local.conf` \ No newline at end of file diff --git a/DB/SQL/assets/1555302674155.png b/DB/SQL/assets/1555302674155.png new file mode 100644 index 00000000..02acee25 Binary files /dev/null and b/DB/SQL/assets/1555302674155.png differ diff --git a/DB/SQL/assets/1555302709214.png b/DB/SQL/assets/1555302709214.png new file mode 100644 index 00000000..13118cd9 Binary files /dev/null and b/DB/SQL/assets/1555302709214.png differ diff --git a/DB/SQL/assets/1555302735640.png b/DB/SQL/assets/1555302735640.png new file mode 100644 index 00000000..cec593aa Binary files /dev/null and b/DB/SQL/assets/1555302735640.png differ diff --git a/DB/SQL/assets/1555302764648.png b/DB/SQL/assets/1555302764648.png new file mode 100644 index 00000000..0acc1e8c Binary files /dev/null and b/DB/SQL/assets/1555302764648.png differ diff --git a/DB/SQL/assets/1555302814128.png b/DB/SQL/assets/1555302814128.png new file mode 100644 index 00000000..f11bd904 Binary files /dev/null and b/DB/SQL/assets/1555302814128.png differ diff --git a/DB/SQL/assets/1555302844626.png b/DB/SQL/assets/1555302844626.png new file mode 100644 index 00000000..2d77eae9 Binary files /dev/null and b/DB/SQL/assets/1555302844626.png differ diff --git a/DB/SQL/assets/1555302881604.png b/DB/SQL/assets/1555302881604.png new file mode 100644 index 00000000..65624e68 Binary files /dev/null and b/DB/SQL/assets/1555302881604.png differ diff --git a/DB/SQL/assets/1555302904870.png b/DB/SQL/assets/1555302904870.png new file mode 100644 index 00000000..8fa0c64b Binary files /dev/null and b/DB/SQL/assets/1555302904870.png differ diff --git a/DB/SQL/images/1_1.png b/DB/SQL/images/1_1.png new file mode 100644 index 00000000..d3ceb5b9 Binary files /dev/null and b/DB/SQL/images/1_1.png differ diff --git a/DB/SQL/images/1_10.png b/DB/SQL/images/1_10.png new file mode 100644 index 00000000..6f15dc2b Binary files /dev/null and b/DB/SQL/images/1_10.png differ diff --git a/DB/SQL/images/1_2.png b/DB/SQL/images/1_2.png new file mode 100644 index 00000000..9fb56135 Binary files /dev/null and b/DB/SQL/images/1_2.png differ diff --git a/DB/SQL/images/1_3.png b/DB/SQL/images/1_3.png new file mode 100644 index 00000000..8514178d Binary files /dev/null and b/DB/SQL/images/1_3.png differ diff --git a/DB/SQL/images/1_4.png b/DB/SQL/images/1_4.png new file mode 100644 index 00000000..07c5b7b9 Binary files /dev/null and b/DB/SQL/images/1_4.png differ diff --git a/DB/SQL/images/1_5.png b/DB/SQL/images/1_5.png new file mode 100644 index 00000000..b05bd112 Binary files /dev/null and b/DB/SQL/images/1_5.png differ diff --git a/DB/SQL/images/1_6.png b/DB/SQL/images/1_6.png new file mode 100644 index 00000000..6b8d23bd Binary files /dev/null and b/DB/SQL/images/1_6.png differ diff --git a/DB/SQL/images/1_7.png b/DB/SQL/images/1_7.png new file mode 100644 index 00000000..e9a2851e Binary files /dev/null and b/DB/SQL/images/1_7.png differ diff --git a/DB/SQL/images/1_8.png b/DB/SQL/images/1_8.png new file mode 100644 index 00000000..77c6c1e8 Binary files /dev/null and b/DB/SQL/images/1_8.png differ diff --git a/DB/SQL/images/1_9.png b/DB/SQL/images/1_9.png new file mode 100644 index 00000000..3d347537 Binary files /dev/null and b/DB/SQL/images/1_9.png differ diff --git "a/DB/SQL/\344\270\203\347\247\215inner join.md" "b/DB/SQL/\344\270\203\347\247\215inner join.md" new file mode 100644 index 00000000..3b048e5d --- /dev/null +++ "b/DB/SQL/\344\270\203\347\247\215inner join.md" @@ -0,0 +1,37 @@ +## 七种inner join + +先给出案例表: + +

+ +七种查询总结图: + +![1_2.png](images/1_2.png) + +第一种`inner join`: + +![1_4.png](images/1_4.png) + +第二种`left join`: + +![1_5.png](images/1_5.png) + +第三种`right join`: + +![1_6.png](images/1_6.png) + +第四种`left join where b.id is null`: + +![1_7.png](images/1_7.png) + +第五种`right join where a.deptId is null`: + +![1_8.png](images/1_8.png) + +第六种`FULL OUT JOIN`: (使用`union`实现并集和去重) + +![1_9.png](images/1_9.png) + +第七种: `a、b两者的独有`: + +![1_10.png](images/1_10.png) \ No newline at end of file diff --git "a/DB/SQL/\347\211\233\345\256\242SQL(1 - 6).md" "b/DB/SQL/\347\211\233\345\256\242SQL(1 - 6).md" new file mode 100644 index 00000000..26957abf --- /dev/null +++ "b/DB/SQL/\347\211\233\345\256\242SQL(1 - 6).md" @@ -0,0 +1,174 @@ +# 牛客SQL(1 - 6) +* 查找最晚入职员工的所有信息 +* 查找入职员工时间排名倒数第三的员工所有信息 +* 查找当前薪水详情以及部门编号`dept_no` +* 查找所有已经分配部门的员工的`last_name`和`first_name` +* 查找所有员工的`last_name`和`first_name`以及对应部门编号`dept_no` +* 查找所有员工入职时候的薪水情况 +*** +## 一、查找最晚入职员工的所有信息 +#### [题目链接](https://www.nowcoder.com/practice/218ae58dfdcd4af195fff264e062138f?tpId=82&tqId=29753&tPage=1&rp=&ru=/ta/sql&qru=/ta/sql/question-ranking) + +> https://www.nowcoder.com/practice/218ae58dfdcd4af195fff264e062138f?tpId=82&tqId=29753&tPage=1&rp=&ru=/ta/sql&qru=/ta/sql/question-ranking + +#### 题目 +![1555302674155](assets/1555302674155.png) +#### 解析 +* 思路一: 使用嵌套查询(子查询),内层查询先查询出来表中的最大的`hire_date`,外层再查一次; +* 思路二: 按照`hire_date`降序排列,然后使用`limit`关键字取出第一项即可; + +```sql +select * from employees where hire_date = (select max(hire_date) from employees); + +select * from employees order by hire_date desc limit 0,1; + +select * from employees order by hire_date desc limit 1; +``` +注意limit关键字的使用: +* `LIMIT m,n` : 表示从第`m+1`条开始,取`n`条数据; +* `LIMIT n `: 表示从第`0`条开始,取`n`条数据,是`limit(0,n)`的缩写。 + +*** +## 二、查找入职员工时间排名倒数第三的员工所有信息 +#### [题目链接](https://www.nowcoder.com/practice/ec1ca44c62c14ceb990c3c40def1ec6c?tpId=82&tqId=29754&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/ec1ca44c62c14ceb990c3c40def1ec6c?tpId=82&tqId=29754&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking + +#### 题目 +![1555302709214](assets/1555302709214.png) +#### 解析 +* 思路一: 直接使用`desc hire_date` 降序排列,然后使用`limit`来选出倒数第三个; +* 思路二: 上面的思路如果`hire_date` 有重复的话,就不能选到真正的名词,所以可以使用`distinct`关键字选出不重复的`hire_date`,然后排序,这个查询嵌套在内查询中; + +```sql +-- 简单的想法 +select * from employees order by hire_date desc limit 2,1; + + +-- 严密的思路 +select * from employees +where hire_date = + -- 先筛选出不重复的 hrie_date 然后再排序,然后再选择倒数第三的  + (select distinct hire_date from employees order by hire_date desc limit 2,1); + +``` +*** +## 三、查找当前薪水详情以及部门编号`dept_no` +#### [题目链接](https://www.nowcoder.com/practice/c63c5b54d86e4c6d880e4834bfd70c3b?tpId=82&tqId=29755&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/c63c5b54d86e4c6d880e4834bfd70c3b?tpId=82&tqId=29755&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking + +#### 题目 +![1555302735640](assets/1555302735640.png) +#### 解析 +* 思路一: 使用多表查询,其中使用`where` 条件来连接两个表; +* 思路二: 使用`inner join`来连接查询,`on` 后面带上条件; + +注意这里使用部门表作为主表不能通过,可能因为两个表的逻辑关系,题目要求是薪水情况以及部门编号; +以下三种方式都可以: +```sql + +-- 不使用join +select tb_s.*,tb_d.dept_no +from salaries as tb_s,dept_manager as tb_d +where tb_s.emp_no = tb_d.emp_no +and tb_s.to_date = '9999-01-01' +and tb_d.to_date = '9999-01-01'; + +-- 使用join +select tb_s.*,tb_d.dept_no +from salaries as tb_s inner join dept_manager as tb_d +on tb_s.emp_no = tb_d.emp_no +and tb_s.to_date = '9999-01-01' +and tb_d.to_date = '9999-01-01'; + +select tb_s.*,tb_d.dept_no +from salaries as tb_s inner join dept_manager as tb_d +on tb_s.emp_no = tb_d.emp_no +where tb_s.to_date = '9999-01-01' and tb_d.to_date = '9999-01-01'; +``` +*** +## 四、查找所有已经分配部门的员工的`last_name`和`first_name` +#### [题目链接](https://www.nowcoder.com/practice/6d35b1cd593545ab985a68cd86f28671?tpId=82&tqId=29756&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/6d35b1cd593545ab985a68cd86f28671?tpId=82&tqId=29756&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking + +#### 题目 +![1555302764648](assets/1555302764648.png) +#### 解析 +* 思路一: 使用等值连接,可以使用`where` 连接,也可以使用`join`; +* 思路二: 直接使用自然连接 `natural join`; + + +注意几种连接的区别: + +![1555302814128](assets/1555302814128.png) + +示意图: + +![1555302844626](assets/1555302844626.png) + +代码: + +```sql +-- 等值连接 where +select tb_e.last_name,tb_e.first_name,tb_d.dept_no +from employees as tb_e,dept_emp as tb_d +where tb_e.emp_no = tb_d.emp_no; + +-- 等值连接 join 连接查询 +select tb_e.last_name,tb_e.first_name,tb_d.dept_no +from employees as tb_e inner join dept_emp as tb_d +on tb_e.emp_no = tb_d.emp_no; + +-- 使用自然连接 (两个表的同一列属性的值相同) +select tb_e.last_name,tb_e.first_name,tb_d.dept_no +from employees tb_e natural join dept_emp tb_d; +``` +*** +## 五、查找所有员工的`last_name`和`first_name`以及对应部门编号`dept_no` +#### [题目连接](https://www.nowcoder.com/practice/dbfafafb2ee2482aa390645abd4463bf?tpId=82&tqId=29757&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/dbfafafb2ee2482aa390645abd4463bf?tpId=82&tqId=29757&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking + +#### 题目 +![1555302881604](assets/1555302881604.png) +#### 解析 +* 这里使用左连接查询,左边的全,右边的为null的就显示null; + +注意`JOIN`的区别: +* `INNER JOIN `两边表同时有对应的数据,即任何一边缺失数据就不显示; +* `LEFT JOIN` 会读取左边数据表的全部数据,即便右边表无对应数据; +* `RIGHT JOIN` 会读取右边数据表的全部数据,即便左边表无对应数据; + +注意`on`与`where`区别,两个表连接时用`on`,在使用`LEFT JOIN`时,`on`和`where`条件的区别如下: +* `on`条件是在生成临时表时使用的条件,它不管`on`中的条件是否为真,都会返回左边表中的记录。 +* `where`条件是在临时表生成好后,再对临时表进行过滤的条件。这时已经没有`LEFT JOIN`的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。 + +更多关于`ON` 和 `WHERE`的知识看[这篇博客](https://www.cnblogs.com/guanshan/articles/guan062.html)。 + +```sql +select tb_e.last_name,tb_e.first_name,tb_d.dept_no +from employees tb_e left join dept_emp as tb_d +on tb_e.emp_no = tb_d.emp_no; +``` +*** +## 六、查找所有员工入职时候的薪水情况 +#### [题目链接](https://www.nowcoder.com/practice/23142e7a23e4480781a3b978b5e0f33a?tpId=82&tqId=29758&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking) + +> https://www.nowcoder.com/practice/23142e7a23e4480781a3b978b5e0f33a?tpId=82&tqId=29758&tPage=1&rp=&ru=%2Fta%2Fsql&qru=%2Fta%2Fsql%2Fquestion-ranking + +#### 题目 +![1555302904870](assets/1555302904870.png) +#### 解析 +* 测试数据中,`salaries.emp_no`不唯一(因为号码为`emp_no`的员工会有多次涨薪的可能,所以在`salaries`中对应的记录不止一条),`employees.emp_no`唯一,即`salaries`的数据会多于`employees`,因此需先找到`tb_e.emp_no`在`tb_s`表中对应的记录`salaries.emp_no`,则有限制条件`tb_e.emp_no = tb_s.emp_no`; +* **根据题意注意到`salaries.from_date` 和`employees.hire_date`的值应该要相等,因此有限制条件` tb_e.hire_date = tb_s.from_date;`** + +```sql +select tb_e.emp_no,tb_s.salary +from employees as tb_e inner join salaries as tb_s +on tb_e.emp_no = tb_s.emp_no +and tb_e.hire_date = tb_s.from_date -- 注意这个员工入职时候的薪水 +order by tb_e.emp_no desc; +``` + diff --git "a/DB/SQL/\347\211\233\345\256\242SQL(7 - 12).md" "b/DB/SQL/\347\211\233\345\256\242SQL(7 - 12).md" new file mode 100644 index 00000000..45f0393f --- /dev/null +++ "b/DB/SQL/\347\211\233\345\256\242SQL(7 - 12).md" @@ -0,0 +1,4 @@ +# 牛客SQL(7 - 12) + + + diff --git a/DB/assets/1555855178659.png b/DB/assets/1555855178659.png new file mode 100644 index 00000000..c88ff6c5 Binary files /dev/null and b/DB/assets/1555855178659.png differ diff --git a/DB/assets/1555855223353.png b/DB/assets/1555855223353.png new file mode 100644 index 00000000..43ceace9 Binary files /dev/null and b/DB/assets/1555855223353.png differ diff --git a/DB/assets/1555855277676.png b/DB/assets/1555855277676.png new file mode 100644 index 00000000..b20ff3df Binary files /dev/null and b/DB/assets/1555855277676.png differ diff --git a/DB/assets/1555855288486.png b/DB/assets/1555855288486.png new file mode 100644 index 00000000..b20ff3df Binary files /dev/null and b/DB/assets/1555855288486.png differ diff --git a/DB/assets/1555855455080.png b/DB/assets/1555855455080.png new file mode 100644 index 00000000..90591503 Binary files /dev/null and b/DB/assets/1555855455080.png differ diff --git a/DB/assets/1555855491884.png b/DB/assets/1555855491884.png new file mode 100644 index 00000000..bc8316f0 Binary files /dev/null and b/DB/assets/1555855491884.png differ diff --git a/DB/assets/1555855501156.png b/DB/assets/1555855501156.png new file mode 100644 index 00000000..c5dd2a95 Binary files /dev/null and b/DB/assets/1555855501156.png differ diff --git "a/DB/\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\347\220\206\350\256\272.md" "b/DB/\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\347\220\206\350\256\272.md" new file mode 100644 index 00000000..6b7174ee --- /dev/null +++ "b/DB/\345\205\263\347\263\273\346\225\260\346\215\256\345\272\223\347\220\206\350\256\272.md" @@ -0,0 +1,171 @@ +# 关系数据库设计理论 + +转: https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F%E5%8E%9F%E7%90%86.md#%E5%87%BD%E6%95%B0%E4%BE%9D%E8%B5%96 + +## 函数依赖 + +记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。 + +如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。 + +**对于 A->B,如果能找到 A 的真子集 A',使得 A'-> B,那么 A->B 就是部分函数依赖**,否则就是完全函数依赖。 + +对于 A->B,B->C,则 A->C 是一个传递函数依赖。 + +## 异常 + +以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade,键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。 + +| Sno | Sname | Sdept | Mname | Cname | Grade | +| ---- | ------ | ------ | ------ | ------ | ----- | +| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | +| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | +| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | +| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | + +不符合范式的关系,会产生很多异常,主要有以下四种异常: + +- **冗余数据**:例如 `学生-2` 出现了两次。 +- 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。 +- 删除异常:删除一个信息,那么也会丢失其它信息。例如删除了 `课程-1` 需要删除第一行和第三行,那么 `学生-1` 的信息就会丢失。 +- 插入异常:例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 + +## 范式 + +范式理论是为了解决以上提到四种异常。 + +高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。 + +

+> 范式 +> * 1NF : 符合1NF的关系中的每个属性都不可再分 +> * 2NF: 属性完全依赖于主键 [ **消除部分子函数依赖**] +> * 3NF:属性不依赖于其它非主属性[ **消除传递依赖**] +> * BCNF:在1NF基础上,任何非主属性不能对主键子集依赖[在 3NF基础上消除对主码子集的依赖] +> * 4NF:要求把同一表内的多对多关系删除。 +> * 5NF:从最终结构重新建立原始结构 + + + +### 1. 第一范式 (1NF) + +属性不可分。 + +### 2. 第二范式 (2NF) + +每个非主属性**完全函数依赖**于键码。 + +可以通过分解来满足。 + +**分解前** + +| Sno | Sname | Sdept | Mname | Cname | Grade | +| ---- | ------ | ------ | ------ | ------ | ----- | +| 1 | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90 | +| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80 | +| 2 | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100 | +| 3 | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95 | + +以上学生课程关系中,`{Sno, Cname} `为键码,有如下函数依赖: + +- `Sno -> Sname, Sdept`; +- `Sdept -> Mname`; +- `Sno, Cname-> Grade`; + +Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。 + +Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。 + +**分解后** + +关系-1 + +| Sno | Sname | Sdept | Mname | +| ---- | ------ | ------ | ------ | +| 1 | 学生-1 | 学院-1 | 院长-1 | +| 2 | 学生-2 | 学院-2 | 院长-2 | +| 3 | 学生-3 | 学院-2 | 院长-2 | + +有以下函数依赖: + +- `Sno -> Sname, Sdept ` +- `Sdept -> Mname` + +关系-2 + +| Sno | Cname | Grade | +| ---- | ------ | ----- | +| 1 | 课程-1 | 90 | +| 2 | 课程-2 | 80 | +| 2 | 课程-1 | 100 | +| 3 | 课程-2 | 95 | + +有以下函数依赖: + +- `Sno, Cname -> Grade` + +### 3. 第三范式 (3NF) + +**非主属性不传递函数依赖于键码**。 + +上面的 关系-1 中存在以下传递函数依赖: + +- `Sno -> Sdept -> Mname` + +可以进行以下分解: + +关系-11 + +| Sno | Sname | Sdept | +| ---- | ------ | ------ | +| 1 | 学生-1 | 学院-1 | +| 2 | 学生-2 | 学院-2 | +| 3 | 学生-3 | 学院-2 | + +关系-12 + +| Sdept | Mname | +| ------ | ------ | +| 学院-1 | 院长-1 | +| 学院-2 | 院长-2 | + +# 八、ER 图 + +Entity-Relationship,有三个组成部分:实体、属性、联系。 + +用来进行关系型数据库系统的概念设计。 + +## 实体的三种联系 + +包含一对一,一对多,多对多三种。 + +- 如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B; +- 如果是一对一,画两个带箭头的线段; +- 如果是多对多,画两个不带箭头的线段。 + +下图的 Course 和 Student 是一对多的关系。 + +

+## 表示出现多次的关系 + +一个实体在联系出现几次,就要用几条线连接。 + +下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。 + +

+## 联系的多向性 + +虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。 + +

+ +一般只使用二元联系,可以把多元联系转换为二元联系。 + +![1555855491884](assets/1555855491884.png) + + +## 表示子类 + +用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。 + +

diff --git "a/Frame/ElasticSearch/ElasticSearch-1-\347\264\242\345\274\225\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/Frame/ElasticSearch/ElasticSearch-1-\347\264\242\345\274\225\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 00000000..303f47ca --- /dev/null +++ "b/Frame/ElasticSearch/ElasticSearch-1-\347\264\242\345\274\225\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,253 @@ +# ElasticSearch + +转载: https://www.cnblogs.com/dreamroute/p/8484457.html + +## 介绍 + +Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作: + +- 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。 +- 实时分析的分布式搜索引擎。 +- 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。 + +### 基本概念 + +先说Elasticsearch的文件存储,Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式,比如下面这条用户数据: + +```json +{ + "name" : "John", + "sex" : "Male", + "age" : 25, + "birthDate": "1990/05/01", + "about" : "I love to go rock climbing", + "interests": [ "sports", "music" ] +} +``` + +用Mysql这样的数据库存储就会容易想到建立一张User表,有balabala的字段等,在Elasticsearch里这就是一个*文档*,当然这个文档会属于一个User的*类型*,各种各样的类型存在于一个*索引*当中。这里有一份简易的将Elasticsearch和关系型数据术语对照表: + +```json +关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns) + +Elasticsearch ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields) +``` + +一个 Elasticsearch 集群可以包含多个索引(数据库),也就是说其中包含了很多类型(表)。这些类型中包含了很多的文档(行),然后每个文档中又包含了很多的字段(列)。Elasticsearch的交互,可以使用Java API,也可以直接使用HTTP的Restful API方式,比如我们打算插入一条记录,可以简单发送一个HTTP的请求: + +```json +PUT /megacorp/employee/1 +{ + "name" : "John", + "sex" : "Male", + "age" : 25, + "about" : "I love to go rock climbing", + "interests": [ "sports", "music" ] +} +``` + +更新,查询也是类似这样的操作,具体操作手册可以参见[Elasticsearch权威指南](http://www.learnes.net/data/README.html) + +------ + +## 索引 + +Elasticsearch最关键的就是提供强大的索引能力了,其实InfoQ的这篇[时间序列数据库的秘密(2)——索引](http://www.infoq.com/cn/articles/database-timestamp-02?utm_source=infoq&utm_medium=related_content_link&utm_campaign=relatedContent_articles_clk)写的非常好,我这里也是围绕这篇结合自己的理解进一步梳理下,也希望可以帮助大家更好的理解这篇文章。 + +Elasticsearch索引的精髓: + +> 一切设计都是为了提高搜索的性能 + +另一层意思:为了提高搜索的性能,难免会牺牲某些其他方面,比如插入/更新,否则其他数据库不用混了。前面看到往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,比如上面例子中的*name, sex, age, about, interests*,那么在插入这些数据到Elasticsearch的同时,Elasticsearch还默默[1](http://blog.pengqiuyuan.com/ji-chu-jie-shao-ji-suo-yin-yuan-li-fen-xi/#fn:1)的为这些字段建立索引--倒排索引,因为Elasticsearch最核心功能是搜索。 + +### Elasticsearch是如何做到快速索引的 + +InfoQ那篇文章里说Elasticsearch使用的倒排索引比关系型数据库的B-Tree索引快,为什么呢? + +#### 什么是B-Tree索引? + +上大学读书时老师教过我们,二叉树查找效率是logN,同时插入新的节点不必移动全部节点,所以用树型结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,再结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这样的数据结构: + +![1566289291258](assets/1566289291258.png) + +为了提高查询的效率,减少磁盘寻道次数,将多个值作为一个数组通过连续区间存放,一次寻道读取多个数据,同时也降低树的高度。 + +#### 什么是倒排索引? + +![1566289299841](assets/1566289299841.png) + +继续上面的例子,假设有这么几条数据(为了简单,去掉about, interests这两个field): + +```json +| ID | Name | Age | Sex | +| -- |:------------:| -----:| -----:| +| 1 | Kate | 24 | Female +| 2 | John | 24 | Male +| 3 | Bill | 29 | Male +``` + +ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下: + +**Name:** + +```json +| Term | Posting List | +| -- |:----:| +| Kate | 1 | +| John | 2 | +| Bill | 3 | +``` + +**Age:** + +``` +| Term | Posting List | +| -- |:----:| +| 24 | [1,2] | +| 29 | 3 | +``` + +**Sex:** + +``` +| Term | Posting List | +| -- |:----:| +| Female | 1 | +| Male | [2,3] | +``` + +##### Posting List + +Elasticsearch分别为每个field都建立了一个倒排索引,Kate, John, 24, Female这些叫term,而[1,2]就是**Posting List**。Posting list就是一个int的数组,存储了所有符合某个term的文档id。 + +看到这里,不要认为就结束了,精彩的部分才刚开始... + +通过posting list这种索引方式似乎可以很快进行查找,比如要找age=24的同学,爱回答问题的小明马上就举手回答:我知道,id是1,2的同学。但是,如果这里有上千万的记录呢?如果是想通过name来查找呢? + +##### Term Dictionary + +Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是**Term Dictionary**。现在再看起来,似乎和传统数据库通过B-Tree的方式类似啊,为什么说比B-Tree的查询快呢? + +##### Term Index + +B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了**Term Index**,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树: + +![1566289323552](assets/1566289323552.png) + +这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。 + +![1566289331810](assets/1566289331810.png) + +所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。 + +这时候爱提问的小明又举手了:"那个FST是神马东东啊?" + +一看就知道小明是一个上大学读书的时候跟我一样不认真听课的孩子,数据结构老师一定讲过什么是FST。但没办法,我也忘了,这里再补下课: + +> FSTs are finite-state machines that **map** a **term (byte sequence)** to an arbitrary **output**. + +假设我们现在要将mop, moth, pop, star, stop and top(term index里的term前缀)映射到序号:0,1,2,3,4,5(term dictionary的block位置)。最简单的做法就是定义个Map,大家找到自己的位置对应入座就好了,但从内存占用少的角度想想,有没有更优的办法呢?答案就是:**FST**([理论依据在此,但我相信99%的人不会认真看完的](http://www.cs.nyu.edu/~mohri/pub/fla.pdf)) + +![1566289346225](assets/1566289346225.png) + +⭕️表示一种状态 + +-->表示状态的变化过程,上面的字母/数字表示状态变化和权重 + +将单词分成单个字母通过⭕️和-->表示出来,0权重不显示。如果⭕️后面出现分支,就标记权重,最后整条路径上的权重加起来就是这个单词对应的序号。 + +> FSTs are finite-state machines that map a term (**byte sequence**) to an arbitrary output. + +FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源。 + +后面的更精彩,看累了的同学可以喝杯咖啡…… + +------ + +#### 压缩技巧 + +Elasticsearch里除了上面说到用FST压缩term index外,对posting list也有压缩技巧。 +小明喝完咖啡又举手了:"posting list不是已经只存储文档id了吗?还需要压缩?" + +嗯,我们再看回最开始的例子,如果Elasticsearch需要对同学的性别进行索引(这时传统关系型数据库已经哭晕在厕所……),会怎样?如果有上千万个同学,而世界上只有男/女这样两个性别,每个posting list都会有至少百万个文档id。 Elasticsearch是如何有效的对这些文档id压缩的呢? + +##### Frame Of Reference + +> 增量编码压缩,将大数变小数,按字节存储 + +首先,Elasticsearch要求posting list是有序的(为了提高搜索的性能,再任性的要求也得满足),这样做的一个好处是方便压缩,看下面这个图例: + +![1566289358967](assets/1566289358967.png) + +如果数学不是体育老师教的话,还是比较容易看出来这种压缩技巧的。 + +原理就是通过增量,将原来的大数变成小数仅存储增量值,再精打细算按bit排好队,最后通过字节存储,而不是大大咧咧的尽管是2也是用int(4个字节)来存储。 + +##### Roaring bitmaps + +说到Roaring bitmaps,就必须先从bitmap说起。Bitmap是一种数据结构,假设有某个posting list: + +[1,3,4,7,10] + +对应的bitmap就是: + +[1,0,1,1,0,0,1,0,0,1] + +非常直观,用0/1表示某个值是否存在,比如10这个值就对应第10位,对应的bit值是1,这样用一个字节就可以代表8个文档id,旧版本(5.0之前)的Lucene就是用这样的方式来压缩的,但这样的压缩方式仍然不够高效,如果有1亿个文档,那么需要12.5MB的存储空间,这仅仅是对应一个索引字段(我们往往会有很多个索引字段)。于是有人想出了Roaring bitmaps这样更高效的数据结构。 + +Bitmap的缺点是存储空间随着文档个数线性增长,Roaring bitmaps需要打破这个魔咒就一定要用到某些指数特性: + +将posting list按照65535为界限分块,比如第一块所包含的文档id范围在0~65535之间,第二块的id范围是65536~131071,以此类推。再用<商,余数>的组合表示每一组id,这样每组里的id范围都在0~65535内了,剩下的就好办了,既然每组id不会变得无限大,那么我们就可以通过最有效的方式对这里的id存储。 + +![1566289369290](assets/1566289369290.png) + +细心的小明这时候又举手了:"为什么是以65535为界限?" + +程序员的世界里除了1024外,65535也是一个经典值,因为它=2^16-1,正好是用2个字节能表示的最大数,一个short的存储单位,注意到上图里的最后一行“If a block has more than 4096 values, encode as a bit set, and otherwise as a simple array using 2 bytes per value”,如果是大块,用节省点用bitset存,小块就豪爽点,2个字节我也不计较了,用一个short[]存着方便。 + +那为什么用4096来区分大块还是小块呢? + +个人理解:都说程序员的世界是二进制的,4096*2bytes = 8192bytes < 1KB, 磁盘一次寻道可以顺序把一个小块的内容都读出来,再大一位就超过1KB了,需要两次读。 + +------ + +#### 联合索引 + +上面说了半天都是单field索引,如果多个field索引的联合查询,倒排索引如何满足快速查询的要求呢? + +- 利用跳表(Skip list)的数据结构快速做“与”运算,或者 +- 利用上面提到的bitset按位“与” + +先看看跳表的数据结构: + +![1566289378676](assets/1566289378676.png) + +将一个有序链表level0,挑出其中几个元素到level1及level2,每个level越往上,选出来的指针元素越少,查找时依次从高level往低查找,比如55,先找到level2的31,再找到level1的47,最后找到55,一共3次查找,查找效率和2叉树的效率相当,但也是用了一定的空间冗余来换取的。 + +假设有下面三个posting list需要联合索引: + +![1566289387855](assets/1566289387855.png) + +如果使用跳表,对最短的posting list中的每个id,逐个在另外两个posting list中查找看是否存在,最后得到交集的结果。 + +如果使用bitset,就很直观了,直接按位与,得到的结果就是最后的交集。 + +------ + +### 总结和思考 + +Elasticsearch的索引思路: + +> 将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数(同时也利用磁盘顺序读特性),结合各种奇技淫巧的压缩算法,用及其苛刻的态度使用内存。 + +所以,对于使用Elasticsearch进行索引时需要注意: + +- 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的 +- 同样的道理,对于String类型的字段,不需要analysis的也需要明确定义出来,因为默认也是会analysis的 +- 选择有规律的ID很重要,随机性太大的ID(比如java的UUID)不利于查询 + +关于最后一点,个人认为有多个因素: + +其中一个(也许不是最重要的)因素: 上面看到的压缩算法,都是对Posting list里的大量ID进行压缩的,那如果ID是顺序的,或者是有公共前缀等具有一定规律性的ID,压缩比会比较高; + +另外一个因素: 可能是最影响查询性能的,应该是最后通过Posting list里的ID到磁盘中查找Document信息的那步,因为Elasticsearch是分Segment存储的,根据ID这个大范围的Term定位到Segment的效率直接影响了最后查询的性能,如果ID是有规律的,可以快速跳过不包含该ID的Segment,从而减少不必要的磁盘读次数,具体可以参考这篇[如何选择一个高效的全局ID方案](http://blog.mikemccandless.com/2014/05/choosing-fast-unique-identifier-uuid.html)(评论也很精彩) \ No newline at end of file diff --git "a/Frame/ElasticSearch/ElasticSearch-2-\345\200\222\346\216\222\347\264\242\345\274\225.md" "b/Frame/ElasticSearch/ElasticSearch-2-\345\200\222\346\216\222\347\264\242\345\274\225.md" new file mode 100644 index 00000000..a5c3a6bf --- /dev/null +++ "b/Frame/ElasticSearch/ElasticSearch-2-\345\200\222\346\216\222\347\264\242\345\274\225.md" @@ -0,0 +1,18 @@ +# 倒排索引 + +b站有一个小视频解释: https://www.bilibili.com/video/av48419102 + +倒排索引的英文是Inverted Index。比如有一个文档列表,每个文档都会有唯一的ID,我们会以这个ID作为索引去获取文档的具体信息。但是一般我们在查找一个文档的时候并不知道其ID是什么,只知道大概想搜索什么内容,所以我们输入一些关键字,和搜索引擎一样进行搜索。 + +这时,我们把文档中的关键字做为索引,而不是ID,比如标记“中国”这个词在哪些文档中出现过,给这些关腱字做索引,可能通过“中国”这个词获取了1、5、11、24这几个ID的文档,再根据ID去获取文档:或者直接根据关键字获取文档。 + +我们在进行文章搜索时,希望搜索内容优先匹配标题,其次匹配文章内容,也就是相关度。我们还可以对两种内容分别索引,再根据所谓的相关度进行显示。这时我们建立的这个索引是倒排索引。 + +举个例子 + +![1566390474088](assets/1566390474088.png) + +![1566390608100](assets/1566390608100.png) + +可以发现,每个词汇都对应一些文档。有了这个索引表,我们在输入一些词汇后,只需对这些索引的结果取并集就可以了。还有一点需要注意,对于英文来说,虽然单词是This,但是我们做索引的时候应该不区分大小写,不管输入this还是this等,都能搜索出this才对。当然,每个关键字对应的文档集合可以用不同的数据结构来存储。 + diff --git a/Frame/ElasticSearch/ElasticSearch-3-core-1.md b/Frame/ElasticSearch/ElasticSearch-3-core-1.md new file mode 100644 index 00000000..ed7127bc --- /dev/null +++ b/Frame/ElasticSearch/ElasticSearch-3-core-1.md @@ -0,0 +1,368 @@ +# ElasticSearch + +## 一、基本概念和优势 + +1、ES相对于数据库的优势 + +1)、搜索比数据库快。ES有分片的概念,一个大的索引会被分成多个分片来进行存储数据,使用分布式的架构**对分片进行并行搜索**(基于倒排) 传统数据库的遍历,属于正向全表扫描 (数据库比如要搜索的话 `%xxx%`这种,会进行全表扫描) + +2)、数据库不能将搜索词拆分开来,尽可能搜索更多你期望的结果。比如输入"生化机",就搜索不出来"生化危机" + +> ES应用场景: +> +> 检索:ES本身作为一个搜索引擎,用来处理检索的任务再合适不过。 +> +> 统计(数据分析): ES的统计也是基于检索功能的,聚合功能使得统计结果处理起来非常方便。如果你只需要统计而不用检索,可能有其他工具更适合你,比如Spark SQL。 +> +> 分布式(近实时): ES自动可以将海量数据分散到多台服务器上去存储和检索。(lucene是单机的) + +全文检索,倒排索引: + +![1567167130066](assets/1567167130066.png) + +分布式和集群管理: + +![1567168565533](assets/1567168565533.png) + +几个核心概念: + +1)Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常 + +2)Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为"elasticsearch"的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群 + +3)shard:**单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index**。 + +4)replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。 + +![1567170581171](assets/1567170581171.png) + + + +![1567171354916](assets/1567171354916.png) + +## 二、分布式架构、分片、容错 + +1、Elasticsearch对复杂分布式机制的透明隐藏特性,Elasticsearch是一套分布式的系统,分布式是为了应对大数据量隐藏了复杂的分布式机制 + +* 分片机制(我们之前随随便便就将一些document插入到es集群中去了,我们有没有care过数据怎么进行分片的,数据到哪个shard中去) + +* cluster discovery(集群发现机制,我们之前在做那个集群status从yellow转green的实验里,直接启动了第二个es进程,那个进程作为一个node自动就发现了集群,并且加入了进去,还接受了部分数据,replica shard) + +* shard负载均衡(举例,假设现在有3个节点,总共有25个shard要分配到3个节点上去,es会自动进行均匀分配,以保持每个节点的均衡的读写负载请求) + +* shard副本,请求路由,集群扩容,shard重分配 + +![1567179626650](assets/1567179626650.png) + +**master节点** + +(1)创建或删除索引 + +(2)增加或删除节点 + +**节点平等的分布式架构** + +1)节点对等,每个节点都能接收所有的请求 + +2)自动请求路由 + +3)响应收集 + +**shard&replica机制再次梳理** + +1)index包含多个shard + +2)每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力 + +3)增减节点时,shard会自动在nodes中负载均衡 + +4)primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard + +5)replica shard是primary shard的副本,负责容错,以及承担读请求负载 + +6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改 + +7)primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard + +8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失, + +起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上 + +**图解单node环境下创建index是什么样子的** + +1)单node环境下,创建一个index,有3个primary shard,3个replica shard + +2)集群status是yellow + +3)这个时候,只会将3个primary shard分配到仅有的一个node上去,另外3个replica shard是无法分配的 + +4)集群可以正常工作,但是一旦出现节点宕机,数据全部丢失,而且集群不可用,无法承接任何请求 + +```java +PUT /test_index +{ + "settings" : { + "number_of_shards" : 3, + "number_of_replicas" : 1 + } +} +``` + +**图解2个node环境下replica shard是如何分配的** + +1)replica shard分配 + +2)primary ---> replica同步 + +3)读请求:primary/replica + +![1567181093355](assets/1567181093355.png) + +**图解横向扩容过程,如何超出扩容极限,以及如何提升容错性** + +1)primary&replica自动负载均衡,6个shard,3 primary,3 replica + +2)每个node有更少的shard,IO/CPU/Memory资源给每个shard分配更多,每个shard性能更好 + +3)扩容的极限,6个shard(3 primary,3 replica),最多扩容到6台机器,每个shard可以占用单台服务器的所有资源,性能最好 + +4)超出扩容极限,动态修改replica数量,9个shard(3 primary,6 replica),扩容到9台机器,比3台机器时,拥有3倍的读吞吐量 + +5)3台机器下,9个shard(3 primary,6 replica),资源更少,但是容错性更好,最多容纳2台机器宕机,6个shard只能容纳0台机器宕机 + +6)这里的这些知识点,你综合起来看,就是说,一方面告诉你扩容的原理,怎么扩容,怎么提升系统整体吞吐量;另一方面要考虑到系统的容错性,怎么保证提高容错性,让尽可能多的服务器宕机,保证数据不丢失 + +![1567182373815](assets/1567182373815.png) + +**图解Elasticsearch容错机制:master选举,replica容错,数据恢复** + +1)9 shard,3 node + +2)master node宕机,自动master选举,red + +3)replica容错:新master将replica提升为primary shard,yellow + +4)重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green + +![1567182772986](assets/1567182772986.png) + +## 三、核心元数据 + +**_index元数据** + +1)代表一个document存放在哪个index中 + +2)类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。 + +3)index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。 + +**_id元数据** + +1)代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document + +2)我们可以手动指定document的id(put /index/type/id),也可以不指定,由es自动为我们创建一个id + +**手动制定_id和自动生成_id** + +手动指定document id + +1)根据应用情况来说,是否满足手动指定document id的前提: + +一般来说,是从某些其他的系统中,导入一些数据到es时,会采取这种方式,就是使用系统中已有数据的唯一标识,作为es中document的id。如果将数据导入到es中,此时就比较适合采用数据在数据库中已有的primary key。 + +2)自动生成的id,长度为20个字符,URL安全,base64编码,**GUID,分布式系统并行生成时不可能会发生冲突** + +![1567213264110](assets/1567213264110.png) + +**document的全量替换、强制创建和lazy delete机制** + +1、document的全量替换 + +1)语法与创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容 + +2)document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容 + +3)es会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document + +2、document的强制创建 + +1)创建文档与全量替换的语法是一样的,有时我们只是想新建文档,不想替换文档,如果强制进行创建呢? + +2)PUT /index/type/id?op_type=create,PUT /index/type/id/_create + +3、document的删除 + +1)DELETE /index/type/id + +2)**不会理解物理删除,只会将其标记为deleted,当数据越来越多的时候,在后台自动删除** + +![1567215035353](assets/1567215035353.png) + +## 四、并发、partial update + +![1567215746337](assets/1567215746337.png) + +ES使用乐观锁(版本号): + +![1567216795620](assets/1567216795620.png) + +**_version的变换** + +* 第一次创建一个document的时候,它的_version内部版本号就是1;以后,每次对这个document执行修改或者删除操作,都会对这个_version版本号自动加1;哪怕是删除,也会对这条数据的版本号加1 +* 我们会发现,在删除一个document之后,可以从一个侧面证明,它不是立即物理删除掉的,因为它的一些版本号等信息还是保留着的。先删除一条document,再重新创建这条document,其实会在delete version基础之上,再把version号加1 + +![1567217709519](assets/1567217709519.png) + +**基于_version并发实战(访问的带上_version)** + +```java +1、上机动手实战演练基于_version进行乐观锁并发控制 + +(1)先构造一条数据出来 + +PUT /test_index/test_type/7 +{ + "test_field": "test test" +} + +(2)模拟两个客户端,都获取到了同一条数据 + +GET test_index/test_type/7 + +{ + "_index": "test_index", + "_type": "test_type", + "_id": "7", + "_version": 1, + "found": true, + "_source": { + "test_field": "test test" + } +} + +(3)其中一个客户端,先更新了一下这个数据 + +同时带上数据的版本号,确保说,es中的数据的版本号,跟客户端中的数据的版本号是相同的,才能修改 + +PUT /test_index/test_type/7?version=1 +{ + "test_field": "test client 1" +} + +{ + "_index": "test_index", + "_type": "test_type", + "_id": "7", + "_version": 2, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "created": false +} + +(4)另外一个客户端,尝试基于version=1的数据去进行修改,同样带上version版本号,进行乐观锁的并发控制 + +PUT /test_index/test_type/7?version=1 +{ + "test_field": "test client 2" +} + +{ + "error": { + "root_cause": [ + { + "type": "version_conflict_engine_exception", + "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]", + "index_uuid": "6m0G7yx7R1KECWWGnfH1sw", + "shard": "3", + "index": "test_index" + } + ], + "type": "version_conflict_engine_exception", + "reason": "[test_type][7]: version conflict, current version [2] is different than the one provided [1]", + "index_uuid": "6m0G7yx7R1KECWWGnfH1sw", + "shard": "3", + "index": "test_index" + }, + "status": 409 +} + +(5)在乐观锁成功阻止并发问题之后,尝试正确的完成更新 + +GET /test_index/test_type/7 + +{ + "_index": "test_index", + "_type": "test_type", + "_id": "7", + "_version": 2, + "found": true, + "_source": { + "test_field": "test client 1" + } +} + +基于最新的数据和版本号,去进行修改,修改后,带上最新的版本号,可能这个步骤会需要反复执行好几次,才能成功,特别是在多线程并发更新同一条数据很频繁的情况下 + +PUT /test_index/test_type/7?version=2 +{ + "test_field": "test client 2" +} + +{ + "_index": "test_index", + "_type": "test_type", + "_id": "7", + "_version": 3, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "created": false +} + + +``` + +还有一个基于`external_version`的,这个是用户自定义的`_version`,只要比ES中的`_version`大就可以了。 + +**Partial Update** + +PUT /index/type/id,创建文档&替换文档,就是一样的语法 + +一般对应到应用程序中,每次的执行流程基本是这样的: + +1)应用程序先发起一个get请求,获取到document,展示到前台界面,供用户查看和修改 + +2)用户在前台界面修改数据,发送到后台 + +3)后台代码,会将用户修改的数据在内存中进行执行,然后封装好修改后的全量数据 + +4)然后发送PUT请求,到es中,进行全量替换 + +5)es将老的document标记为deleted,然后重新创建一个新的document + +```java + +partial update + +post /index/type/id/_update +{ + "doc": { + "要修改的少数几个field即可,不需要全量的数据" + } +} + +``` + +看起来,好像就比较方便了,每次就传递少数几个发生修改的field即可,不需要将全量的document数据发送过去 + +**partial update实现原理以及其优点** + +* 所有的查询、修改、和写回操作,都发生在es中的一个shard内部,避免了所有的网络数据传输的开销(减少了2次网络情况),大大提高了性能; +* 减少了查询和修改中的时间间隔,可以有效减少并发冲突情况; \ No newline at end of file diff --git a/Frame/ElasticSearch/assets/1566289291258.png b/Frame/ElasticSearch/assets/1566289291258.png new file mode 100644 index 00000000..259cbc64 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289291258.png differ diff --git a/Frame/ElasticSearch/assets/1566289299841.png b/Frame/ElasticSearch/assets/1566289299841.png new file mode 100644 index 00000000..71cd4107 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289299841.png differ diff --git a/Frame/ElasticSearch/assets/1566289323552.png b/Frame/ElasticSearch/assets/1566289323552.png new file mode 100644 index 00000000..cd34a6fa Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289323552.png differ diff --git a/Frame/ElasticSearch/assets/1566289331810.png b/Frame/ElasticSearch/assets/1566289331810.png new file mode 100644 index 00000000..339a223f Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289331810.png differ diff --git a/Frame/ElasticSearch/assets/1566289346225.png b/Frame/ElasticSearch/assets/1566289346225.png new file mode 100644 index 00000000..48783ffe Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289346225.png differ diff --git a/Frame/ElasticSearch/assets/1566289358967.png b/Frame/ElasticSearch/assets/1566289358967.png new file mode 100644 index 00000000..24f2965c Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289358967.png differ diff --git a/Frame/ElasticSearch/assets/1566289369290.png b/Frame/ElasticSearch/assets/1566289369290.png new file mode 100644 index 00000000..ca662084 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289369290.png differ diff --git a/Frame/ElasticSearch/assets/1566289378676.png b/Frame/ElasticSearch/assets/1566289378676.png new file mode 100644 index 00000000..16e60e79 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289378676.png differ diff --git a/Frame/ElasticSearch/assets/1566289387855.png b/Frame/ElasticSearch/assets/1566289387855.png new file mode 100644 index 00000000..9e10ef52 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566289387855.png differ diff --git a/Frame/ElasticSearch/assets/1566390474088.png b/Frame/ElasticSearch/assets/1566390474088.png new file mode 100644 index 00000000..5af71645 Binary files /dev/null and b/Frame/ElasticSearch/assets/1566390474088.png differ diff --git a/Frame/ElasticSearch/assets/1566390608100.png b/Frame/ElasticSearch/assets/1566390608100.png new file mode 100644 index 00000000..25714f8d Binary files /dev/null and b/Frame/ElasticSearch/assets/1566390608100.png differ diff --git a/Frame/ElasticSearch/assets/1567167130066.png b/Frame/ElasticSearch/assets/1567167130066.png new file mode 100644 index 00000000..fdf94105 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567167130066.png differ diff --git a/Frame/ElasticSearch/assets/1567168525721.png b/Frame/ElasticSearch/assets/1567168525721.png new file mode 100644 index 00000000..2e093bc5 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567168525721.png differ diff --git a/Frame/ElasticSearch/assets/1567168565533.png b/Frame/ElasticSearch/assets/1567168565533.png new file mode 100644 index 00000000..e7e0d488 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567168565533.png differ diff --git a/Frame/ElasticSearch/assets/1567170581171.png b/Frame/ElasticSearch/assets/1567170581171.png new file mode 100644 index 00000000..33091728 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567170581171.png differ diff --git a/Frame/ElasticSearch/assets/1567171354916.png b/Frame/ElasticSearch/assets/1567171354916.png new file mode 100644 index 00000000..d490bb25 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567171354916.png differ diff --git a/Frame/ElasticSearch/assets/1567179626650.png b/Frame/ElasticSearch/assets/1567179626650.png new file mode 100644 index 00000000..9032ced4 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567179626650.png differ diff --git a/Frame/ElasticSearch/assets/1567181093355.png b/Frame/ElasticSearch/assets/1567181093355.png new file mode 100644 index 00000000..5c3d3af1 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567181093355.png differ diff --git a/Frame/ElasticSearch/assets/1567181972599.png b/Frame/ElasticSearch/assets/1567181972599.png new file mode 100644 index 00000000..0a5f05fa Binary files /dev/null and b/Frame/ElasticSearch/assets/1567181972599.png differ diff --git a/Frame/ElasticSearch/assets/1567182146463.png b/Frame/ElasticSearch/assets/1567182146463.png new file mode 100644 index 00000000..e761b485 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567182146463.png differ diff --git a/Frame/ElasticSearch/assets/1567182373815.png b/Frame/ElasticSearch/assets/1567182373815.png new file mode 100644 index 00000000..cd49d691 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567182373815.png differ diff --git a/Frame/ElasticSearch/assets/1567182772986.png b/Frame/ElasticSearch/assets/1567182772986.png new file mode 100644 index 00000000..573fec8f Binary files /dev/null and b/Frame/ElasticSearch/assets/1567182772986.png differ diff --git a/Frame/ElasticSearch/assets/1567213264110.png b/Frame/ElasticSearch/assets/1567213264110.png new file mode 100644 index 00000000..b5fc1d08 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567213264110.png differ diff --git a/Frame/ElasticSearch/assets/1567215035353.png b/Frame/ElasticSearch/assets/1567215035353.png new file mode 100644 index 00000000..48487332 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567215035353.png differ diff --git a/Frame/ElasticSearch/assets/1567215702466.png b/Frame/ElasticSearch/assets/1567215702466.png new file mode 100644 index 00000000..d35e5d41 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567215702466.png differ diff --git a/Frame/ElasticSearch/assets/1567215746337.png b/Frame/ElasticSearch/assets/1567215746337.png new file mode 100644 index 00000000..6594d2ed Binary files /dev/null and b/Frame/ElasticSearch/assets/1567215746337.png differ diff --git a/Frame/ElasticSearch/assets/1567216795620.png b/Frame/ElasticSearch/assets/1567216795620.png new file mode 100644 index 00000000..05ff8857 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567216795620.png differ diff --git a/Frame/ElasticSearch/assets/1567217709519.png b/Frame/ElasticSearch/assets/1567217709519.png new file mode 100644 index 00000000..57e4f051 Binary files /dev/null and b/Frame/ElasticSearch/assets/1567217709519.png differ diff --git "a/Frame/ElasticSearch/course/1-\347\256\200\345\215\225CRUD.txt" "b/Frame/ElasticSearch/course/1-\347\256\200\345\215\225CRUD.txt" new file mode 100644 index 00000000..e8801225 --- /dev/null +++ "b/Frame/ElasticSearch/course/1-\347\256\200\345\215\225CRUD.txt" @@ -0,0 +1,302 @@ +γ̴ + +1documentݸʽ +2վƷ +3򵥵ļȺ +4ƷCRUDdocument CRUD + +---------------------------------------------------------------------------------------------------------------------------- + +1documentݸʽ + +ĵ + +1Ӧϵͳݽṹģӵ +2ݴ洢ݿУֻܲ⿪ΪƽĶűÿβѯʱ򻹵ûԭضʽ൱鷳 +3ESĵģĵд洢ݽṹݽṹһģĵݽṹesṩӵȫļۺϵȹ +4esdocumentjsonݸʽ + +public class Employee { + + private String email; + private String firstName; + private String lastName; + private EmployeeInfo info; + private Date joinDate; + +} + +private class EmployeeInfo { + + private String bio; // Ը + private Integer age; + private String[] interests; // Ȥ + +} + +EmployeeInfo info = new EmployeeInfo(); +info.setBio("curious and modest"); +info.setAge(30); +info.setInterests(new String[]{"bike", "climb"}); + +Employee employee = new Employee(); +employee.setEmail("zhangsan@sina.com"); +employee.setFirstName("san"); +employee.setLastName("zhang"); +employee.setInfo(info); +employee.setJoinDate(new Date()); + +employeeEmployeeԼԣһEmployeeInfo + +űemployeeemployee_infoemployee²EmployeeݺEmployeeInfo +employeeemailfirst_namelast_namejoin_date4ֶ +employee_infobioageinterests3ֶΣ⻹һֶΣemployee_idemployee + +{ + "email": "zhangsan@sina.com", + "first_name": "san", + "last_name": "zhang", + "info": { + "bio": "curious and modest", + "age": 30, + "interests": [ "bike", "climb" ] + }, + "join_date": "2017/01/01" +} + +ǾesdocumentݸʽݿĹϵݸʽ + +---------------------------------------------------------------------------------------------------------------------------- + +2վƷ + +һվҪΪESһ̨ϵͳṩ¹ܣ + +1ƷϢCRUDɾIJ飩 +2ִм򵥵Ľṹѯ +3ִм򵥵ȫļԼӵphrase +4ȫļĽԽиʾ +5ݽм򵥵ľۺϷ + +---------------------------------------------------------------------------------------------------------------------------- + +3򵥵ļȺ + +1ټ鼯ȺĽ״ + +esṩһapicat apiԲ鿴esиָ + +GET /_cat/health?v + +epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent +1488006741 15:12:21 elasticsearch yellow 1 1 1 1 0 0 1 0 - 50.0% + +epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent +1488007113 15:18:33 elasticsearch green 2 2 2 1 0 0 0 0 - 100.0% + +epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent +1488007216 15:20:16 elasticsearch yellow 1 1 1 1 0 0 1 0 - 50.0% + +ο˽⼯ȺĽ״greenyellowred + +greenÿprimary shardreplica shardactive״̬ +yellowÿprimary shardactive״̬ģDzreplica shardactive״̬ڲõ״̬ +redprimary shardactive״̬ģݶʧ + +Ϊʲôڻᴦһyellow״̬ + +ھһʼDZԣһeṣ൱ھֻһnodeesһindexkibanaԼýindexĬϵǸÿindex5primary shard5replica shardprimary shardreplica shardͬһ̨ϣΪݴkibanaԼindex1primary shard1replica shardǰһnodeֻ1primary shard˺ˣһreplica shardûеڶ̨ȥ + +һСʵ飺ʱֻҪڶeṣͻesȺ2nodeȻ1replica shardͻԶȥȻcluster statusͻgreen״̬ + +2ٲ鿴ȺЩ + +GET /_cat/indices?v + +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +yellow open .kibana rUm9n9wMRQCCrRDEhqneBg 1 1 1 0 3.1kb 3.1kb + +3򵥵 + +PUT /test_index?pretty + +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +yellow open test_index XmS9DTAtSkSZSwWhhGEKkQ 5 1 0 0 650b 650b +yellow open .kibana rUm9n9wMRQCCrRDEhqneBg 1 1 1 0 3.1kb 3.1kb + +ɾDELETE /test_index?pretty + +health status index uuid pri rep docs.count docs.deleted store.size pri.store.size +yellow open .kibana rUm9n9wMRQCCrRDEhqneBg 1 1 1 0 3.1kb 3.1kb + +---------------------------------------------------------------------------------------------------------------------------- + +4ƷCRUD + +1Ʒĵ + +PUT /index/type/id +{ + "json" +} + +PUT /ecommerce/product/1 +{ + "name" : "gaolujie yagao", + "desc" : "gaoxiao meibai", + "price" : 30, + "producer" : "gaolujie producer", + "tags": [ "meibai", "fangzhu" ] +} + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "created": true +} + +PUT /ecommerce/product/2 +{ + "name" : "jiajieshi yagao", + "desc" : "youxiao fangzhu", + "price" : 25, + "producer" : "jiajieshi producer", + "tags": [ "fangzhu" ] +} + +PUT /ecommerce/product/3 +{ + "name" : "zhonghua yagao", + "desc" : "caoben zhiwu", + "price" : 40, + "producer" : "zhonghua producer", + "tags": [ "qingxin" ] +} + +esԶindextypeҪǰesĬϻdocumentÿfieldԱ + +2ѯƷĵ + +GET /index/type/id +GET /ecommerce/product/1 + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 1, + "found": true, + "_source": { + "name": "gaolujie yagao", + "desc": "gaoxiao meibai", + "price": 30, + "producer": "gaolujie producer", + "tags": [ + "meibai", + "fangzhu" + ] + } +} + +3޸Ʒ滻ĵ + +PUT /ecommerce/product/1 +{ + "name" : "jiaqiangban gaolujie yagao", + "desc" : "gaoxiao meibai", + "price" : 30, + "producer" : "gaolujie producer", + "tags": [ "meibai", "fangzhu" ] +} + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 1, + "result": "created", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "created": true +} + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 2, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + }, + "created": false +} + + +PUT /ecommerce/product/1 +{ + "name" : "jiaqiangban gaolujie yagao" +} + +滻ʽһãʹеfieldȥϢ޸ + +4޸Ʒĵ + +POST /ecommerce/product/1/_update +{ + "doc": { + "name": "jiaqiangban gaolujie yagao" + } +} + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 8, + "result": "updated", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + } +} + +5ɾƷɾĵ + +DELETE /ecommerce/product/1 + +{ + "found": true, + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_version": 9, + "result": "deleted", + "_shards": { + "total": 2, + "successful": 1, + "failed": 0 + } +} + +{ + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "found": false +} + diff --git "a/Frame/ElasticSearch/course/2-DSL\347\256\200\345\215\225\346\220\234\347\264\242.txt" "b/Frame/ElasticSearch/course/2-DSL\347\256\200\345\215\225\346\220\234\347\264\242.txt" new file mode 100644 index 00000000..a8e16df2 --- /dev/null +++ "b/Frame/ElasticSearch/course/2-DSL\347\256\200\345\215\225\346\220\234\347\264\242.txt" @@ -0,0 +1,330 @@ +γ̴ + +1query string search +2query DSL +3query filter +4full-text search +5phrase search +6highlight search + +--------------------------------------------------------------------------------------------------------------------------------- + + +1query string search + +ȫƷGET /ecommerce/product/_search + +tookķ˼ +timed_outǷʱû +_shardsݲ5ƬԶ󣬻еprimary shardijreplica shardҲԣ +hits.totalѯ3document +hits.max_scorescoreĺ壬documentһsearchضȵƥԽأԽƥ䣬Ҳ +hits.hitsƥdocumentϸ + + +{ + "took": 2, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 3, + "max_score": 1, + "hits": [ + { + "_index": "ecommerce", + "_type": "product", + "_id": "2", + "_score": 1, + "_source": { + "name": "jiajieshi yagao", + "desc": "youxiao fangzhu", + "price": 25, + "producer": "jiajieshi producer", + "tags": [ + "fangzhu" + ] + } + }, + { + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_score": 1, + "_source": { + "name": "gaolujie yagao", + "desc": "gaoxiao meibai", + "price": 30, + "producer": "gaolujie producer", + "tags": [ + "meibai", + "fangzhu" + ] + } + }, + { + "_index": "ecommerce", + "_type": "product", + "_id": "3", + "_score": 1, + "_source": { + "name": "zhonghua yagao", + "desc": "caoben zhiwu", + "price": 40, + "producer": "zhonghua producer", + "tags": [ + "qingxin" + ] + } + } + ] + } +} + +query string searchΪsearchhttpquery string + +ƷаyagaoƷҰۼ۽GET /ecommerce/product/_search?q=name:yagao&sort=price:desc + +ʱʹһЩߣcurlٵķҪϢѯܸӣǺȥ +Уʹquery string search + +--------------------------------------------------------------------------------------------------------------------------------- + +2query DSL + +DSLDomain Specified Languageض +http request body壬jsonĸʽѯ﷨ȽϷ㣬Թָӵ﷨query string search϶ǿ + +ѯеƷ + +GET /ecommerce/product/_search +{ + "query": { "match_all": {} } +} + +ѯưyagaoƷͬʱռ۸ + +GET /ecommerce/product/_search +{ + "query" : { + "match" : { + "name" : "yagao" + } + }, + "sort": [ + { "price": "desc" } + ] +} + +ҳѯƷܹ3Ʒÿҳʾ1Ʒʾ2ҳԾͲ2Ʒ + +GET /ecommerce/product/_search +{ + "query": { "match_all": {} }, + "from": 1, + "size": 1 +} + +ָҪѯƷƺͼ۸Ϳ + +GET /ecommerce/product/_search +{ + "query": { "match_all": {} }, + "_source": ["name", "price"] +} + +ʺʹãԹӵIJѯ + +--------------------------------------------------------------------------------------------------------------------------------- + +3query filter + +Ʒưyagaoۼ۴25ԪƷ + +GET /ecommerce/product/_search +{ + "query" : { + "bool" : { + "must" : { + "match" : { + "name" : "yagao" + } + }, + "filter" : { + "range" : { + "price" : { "gt" : 25 } + } + } + } + } +} + +--------------------------------------------------------------------------------------------------------------------------------- + +4full-text searchȫļ + +GET /ecommerce/product/_search +{ + "query" : { + "match" : { + "producer" : "yagao producer" + } + } +} + + +producerֶΣȱ⣬ + +special 4 +yagao 4 +producer 1,2,3,4 +gaolujie 1 +zhognhua 3 +jiajieshi 2 + +yagao producer ---> yagaoproducer + +{ + "took": 4, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 4, + "max_score": 0.70293105, # ƥߵ Ļƥ䣬ûscore + "hits": [ + { + "_index": "ecommerce", + "_type": "product", + "_id": "4", + "_score": 0.70293105, + "_source": { + "name": "special yagao", + "desc": "special meibai", + "price": 50, + "producer": "special yagao producer", + "tags": [ + "meibai" + ] + } + }, + { + "_index": "ecommerce", + "_type": "product", + "_id": "1", + "_score": 0.25811607, + "_source": { + "name": "gaolujie yagao", + "desc": "gaoxiao meibai", + "price": 30, + "producer": "gaolujie producer", + "tags": [ + "meibai", + "fangzhu" + ] + } + }, + { + "_index": "ecommerce", + "_type": "product", + "_id": "3", + "_score": 0.25811607, + "_source": { + "name": "zhonghua yagao", + "desc": "caoben zhiwu", + "price": 40, + "producer": "zhonghua producer", + "tags": [ + "qingxin" + ] + } + }, + { + "_index": "ecommerce", + "_type": "product", + "_id": "2", + "_score": 0.1805489, + "_source": { + "name": "jiajieshi yagao", + "desc": "youxiao fangzhu", + "price": 25, + "producer": "jiajieshi producer", + "tags": [ + "fangzhu" + ] + } + } + ] + } +} + +--------------------------------------------------------------------------------------------------------------------------------- + +5phrase search + +ȫļӦ෴ȫļὫ⿪ȥȥһһƥ䣬ֻҪƥһĵʣͿΪ +phrase search(ȷƥ)ҪֶָıУȫһģһģſƥ䣬Ϊ + +GET /ecommerce/product/_search +{ + "query" : { + "match_phrase" : { # ȷƥ + "producer" : "yagao producer" + } + } +} + +{ + "took": 11, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 1, + "max_score": 0.70293105, + "hits": [ + { + "_index": "ecommerce", + "_type": "product", + "_id": "4", + "_score": 0.70293105, + "_source": { + "name": "special yagao", + "desc": "special meibai", + "price": 50, + "producer": "special yagao producer", + "tags": [ + "meibai" + ] + } + } + ] + } +} + +--------------------------------------------------------------------------------------------------------------------------------- + +6highlight search + +GET /ecommerce/product/_search +{ + "query" : { + "match" : { + "producer" : "producer" + } + }, + "highlight": { + "fields" : { + "producer" : {} + } + } +} diff --git "a/Frame/ElasticSearch/course/3-\350\201\232\345\220\210.txt" "b/Frame/ElasticSearch/course/3-\350\201\232\345\220\210.txt" new file mode 100644 index 00000000..e5cf2f5d --- /dev/null +++ "b/Frame/ElasticSearch/course/3-\350\201\232\345\220\210.txt" @@ -0,0 +1,221 @@ +һ󣺼ÿtagµƷ + +GET /ecommerce/product/_search +{ + "aggs": { # ۺ + "group_by_tags": { # + "terms": { "field": "tags" } # ÿgroup + } + } +} + +ıfieldfielddataΪtrue + +PUT /ecommerce/_mapping/product +{ + "properties": { + "tags": { + "type": "text", + "fielddata": true + } + } +} + +GET /ecommerce/product/_search +{ + "size": 0, + "aggs": { + "all_tags": { + "terms": { "field": "tags" } + } + } +} + +{ + "took": 20, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 4, + "max_score": 0, + "hits": [] + }, + "aggregations": { + "group_by_tags": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "fangzhu", + "doc_count": 2 + }, + { + "key": "meibai", + "doc_count": 2 + }, + { + "key": "qingxin", + "doc_count": 1 + } + ] + } + } +} + +---------------------------------------------------------------------------------------------------------------- + +ڶۺϷ󣺶аyagaoƷÿtagµƷ + +GET /ecommerce/product/_search +{ + "size": 0, + "query": { + "match": { + "name": "yagao" + } + }, + "aggs": { + "all_tags": { + "terms": { + "field": "tags" + } + } + } +} + +---------------------------------------------------------------------------------------------------------------- + +ۺϷȷ飬ÿƽֵÿtagµƷƽ۸ + +GET /ecommerce/product/_search +{ + "size": 0, + "aggs" : { + "group_by_tags" : { # Ƚз + "terms" : { "field" : "tags" }, + "aggs" : { + "avg_price" : { # Ȼƽֵ + "avg" : { "field" : "price" } + } + } + } + } +} + +{ + "took": 8, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "failed": 0 + }, + "hits": { + "total": 4, + "max_score": 0, + "hits": [] + }, + "aggregations": { + "group_by_tags": { + "doc_count_error_upper_bound": 0, + "sum_other_doc_count": 0, + "buckets": [ + { + "key": "fangzhu", + "doc_count": 2, + "avg_price": { + "value": 27.5 + } + }, + { + "key": "meibai", + "doc_count": 2, + "avg_price": { + "value": 40 + } + }, + { + "key": "qingxin", + "doc_count": 1, + "avg_price": { + "value": 40 + } + } + ] + } + } +} + +---------------------------------------------------------------------------------------------------------------- + +ĸݷ󣺼ÿtagµƷƽ۸񣬲Ұƽ۸ + +GET /ecommerce/product/_search +{ + "size": 0, + "aggs" : { + "all_tags" : { + "terms" : { "field" : "tags", "order": { "avg_price": "desc" } }, + "aggs" : { + "avg_price" : { + "avg" : { "field" : "price" } + } + } + } + } +} + +ȫesrestful apiѧϰͽes֪ʶ͹ܵ㣬ûʹһЩȥ⣨javaԭ£ + +1esҪapiǽиֳԡѧϰijЩ½ʹõapirestful apiѧϰes restful apijava apiesҲǿԵģ©es֪ʶһ飬㶼֪Ҫrestful apiôõ +2֪ʶ㣬es restful apiӷ㣬ݣÿζдjava룬ܼӿ콲εЧʺٶȣͬѧǹעes֪ʶ͹ܵѧϰ +3ͨὲes֪ʶ󣬿ʼϸjava apijava apiִиֲ +4ÿƪ¶һĿʵսĿʵսȫjavaȥʵĿϵͳ + +---------------------------------------------------------------------------------------------------------------- + +ݷ󣺰ָļ۸Χз飬Ȼÿٰtagз飬ټÿƽ۸ + +GET /ecommerce/product/_search +{ + "size": 0, + "aggs": { + "group_by_price": { + "range": { + "field": "price", + "ranges": [ + { + "from": 0, + "to": 20 + }, + { + "from": 20, + "to": 40 + }, + { + "from": 40, + "to": 50 + } + ] + }, + "aggs": { + "group_by_tags": { + "terms": { + "field": "tags" + }, + "aggs": { + "average_price": { + "avg": { + "field": "price" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Frame/FastDFS/FastDFS.md b/Frame/FastDFS/FastDFS.md new file mode 100644 index 00000000..fac27f91 --- /dev/null +++ b/Frame/FastDFS/FastDFS.md @@ -0,0 +1,116 @@ +# FastDFS + +安装FastDFS这篇文章最好: (这里用单服务器) + +## 一、分布式文件系统 + +为什么会有分布文件系统呢? + +分布式文件系统是面对互联网的需求而产生,互联网时代对海量数据如何存储?靠简单的增加硬盘的个数已经满足 +不了我们的要求,因为硬盘传输速度有限但是数据在急剧增长,另外我们还要要做好数据备份、数据安全等。 +**采用分布式文件系统可以将多个地点的文件系统通过网络连接起来,组成一个文件系统网络,结点之间通过网络进** +**行通信,一台文件系统的存储和传输能力有限,我们让文件在多台计算机上存储,通过多台计算共同传输**。如下 + +![1566626555590](assets/1566626555590.png) + +好处: + +1、一台计算机的文件系统处理能力扩充到多台计算机同时处理。 + +2、一台计算机挂了还有另外副本计算机提供数据。 + +3、每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。 + +为什么选择FastDFS? + +NFS、GFS都是通用的分布式文件系统,通用的分布式文件系统的优点的是开发体验好,但是系统复杂 +性高、性能一般,而专用的分布式文件系统虽然开发体验性差,但是系统复杂性低并且性能高。 + +**fastDFS非常适合存储图片等那些小文件,fastDFS不对文件进行分块,所以它就没有分块合并的开销,fastDFS网络通信采用socket,通信速度很快**。 + +## 二、FastDFS组成 + +**客户请求Tracker server进行文件上传、下载,通过Trackerserver调度最终由Storage server完成上传和下载**。 + +FastDFS 系统有三个角色:跟踪服务器(Tracker Server)、存储服务器(Storage Server)和客户端(Client)。 + +* **Tracker Server**:跟踪服务器,主要做调度工作,起到均衡的作用;负责管理所有的 storage server和 group,每个 storage 在启动后会连接 Tracker,告知自己所属 group 等信息,并保持周期性心跳。 +* **Storage Server**:存储服务器,主要提供容量和备份服务;以 group 为单位,每个 group 内可以有多台 storage server,数据互为备份。 +* **Client**:客户端,上传下载数据的服务器,也就是我们自己的项目所部署在的服务器。 + +![1566626799972](assets/1566626799972.png) + +1)Tracker + +Tracker Server作用是负载均衡和调度,通过Tracker server在文件上传时可以根据一些策略找到Storage server提 +供文件上传服务。可以将tracker称为追踪服务器或调度服务器。 + +FastDFS集群中的Tracker server可以有多台,Tracker server之间是相互平等关系同时提供服务,Tracker server +不存在单点故障。客户端请求Tracker server采用轮询方式,如果请求tracker无法提供服务则换另一台tracker。 + +2)Storage + +Storage Server作用是文件存储,客户端上传的文件最终存储在Storage服务器上,Storage server没有实现自己 +的文件系统而是使用操作系统的文件系统来管理文件。可以将storage称为存储服务器。 + +Storage集群采用了**分组存储方式**。storage集群由一个或多个组构成,集群存储总容量为集群中所有组的存储容 +量之和。一个组由一台或多台存储服务器组成,组内的Storage server之间是平等关系,不同组的Storage server +之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步,从而保证同组内每个storage上的文件完全一致的(备份、负载)。一个组的存储容量为该组内的存储服务器容量最小的那个,由此可见组内存储服务器的软硬件配置最好是一致的。 + +采用分组存储方式的好处是灵活、可控性较强。比如上传文件时,可以由客户端直接指定上传到的组也可以由 +tracker进行调度选择。一个分组的存储服务器访问压力较大时,可以在该组增加存储服务器来扩充服务能力(纵向扩容)。当系统容量不足时,可以增加组来扩充存储容量(横向扩容)。当存储空间不足或即将耗尽时,可以动态添加卷。只需要增加一台或多台服务器,并将它们配置为一个新的组,这样就扩大了存储系统的容量。 + +3)Storage状态收集 + +Storage server会连接集群中所有的Tracker server,定时向他们报告自己的状态,包括磁盘剩余空间、文件同步 +状况、文件上传下载次数等统计信息。 + +## 三、FastDFS文件上传流程 + +![1566627269715](assets/1566627269715.png) + +客户端上传文件后存储服务器将文件ID返回给客户端,**此文件ID用于以后访问该文件的索引信息**。文件索引信息 +包括:组名,虚拟磁盘路径,数据两级目录,文件名。 + +当Tracker收到客户端上传文件的请求时,会为该文件分配一个可以存储文件的group,当选定了group后就要决定给客户端分配group中的哪一个storage server。当分配好storage server后,客户端向storage发送写文件请求,storage将会为文件分配一个数据存储目录。**然后为文件分配一个fileid**,最后根据以上的信息生成文件名存储文件。 + +![1566632462633](assets/1566632462633.png) + +* **组名**:文件上传后所在的storage组名称,在文件上传成功后有storage服务器返回,需要客户端自行保存。 +* **虚拟磁盘路径**:storage配置的虚拟路径,与磁盘选项store_path*对应。如果配置了store_path0则是M00, + 如果配置了store_path1则是M01,以此类推。 +* **数据两级目录**:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。 +* **文件名**:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址、文件创 + 建时间戳、文件大小、随机数和文件拓展名等信息。 + +## 四、FastDFS文件下载流程 + +客户端uploadfile成功后,会拿到一个storage生成的文件名,接下来客户端根据这个文件名即可访问到该文件。 + +![1566632561364](assets/1566632561364.png) + +tracker根据请求的文件路径即**文件ID** 来快速定义文件。 + +比如请求下边的文件: + +![1566632462633](assets/1566632462633.png) + +1、通过组名tracker能够很快的定位到客户端需要访问的**存储服务器组是group1**,并选择合适的存储服务器提供客户端访问。 +2、存储服务器根据"文件存储虚拟磁盘路径"和"数据文件两级目录"可以很快定位到文件所在目录,并根据文件名找到客户端需要访问的文件。 + +## 五、FastDFS的文件同步 + +写文件时,客户端将文件写至group内一个storage server即认为写文件成功,storage server写完文件后,会由**后台线程**将文件同步至同group内其他的storage server。 + +每个storage写文件后,同时会写一份binlog,binlog里不包含文件数据,只包含文件名等元信息,这份binlog用于后台同步,storage会记录向group内其他storage同步的进度,以便重启后能接上次的进度继续同步;进度以时间戳的方式进行记录,所以最好能保证集群内所有server的时钟保持同步。 + +storage的同步进度会作为元数据的一部分汇报到tracker上,tracke在选择读storage的时候会以同步进度作为参考。 + +## 六、Nginx配置 + +在 storage server 上安装 nginx 的目的是对外通过 http 访问 storage server 上的文 件。使用 nginx 的模块 +FastDFS-nginx-module 的作用是**通过 http 方式访问 storage 中 的文件**,当 storage 本机没有要找的文件时向源 +storage 主机代理请求文件。 + +图片服务虚拟主机的作用是负载均衡,将图片请求转发到storage server上。 + diff --git a/Frame/FastDFS/assets/1566626555590.png b/Frame/FastDFS/assets/1566626555590.png new file mode 100644 index 00000000..29d30fb7 Binary files /dev/null and b/Frame/FastDFS/assets/1566626555590.png differ diff --git a/Frame/FastDFS/assets/1566626799972.png b/Frame/FastDFS/assets/1566626799972.png new file mode 100644 index 00000000..7e57021a Binary files /dev/null and b/Frame/FastDFS/assets/1566626799972.png differ diff --git a/Frame/FastDFS/assets/1566627269715.png b/Frame/FastDFS/assets/1566627269715.png new file mode 100644 index 00000000..0750ead4 Binary files /dev/null and b/Frame/FastDFS/assets/1566627269715.png differ diff --git a/Frame/FastDFS/assets/1566632462633.png b/Frame/FastDFS/assets/1566632462633.png new file mode 100644 index 00000000..75c327ec Binary files /dev/null and b/Frame/FastDFS/assets/1566632462633.png differ diff --git a/Frame/FastDFS/assets/1566632561364.png b/Frame/FastDFS/assets/1566632561364.png new file mode 100644 index 00000000..59a38a97 Binary files /dev/null and b/Frame/FastDFS/assets/1566632561364.png differ diff --git a/Frame/Mybatis/Mybatis.md b/Frame/Mybatis/Mybatis.md new file mode 100644 index 00000000..8f106db0 --- /dev/null +++ b/Frame/Mybatis/Mybatis.md @@ -0,0 +1,338 @@ +# Mybatis + +## 一、statement 、prepareStatement区别 + +preparedStatement是**预编译**的,对于批量处理可以大大提高效率. 也叫JDBC存储过程。 + +PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。 + +statement每次执行sql语句,相关数据库都要执行sql语句的编译。 + +preparedstatement是预编译得, preparedstatement支持批处理。 + +prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。   + +Statement不会初始化,没有预处理,每次都是从0开始执行SQL。 + +**ParperStatement提高了代码的灵活性和执行效率。** + +关于SQL注入: + +**最后但也是最重要的一个大大的比Statement好的优点,那就是安全!** + +```java +String sql ="select * from user where username= '"+varname+"' anduserpwd='"+varpasswd+"'"; + +stmt =conn.createStatement(); + +rs =stmt.executeUpdate(sql); +``` + +这是验证用户名密码的,对吧。但要是我们把`'or '1' = 1'`当作密码传进去,你猜猜会发生啥。 + +`select * fromuser where username = 'user' and userpwd = '' or '1' = '1';` + +发现了吧!这是个永真式,因为1永远等于1。所以不管怎样都能获取到权限。哇。这就坏咯!这还不是最坏的,你再看! + +```java +String sql ="select * from user where username= '"+varname+"' and userpwd='"+varpasswd+"'"; + +stmt =conn.createStatement(); + +rs =stmt.executeUpdate(sql); +``` + +依旧是这行代码。这次我们把`'or '1' = 1';drop table book;`当成密码传进去。哇!又坏了!这次直接把表给删了。但是,你如果用PrepareStatement的话就不会出现这种问题。**你传入的这些数据根本不会跟原来的数据有任何的交集**,也不会发生这些问题。 + +## 二 、讲下 MyBatis 的缓存 + +**一级缓存:** + +mybatis的一级缓存是SQLSession级别的缓存,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据,不同的SqlSession之间缓存数据区域(HashMap)是互相不影响的。 + +一级缓存的作用域是SqlSession范围的,当在同一个SqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存)中,第二次查询时会从缓存中获取数据,不再去底层进行数据库查询,从而提高了查询效率。需要注意的是:如果SqlSession执行了DML操作(insert、update、delete),并执行commit()操作,mybatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存数据中存储的是最新的信息,避免出现脏读现象。 + +当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了,Mybatis默认开启一级缓存,不需要进行任何配置。 + +注意:Mybatis的缓存机制是基于id进行缓存,也就是说Mybatis在使用HashMap缓存数据时,是使用对象的id作为key,而对象作为value保存 + +**二级缓存:** + +二级缓存是mapper级别的缓存,使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMapper进行数据存储,相比一级缓存SqlSession,二级缓存的范围更大,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。 + +二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。 + +Mybatis默认没有开启二级缓存,需要在setting全局参数中配置开启二级缓存。 + +在mybatis-config.xml中配置: + +```xml + + + +``` + +cacheEnabled的value为true表示在此配置文件下开启二级缓存,该属性默认为false。 + +在EmployeeMapper.xml中配置: + +```xml + + +``` + +以上配置创建了一个LRU缓存,并每隔60秒刷新,最大存储512个对象,而且返回的对象被认为是只读。 + +## 三 、 Mybatis 是如何进行分页的?分页插件的原理是什么? + +**startPage方法中使用了一个ThreadLocal对象来存储线程副本变量即page对象来实现线程隔离,因此不同线程中的page对象互不影响,这个page对象就是分页参数对象。然后通过mybatis的拦截器处理方法中通过ThreadLocal对象取出分页参数page对象,然后对sql语句进行扩展,如此实现分页查询效果。** +**也就是使用了ThreadLocal与MyBatis拦截器来实现的**。 + +分页 + +答: + +1 ) Mybatis 使用 RowBounds 对象进行分页,也可以直接编写 sql 实现分页,也可以使用 +Mybatis 的分页插件。 + +2 )分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。 +举例:select * from student,拦截 sql 后重写为:`select t.* from ( select * from student) t +limit 0 , 10` + +## 四 、简述 Mybatis 的插件运行原理,以及如何编写一个插件? +答: + +1 ) Mybatis 仅可以编写针对 ParameterHandler、 ResultSetHandler、 StatementHandler、Executor 这 4 种接口的插件,**Mybatis 通过动态代理**,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,**就会进入拦截方法,具体就是InvocationHandler 的 invoke()方法**,当然,只会拦截那些你指定需要拦截的方法。 + +2 )实现 Mybatis 的 Interceptor 接口并复写 intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,**别忘了在配置文件中配置你编写的插件**。 + +### 5 、 Mybatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不? + +答: +1 ) Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。 +2 ) Mybatis 提供了 9 种动态 sql 标签: +`trim|where|set|foreach|if|choose|when|otherwise|bind`。 + +3 )其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。 + +### 6 、 #{}和 ${}的区别是什么? + +1 ) **#{}是预编译处理,${}是字符串替换**。 + +2 ) Mybatis 在处理`#{}`时,会将 sql 中的`#{}`替换为? 号,调用 PreparedStatement 的 set 方法来赋值; + +3 ) Mybatis 在处理`${}`时,就是把`${}`替换成变量的值。 + +4 )使用`#{}`可以有效的防止 SQL 注入,提高系统安全性。 + +1、`#`将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。 +如:`where username=#{username}`,如果传入的值是`111`,那么解析成sql时的值为`where username="111"`, 如果传入的值是id,则解析成的sql为`where username="id"`.  + +2、`$`将传入的数据直接显示生成在sql中。 +如:`where username=${username}`,如果传入的值是111,那么解析成sql时的值为where username=111; +如果传入的值是;drop table user;,则解析成的sql为:`select id, username, password, role from user where username=;drop table user;` + +3、`#`方式能够很大程度防止sql注入,`$`方式无法防止Sql注入。 + +4、`$`方式一般用于传入数据库对象,例如传入表名。 + +5、一般能用#的就别用$,若不得不使用`“${xxx}”`这样的参数,要手工地做好过滤工作,来防止sql注入攻击。 + +6、在MyBatis中,`“${xxx}”`这样格式的参数会直接参与SQL编译,从而不能避免注入攻击。但涉及到动态表名和列名时,只能使用`“${xxx}”`这样的参数格式。所以,这样的参数需要我们在代码中手工进行处理来防止注入。 + +【结论】在编写MyBatis的映射语句时,尽量采用“#{xxx}”这样的格式。若不得不使用“${xxx}”这样的参数,要手工地做好过滤工作,来防止SQL注入攻击。 + +### 7 、为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里? + +答:Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,**可以根据对象关系模型直接获取**,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。 + +### 8 、 Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么? + +答: +1 ) **Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载,association** +**指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否** +**启用延迟加载 lazyLoadingEnabled=true|false**。 + +2 )它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方 +法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单 +独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的 +对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原 +理。 + +### 9 、 MyBatis 与 Hibernate 有哪些不同? + +答: +1 ) Mybatis 和 hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己 +编写 Sql 语句,不过 mybatis 可以通过 XML 或注解方式灵活配置要运行的 sql 语句,并将 +java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对 +象。 +2 ) Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性 +能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运 +营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的 +前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定 +义多套 sql 映射文件,工作量大。 +3 ) Hibernate 对象/ 关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如 +需求固定的定制化软件)如果用 hibernate 开发可以节省很多代码,提高效率。但是 +Hibernate +的缺点是学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象 + +模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。 +总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都 +是好架构,所以框架只有适合才是最好。 + +### 11、简述 Mybatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系? + +答:Mybatis 将所有 Xml 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 +Xml 映射文件中,``标签会被解析为 ParameterMap 对象,其每个子元素会 +被解析为 ParameterMapping 对象。``标签会被解析为 ResultMap 对象,其每个子 +元素会被解析为 ResultMapping 对象。每一个`