关于算法-数据结构:PAT甲级1091-Acute-Stroke

题目粗心:给定一个三维数组,数组元素的取值为0或者1,与某一元素相邻的元素是上下左右前后6个方向的元素,如果有若干个1相邻,那么就称这些1所组成的区域为一个块,如果块中1的个数不低于T个,那就称这个块为stroke core,当初要求计算所有stroke core的1个累计个数. 算法思路:这里的块实际上和咱们常见的图外面的连通重量的概念很类似,首先想到的就是遍历这个3维矩阵的每一个连通块,而后计算非法的每个连通块的1的个数(大于等于T),这里咱们采纳邻接矩阵存储这个3维图像,留神到一点,咱们遍历中邻接点的地位为上下左右前后6个方向,那么咱们就只须要每次往则6个方向后退一格就好,应用增量矩阵$incrementOfX$,$incrementOfY$,$incrementOfZ$对以后地位x,y,z进行加减取得下一个结点的地位.咱们能够采纳BFS的思维来进行解决(用DFS有可能会导致段谬误),首先,每次抉择进行BFS的终点都是没有入队并且为数字1的点,入队终点后,在BFS的搜寻过程中,只有队列不空,出队队首节点,应用$count$统计以后连通块1的个数(队列中的节点都是数字为1的点),而后将6个领接方向的点中没有越界,数字为1并且没有退出队列的节点进行入队操作,循环完结时就返回$count$。最初应用$volumeOfStrokeCore$累计所有$count>=T$的$count$即可。 留神点:1、将多维数组写在main办法之外,能够主动赋初始值,节约工夫,避免段谬误。2、应用DFS会呈现最初2个测试点段谬误。提交后果: AC代码:#include <cstdio>#include <queue>using namespace std;struct Position{ int x,y,z;};int image[1290][130][70];bool isQueued[1290][130][70];// x,y,z地位的元素是否曾经入队int incrementOfX[6] = {0,0,0,0,1,-1};int incrementOfY[6] = {0,0,1,-1,0,0};int incrementOfZ[6] = {1,-1,0,0,0,0};int M,N,L,T;// 长宽高和阈值queue<Position> q;// 判断x,y,z是否有必要拜访bool isQualified(int x,int y,int z){ // 如果越界,则不再查问 if(x>=M||x<0||y>=N||y<0||z>=L||z<0) return false; // 0不拜访,曾经入队的不拜访 return !(image[x][y][z] == 0 || isQueued[x][y][z]);}int BFS(int x,int y,int z){ int count = 0;//以后连通块1的个数 Position p{x,y,z}; q.push(p); isQueued[x][y][z] = true; while (!q.empty()){ // 队首元素出队 p = q.front(); q.pop(); ++count; // 枚举6个方向的领接点 for (int i = 0; i < 6; ++i) { int X = p.x+incrementOfX[i]; int Y = p.y+incrementOfY[i]; int Z = p.z+incrementOfZ[i]; if(isQualified(X,Y,Z)){ Position t{X,Y,Z}; q.push(t); isQueued[X][Y][Z] = true; } } } return count;}int main(){ scanf("%d %d %d %d",&M,&N,&L,&T); for (int k = 0; k < L; ++k) { for (int i = 0; i < M; ++i) { for (int j = 0; j < N; ++j) { scanf("%d",&image[i][j][k]); } } } int volumeOfStrokeCore = 0; for (int k = 0; k < L; ++k) { for (int i = 0; i < M; ++i) { for (int j = 0; j < N; ++j) { // 遍历每一个元素 if(image[i][j][k]==1&&!isQueued[i][j][k]){ // 只有以后元素为1,并且没有退出到队列之中 int countOne = BFS(i,j,k);//取得以后连通块中的个数 if(countOne>=T){ volumeOfStrokeCore += countOne; } } } } } printf("%d",volumeOfStrokeCore); return 0;}

November 11, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1103-Integer-Factorization

题目粗心:给定正整数N,K,P,将N示意为K个正整数的P次方和(递加排序),如果有多个抉择底数和最大的计划,如果还有多个,抉择底数序列字典序最大的序列 算法思路:题目的思路倒是不难,惯例遍历都能够想得到,然而设计到的技巧还有些,最容易想到的就是从1开始遍历数字,累加以后遍历数字的P次方和,找到相等于N的即可那么在遍历数字的时候会呈现抉择该数或不抉择该数的情,并且能够反复抉择同一个数字,很天然就想到深度优先遍历的思维。上面就大体说下深度优先遍历的根本过程: 1、函数的参数有5个,别离是$factor,sum,num,N,K,P$,代表的含意别离是以后抉择的因子,累计的$P$次方和,抉择数字的个数,$N,K,P$为输出的数字,个数和次方数2、在遍历数字的时候就会发现,当数字$N$给定的时候,$N$的因子是有一个上界的(最大也就为$N$),那么咱们能够首先获取$N$的因子的搜寻上界,应用$sup$来保留。3、因为要求抉择字典序最大的序列,那么咱们优先从数字大的($sup$)开始搜寻始终到1为止,这样在获取到$P$次方和为$N$,个数为$K$的序列的时候就只须要获取底数和最大的了4、DFS递归边界:如果找到$P$次方和为$N$,因子个数为$K$的序列,就获取底数更大的序列,如果是$P$次方和大于$N$或者因子个数大于$K$或者待抉择因子等于0就回退。5、DFS递归体:首先,抉择以后因子$factor$,将$factor$保留到$vector$<$int$>$ t$中,而后进入下一轮搜寻,这里的搜寻是能够持续抉择以后因子。DFS(factor,sum+getPowerP(factor,P),num+1,N,K,P);如果不抉择以后因子,那么先将t退出之前抉择的因子,而后在进入下一轮搜寻DFS(factor-1,sum,num,N,K,P);这里应用了$vector$<$int$>$ result$寄存最优解,每一次在取得一个符合条件的序列后,就应用$getLargerSequence$函数取得序列底数更大的序列,具体代码如下: // 取得底数更大的序列void getLargerSequence(){ if(result.empty()){ result = t; } else { int sumT = 0; int sumR = 0; for (int i = 0; i < t.size(); ++i) { sumT += t[i]; sumR += result[i]; } if(sumT>sumR){ result.clear(); result = t; } }}后果的输入:如果result为空,阐明没有解,输入Impossible,否则从前往后输入每一个因子(搜寻是从大到小,所以就从前往后输入即可) 留神点:1、抉择的因子的上界不能抉择数字N,得通过计算取得其上界sup,不然测试点3,5,6,7运行超时2、在增加了上界sup条件后,如果是从1到sup进行搜寻,会导致测试点5超时3、将搜寻方向改为从大到小后,测试点2有可能会呈现谬误,第一个可能的起因是,没有增加抉择底数之和更大的序列(留神不是字典序之和更大,与从1开始搜寻的区别开)第二个起因可能是DFS的执行过程抉择了先不抉择因子factor,而后再抉择因子factor,也会导致测试点2出错(测试后果是会导致序列乱序的状况,相当于一棵树,走到叶子节点后,再下来一点又上来,到叶子节点后又下来的状况,数字的抉择出现波浪)。4、如果以上还是不能解决运行超时的问题,能够思考应用打表的技巧,将所有N的P次方下的因子全副求出存在在一个数组外面进行搜寻(不肯定有用)。5、vector的赋值操作应用援用赋值能够缩短工夫(尽管不举荐应用)提交后果: AC代码:#include <cstdio>#include <vector>using namespace std;vector<int> t;//暂存以后搜寻的一个解vector<int> result;//寄存最优解// 取得底数更大的序列void getLargerSequence(){ if(result.empty()){ result = t; } else { int sumT = 0; int sumR = 0; for (int i = 0; i < t.size(); ++i) { sumT += t[i]; sumR += result[i]; } if(sumT>sumR){ result.clear(); result = t; } }}// 取得factor的P次方int getPowerP(int factor,int P){ int r = 1; for (int i = 0; i < P; ++i) { r *= factor; } return r;}void DFS(int factor,int sum,int num,int N,int K,int P){ if(sum==N&&num==K){ // 找到平方和为N,因子个数为K的序列了 getLargerSequence();// 保留更大的序列 return; } if(sum>N||num>K||factor==0) return; //抉择factor t.push_back(factor); DFS(factor,sum+getPowerP(factor,P),num+1,N,K,P); //不抉择factor t.pop_back(); DFS(factor-1,sum,num,N,K,P);}int main(){ int N,K,P;// N为待比拟的数字,K为因子个数,P为指数 scanf("%d %d %d",&N,&K,&P); // 取得factor的上界 int sup = 1; while (getPowerP(sup,P)<=N){ ++sup; } DFS(sup-1,0,0,N,K,P); if(result.empty()){ printf("Impossible"); } else { printf("%d =",N); for(int i=0;i<result.size();++i){ printf(" %d^%d",result[i],P); if(i<result.size()-1) printf(" +"); } } return 0;}上面给出应用了打表技巧的代码,速度略微快一点#include<cstdio>#include<vector>using namespace std;/*题目要求:给定正整数N,K,P,将N示意为K个正整数的P次方和(递加排序),如果有多个抉择底数和最小的计划,如果还有多个,抉择底数序列字典序最大的序列算法思路:题目的思路倒是不难,惯例遍历都能够想得到,然而设计到的技巧还有有些,最容易想到的就是从1开始遍历数字,累加以后遍历数字的P次方和,找到相等于n的即可那么在遍历数字的时候会呈现抉择该数或不抉择该数的状况,很天然就想到深度优先遍历的思维.1.在遍历数字的时候就会发现,当数字n给定的时候,n的因子是有一个上界的(最大也就为n),而且P最小还为2,那么为了不便起见,能够应用打表的技巧将可能为n的因子的数字全副保存起来,而后问题就天然转化为在所有可能的因子中寻找组合是的因子的P次方和等于n,这样就好求的多,所以编写函数init(),起始数字从0开始(0不能取),将所有数字的P次方小于等于N的增加进行数组factor中。其下标就是数字,对应的值就是该数字的P次方2.在DFS中咱们有4个参数count:统计因子的个数,sumF:因子的总和,sumQ:因子的p次方和,num:以后待增加的因子,同时也是factor的下标,遍历程序咱们能够从前往后,也能够从后往前遍历,然而这里只能从后往前,前面再解释,DFS分为递归边界和递归体 1)递归边界,第一个递归边界就是找到满足条件序列时,如果以后的序列的sumF更大,那么保留sumF并且更新序列,第二个递归条件就是找不到满足条件序列时,这里有3个 num==0||count>k||sumQ>n,也即是0数字不能为因子,如果遍历到0阐明遍历结束,或者曾经抉择了k个数了,不能再抉择,或者以后序列P次方和曾经大于N了,不能再抉择 2)递归体:分为抉择num数字和不抉择num数字,当咱们抉择num数字的时候要留神一点,这里num可反复抉择,所以递归时num不变,不抉择时,num是减一 !!!!这里有一点让我百思不得其解的中央,就是如果不抉择在抉择的后面会在测试点2的中央呈现谬误 阐明:1.如果是从返回后遍历得将递归失败num的条件该为num>factor.size()-1,判断上界. 2.如果是从返回后遍历得那就得每次取得序列后判断是否比之前取得序列字典序更大,这样会导致测试点5超时,所以依照字典序大到小遍历数字会更快3.判断没有因子的条件是maxFsum==-1*/vector<int> r,temp,factor;//保留最终的合成序列和每次遍历的合成序列 ,factor保留数字n可能的因子的p次方,不写这个可能会超时 int n,k,p;//数字n,合成的个数,幂次int maxFsum = -1;//保留最大因子的和 //n的p次方 int Pow(int n){ int r = 1; for(int i=0;i<p;++i){ r *= n; } return r;}void init(){//初始化factor数组 for(int i=0;Pow(i)<=n;++i){ factor.push_back(Pow(i)); }}/*count:统计因子的个数sumF:因子的总和sumQ:因子的p次方和 num:以后待增加的因子,同时也是factor的下标 */void DFS(int count,int sumF,int sumQ,int num){ if(count==k&&sumQ==n){//递归边界,找到了p次方和为n并且因子个数为k的序列了 if(maxFsum<sumF){//temp的序列因子和更大 r = temp;//将temp赋值给r maxFsum = sumF; } return; } //num>factor.size()-1,如果是从1开始往前遍历的话,得扭转num的判断条件为这句话,因为上界就是factor.size()-1,不然会进入有限递归 if(num==0||count>k||sumQ>n) return;//不能为数字0,曾经抉择了k个数字,或者p次方和大于n,间接返回// DFS(count,sumF,sumQ,num-1);//不抉择num,如果先不抉择num的话,测试点2过不了 temp.push_back(num); DFS(count+1,sumF+num,sumQ+factor[num],num);//num的抉择可能有多个,这里额定要留神 temp.pop_back();//退出以后num DFS(count,sumF,sumQ,num-1);//不抉择num}int main(){ scanf("%d %d %d",&n,&k,&p); init(); DFS(0,0,0,factor.size()-1);//从最初一个可能的因子开始抉择 if(maxFsum==-1){//没有因子 printf("Impossible"); } else{//输入r即可,不过得倒序输入 printf("%d = ",n); int len = r.size(); for(int i=0;i<len;++i){ if(i==0){ printf("%d^%d",r[i],p); }else{ printf(" + %d^%d",r[i],p); } } } return 0;}

November 11, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1097-Deduplication-on-a-Linked-List

题目粗心:给定一条单链表,要求合成为两条链表,第一条是去除绝对值反复数值结点的链表,第二条是去除的结点组成的链表 算法思路:首先应用node存储所有输出的节点,removed存储须要删除的节点。因为须要判断node中的节点是否须要删除,咱们应用isExist示意与该节点数据的绝对值雷同的节点是否存在,应用work_n保留以后指向在node的哪个节点,初始为begin_address,只有isExist[abs(node[work_n].data)]==true,就阐明须要将该节点移除到removed链表中,具体方法是结构一个和以后节点地址和数据雷同的temp节点,而后增加进removed中,并且应用work_r始终保留removed链表的尾结点地址,并且须要删除work_n指向的节点,这里应用pre_work_n保留了work_n前驱节点地址,初始为-1,让pre_work_n指向work_n的下一个节点,并且work_n后移即可。如果isExist[abs(node[work_n].data)]==false,阐明须要保留该节点,将isExist[abs(node[work_n].data)]置为true,并且pre_work_n和work_n均向后挪动。最初依照要求输入即可。 留神点:1、地址得保留5位有效数字,-1得特判。2、对于PAT的链表题目都有可能会存在输出了不在链表上的节点。 提交后果: AC代码:#include <cstdio>#include <algorithm>#include <unordered_map>using namespace std;struct Node{ int address; int data; int next;}node[100005],removed[100005];unordered_map<int,bool> isExist;int main(){ int N,begin_address; scanf("%d %d",&begin_address,&N); Node p{}; for (int i = 0; i < N; ++i) { scanf("%d %d %d",&p.address,&p.data,&p.next); node[p.address] = p; } int work_n = begin_address;// work_n为node链表的工作节点 int pre_work_n = -1;//pre_work_n为work_n在node中的前驱 // 结构removed的头结点 Node head{100004,-1,-1}; removed[head.address] = head; int work_r = head.address;//work_r为removed链表的工作节点 while (work_n!=-1){ int data = abs(node[work_n].data); if(isExist[data]){ // 须要移除该节点 Node temp{work_n,node[work_n].data,-1}; // 采纳尾插法将temp插入搭配removed链表中 removed[temp.address] = temp; removed[work_r].next = temp.address; work_r = removed[work_r].next; // 删除work_n指向节点,并更新work_n node[pre_work_n].next = node[work_n].next; work_n = node[work_n].next; } else { // 保留 isExist[data] = true; pre_work_n = work_n; work_n = node[work_n].next; } } // 输入node链表 for (;begin_address!=-1;begin_address = node[begin_address].next){ if(node[begin_address].next!=-1){ printf("%05d %d %05d\n",begin_address,node[begin_address].data,node[begin_address].next); } else { printf("%05d %d -1\n",begin_address,node[begin_address].data); } } //输入removed链表 for(int address=removed[100004].next;address!=-1;address=removed[address].next){ if(removed[address].next!=-1){ printf("%05d %d %05d\n",address,removed[address].data,removed[address].next); } else { printf("%05d %d -1\n",address,removed[address].data); } } return 0;}

November 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1052-Linked-List-Sorting

题目粗心:给定N个结点起始地址为$begin$_$address$的单链表,要求依据data进行排序,而后输入链表 算法思路:咱们首先用$node$数组存储所有输出的结点,在输出的时候应用$dataToAddress$记录数据到地址的映射(数据和地址是绑定的,无论怎么样都不会变动),因为对于输出可能会有不在链表上的结点,所以应用$isExist$记录所有输出的节点,这样就能够判断起始节点是否存在输出节点中,目标是为了解决链表为空的状况。对于只有局部无效节点的状况,咱们须要遍历$node$数组,并应用$data$数组保留所有在链表中的节点的数据,$count$记录无效节点的个数,这里排序采纳的是索引排序的技巧,间接对$data$数组排序,那么再利用$dataToAddress$就能够不便的晓得对应节点的地址,并且$next$就是下一个节点的$address$,不过得留神,最初一个节点的$next$为-1,须要手动赋值,同时须要记录新的起始地址为$data$数组中第一个节点的地址。最初依照要求进行输入即可 留神点:1、输出节点会呈现有效的状况,所以得遍历输出节点,保留无效节点的信息,测试点1和4考查。 2、测试点4考查所有节点都是有效的状况,这个时候得输入"0 -1" 3、地址保留5位有效数字,-1得特判。 提交后果: AC代码:#include <cstdio>#include <unordered_map>#include <algorithm>using namespace std;struct Node{ int address; int data; int next;}node[100005];unordered_map<int,int> dataToAddress;unordered_map<int,bool > isExist;int main(){ int N,begin_address; scanf("%d %d",&N,&begin_address); Node p{}; for (int i = 0; i < N; ++i) { scanf("%d %d %d",&p.address,&p.data,&p.next); node[p.address] = p; dataToAddress[p.data] = p.address; isExist[p.address] = true; } if(!isExist[begin_address]){ printf("0 -1\n"); return 0; } int data[N];// 保留所有的数据 int count = 0;//链表上的节点个数 for (int address = begin_address; address !=-1 ; address=node[address].next) { data[count++] = node[address].data; } sort(data,data+count); for (int i = 0; i < count-1; ++i) { int address = dataToAddress[data[i]]; int nextAddress = dataToAddress[data[i+1]]; node[address].next = nextAddress; if(i==0){ // 记录新的起始地址 begin_address = address; } } // 最初一个节点的下一个节点地址为-1 node[dataToAddress[data[count-1]]].next = -1; printf("%d %05d\n",count,begin_address); while (begin_address!=-1){ if(node[begin_address].next!=-1){ printf("%05d %d %05d\n",node[begin_address].address,node[begin_address].data,node[begin_address].next); } else { printf("%05d %d -1\n",node[begin_address].address,node[begin_address].data); } begin_address = node[begin_address].next; } return 0;}

November 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1032-Sharing

题目粗心:给定两条链表的首地址和若干结点地址、数据、下一个结点的地址,求两条链表的公共节点地址,如果没有输入-1。 算法思路:因为之前在考408做过做过题,所以思路用的就是王道书上讲的(过了良久还记得,$=(′`*)))$唉),咱们应用$node$数组存储所有输出的节点,应用$word1$存储第一个单词组成的链表,$word2$存储第二个单词组成的链表。咱们首先遍历$node$数组,获取$word1$链表和其长度$lengthOfWord1$,$word2$链表和其长度$lengthOfWord2$.而后让长度更长的链表的头指针先向前走$|lengthOfWord1-lengthOfWord2|$步,而后再让$word1$和$word2$的工作指针同时向后走,在指针相遇的时候就退出循环,如果以后指针的地址为-1,则输入-1,否则保留5位数输入地址。 留神点:1、地址得保留5位输入。2、输出的时候得加空格,因为%c能够承受空格。 提交后果: AC代码:#include <cstdio>using namespace std;struct Node{ int address; char data; int next;}node[100005],word1[100005],word2[100005];int main(){ int begin_word1,begin_word2,N; scanf("%d %d %d",&begin_word1,&begin_word2,&N); Node p{}; for (int i = 0; i < N; ++i) { scanf("%d %c %d",&p.address,&p.data,&p.next); node[p.address] = p; } int lengthOfWord1=0,lengthOfWord2=0; for (int address=begin_word1;address!=-1;address=node[address].next) { p.address = node[address].address; p.data = node[address].data; p.next = node[address].next; word1[p.address] = p; ++lengthOfWord1; } for (int address=begin_word2;address!=-1;address=node[address].next) { p.address = node[address].address; p.data = node[address].data; p.next = node[address].next; word2[p.address] = p; ++lengthOfWord2; } if(lengthOfWord1>lengthOfWord2){ // begin_word1先走lengthOfWord1-lengthOfWord2步 for (int i = 0; i < lengthOfWord1 - lengthOfWord2; ++i) { begin_word1 = word1[begin_word1].next; } } else { // begin_word2先走lengthOfWord2-lengthOfWord1步 for (int i = 0; i < lengthOfWord2 - lengthOfWord1; ++i) { begin_word2 = word2[begin_word2].next; } } // 而后两者同时后退,相遇的时候要么是独特的节点,要么是-1 while (begin_word1!=begin_word2){ begin_word1 = word1[begin_word1].next; begin_word2 = word2[begin_word2].next; } if(begin_word1==-1){ printf("-1"); } else { printf("%05d",begin_word1); } return 0;}

November 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1074-Reversing-Linked-List

题目粗心:现有一个长度为N,终点为begin_address的单链表,要求每K个结点进行逆置,最初不够K个结点的不必逆置,要求输入最初逆置实现的单链表。 算法思路:这里采纳模仿单链表逆置的办法,咱们应用node数组存储所有输出的节点,result存储最初逆置完结后的单链表。对于单链表的逆置应该想到头插法,于是咱们首先为result链表创立一个头结点,这里头结点的地址得保障肯定不在输出的链表之中,于是选取了100004作为头结点地址。而后计算该单链表有几组须要逆置的结点,应用group变量保留,对于每一组,咱们应用头插法插入到新建链表result中,这样就实现了K个结点的逆置,同时为了不便更新头结点的地位,咱们应用tail来标记result尾节点的地位,在第一次插入的时候更新为插入节点的地址。这样在每一组插入结束后就更新头结点地位为tail,以此类推,待所有组的节点均插入到result链表之中。最初判断是否还残余结点,如果还有,那么用尾插法插入到result链表中,而后从头结点的下一个结点输入该新链表即可。 留神点:1、对于PAT的题目有可能输出的数据有效,尤其是单链表,所有咱们得先遍历一遍链表,统计链表无效结点个数count(在输出单链表上的节点个数),目标是为了计算逆置的组数group。2、因为题目给的地址全是5位数字,输入也得保障是5位数,除了-1例外。3、判断是否还有残余插入结点的办法就是work_n是否等于-1,因为等于-1阐明所有组数的结点插入结束后就达到了空结点,前面不可能再有结点4、对于result链表的取得,肯定得新建一个节点p,而后更新p的数据再插入到result中,不要间接更新result的地址,比方result[work_n].address = work_n,因为result[work_n]示意的是节点,节点都没有增加进链表中,如何记录地址信息呢?5、能够应用建设address与data,address与next的映射关系,间接逆置address,而后其next为上一个节点的address来解决,然而并没有间接逆置单链表直观(容易想到)。 提交后果: AC代码:#include <cstdio>using namespace std;struct Node{ int address; int data; int next;}node[100005],result[100005];int main(){ int begin_address,N,K; scanf("%d %d %d",&begin_address,&N,&K); Node n{};// 暂存输出的每一个节点 for (int i = 0; i < N; ++i) { scanf("%d %d %d",&n.address,&n.data,&n.next); node[n.address] = n; } // 统计输出的链表中无效节点的个数 int count = 0; int work_n = begin_address;//工作在node链表上的指针 while (work_n!=-1){ ++count; work_n = node[work_n].next; } // 计算能够逆置的组数 int group = count/K; // 创立result链表的头结点 Node head{100004,0,-1}; result[head.address] = head; // 对于每一组的每个节点采纳头插法插入到result链表中去 work_n = begin_address;// 重置work_n int begin = head.address;// result的头指针 int tail = -1;//result的尾指针,指向最初一个节点 for (int i = 0; i < group; ++i) { for (int j = 0; j < K; ++j) { Node p{work_n, node[work_n].data, result[begin].next}; result[begin].next = p.address; result[p.address] = p; work_n = node[work_n].next;// 指向下一个节点地位 if(j==0){ tail = p.address;//更新尾节点的地址 } } // 每一组节点逆置结束就更新头节点 begin = tail; } while (work_n!=-1){ // 前面还有一些无需逆置的节点,采纳尾插法插入到链表中 Node p{work_n, node[work_n].data, -1}; result[begin].next = p.address; result[p.address] = p; begin = result[begin].next;// 指向result下一个节点地位 work_n = node[work_n].next;// 指向node下一个节点地位 } for(begin = result[100004].next;begin!=-1;begin = result[begin].next){ if(result[begin].next==-1){ printf("%05d %d -1\n",result[begin].address,result[begin].data); } else { printf("%05d %d %05d\n",result[begin].address,result[begin].data,result[begin].next); } } return 0;}

November 9, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1056-Mice-and-Rice

题目粗心:给出NP只老鼠的品质,并且给出它们较量的程序,而后每NG只老鼠为一组,最初不够NG只的也为一组,而后组内比赛,体重最大的胜出进入下一轮较量,本轮较量输掉的排名均一样,要求输入依照编号从小到大输入最初的排名,这里得留神下题目的意思,也就是第3行给的数据,这个实际上是老鼠的下标,而后输入的程序是依照0到NP-1的程序 算法思路:示例解读: 首先得明确一点,在给定初始参赛人数和每组人数的状况下,能够明确晓得具体较量的次数(轮次)和每一次较量结束后,失败者的名次。对于具体较量的次数的获取如下代码: int round = 1;//总轮次// 计算出总轮次while(NP!=1){ if(NP%NG==0){ NP = NP/NG; }else { NP = NP/NG + 1; } ++round;}每一场较量失败者的名次的获取如下代码: int round = 1;//总轮次// 计算出总轮次和每一次较量失败者的名次 while(NP!=1){ if(NP%NG==0){ NP = NP/NG;// 以后round轮较量有NP集体胜出,表明以后轮的loser后面有NP集体,他们的排名都是NP+1 }else { NP = NP/NG + 1; } rankByRound[round] = NP+1; ++round;}rankByRound[round] = 1;// 记录最初一轮的名次这里应用了round记录总的较量次数,rankByRound数组记录了每一轮较量失败者的名次。而后咱们将每一场较量的参赛人数应用一个队列保留,queue<int> q[currentRound]保留的就是以后轮次较量的参赛人数,而后接着就是模仿较量的过程,咱们应用popNum记录以后轮次曾经加入了较量的人数,size记录以后轮次总的较量人数,如果size==1阐明只有一个人,间接赋值排名即可,否则,只有popNum<size阐明以后的较量还未完结,就让needPop记录以后较量小队的人数,而后在needPop的人中选择权值最大的人作为winner,退出下一轮的较量中q[currentRound+1].push(winner); 。所有的失败者都会被赋予排名并且从新回到以后队列(rank[loser] = rankByRound[currentRound];q[currentRound].push(loser))。记得更新popNum和currentRound,最初输入排名即可。 留神点:1、当队列中只有一个人的时候须要特判,间接退出循环即可。2、如果测试点5运行超时,能够思考更改获取排名的形式,具体见AC代码1。如果不想扭转获取排名的形式,能够参考AC代码2。 提交后果: 这里给出2种不同的实现形式,次要差别在于获取排名的形式上,AC代码1应用了先计算出所有轮次的排名,在每一轮较量中,对失败者赋值排名。AC代码2而是最初遍历每一个队列进行的排名赋值操作。AC代码1应用遍历队列残余元素获取排名会导致最初一个测试点超时。AC代码1:#include<cstdio>#include<queue>using namespace std;queue<int> q[15];// 每一轮较量都应用一个队列来存储int main(){ int NP,NG;//程序员人数和最多NG只老鼠组成一组 scanf("%d %d",&NP,&NG); int weight[NP];// 每一只老鼠的权值 int rank[NP];// 每一只老鼠的最终排名 int rankByRound[NP];// 每一轮失败者的名次 for(int i=0;i<NP;++i){ scanf("%d",&weight[i]); } int currentRound = 1;//以后较量的轮次 // 初始化较量对象 int order; for(int i=0;i<NP;++i){ scanf("%d",&order); q[currentRound].push(order); } int n = NP; int round = 1;//总轮次 // 计算出总轮次和每一次较量失败者的名次 while(NP!=1){ if(NP%NG==0){ NP = NP/NG;// 以后round轮较量有NP集体胜出,表明以后轮的loser后面有NP集体,他们的排名都是NP+1 }else { NP = NP/NG + 1; } rankByRound[round] = NP+1; ++round; } rankByRound[round] = 1;// 记录最初一轮的名次 // 较量开始 while(currentRound<=round){ int popNum = 0;//曾经出队的人数 int size = q[currentRound].size();//以后轮较量参赛人数 if(size==1){ // 以后轮为最初一轮,无需较量,间接赋值排名即可 rank[q[currentRound].front()] = rankByRound[currentRound]; break; } while(popNum<size){ int needPop = size-popNum>=NG?NG:size-popNum;//须要出队人数 int winner = q[currentRound].front();//首先保留队首元素为赢家 q[currentRound].pop(); ++popNum; for(int i=0;i<needPop-1;++i){ // 在残余的needPop-1个较量人员中获取权值最大的那个 if(weight[winner]<weight[q[currentRound].front()]){ // 队首元素胜,首先将winner退出队列,而后更新winner rank[winner] = rankByRound[currentRound];// 给loser赋值以后排名 q[currentRound].push(winner); winner = q[currentRound].front(); q[currentRound].pop(); } else { // 队首元素败,将队首元素增加至开端 int loser = q[currentRound].front(); rank[loser] = rankByRound[currentRound];// 给loser赋值以后排名 q[currentRound].pop(); q[currentRound].push(loser); } ++popNum; } // 将赢家退出下一轮的较量中 q[currentRound+1].push(winner); } ++currentRound; } // 输入排名 for(int i=0;i<n;++i){ printf("%d",rank[i]); if(i<n-1) printf(" "); } return 0;} AC代码2:#include<cstdio>#include<queue>#include<vector>using namespace std;/*题目要求: 给出NP只老鼠的品质,并且给出它们较量的程序,而后每NG只老鼠为一组,最初不够NG只的也为一组,而后组内比赛,体重最大的胜出进入下一轮较量,本轮较量输掉的排名均一样,要求输入依照编号从小到大输入最初的排名,这里得留神下题目的意思,也就是第3行给的数据,这个实际上是老鼠的下标,而后输入的程序是依照0到NP-1的程序 算法思路:这里放上本人的手稿,不便了解,就是个模仿的题目(最烦这种的了)第一轮(0): 6 0 (8)(第一组,8获胜) ,(7) 10 5(第二组,7获胜),(9) 1 4(第三组,9获胜),2 (3)(第4组,3获胜)第二轮(1): (8) 7 9(第一组,8获胜),(3)(第二组,就一个,3获胜)第三轮(2): (8) 3(第一组,8获胜)第四轮(3): (8)(第一组,只有一个较量完结)1.数据的存储:应用w,order数组存储每只老鼠的数量和较量的初始程序,rank数组记录最初的每只老鼠的排名,queue<int> v[1000]保留每一轮的失败者,最初一轮是胜利者turn记录以后较量的轮次,初始为0(和下面第几轮前面括号雷同) 2.最开始的较量程序全副保留在v[0]中,而后对每一轮较量进行模仿,咱们应用count记录每一轮的参赛残余老鼠数目,起因是咱们将会把较量输掉的老鼠从新入队,避免出队谬误,那么只有count!=0就阐明以后轮较量还没完结,应用vector<int> a暂存以后队伍的老鼠的下标,目标是为了求得以后最大体重的老鼠的下标,而后只有count>=ng就出队ng只老鼠进a中,否则就出队count只老鼠(切记不能以队列为空为判断条件,因为之前队伍较量完后输掉的老鼠从新入队了),而后遍历a数组求得最大体重老鼠的体重maxW,最初从新再遍历a,只有以后老鼠的体重不等于maxW,阐明是失败者,就从新进入以后v[turn]队列,否则就进入下一轮较量v[turn+1],在以后轮较量结束后,++turn进入下一轮较量,并且在以后轮参赛老鼠个数为1的时候退出循环(因为没有老鼠可比拟了,阐明是最初的胜者)3.排名的获取:遍历v数组,不过得从以后最初的一轮较量往前遍历,因为最初胜出的才是第一名,当i==turn时,阐明是第一名,则rank[v[i].front()] = 1;否则以后轮所有老鼠的排名均为后面老鼠数量num+1(num初始为0),而后更新下一轮老鼠后面的个数num+=v[i].size()4.最初依照程序输入rank即可一点疑难:vector<queue<int> > v无奈应用,而queue<int> v[1000]却能够不晓得为什么,求指导 */queue<int> v[1000];//保留每一轮的失败者,最初一轮是胜利者 int turn = 0;//以后较量的轮次 int main(){ int np,ng;//总老鼠数和每队老鼠数 scanf("%d %d",&np,&ng); int w[np],order[np];//每只老鼠的分量,较量的程序 int rank[np];//最终的排名 int count;//每一轮的参赛残余老鼠数目 for(int i=0;i<np;++i){ scanf("%d",&w[i]); } for(int i=0;i<np;++i){ scanf("%d",&order[i]); v[turn].push(order[i]);//将第一轮较量程序增加进队列中 } while(true){ count = v[turn].size();//count初始为以后轮所有老鼠 if(count==1){//以后轮较量只有一只老鼠阐明较量完结 break; } while(count!=0){//同一轮次 vector<int> a;//暂存以后队伍的老鼠的下标 if(count>=ng){//残余的老鼠数目大于等于ng,就出队ng个老鼠 for(int i=0;i<ng;++i){ a.push_back(v[turn].front());//将以后队伍老鼠退出a中 v[turn].pop(); } count -= ng;//更新count }else{//残余老鼠个数不够ng个,所有残余count只老鼠为一对 while(count!=0){ a.push_back(v[turn].front()); v[turn].pop(); --count; } } int maxW=-1;//以后队伍最重的老鼠的体重 int len = a.size();//以后队伍人数 for(int i=0;i<len;++i){//找到以后队伍中最重的老鼠 if(w[a[i]]>maxW){ maxW = w[a[i]]; } } for(int i=0;i<len;++i){ if(w[a[i]]!=maxW){//失败者进入留在以后轮 v[turn].push(a[i]); }else{//胜利者进入下一轮 v[turn+1].push(a[i]); } } } ++turn;//进入下一轮较量 } int num = 0;//记录以后轮后面有多少只老鼠 for(int i=turn;i>=0;--i){ int len = v[i].size(); for(int j=0;j<len;++j){//第i轮队列残余的人的排名均雷同 rank[v[i].front()] = num+1; v[i].pop(); } num += len;//更新为下一轮老鼠后面有多少只老鼠 } for(int i=0;i<np;++i){ printf("%d",rank[i]); if(i<np-1) printf(" "); } return 0;}

November 8, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1051-Pop-Sequence

题目粗心:现有容量为M的栈,入栈序列是1到N,现有K个长度为N的出栈序列,请问是否依照固定的入栈序列是否能够通过出栈取得输出的出栈序列 算法思路:第一个就是得看清楚题目,题目说的是入栈序列为1到N,意思是入栈的程序得依照1到N进行输出,在输出的时候能够有出栈操作。而后咱们抉择 STL库中的stack<int> s作为咱们的操作数栈,应用pop_num保留以后待比拟的出栈序列的元素(须要输出),而后在每一次的查问过程中,用isPossible记录以后查问的出栈序列是否非法,初始为true,应用push_num记录待入栈的元素(和栈顶元素没有什么关系),接来下就是具体的比拟操作,咱们首先将栈清空,这里也能够抉择将栈的定义写在循环体外部,对于每一次输出的pop_num,如果push_num<=pop_num,阐明待出栈元素还没有入栈,得先顺次入栈所有小于等于pop_num的元素,如果栈的容量无奈退出须要入栈的元素,那么就让isPossible置为false,并且终止程序持续运行,否则就模仿入栈过程,并且出栈栈顶元素,如果push_num>pop_num,阐明待出栈元素曾经入栈,那么就只须要出栈栈顶元素,而后比拟和pop_num是否相等,如果不相等就让isPossible置为false,并且终止程序持续运行。最初在所有数字输出结束后判断isPossible,为true输入YES,否则输入NO. 留神点:1、这里因为是应用的pop_num承受每一个待比拟的数字,所以在isPossible置为false后不能退出循环,得应用continue,并且在承受pop_num后,只有isPossible为false,就持续循环,不让程序往下执行。2、每次比拟的时候栈得清空并且push_num每次都得置为1. 提交后果: AC代码1:#include<cstdio>#include<stack>using namespace std;stack<int> s;int main(){ int M,N,K;//栈的容量,入栈序列长度,比拟的序列个数 scanf("%d %d %d",&M,&N,&K); int pop_num;// 以后待比拟的出栈序列的元素 for(int i=0;i<K;++i){ bool isPossible = true; int push_num = 1;//待入栈的元素 if(i!=0){ // 每次都得清空栈的元素 while(!s.empty()){ s.pop(); } } for(int j=0;j<N;++j){ scanf("%d",&pop_num); if(!isPossible){ continue; } if(push_num<=pop_num){ if(pop_num-push_num+1>M-s.size()){ // 须要进栈的元素个数超过栈的残余容量 isPossible = false; continue; } // 栈能够容入须要入栈的元素 while(push_num<=pop_num){ // 入栈 s.push(push_num); ++push_num; } // 出栈 s.pop(); // 这里不必判断出栈的元素和出栈序列中须要比拟的元素是否相等 }else { //出栈 int top_num = s.top(); s.pop(); if(top_num!=pop_num){ isPossible = false; continue; } } } if(isPossible){ printf("YES\n"); }else{ printf("NO\n");; } } return 0; }下面的写法必须得接管结束所有的pop_num,否则程序会进行,这里给出另外一种办法,应用数组先接管所有待比拟的出栈序列,而后在进行查问。AC代码2:#include<cstdio>#include<stack>#include<cstring>using namespace std;int main(){ int M,N,K;//栈的容量,入栈序列长度,须要判断的序列个数 scanf("%d %d %d",&M,&N,&K); int num; for(int i=0;i<K;++i){ bool flag = true;//判断以后序列是否能够输入 stack<int> s; int num = 1;//以后须要入栈的元素 int a[N]; for(int j=0;j<N;++j){ scanf("%d",&a[j]); } for(int j=0;j<N;++j){ int temp = a[j];//以后须要出栈的元素 while(num<=temp){//将num到temp的元素全副进栈 s.push(num); if(s.size()>M){ flag = false; break; } ++num; } if(!flag) break;//栈溢出 int k = s.top(); if(k!=temp){ flag = false;//num>temp并且栈顶元素和出栈元素不同 break; }else{ s.pop();//雷同,则出栈 } } if(!flag){ printf("NO\n"); }else{ printf("YES\n"); } } return 0;}

November 7, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1022-Digital-Library

题目粗心:给出N本书的编号、书名、作者、关键词(可能有多个)、出版社及出版年份,并给出M个查问,每个查问给出书名、作者、关键词(单个)、出版社及出版年份中的一个,要求输入满足该给出信息的所有书的编号。 算法思路:咱们应用unordered_map<string,vector<int>> hashToID[6]保留5个关键字查问对应的所有已知的id,1到5别离示意title,author,keyword,publisher,publish_year与书籍id的对应.在输出的时候,咱们对于每一个输出的字符串依据其输出程序建设不同的类别到id的映射,比方第一个输出的字符串s肯定是book title,那么就将id增加到hashToID[1][s]中,顺次类推,惟一不同的是解决keywords的状况,这里应用了deal函数来解决,将每一个字符增加到s中,遇到空格阐明是一个word,就建设该word与id的映射,不过得记得解决最初一个word(在循环外部无奈解决)。在最初输入的时候,咱们应用tag记录查问的类别,s为查问的关键字,如果hashToID[tag].find(s)==hashToID[tag].end(),阐明以后类别中没有该关键字,就输入Not Found,否则输入hashToID[tag][s]的每一个id号。 以后这种写法应该是耗时最短的了,并且代码量也较小。留神点:1、id号固定为7位,所以输入得保留7位有效数字,测试点3和4考查,如果应用string就不会有这个问题。2、getline的输出前得应用getchar()承受回车,不然就会出错。 提交后果: AC代码:#include <cstdio>#include <unordered_map>#include <vector>#include <iostream>#include <algorithm>using namespace std;unordered_map<string,vector<int>> hashToID[6];// 解决keywordsvoid deal(const string& keywords,int id){ string s; for (char keyword : keywords) { if(keyword!=' '){ s += keyword; } else { hashToID[3][s].push_back(id); s = ""; } } // 解决最初一个keyword hashToID[3][s].push_back(id);}int main(){ int N; scanf("%d",&N); int id; string s; for (int i = 0; i < N; ++i) { scanf("%d",&id);// 首先输出id getchar();// 承受回车 getline(cin,s);// 输出book title hashToID[1][s].push_back(id); getline(cin,s);// 输出name of an author hashToID[2][s].push_back(id); getline(cin,s);// 输出keywords deal(s,id); getline(cin,s);// 输出name of a publisher hashToID[4][s].push_back(id); getline(cin,s);// 输出 year hashToID[5][s].push_back(id); } // 开始查问 scanf("%d",&N); int tag; for (int k = 0; k < N; ++k) { scanf("%d: ",&tag); getline(cin,s); printf("%d: %s\n",tag,s.c_str()); if(hashToID[tag].find(s)==hashToID[tag].end()){ printf("Not Found\n"); } else { sort(hashToID[tag][s].begin(),hashToID[tag][s].end()); for(auto &item:hashToID[tag][s]){ printf("%07d\n",item); } } } return 0;}

November 6, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1071-Speech-Patterns

题目粗心:给出一个字符串,要求统计呈现次数最多的单词,并且输入该单词和次数(不辨别大小写,输入小写),单词为数字和英文字母的组合 算法思路:间接一边输出一边解决就好,应用c保留输出的每一个字符,s保留呈现的每一个单词,如果是大写字符就转化为小写字符,而后将c增加到s的开端,如果是小写字符或者数字将c增加到s的开端,如果是其余字符并且s不为空,就开始统计该单词呈现的次数,同时判断以后单词s的次数是否为最大,如果是就更新max_count和r(保留输入后果),最初如果是回车就退出循环,输入后果即可 留神点:1、如果输出a,该当输入a 1,对应测试点4,这里的解决就是在最初判断是否是回车,让统计次数逻辑首先进行。 提交后果: AC代码:#include <cstdio>#include <unordered_map>#include <string>using namespace std;unordered_map<string,int> counts;//每一个word呈现的频次int main(){ char c; string s;// 暂存每一个单词 int max_count = -1;// 呈现的最高频次 string r;// max_count对应的word while (true){ scanf("%c",&c); if(c>='A'&&c<='Z'){ // 大写字符转化为小写字符 c = c+32; s += c; } else if((c>='a'&&c<='z')||(c>='0'&&c<='9')){ // 小写字母或者数字 s += c; } else { // 不是英文字符 if(!s.empty()){ ++counts[s]; if(counts[s]>max_count){ max_count = counts[s]; r = s; } s = "";// 清空s } } if(c=='\n') break; } printf("%s %d",r.c_str(),max_count); return 0;}

November 6, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1100-Mars-Numbers

题目粗心:实现火星文和地球文的互相转化 算法思路1:用unit数组保留10进制个位到火星文个位的映射,用decade数组保留10进制十位到火星文10位的映射,留神最大不超过169,阐明火星文最大2位,应用Unit和Decade别离保留火星个位和十位与10进制的个位和十位的映射.记得初始化!!!!!,应用字符串承受输出的数字(读一行),而后判断s[0]是否是数字,如果是,就将该数字首先转化为13进制数,每一位用数组t保留,而后判断是否只有个位或者只有十位,如果是得只输入个位或者十位,否则就是得先输入十位而后输入个位,这里有个特例得特判输入就是数字0,输出数字0得输入tret,不然会呈现测试点1格局谬误,如果s[0]不是数字就将获取以后位的火星文对应的10进制数(用字符串切割),而后将其转化为10进制数,而后输入即可,留神在火星文只有1位的时候得判断是个位还是十位的火星文。 算法思路2:同样用unit数组保留10进制个位到火星文个位的映射,用decade数组保留10进制十位到火星文10位的映射,然而留神到数字最大是168,这个数字其实算是比拟小的了,所以间接通过将0~168的数字和火星文建设互相一一映射,在输出的时候间接查表就能够实现输入,无需像思路1那样,每次都进行解决。 建设十进制数和火星文的一一映射的办法:首先应用MarsToDecimal和DecimalToMars别离保留火星文到十进制数和十进制数到火星文的映射,对于任意一个不超过2位的数字,都能够看做是十位和个位的联合,对于只有1位的,十位看成0就好,(看上去是废话,其实不是)比方十进制数13就能够写成10+3别离是10的1倍数字和3的联合,25能够写成20+5别离是10的2倍和数字5的联合,那么咱们能够看到,10为进制数,是固定的数字,而1,3和2,5都理论是个位数字,能够看出对于13进制数,取得168数字的全副只须要个位数字和进制数13就能够实现。那么咱们就先建设个位和13的倍数与火星文的映射,其实就是unit数组和decade数组保留的映射关系,而后咱们应用指针i示意十位的数字(25的十位数字为2),j示意个位数字,都从1到12遍历,那么i*13+j就是没有建设映射的数字,其对应的火星文为decade[i]+" "+unit[j],比方14对应tam jan(decade[1]+" "+unit[1])。那么保留该映射关系就是如下代码DecimalToMars[i*13+j] = decade[i]+" "+unit[j]和MarsToDecimal[DecimalToMars[i*13+j]] = i*13+j 提交后果: 留神点:1、13的整倍数最初不要输入tret,比方13应该输入tam而不是tam tret。AC代码1:#include<cstdio>#include<string>#include<unordered_map>#include<cstring>#include<iostream> using namespace std;string unit[13] = {"tret","jan", "feb", "mar", "apr", "may", "jun", "jly", "aug", "sep", "oct", "nov", "dec"};//个位string decade[13] = {"","tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mer", "jou"};//十位unordered_map<string,int> Unit;//火星个位对应的10进制个位unordered_map<string,int> Decade;//火星十位对应的10进制十位void init(){ for(int i=0;i<13;++i){ Unit[unit[i]] = i; } for(int i=1;i<13;++i){ Decade[decade[i]] = i; }} int main(){ init(); int n; scanf("%d%*c",&n);//疏忽一个字符型输出项 for(int i=0;i<n;++i){ string s; getline(cin,s); if(s[0]>='0'&&s[0]<='9'){//数字,转化为火星文 int t[2];//暂存每一位数字 memset(t,0,sizeof(t));//初始化t,必须得有 int q = stoi(s);//将字符串s转化为数字 if(q==0){//输出的数字为0得特判输入,测试点1考查,如果不写就会使" tret"导致格局谬误 printf("tret"); continue; } for(int i=0;q!=0;++i){//取q的每一位 t[i] = q%13; q /= 13; } if(t[1]==0&&t[0]!=0){//高位没有,火星文只有个位 printf("%s\n",unit[t[0]].c_str()); }else if(t[0]==0&&t[1]!=0){//个位没有,只有高位 printf("%s\n",decade[t[1]].c_str()); }else{ printf("%s %s\n",decade[t[1]].c_str(),unit[t[0]].c_str()); } }else{//火星文,输入数字 if(s.size()==3){//只有一位 if(Unit.find(s)!=Unit.end()){//是个位 printf("%d\n",Unit[s]); }else{//是十位 printf("%d\n",Decade[s]*13);//转化为10进制 } }else{//2位火星数字 string s1 =s.substr(0,3); string s2 = s.substr(4,3); int r = Decade[s1]*13+Unit[s2];////转化为10进制 printf("%d\n",r); } } } return 0;}AC代码2:#include <cstdio>#include <unordered_map>#include <string>#include <iostream>using namespace std;string unit[13] = {"tret","jan", "feb", "mar", "apr", "may", "jun", "jly", "aug", "sep", "oct", "nov", "dec"};//个位string decade[13] = {"tret","tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mer", "jou"};//十位unordered_map<string,int> MarsToDecimal;//火星文到十进制数的映射unordered_map<int,string> DecimalToMars;//十进制数到火星文的映射void init(){ // 首先实现个位和13的倍数的映射 for (int i = 0; i < 13; ++i) { // 个位 DecimalToMars[i] = unit[i]; MarsToDecimal[unit[i]] = i; // 十位 DecimalToMars[i*13] = decade[i]; MarsToDecimal[decade[i]] = i*13; } for (int i = 1; i < 13; ++i) { for (int j = 1; j < 13; ++j) { DecimalToMars[i*13+j] = decade[i]+" "+unit[j]; MarsToDecimal[DecimalToMars[i*13+j]] = i*13+j; } }}bool isDecimal(string s){ for (char i : s) { if(!(i>='0'&&i<='9')){ return false; } } return true;}int main(){ init(); int N; scanf("%d",&N); getchar();// 排汇回车 string s; for (int i = 0; i < N; ++i) { getline(cin,s); if(isDecimal(s)){ // 为数字,转化为火星文 int num = 0; for (int j = 0; j < s.size(); ++j) { num = num*10+(s[j]-'0'); } printf("%s\n",DecimalToMars[num].c_str()); } else { // 为火星文,转化为数字 printf("%d\n",MarsToDecimal[s]); } } return 0;}

November 6, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1054-The-Dominant-Color

题目粗心:给出N行M列的数字矩阵,求其中超过半数的呈现次数最多的数字。 算法思路:应用unordered_map<int,int> counts;统计每一个数字呈现的次数,对于输出的每一个数字num,将counts[num]自增,依照情理来说是须要遍历counts取得呈现半数最多的那个数字,然而通过测试发现,有且只有一个数字呈现超过半数,所以在统计counts[num]直接判断是否曾经超过半数,如果是就输入num完结程序即可。 提交后果: AC代码:#include <cstdio>#include <unordered_map>using namespace std;int main(){ int N,M; scanf("%d %d",&N,&M); unordered_map<int,int> counts;// 统计每一个数字呈现的次数 int num; for (int i = 0; i < N; ++i) { for (int j = 0; j < M; ++j) { scanf("%d",&num); ++counts[num]; if(2*counts[num]>M*N){ printf("%d",num); return 0; } } } return 0;}

November 5, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1060-Are-They-Equal

题目粗心:给定两个位数不超过100的非负浮点数,如果保留n位有效数字的状况下写成0.@@@@*10^@的模式,如果两者雷同,则输入YES和该数字,如果不同输入NO并且别离输入2个数。 算法思路:认真思考下,这种模式和科学技术法很类似然而有不同于迷信计数法,因为后面肯定得是0.结尾,那么输出的数字的模式依照情理来说只有一种那就是$abcd.efg$,然而在这里认真分成2类,一类就是$0.efg$ 的模式,一类就是$abcd.@@@$ 的模式(如同和没说一样,哈哈哈),这里把第一类分进去是因为在解决第二类的时候会呈现$abcd$全副是0的状况(这是一个陷阱),这样就进化到了第一类,而后就是输出的数字有可能原本就是第一类,这样不便代码解耦(个人观点),接下来就具体说这2类数字的解决办法. 第一类$0.efg$首选咱们晓得最初肯定是0.结尾,所以应用string r="0."保留最初后果,这类数字分为2种状况,一种就是小数点前面全是0,这种状况下就间接让r前面增加n个0和10^0即可(这也是一个陷阱),另外一种就是小数点前面有不为0的数字,那么用index保留其地位,让r增加原字符串的index到最初地位的子串,留神如果该子串的长度s.size()-index<n,则得在前面补充0,使得其小数点前面的无效位为n(这也是一个陷阱),而后再增加指数局部,指数的次幂pos+1-index(index-pos-1就是小数点到不为0之间0的个数,相反数就是指数局部) 。最初返回r即可。该类数字应用type1函数解决。 第二类$abcd.@@@$(蕴含整数)这类数字也分为2种状况,第一种状况就是小数点后面全副是0,这样的数字(比方0000.@@@,等效于0.@@@)等价于第一类数字,间接将小数点前面的一个0到最初地位截断,而后调用type1解决即可,第二种状况就是小数点后面有不为0的数字,这样咱们用index保留小数点后面不为0的数字的地位,同样的让r初始化为"0."而后再移除小数点(移到后面去了),将index前面n位子字符串拼接到r的前面,如果该子字符串的长度s.size()-index<n,要在前面补0,最初再增加指数局部,指数的次幂为pos-index(pos-index为小数点后面不为0数字的个数,也就是小数点挪动的位数) 留神点:1、数字签名可能会有前导0,比方000 000.12 00004522145.155,测试点2,4,6考查,解决办法能够采取去除前导0,这里是间接归类于第二类,而后会跳转到第一类,间接解决小数点前面的数字即可。2、对于000.00的状况须要输入0.@@@(n个@)*10^0,测试点6考查。3、对于整数的解决,这里采纳在最初增加一个小数点演绎在第二类中。4、对于自身不须要挪动小数点的状况,除了000.00这样的须要在最初增加10^0外,其余的点没有考查,也就是像0.12保留2位数字输入0.12和0.12*10^0都是能够的。 提交后果: 测试数据:4 0000 0000.00 //YES 0.0000*10^04 00123.5678 0001235 //NO 0.123510^3 0.123510^43 0.0520 0.0521 // NO 0.52010^-1 0.52110^-14 00000.000000123 0.0000001230 // YES 0.1230*10^-64 00100.00000012 100.00000013 // YES 0.1000*10^35 0010.013 10.012 // NO 0.1001310^2 0.1001210^24 123.5678 123.5 // YES 0.1235*10^33 123.5678 123 // YES 0.123*10^34 123.0678 123 // YES 0.1230*10^33 0.000 0 // YES 0.000*10^0AC代码:#include<cstdio>#include<string>using namespace std;string type1(string s,int n,int pos){//解决模式0.*****的字符串 string r = "0.";//保留最初后果 int index;//保留小数点前面第一个不为0的地位 for(index=pos+1;index<s.size();++index){ if(s[index]!='0') break; } if(index==s.size()){//阐明小数点前面全副为0(或者小数点前面没有了,比方输出了0),取无效长度的0,而后增加10^0即可 for(int i=0;i<n;++i){//最初一个测试点考查的就是这一点 r += "0"; } r += "*10^0"; }else{//存在不为0的数字,那么该数字前面n位为无效位,不够补0 r += s.substr(index,n); if(s.size()-index<n){//阐明无效位长度不够,得补0 for(int i=0;i<n-(s.size()-index);++i){ r += "0"; } } r += "*10^"+to_string(pos+1-index);//index-pos-1就是小数点到不为0之间0的个数,相反数就是指数局部 } return r;} string type2(string s,int n,int pos){//解决模式abcd.***** int index;//保留小数点后面第一个不为0的地位 string r;//保留最初后果 for(index=0;index<pos;++index){ if(s[index]!='0') break; } if(index==pos){//小数点后面全副是0,从小数点前面找,转化为type1类型 s = s.substr(pos-1);//截取0.*****局部 r = type1(s,n,1);//pos地位就是1 }else{//小数点后面有不为0的局部 r += "0."; s.erase(s.begin()+pos);//移除小数点 r += s.substr(index,n);//截取index前面n为字符串,不够补0 if(s.size()-index<n){//无效位不够,补0 for(int i=0;i<n-(s.size()-index);++i){ r += "0"; } } r += "*10^"+to_string(pos-index);//增加指数局部 } return r;}string process(string s,int n){//将以后数字写成0.*****的模式并且保留n位 if(s.find(".")==string::npos){//以后字符串没有小数点是整数 s += ".";//在最初增加小数点 } int pos = s.find(".");//取得小数点的下标 string r;//保留最终后果 if(pos==1&&s[0]=='0'){//该数字原本就是0.****的模式 r = type1(s,n,pos); } else{//abcd.****的模式 r = type2(s,n,pos); } return r; }int main(){ int n; string s1,s2; char t1[101],t2[101]; scanf("%d %s %s",&n,t1,t2); s1 = t1; s2 = t2; s1 = process(s1,n); s2 = process(s2,n); if(s1==s2){ printf("YES %s",s1.c_str()); }else{ printf("NO %s %s",s1.c_str(),s2.c_str()); } return 0;}

November 5, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1063-Set-Similarity

题目粗心:给定N个汇合,要求计算查问的2个汇合的Nc/Nt的值,其中Nc指的是2汇合雷同元素的个数,Nt指的是2汇合不同元素的个数. 算法思路:很天然就想到用set容器来保留数据元素了,最显著的特色就distinct,要求元素不反复的个数,咱们首先应用unordered_set<int> sets[55]保留每个容器的所有元素(主动去反复了),而后编写函数caculate用来计算2个汇合的并集和交加元素个数,就是遍历其中一个汇合而后在另外一个汇合应用find函数判断是否查找胜利,如果是就累计Nc,否则就累计Nt(初始为其中一个汇合的元素个数),最初计算比率输入即可 留神点:1、如果通过将2汇合增加进另外一个新的汇合中计算不同元素的大小,最初一个点会超时。2、%要写成%%输入3、这里只须要set的去重操作,所以应用unordered_set比拟节省时间4、不要应用algorithm自带的set_intersection和set_union办法,无奈通过测试。 提交后果: AC代码:#include <cstdio>#include <unordered_set>using namespace std;unordered_set<int> sets[55];//N个汇合void caculate(int first,int second){ int Nt = sets[first].size();// first汇合和second汇合的并集元素总个数 int Nc = 0;// first汇合和second汇合的交加元素总个数 unordered_set<int>::iterator it; for(it=sets[second].begin();it!=sets[second].end();++it){ if(sets[first].find(*it)!=sets[first].end()){ // 在first汇合中找到second汇合中的元素 ++Nc; } else { // 没有找到 ++Nt; } } printf("%.1f%%\n",Nc*100.0/Nt);}int main(){ int N;//汇合数目 scanf("%d",&N); int M;// 每一个汇合的元素个数 int num;// 汇合元素 for (int i = 1; i <= N; ++i) { scanf("%d",&M); for (int j = 0; j < M; ++j) { scanf("%d",&num); sets[i].insert(num); } } int K;//查问个数 scanf("%d",&K); int index_first,index_second;// 查问的第一个汇合和第二个汇合的下标 for (int k = 0; k < K; ++k) { scanf("%d %d",&index_first,&index_second); caculate(index_first,index_second); } return 0;}

November 4, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1047-Student-List-for-Course

题目粗心:给出选课人数和课程数目,而后再给出每个人的选课状况,请针对每门课程输入选课人数以及所有选该课的学生的姓名。 算法思路:该题和1039是姊妹题,思路其实是一样的,就是给定了一个正向的关系,当初须要建设一个反向的关系,这里是须要建设每一个课程与所有抉择该门课程的学生。咱们应用unordered_map<int,vector<string>> courseToStudents代表这个映射,所有的映射关系在输出的时候就能够建设(具体见代码),而后再输入所有的每一个课程的编号,抉择该课程的学生数,以及所有的所有的学生。在输入所有的学生前得先排序。 留神点:1、如果最初一组数据超时,将string换成char数组,cin换成scanf,cout换成printf。2、不要应用set,不仅工夫消耗大而且最初一个测试点无奈通过,不晓得什么起因,我感觉正当的解释就是存在一门课外面有雷同名字的学生。 提交后果: AC代码:#include <cstdio>#include <vector>#include <unordered_map>#include <algorithm>#include <string>using namespace std;unordered_map<int,vector<string>> courseToStudents;// 课程和所有选该课的学生映射int main(){ int N,K;//学生数目和课程数目 scanf("%d %d",&N,&K); string student; int courseNum; int course; char s[30]; for (int i = 0; i < N; ++i) { scanf("%s %d",s,&courseNum); student = s; for (int j = 0; j < courseNum; ++j) { scanf("%d",&course); courseToStudents[course].push_back(student); } } // 输入每一门课的所有学生 for (int k = 1; k <= K; ++k) { printf("%d %lu\n",k,courseToStudents[k].size()); sort(courseToStudents[k].begin(),courseToStudents[k].end()); for (auto & i : courseToStudents[k]) { printf("%s\n",i.c_str()); } } return 0;}

November 4, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1039-Course-List-for-Student

题目粗心:有N个学生,K门课。当初给出抉择每门课的学生姓名,并在之后给出N个学生的姓名,要求按程序给出每个学生的选课状况。 算法思路:输出的时候给出的是每一个课程的编号与所有抉择该们课程的学生的关系,咱们当初只须要建设每一个学生与所有抉择的课程的关系即可,这里应用unordered_map<string,set<int>> studentToCourses来存储每一个学生所抉择所有的课程编号,这样在输出每一个课程course的学生student的时候就能够为每一个学生保留抉择的课程studentToCourses[student].insert(course),而后对于每一个查问的学生,就能够间接输入每一个学生抉择的课程数和所有的课程。 留神点:1、开二维数组的化,最初一组测试点会内存超限。2、这里应用了set实现了主动排序,这会导致工夫过长,如果呈现超时的状况,能够换成vector,不过得在输入的时候得排序。 提交后果: AC代码:#include <cstdio>#include <set>#include <unordered_map>#include <iostream>using namespace std;unordered_map<string,set<int>> studentToCourses;// 存储每一个学生的所有的课程int main(){ int N,K;//查问课程的人数和课程数目 scanf("%d %d",&N,&K); // 对于每一个课程,课程号从1开始 int course,num;// 课程号和抉择该课程的学生数目 string student;// 学生名字 for (int i = 0; i < K; ++i) { scanf("%d %d",&course,&num); for (int j = 0; j < num; ++j) { cin>>student; studentToCourses[student].insert(course);//为每一个学生增加其抉择的课程 } } // 查问开始 for (int k = 0; k < N; ++k) { cin>>student; // 输入该学生的所有的抉择课程 cout<<student<<" "<<studentToCourses[student].size(); set<int>::iterator it; for(it = studentToCourses[student].begin();it!=studentToCourses[student].end();++it){ printf(" %d",(*it)); } printf("\n"); } return 0;}

November 4, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1024-Palindromic-Number

题目粗心:定义种操作:让一个条数加上这个整数首尾颠倒后的数字。例如对整数1257执行操作就是1257 + 7521 = 8778.* 当初给出一个正整数和操作次数限度,问在限定的操作次数内能是否能失去回文数。如果能失去,则输入那个回文数,并输入操作的次数;否则,输入最初一次操作失去的数字以及操作次数。 算法思路:此题的数据范畴有可能会超过long long类型(自己没有测试),并且这里波及到了逆置操作,应用字符串才解决该数字较为不便,这个问题的要害两个问题就是如何判断一个数字是回文数,第二个就是如何实现2个字符串或者大整数的加法操作。第一个问题只须要判断字符串中对称地位的字符是否相等即可(对称地位的和为字符串长度),第二个问题在这里也比拟好解决,因为第二个整数是第一个整数逆置失去的,所以这两个整数的长度是雷同的,那么就只须要应用指针i从后往前扫描每一位,而后计算对应的局部和partialSum = (s1[i]-'0')+(s2[i]-'0')+carry;这里应用carry保留上一位的进位,初始为0,而后计算进位carry = partialSum/10;并且应用字符串r保留相加后的本位数字partialSum%10,最初得留神如果最高位有进位得增加进位到r中。 算法流程:应用step记录执行加法的次数,初始为0,而后只有step<K那么就进入循环,如果以后数字N为回文数字,就退出循环进行输入操作,否则就应用s保留N的逆置数,将N和s相加的后果保留到N中,并step自增。最初在循环退出的时候输入后果和执行次数。 提交后果: AC代码:#include <cstdio>#include <string>#include <iostream>#include <algorithm>using namespace std;bool isPalindromic(string s){ for (int i = 0; i < s.size() / 2; ++i) { if(s[i]!=s[s.size()-i-1]){ return false; } } return true;}string add(string s1,string s2){ int carry = 0; int partialSum = 0; string r; for (int i = s1.size()-1; i >= 0; --i) {// s1和s2理论是一样长的 partialSum = (s1[i]-'0')+(s2[i]-'0')+carry; carry = partialSum/10; r += to_string(partialSum%10); } // 因为s1和s2是一样长的,就无需判断s1或者s2还有局部须要相加了。 if(carry!=0){ r += to_string(carry); } reverse(r.begin(),r.end()); return r;}int main(){ string N; cin>>N; int K; scanf("%d",&K); int step = 0; string s; while (step<K){ if(isPalindromic(N)) break; s = N; reverse(s.begin(),s.end()); N = add(N,s); ++step; } printf("%s\n%d",N.c_str(),step); return 0;}

November 3, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1023-Have-Fun-with-Numbers

题目粗心:给出一个长度不超过20的整数,问这个整数乘以2当前的数位是否为原数数位的一个排列 算法思路:因为长度有可能达到20位,超过了long long的存储范畴,所以这里采纳string存储输出的整数。该题只须要解决两个问题,第一个就是如何判断2个整数的互为排列,第二个就是如何计算一个字符串与2的乘法。解决第一个问题的思路就是利用hash映射的思维,利用countOfS存储0~9数字呈现的次数,在输出的时候做加法,对于输出的数字s的每一位s[i],++countOfS[s[i]-'0'],在做完乘法后失去数字r,而后对r的每一个数字r[k]做减法,--countOfS[r[k]-'0'];如果减完后呈现小于0的状况就阐明这两个不是互为排列并且应用isTrue记录下来。第二个问题的解决思路就是应用指针j对s从后向前扫描,并且应用carry记录上一位的进位,对于每一位数字s[j],都乘以2而后加上进位carry,(s[j]-'0')*2 + carry,该后果应用multi保留,而后计算进位multi/10,并将本位应用r保留,r += to_string(multi%10);最初依据isTrue是否为true输入Yes和No,而后再输入逆置后的r即可。 留神点:1、在进行乘法运算的时候,最高位如果有进位的化,也就是carry在退出循环后不为0得再增加到r中,测试点2和测试点7考查。 提交后果: AC代码:#include <cstdio>#include <string>#include <iostream>#include <algorithm>using namespace std;int main(){ string s; cin>>s; int countOfS[11] = {};// 统计s中每一个数字呈现的次数 for (int i = 0; i < s.size(); ++i) { ++countOfS[s[i]-'0']; } // 将s乘以2 string r; int carry = 0; int multi;// 局部乘积 for (int j = s.size()-1; j >=0 ; --j) { multi = (s[j]-'0')*2 + carry; carry = multi/10; r += to_string(multi%10); } // 最高位还有进位 if(carry!=0){ r += to_string(carry); } reverse(r.begin(),r.end()); bool isTrue = true; for (int k = 0; k < r.size(); ++k) { --countOfS[r[k]-'0']; if(countOfS[r[k]-'0']<0){ isTrue = false; break; } } if(isTrue){ printf("Yes\n"); } else { printf("No\n"); } printf("%s",r.c_str()); return 0;}

November 3, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1059-Prime-Factors

题目粗心:给出一个int范畴的整数,依照从小到大的程序输入其合成为质因数的乘法算式。 算法思路:此题考查的是质因子合成的内容,对于每一个质因子咱们应用构造体Factor进行存储,其中保留了因子和呈现的次数。接下来就是获取数字N的每一个质因子. 获取办法如下:首先应用prime_table保留$10^5$以内的所有素数,而后遍历每一个在2和根号N之间的素数prime_table[i],只有N%prime_table[i]==0,阐明prime_table[i]为N的一个质因子,而后只有prime_table[i]还是N的因子,就讲对于的次数count加一,同时N除以prime_table[i],当prime_table[i]不在是N的因子的时候,让不同因子的数目index_factor加一即可。同时因为对于一个数N,最多只有一个大于根号N的因子,也就是说,在2到根号N之间的所有因子都曾经处理完毕后,N仍然不为1,阐明还有一个大于根号N的因子,此时须要记录该因子(其实就是以后的N)和次数(1)。 留神点:1、对于N等于1的时候没法合成,须要特判输入,测试点3考查。2、因为从2开始的素数到第11个素数的时候,所有素数的累乘就超过了int的返回,所以factor数组开到10就行了。提交后果: AC代码:#include <cstdio>#include <cmath>using namespace std;struct Factor{ int prime{};// 因子 int count{};// 个数}factor[10];int index_factor = 0;int prime_table[100000];// 素数表int num = 0;bool isPrime(int N){ if(N<4){ return N>1; } if(N%6!=1&&N%6!=5) return false; for (int i = 5; i < sqrt(N*1.0); i+=6) { if(N%i==0||N%(i+2)==0){ return false; } } return true;}// 获取素数表void init(){ for (int i = 2; i < 100000; ++i) { if(isPrime(i)){ prime_table[num++] = i; } }}int main(){ init(); int N; scanf("%d",&N); if(N==1){ printf("1=1");// 特判1,测试点3考查 return 0; } printf("%d=",N); int sqrt_N = (int)sqrt(N*1.0); // 遍历每一个素数 for (int i = 0; i < num&&prime_table[i]<=sqrt_N; ++i) { if (N%prime_table[i]==0){ factor[index_factor].prime = prime_table[i]; while (N%prime_table[i]==0){ ++factor[index_factor].count; N /= prime_table[i]; } ++index_factor; } } // 还存在一个大于根号N的因子 if(N!=1){ factor[index_factor].prime = N; ++factor[index_factor++].count; } for (int j = 0; j < index_factor; ++j) { printf("%d",factor[j].prime); if(factor[j].count>1){ printf("^%d",factor[j].count); } if(j<index_factor-1) printf("*"); } return 0;}

November 2, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1096-Consecutive-Factors

题目粗心:给出一个正整数N,求一段间断的整数, 使得N能被这段间断整数的乘积整除。如果有多个计划,输入间断整数个数最多的计划;如果还有多种计划,输入其中第一个数最小的计划。 算法思路:刚开始审题不当认为是质因子合成,导致节约了很多工夫,该题的想法也比拟直观,咱们遍历从2到N之间的每一个数字,用consecutiveMulti保留每一个能够被N整除的间断序列的乘积,并且应用len保留最长的序列长度,begin保留最长的序列的起始数字,让consecutiveMulti一直累乘直到不能被N整除地位(就相似于从2开始始终乘以3,4,5,6...直到N不能整除该间断乘积为止,而后再接着从3开始始终乘以4,5,6,7,8...以此类推),而后更新长度len和起始地位begin,这里得留神i的含意是每一个间断序列的起始地位,咱们这里应用nextNum保留以后序列的下一个待乘的数字,初始为i,而后在循环中,consecutiveMulti会乘以nextNum,并且其长度能够应用nextNum-i+1来代替,因为在数字i和nextNum之间的数字都是间断的,每次更新nextNum加一即可。其实在这里不必遍历2到N,只用遍历到根号N即可,因为,第一,不存在2个因子都大于更号N。第二,N如果存在大于根号N的因子肯定无奈和后面的因子组成间断序列。第三,如果在2到根号N中没有N的因子,那么在根号N和N之间也必然不会有N的因子,N的因子只有1和N自身,然而题目因为对于1不思考,所以在2和根号N之间没有解的时候只有长度为1的间断序列N。 提交后果: AC代码:#include <cstdio>#include <cmath>using namespace std;int main(){ int N; scanf("%d",&N); int sqrt_N = (int)sqrt(N * 1.0); int begin = 0; int len = 0; for (int i = 2; i <=sqrt_N ; ++i) { int consecutiveMulti = 1;// 从数字i开始的局部间断乘积 int nextNum = i;//以后序列前面待乘的一个数字 while (true){ consecutiveMulti *= nextNum; if(N%consecutiveMulti!=0) break;// 无奈整除以后序列退出循环 // 能够整除,更新长度 if(len<nextNum-i+1){ len = nextNum-i+1; begin = i; } ++nextNum;// 更新下一个待乘的数字 } } if(len==0){ // 阐明不存在2到根号N的数字局部乘积能够整除N,也即是以后的N只有一个因子(它本人)。 printf("1\n%d",N); }else { printf("%d\n",len); for (int i = begin; i < begin+len; ++i) { printf("%d",i); if(i<begin+len-1) printf("*"); } } return 0;}

November 2, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1078-Hashing

题目粗心:给出散列表长MSize和欲插入的元素,将这些元素按读入的程序插入散列表中,其中散列函数为H(key)= key %MSize,解决抵触采纳只往正向减少的次探查法(即二次方 探查法)。另外,如果题目给出的MSize不是素数,那么须要将MSize从新赋值为第一个比MSize大的素数再进行元素插入。 算法思路:间接模仿hash映射的过程,首选对于输出的MSize如果不是素数就找到第一个比它大的素数,而后就对每一个输出的数据进行hash映射,应用pos记录映射的地位,如果hashTable[pos]!=0阐明以后地位存在抵触,那么就应用二次方探测法进行rehash,如果找到空位后退出循环,如果rehash的次数大于或者等于hash表的长度就表明没有适合的空位也退出循环。这里应用step记录以后存在hash抵触的时候rehash的次数。退出循环后判断step是否大于等于MSize,如果是输入"-",否则输入pos,并且将pos对应地位设置为num。最初留神输入空格。判断一个数字N是否为素数的办法如下: 办法一:依据素数的定义可知,素数是指大于1,且只能被1和它本身整除的自然数。1不是素数。依据乘法的个性,可知若从2开始遍历到被判断数的平方根都没有找到能被整除的数,则这个数肯定为素数。对应代码如下: bool isPrime(int N){ if(N<=1) return false; for (int i = 2; i <= sqrt(N * 1.0); ++i) { if(N%i==0) return false; } return true;}办法二:对于一个大于等于5的素数,其特点是总是等于6x-1和6x+1,其中 x 是大于等于1的自然数.那么依据这个特点,失去的论断为,在6的倍数两边的数字不肯定是素数,然而不在6的倍数两边的数字肯定不是素数,这样就能够优化下面的代码,思路就是首先特判小于4的数字,只有2和3才是素数,而后对于所有对6整除余数不为1和5的阐明不在6的倍数的两边,肯定不是素数,最初从5开始,步长为6进行遍历每一个在6两边的数字,对于能够对于6的倍数两边的数字能够整除的肯定不是素数。代码如下: bool isPrime(int N){ if(N<=3){ return N>1;//2和3才是素数 } if(N%6!=1&&N%6!=5) return false; for (int i = 5; i <= sqrt(N * 1.0); i+=6) { if(N%i==0||N%(i+2)==0) return false; } return true;}对于rehash次数只须要MSize次的证实(思否的公式编辑存在bug,预览的时候没问题,在公布就有乱码了,上面是预览的截图) 留神点:1、对于测试点0和3谬误的状况,能够认真看看是不是二次方探测的办法写错了,正确写法是pos = (num+step*step)%MSize;而不是pos = (pos+step*step)%MSize;2、如果二次方探测始终没有找到空位,得应用step管制rehash的次数不超过MSize,否则测试点3超时。3、如果谬误的判断1也是素数的化,测试点1会出错。 提交后果: ...

October 30, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1049-Counting-Ones

题目粗心:给出一个数字N,求1~N的所有数字中呈现1的个数 算法思路:自己采纳了暴力遍历求解,然而只得了22分,参考了算法笔记的思路,间接写在上面思考30710这个数: 首先思考开端为0咱们计算开端有多少个数是笼罩了1的,那么开端为1的时候后面是只能取0-3070,因为如果取3071,那么就是30711>30710了,共有3071种抉择 而后思考倒数第二位1它为1,那么后面为0-306的时候,前面一位轻易取都行,当后面为307时,前四位为3071,开端只能取0,那么有306*10+1种取法 而后思考倒数第三位它为7,如果它为1的话,后面前面都能够轻易取,因为它为7比1要大,只有他们组成的数不大于30710,都是无效的,那么取法共有(30+1)*102种 同理倒数第四位为0那要令它为1,后面只能取0-2,前面能够任取,共有3*103种 最初倒数第五位它是3,比1要大,所以它为1时,前面能够任取,共有104种 总结如果把某个数右边取为leftNum,左边取为rightNum,那么失去的后果有三种可能,expo是以后数字的位数次方(例如123中的2,其位数为1,对应的expo就为10^1);1.以后位为0,count += leftNum * expo;2.以后位为1,count += leftNum*expo+rightNum+1;3.以后为>1,count += (leftNum+1)*expo 留神点:1、通过测试,PAT的数据最大数字不超过5位数字,所以数组开到4就好了。 提交后果 AC代码:#include <cstdio>#include <algorithm>using namespace std;int a[4] = {};int main(){ int N,n; scanf("%d",&N); n = N; int index = 0; while(N!=0){ a[index++] = N%10; N /= 10; } // 逆置 for (int i = 0; i < index/2; ++i) { swap(a[i],a[index-i-1]); } int count = 0;//统计1的个数 int expo = 1;// a[i]对应的10^i int leftNum = n/10; int rightNum = 0; for (int i = index-1; i >= 0; --i) { if(a[i]>1){ // 以后位大于1 count += (leftNum+1)*expo; } else if(a[i]==1){ count += leftNum*expo+rightNum+1; } else { count += leftNum * expo; } expo *= 10; rightNum = n%expo; leftNum /= 10; } printf("%d",count); return 0;}

October 26, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1008-Elevator

题目粗心:有一部电梯,最开始停在第0层,上一层楼须要6s,下二层楼须要4s, 每次达到以后目标楼层还须要停留5s。现给出电梯要去的楼层的程序,求总共须要破费多少工夫(最初不须要回到第0层)。 算法思路:应用beginFloor记录以后电梯所在的地位,nextFloor记录下一次要去的地位,没输出一个nextFloor,如果大于beginFloor就代表上楼,让seconds累计上楼工夫6*(nextFloor-beginFloor),否则就是下楼,累计下楼工夫4*(beginFloor-nextFloor),而后得记录停留5秒的工夫,更新此时电梯所在的地位。在输出完结的时候就能够输入数据。 提交后果: AC代码:#include <cstdio>using namespace std;int main(){ int N; scanf("%d",&N); int beginFloor = 0;//以后电梯所在的地位 int nextFloor; int seconds = 0;//所破费的工夫 for (int i = 0; i < N; ++i) { scanf("%d",&nextFloor); if(nextFloor>beginFloor){ // 上楼 seconds += 6*(nextFloor-beginFloor); } else { //下楼 seconds += 4*(beginFloor-nextFloor); } // 停留5秒 seconds += 5; // 更新电梯地位 beginFloor = nextFloor; } printf("%d",seconds); return 0;}

October 26, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1104-Sum-of-Number-Segments

题目粗心:给定一个序列,求出所有片段的和 算法思路:其实就是找法则,也不晓得为啥第一次写的时候齐全找不进去,看答案都看不懂,第二次就一下看进去了,画个图看看就明确了 留神点:1、应用双层循环测试点2和3会超时。2、测试点2数据太大,应用double会出错,得换成long double,2020年5月前不会出错,应该是新增加的数据点 提交后果: AC代码:#include <cstdio>using namespace std;int main(){ int N; scanf("%d",&N); long double a[N]; for (int i = 0; i < N; ++i) { scanf("%Lf",&a[i]); } long double ans = 0; for (int i = 0; i < N; ++i) { //迭代N次 ans += a[i]*(N-i)*(i+1);//a[i]在第i+1轮迭代中呈现了(N-i)次,总的迭代轮次为i+1 } printf("%.2Lf",ans); return 0;}

October 23, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1101-Quick-Sort

题目粗心:本题输出一个序列,蕴含N个正整数,如果一个数右边的所有数都比它小、左边的所有数都比它大,那么称这个数为序列的一个pivot。求序列中pivot的个数。 算法思路:因为判断任意地位的数字是否是pivot必须得晓得它是否比右边的都大,比左边的都小,那么就应用数组leftMax,rightMin别离保留以后地位的右边最小的数和左边最大的数,先从左往右遍历,如果以后地位j==0,那么就设置其右边最小的数字为-1,如果不是第一个数字就在j-1和j-1右边的所有最小的数字中抉择一个更小的数字作为以后地位的右边的最小数字,而后从右往左遍历,如果以后的地位k==N-1,那么就设置其左边的最大的数字为0x3ffffff,如果不是最初一个数字,就在k+1和k+1左边的所有数字中抉择一个更大的数字作为以后数字左边所有数字中的最大数。最初再遍历一次数组元素,将符合条件的元素增加进set汇合中,而后输入即可。 留神点:1、必须得输入两行,也就就是在没有pivot的时候须要输入0而后第二行得输入空行,测试点2考查。 提交后果: AC代码:#include <set>#include <cstdio>using namespace std;int main(){ int N; scanf("%d",&N); int a[N]; for (int i = 0; i < N; ++i) { scanf("%d",&a[i]); } int leftMax[N];// 保留以后地位的右边最大的数字 int rightMin[N];// 保留以后地位的左边最小的数字 for (int j = 0; j < N; ++j) { if(j==0){ leftMax[j] = -1;//最右边的数字右边没有数字 } else { leftMax[j] = a[j-1]>leftMax[j-1]?a[j-1]:leftMax[j-1]; } } for (int k = N-1; k >=0 ; --k) { if(k==N-1){ rightMin[k] = 0x3fffffff; } else { rightMin[k] = a[k+1]<rightMin[k+1]?a[k+1]:rightMin[k+1]; } } set<int> pivots;//pivot汇合 for (int l = 0; l < N; ++l) { if(a[l]>leftMax[l]&&a[l]<rightMin[l]){ pivots.insert(a[l]); } } printf("%d\n",pivots.size()); set<int>::iterator it; for(it=pivots.begin();it!=pivots.end();++it){ if(it!=pivots.begin()) printf(" "); printf("%d",*it); } printf("\n"); return 0;}

October 22, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1093-Count-PATs

题目粗心:给定一个只蕴含P,A,T的字符串,让其计算有多少个PAT组合 算法思路:对一个确定地位的A来说,以它造成的PAT的个数等于它右边P的些个数乘以它左边T的个数。例如对字符申APPAPT的两头那个A来说,它右边有两个P,左边有一个T,因而这个A能造成的PAT的个数就是2x1=2.于是问题就转换为:对字符串中的每个A,计算它右边P的个数与它左边T的个数的乘积,而后把所有A的这个乘积相加,最初的后果就是该字符串中所有的PAT组合数目。那么当初就是须要获取每一个地位的右边的P和左边的T的数,在这里咱们应用leftP和rightP别离保留以后地位右边的P和左边的T的个数。之所以能够这样保留,是因为以后地位i右边的P的个数和i-1地位右边P的个数具备继承性,要么相等,要么相差1。那么这样咱们能够先遍历一遍所有的字符串中所有的T呈现的个数,而后应用ans保留所有的PAT组合数目,再次遍历字符串的过程中,如果以后字符为P那么就让leftP加1,如果为T,就让rightT减一,如果为A,就累计leftP*rightT。 留神点:1、测试点3和4考查取模,不能在最初统计实现后再取模,须要每计算一次就取模。 提交后果: AC代码:#include <cstdio>#include <string>#include <iostream>using namespace std;int main(){ string s; cin>>s; int len = s.size(); int leftP = 0; int rightT = 0; int ans = 0;//记录PAT的个数 for (int i = 0; i < len; ++i) { if(s[i]=='T'){ ++rightT; } } for (int j = 0; j < len; ++j) { if(s[j]=='P'){ ++leftP; } else if(s[j]=='T'){ --rightT; } else if(s[j]=='A'){ ans += leftP*rightT; ans %= 1000000007; } } cout<<ans; return 0;}

October 22, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1089-Insert-or-Merge

题目粗心:给出一一个初始序列,能够将它应用插入排序或归并排序进行排序。当初给出一个序列,问它是由插入排序还是归并排序产生的,并输入下一步将会产生的序列。 算法思路:这里须要齐全模仿间接插入排序和归并排序中的每一步,并且对于归并排序这里应用非递归形式比拟好管制排序的轮次。 间接插入排序咱们将长度为n的序列[0,n-1]依照下标i进行划分,[0,i-1]为曾经排好序的序列,[i,n-1]为待排序序列,$1 \leq i\leq n-1$,对于每一个待排序元素temp[i],咱们应用key暂存,并且在[0,i-1]中查找插入地位j,而后让temp[j]的值赋为key,这样一次插入排序过程就实现,接下来应用flag判断是否曾经和指标序列相等,如果是就输入Insertion Sort和temp数组,如果不是就查看以后的temp数组是否和指标数组统一,如果是就让flag赋值为true。 归并排序每一次排序过程会将所有步长为step的元素进行排序,这里的步长从2开始,始终到n,每次归并会退让长成倍增加,对于步长的step的归并过程,每一次都从0地位的元素开始,取每step个元素为一组,进行排序,这里应用sort函数代替,须要留神的是,最初一组序列的长度可能小于step,所以sort函数的end地位得抉择i+step和n的最小值。同样的在一次归并过程完结后应用flag判断是否曾经和指标序列相等,如果是就输入Merge Sort和temp数组,如果不是就查看以后的temp数组是否和指标数组统一,如果是就让flag赋值为true。 留神点:1、对于初始序列和指标序列相等的状况,须要排除该情景,也即是得先做一次排序后而后再比拟是否呈现和指标序列相等的数组。 提交后果: AC代码:#include <cstdio>#include <algorithm>using namespace std;int init[105];int partial_sorted[105];int temp[105];//工作数组// 判断数组a和b是否相等bool isSame(const int a[],const int b[],int n){ for (int i = 0; i < n; ++i) { if(a[i]!=b[i]){ return false; } } return true;}void print(const int a[],int n){ for (int i = 0; i < n; ++i) { printf("%d",a[i]); if(i<n-1) printf(" "); }}// 插入排序bool insertionSort(int n){ bool flag = false;//是否和指标序列相等 // [i,n-1]为待排序元素,[0,i-1]为曾经排序的元素 for (int i = 1; i < n; ++i) { int j=i;//插入地位,初始在有序序列前面插入 int key = temp[i];//待插入元素 while (j>0&&key<temp[j-1]){ temp[j] = temp[j-1]; --j; } // 找到插入地位 temp[j] = key; if(flag) { printf("Insertion Sort\n"); print(temp,n); return true; } if(isSame(temp,partial_sorted,n)){ // 曾经和指标数组相等设置为true,并且在进一步排序后再退出 flag = true; } } return flag;}void mergeSort(int n){ bool flag = false;//是否和指标序列相等 // 步长从2开始,最长为n for (int step=2;step<=n;step*=2) { // 对于所有步长为step的序列进行排序,最初一个序列的长度可能没有step,所以得记录i+step和n的最小值 for (int i = 0; i<n; i+=step) { sort(temp+i,temp+min(i+step,n)); } if(flag) { printf("Merge Sort\n"); print(temp,n); return; } if(isSame(temp,partial_sorted,n)){ // 曾经和指标数组相等设置为true,并且在进一步排序后再退出 flag = true; } }}int main(){ int N; scanf("%d",&N); for (int i = 0; i < N; ++i) { scanf("%d",&init[i]); temp[i] = init[i]; } for (int i = 0; i < N; ++i) { scanf("%d",&partial_sorted[i]); } if(!insertionSort(N)){ // 从新将temp数组赋值为init数组 for (int i = 0; i < N; ++i) { temp[i] = init[i]; } mergeSort(N); } return 0;}

October 20, 2020 · 1 min · jiezi

关于算法-数据结构:算法与数据结构2排序算法

抉择排序过程:arr[0~N-1]范畴上,找到最小值所在的地位,而后把最小值替换到0地位。arr[1~N-1]范畴上,找到最小值所在的地位,而后把最小值替换到1地位。arr[2~N-1]范畴上,找到最小值所在的地位,而后把最小值替换到2地位。…arr[N-1~N-1]范畴上,找到最小值地位,而后把最小值替换到N-1地位。 估算:很显著,如果arr长度为N,每一步常数操作的数量,如等差数列个别所以,总的常数操作数量 = a(N^2) + bN + c (a、b、c都是常数) 所以抉择排序的工夫复杂度为O(N^2)。 public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 0 ~ N-1 // 1~n-1 // 2 for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1 // 最小值在哪个地位上 i~n-1 int minIndex = i; for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); }}public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;}冒泡排序过程:在arr[0~N-1]范畴上:arr[0]和arr[1],谁大谁来到1地位;arr[1]和arr[2],谁大谁来到2地位…arr[N-2]和arr[N-1],谁大谁来到N-1地位 ...

October 19, 2020 · 3 min · jiezi

关于算法-数据结构:PAT甲级1010-Radix

此题难度较大,自己写了3次,在不借助外在帮忙的状况下最好问题才21分,对于第一次尝试的同学,能够临时放弃。题目粗心:输出四个整数N1、N2、tag、radix。其中tag--1示意N1为radix进制数,tag--2示意N2为radix进制数。范畴:N1和N2均不超过10个数位,且每个数位均为0-9或a~z,其中0~9示意数字0~9、a~z示意数字10-35.求N1和N2中未知进制的那个数是否存在,并满足某个进制时和另一个数在十进制下相等的条件。若存在,则输入满足条件的最小进制:否则,输入Impossible. 算法思路:此题的难点次要有2个,一个是N2进制数的范畴的确定,一个是在该范畴中搜寻的办法。咱们在这里假如将曾经晓得的进制数设定为N1,而后如果tag等于2,就让N1和N2进行替换 对于N2进制的范畴确定如下:第一:对于其下界很显著为其数字中呈现的最大数字加1,比方123456789,这个数字起码是10进制的,因为呈现了9 第二:对于其上界的确定比拟没有那么让人容易想到,首先咱们晓得对于1位数字,其进制数和其转化为10进制数的大小没有任何关系所以其上界就是下界,其次,对于位数大于1的数字,想要求出最大的进制数,并且要转化为10进制数后和N1的十进制数相等,就必须得保障该数字最小,天然就能够发现位数大于2位的最小数字就是10,而想要该数字转化为10进制数后和N1的十进制数相等,那么N2的进制数必须为N1的10进制数,综上所述,N2进制数的上界为下界和N1的10进制数的最大值。 对于在下界和上界之间搜寻N2进制的办法:咱们晓得当一个数字的进制数被固定的时候,其十进制数会随着该数字的增大而增大,当其数字固定的时候,其十进制数会随着进制数的增大而增大.那么咱们为了在N2的下界和上界中搜寻出一个数字作为N2的进制数,使其转化为10进制数后和N1的十进制数相等,能够应用二分搜寻的办法查找该进制数.具体做法为咱们取得其区间中值mid,而后将N2转化为10进制数,如果该数字和N1的十进制数相等,输入mid并且返回,如果小于N1的十进制数,阐明以后的进制数较小,向右子区间搜寻,否则像左子区间搜寻。退出循环后阐明没有搜寻到符合条件的进制数,输入Impossible 留神点:1、将N2转化为10进制数的时候有可能会溢出,必须得判断在进行区间的划分,然而N1不会溢出能够不必判断,测试点6,8,12,13,15,16考查N2溢出问题2、有人可能会间接将区间设置为[0,2^63-1],然而理论测试后果是测试点0,1,11,18会谬误得21分(也算是个不错的分数了),如果下界设置为1更加会出错只能失去14分,自己也不晓得为什么。3、题目所说的多个进制数中输入最小的进制数,个人感觉这个条件是没有用的,因为满足不存在数字肯定,进制数不同还能失去十进制数雷同的。4、暴力搜寻会超时,此题基本上找到上下界,就能拿到20分左右。 提交后果: AC代码:#include <cstdio>#include <algorithm>#include <vector>#include <string>#include <iostream>using namespace std;// radix进制数num转化为10进制数long long toDecimal(string num, long long radix){ if(num=="0"){ return 0; } long long result = 0; long long expo = 1; for (int i = num.size()-1; i>=0 ; --i) { if(num[i]>='0'&&num[i]<='9'){ result += (num[i]-'0')*expo; } else if(num[i]>='a'&&num[i]<='z'){ result += (num[i]-'a'+10)*expo; } expo *= radix; } return result;}// 获取N2的最小进制数,为其呈现的最大数字加1long long getInfRadixOfN2(string N2){ int radix = -1; for (int i = 0; i < N2.size(); ++i) { int temp; if(N2[i]>='0'&&N2[i]<='9'){ temp = N2[i]-'0'; } else if(N2[i]>='a'&&N2[i]<='z'){ temp = N2[i]-'a'+10; } if(temp>radix){ radix = temp; } } return radix+1;}int main(){ string N1,N2; cin>>N1>>N2; int tag,radix; // 这里保障radix为N1的,查找N2的进制数使其最靠近N1 scanf("%d %d",&tag,&radix); if(tag==2){ // radix为N2的就替换N1和N2 swap(N1,N2); } long long decimalOfN1 = toDecimal(N1,radix); long long decimalOfN2; long long left=getInfRadixOfN2(N2);// 测试点0,6,19考查,left写成7测试点0和19会正确 long long right=max(left,decimalOfN1); long long mid; while (left<=right){ mid = left + (right-left)/2; decimalOfN2 = toDecimal(N2,mid); if(decimalOfN1==decimalOfN2){ printf("%lld",mid); return 0; } else if(decimalOfN2<decimalOfN1&&decimalOfN2>0){// 测试点6,8,12,13,15,16考查N2溢出问题 left = mid+1; } else { right = mid-1; } } printf("Impossible"); return 0;}

October 19, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1044-Shopping-in-Mars

题目要求:给定一个序列和一个m的值,要求宰割字符串让其和等于M,如果有多个依照i的程序输入下标i-j,如果没有使得宰割的字符串的和等于M,那么找到最靠近m的字符串宰割,如果有多个依照i的从小到大输入下标i-j 算法思路:最开始应用了二维数组保留每个终点到各个起点的和,不过内存超限了,因而改用一维数组,咱们能够采纳partialSum数组保留终点1到i的和,而后任意i到j地位的间断子序列和为partialSum[j]-partialSum[i-1],因为在查找的时候有可能会找的到等于m的和也可能找不到,所以咱们先遍历一次,如果能够找的到就让min_num=M而后退出循环即可,如果没有就让min_num始终更新为最小的大于m的值,因为partialSum数组是递增的能够采纳二分查找。这里的二分查找应用STL库中自带的upper_bound函数来实现。 留神点:1、应用二维数组的形式求解极有可能呈现段谬误和内存超限的状况。提交后果: AC代码:#include <cstdio>#include <algorithm>using namespace std;int main(){ int N,M; scanf("%d %d",&N,&M); int partialSum[N+1];// 保留终点1到任意一点的数字和 partialSum[0] = 0;// 终点0不存在,然而须要参加计算所以赋值为0. for (int i = 1; i <= N; ++i) { scanf("%d",&partialSum[i]); partialSum[i] += partialSum[i-1]; } int min_num = 0x3fffffff;//保留最小的数字,如果存在等于M的数字即为M,如果没有则为大于M的最小数 for (int i = 1; i <= N; ++i) { // 取得第一个大于partialSum[i-1]+M的地位pos,pos-1就是最大的小于等于partialSum[i-1]+M的地位 int pos = upper_bound(partialSum+i,partialSum+N+1,partialSum[i-1]+M)-partialSum; if(partialSum[pos-1]-partialSum[i-1]==M){ // 找到恰好等于M的序列和 min_num = M; break; } // 走到这里阐明没有找到等于M的序列和 if(pos<=N){ // 找到了大于partialSum[i-1]+M才须要保留大于它的最小值,否则会呈现取值出错 min_num = min(partialSum[pos]-partialSum[i-1],min_num); } } // 第二次查找等于min_sum的值,肯定能够找到 for(int i=1;i<=N;++i){ // 取得第一个大于partialSum[i-1]+min_num的地位pos,pos-1就是等于partialSum[i-1]+min_num的地位 int pos = upper_bound(partialSum+i,partialSum+N+1,partialSum[i-1]+min_num)-partialSum; if(partialSum[pos-1]-partialSum[i-1]==min_num){ printf("%d-%d\n",i,pos-1); } } return 0;}

October 19, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1085-Perfect-Sequence

题目粗心:从N个正整数中抉择若干个数,使得选出的这些数中的最大值不超过最小值的p倍。问满足条件的抉择计划中,序列最长的长度是多少。 算法思路:从N个正整数中抉择若干个数字组成一个序列,最大值为M,最小值为m,那么该序列中的数字,也就是从m到M的数字肯定是原序列中的间断序列,因为不然就会存在一个数字大于m小于M不在抉择的序列中,而后将其退出到该序列使其长度变大并且合乎题意,那么原来选出的序列就不是最长的了。所以咱们肯定是在一个有序序列中选出一个间断序列,使其最大值不超过最小值的p倍。为此,应用nums数组存储输出序列,而后将该序列进行排序,在有序序列中进行查找,这里给出2种查找的形式。 算法实现1:应用二分查找,这里借助upper_bound实现,该函数能够找到第一个大于待查找元素的地位pos,如果不存在返回数组长度,那么对于满足mp>=M的序列长度就为pos减去以后遍历元素的地位。而后每次都应用ans记录更大的长度。在pos为N的时候就没有必要持续往后查问了,因为肯定都满足mp>=M,并且其长度肯定小于以后的序列长度 算法实现2:应用2个指针i和j,初始都指向0,让i指向的元素为m,让j一值向后走,晓得第一次遇见nums[i]p<nums[j]时进行,在j走动的过程中不断更新其长度(应用ans保留)而后在j进行后让i向后走一格,j接着之前的地位向后持续走(没有从0开始向后走),因为m增大的时候,小于j地位的元素都满足nums[i]p>=该元素。同样的在nums[i]*p大于序列中最大的元素max_num的时候就没有必要查问j了。 二分查找的函数阐明:1、lower_bound(起始地址,完结地址,要查找的数值) 返回的是数值 第一个 呈现的地位。2、upper_bound(起始地址,完结地址,要查找的数值) 返回的是 第一个大于待查找数值 呈现的地位。3、binary_search(起始地址,完结地址,要查找的数值)  返回的是是否存在这么一个数,是一个bool值。提交后果: AC代码1:#include <cstdio>#include <algorithm>using namespace std;int main(){ int N,p; scanf("%d %d",&N,&p); long long nums[N]; for (int i = 0; i < N; ++i) { scanf("%lld",&nums[i]); } sort(nums,nums+N); long long ans = -1; for (int j = 0; j < N; ++j) { long long m = nums[j]; // 找到第一个大于m*p的元素地位,前一个地位就是满足m*p>=M的地位,并且该序列的长度最长 long long pos = upper_bound(nums+j,nums+N,m*p)-nums; long long len = pos-j; ans = ans>len?ans:len; if(pos==N){ // 从j到N-1的所有的元素都满足m*p>=M,无需再遍历后续元素了,其长度肯定比以后短 break; } } printf("%lld",ans); return 0;}AC代码2:#include <cstdio>#include <algorithm>using namespace std;int main(){ int N,p; scanf("%d %d",&N,&p); long long nums[N]; long long max_num = -1; for (int i = 0; i < N; ++i) { scanf("%lld",&nums[i]); max_num = max_num<nums[i]?nums[i]:max_num; } sort(nums,nums+N); long long ans = -1; int j=0; for (int i=0;i<N;++i) { if(nums[i]*p>=max_num) { //nums[i]*p大于序列中最大的元素max_num,无需再往后遍历了。 ans = ans>N-i?ans:N-i; break; } while (j<N){ if(nums[i]*p<nums[j]){ //找到第一个大于m*p的元素 break; } ans = ans>j-i+1?ans:j-i+1; ++j; } } printf("%lld",ans); return 0;}

October 16, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1038-Recover-the-Smallest-Number

题目粗心:给出若干可能有前导0的数字串,将它们按某个程序拼接,使得生成的数最小。 算法思路:这个题要求一堆数字串拼接取得最小的数字,那么对于其部分问题就是有2个数字串,如何拼接使其最小,答案就是先将其2种可能拼接进去比拟其大小即可,而数字的大小其实和转化为字符串的大小比拟是统一的,而且对于拼接操作指的基本上就是字符串操作。那么也即是对于这样一堆数字串转为字符串,而后对于任意2个字符串a和b的拼接后的数字最小值转化为其字符串拼接后的最小值(后果字典序最小),而后以此类推,拼接好的字符串于另外一个字符串造成新的2个字符串的拼接比拟操作。这样就由部分最优推导出全局最优解。具体操作为应用字符串数组s存储所有的字符串,应用sort函数依据字符串拼接后果字典序从小到大排序,而后应用result将所有的字符串拼接起来,去除前导0输入即可。对于字符串全副为0的要特判输入数字0. 提交后果: AC代码:#include <cstdio>#include <algorithm>#include <string>#include <iostream>using namespace std;bool cmp(const string& a,const string& b){ return a+b<b+a;}int main(){ int N; scanf("%d",&N); string s[N]; for (int i = 0; i < N; ++i) { cin>>s[i]; } sort(s,s+N,cmp); string result; for (int i = 0; i < N; ++i) { result += s[i]; } if(result.find_first_not_of('0')==string::npos){ // 没有不为0的数字,阐明数字都为0,输入0,测试点2考查 cout<<0; } else{ cout<<result.substr(result.find_first_not_of('0')); } return 0;}

October 15, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1067-Sort-with-Swap0-i

题目粗心:给出0, 1.. N-1的一个序列,要求通过两两替换的形式将其变为递增序列,然而规定每次只能用0与其余数进行替换。求最小替换次数。 算法思路:依据题目给出的替换例子,仔细观察能够晓得其替换策略为:如果数字0以后在i号位,则找到数字i以后所处的地位,而后把0与i进行替换。然而认真思考发现如果数字0在0地位就无奈继续执行上来,所以对于该策略,须要对于数字0在地位0上进行特判,将数字0与序列中任意一个不在本位上的数字进行替换,这样策略就能够持续进行上来,直到数字0在0地位上,并且其余数字都在其本位阐明替换完结。因为每一次替换都会一个数字放到本位(数字0在0地位除外),根本能够认为这就是替换次数最小的策略 提交后果:第一次测试:测试点1和2超时,起因是每次查找不在本位上的数字都是从头去找。解决办法:应用数字j保留不在本位上的最小数字,初始为0,而后都与j进行替换,那么每次遇到0在本位上的时候,就能够和间接从j进行查找最小的不在本位上的数字,因为小于数字j的都曾经在本位上了。 留神点:1、建设数字到下标的映射会比拟不便操作,因为每一次都须要找到下标,并且下标的替换也就相当于是数字的替换。2、应用一个数字记录上一次替换中不在本位上的最小数字j,这样在数字0在本位寻找不在本位上的数字的时候就能够从j开始找,不然测试点1和2会超时。3、此题尽管题目不难理解,然而实现过程是真的心累((╥╯^╰╥)),特地容易呈现死循环。 AC代码:#include <cstdio>#include <algorithm>using namespace std;int main(){ int N; scanf("%d",&N); int indexs[N];// 保留所有数字对应的地址 int a; for (int i = 0; i < N; ++i) { scanf("%d",&a); indexs[a] = i;//数字a的地位为i } int num = 0;//替换次数 int j = 0;//不在本位上的最小数字 while (true){ // 首先判断以后数字0是否在0地位上 if(indexs[0]==0){ // 找到任意一个不在本位上的数字i与0进行替换 bool isAllinPosition = true;// 是否所有的数字都在本位上 int i = 1; for (; j < N; ++j) { if(indexs[j]!=j){ i = j; isAllinPosition = false; break; } } if(isAllinPosition) break; swap(indexs[i],indexs[0]); ++num; } else { // 将数字0和数字indexs[0]进行替换 swap(indexs[0],indexs[indexs[0]]); ++num; } } printf("%d",num); return 0;}

October 15, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1037-Magic-Coupon

题目粗心:给出两个汇合,从这两个汇合中别离选取雷同数量的元素进行一对一相乘,问能失去的最大乘积之和是多少。 算法思路:很直观的感触就是将2个汇合中负数大的顺次相乘,正数小的顺次相乘而后再相加。对这2个汇合都从小到大进行排序,那么从左往右将雷同地位的正数进行相乘,而后从右往左将雷同地位的负数进行相乘,并且将其问题累加就是最终后果。 留神点:1、不要通过乘积大于0来判断2个数字都是正数。提交后果: AC代码:#include <cstdio>#include <algorithm>using namespace std;int main(){ int NC;// coupon的数目 scanf("%d",&NC); int coupons[NC]; for (int i = 0; i < NC; ++i) { scanf("%d",&coupons[i]); } int NP;// product的数目 scanf("%d",&NP); int products[NP]; for (int j = 0; j < NP; ++j) { scanf("%d",&products[j]); } sort(coupons,coupons+NC); sort(products,products+NP); int len = NC>NP?NP:NC; int ans = 0; //首先从左到右将正数的coupons和product相乘 for (int k = 0; k < len; ++k) { if(coupons[k]<0&&products[k]<0){ ans += coupons[k]*products[k]; } else{ break; } } // 而后从右往左将负数的coupons和product相乘 for (int i=NC-1,j=NP-1;i>=0&&j>=0;--i,--j) { if(coupons[i]>0&&products[j]>0){ ans += coupons[i]*products[j]; } else{ break; } } printf("%d",ans); return 0;}

October 15, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1033-To-Fill-or-Not-to-Fill

题目粗心:已知终点与起点的间隔为D,油箱的最大油量为Cmax,单位汽油可能反对后退Davg。给定N个加油站的单位油价和离终点的间隔C所有加油站都在一条线上),汽车初始时刻处于终点地位,油箱为空,且能够在任意加油站购买任意量的汽油(前提是不超过油箱容量),求从终点到起点的最小破费。如果无奈达到起点,则输入可能行驶的最远距离。 算法思路:此题考查的是贪婪算法的思维,这里次要阐明加油站的抉择策略,并且提供2中不同的实现代码。对于每一个加油站,假如以后加油站为j,汽车能够行驶的最大间隔为max_dis。咱们首先在从加油站j能够达到的最远加油站(距杭州的间隔小于等于加油站j加上max_dis)之间进行遍历,如果能够找到比加油站j油价低的加油站,那么抉择间隔j最近的加油站为下一站。如果没有,那么找到油价最低的加油站作为下一站。如果从加油站j没有能够达到的加油站,那么阐明没法后退,以后加油站的间隔加上max_dis就是最大行驶间隔. 算法实现1:应用stations数组存储所有的加油站,这里将起点也作为一个加油站,其油价为0,间隔为D,油价之所以为0,是因为当存在max_dis的范畴蕴含起点的时候得抉择油价比以后加油站j的油价低的加油站,不然间接去起点,所以0能够保障肯定存在这样的加油站。而后依照加油站的间隔从小到大进行排序,对于第一个加油站的间隔不为0的得非凡判断,阐明不可达,间接输入"The maximum travel distance = 0.00"。而后遍历每一个加油站(真正的加油站,所以i<N而不是N+1),首先找到下一站的地址min_pos,如果min_pos==-1,阐明不存在下一站,输入最长行驶间隔。否则,进行加油,如果以后的加油站j油价较低,那么在以后加油站加满,否则只加恰好到下一站的油。最初更新以后加油站的地位j=min_pos 算法实现2:相比拟于算法实现1,没有将起点当成是一个加油站,而是进行了非凡解决。这里设置了以后加油站为now,假如在now加满油后,能够来到加油站的最大间隔为tempDis = Cmax*Davg(600),那么间隔杭州的最大间隔为gap = station[now].dis+tempDis。在下一站加油站的选取的实现上也有所区别,这里先取得汽车第一个达不到的加油站为i,那么now+1~i-1的加油站就是所有能够达到的,在now+1~i-1中选出下一站的地位。在now==N-1,也就是达到最初一个加油站的状况在最初一个加油站如果最远行驶中央能够达到起点,就累加跑到起点的油费,如果不能,阐明无奈达到起点,最远距离就是gap,并且让是否跑到起点的标记flag=false;同时如果以后不是最初一个加油站,会呈现一种特判状况,就是以后加油站加满油后能够行驶的最远地位距终点的间隔gap>=D,那么得判断在前面所有的加油站中是否存在比以后now加油站更加便宜的加油站temp,如果有,得先到temp而后再持续判断,如果没有间接达到起点,更新相干信息即可。 留神点:1、所有的数据除了加油站数目是整型int,其余的都是double。2、在终点没有加油站得特判,测试点2考查 提交后果:第一次测试:测试点4谬误。谬误起因未知,换了一种写法就通过了对于算法实现1和2都通过PAT的测试这里同时给上牛客网的测试后果,算法实现1通过了牛客网的测试,算法实现2只通过2个点((╥╯^╰╥)) 测试用例:Sample Input:15 2383 13 10424238335 086 6049 16162 103390 127963 189140 31172 1311 194567 1095Sample Output:The maximum travel distance = 506.00AC代码1:#include <cstdio>#include <algorithm>using namespace std;struct Station{ double price;//汽油单价 double dis;//以后加油站与杭州的间隔};bool cmpByDis(Station a,Station b){ return a.dis<b.dis;}int main(){ double Cmax,D,Davg;// 油箱容量,全长间隔,每单位汽油行驶的间隔 int N; //加油站个数 scanf("%lf %lf %lf %d",&Cmax,&D,&Davg,&N); Station stations[N+1]; for (int i = 0; i < N; ++i) { scanf("%lf %lf",&stations[i].price,&stations[i].dis); } stations[N].dis = D; stations[N].price = 0; // 依照间隔杭州的间隔进行排序 sort(stations,stations+N,cmpByDis); // 把起点也看成是一个加油站 // 初始化数据 double total_price = 0;//达到起点的总消耗 double tank_capacity = 0;//以后的油箱油量 double max_dis = Cmax*Davg;//汽车能够行驶的最大间隔 // 首先判断在终点是否有加油站 if(stations[0].dis!=0){ printf("The maximum travel distance = 0.00"); return 0; } for (int j = 0; j < N; ) { // 找到在间隔以后加油站max_dis内的所有加油站中油价比以后加油站低且最近的加油站,如果没有找到油价最低的加油站 int min_pos = -1; double min_price = 10000000; for (int i=j+1;i<N+1&&stations[j].dis+max_dis>=stations[i].dis;++i) { if(min_price>stations[i].price){ // 更新最小油价和地位 min_pos = i; min_price = stations[i].price; if(min_price<stations[j].price){ // 找到比以后加油站油价更低的加油站i break; } } } if (min_pos==-1){ // 不存在能够达到的加油站 printf("The maximum travel distance = %.2lf",stations[j].dis+max_dis); return 0; } // 将汽车开到min_pos地位的加油站 // 首先计算所须要的油量 double need = (stations[min_pos].dis-stations[j].dis)/Davg; if (stations[min_pos].price>stations[j].price){ //以后的加油站油价更低,加满油再走 total_price += (Cmax-tank_capacity)*stations[j].price;//更新消耗 tank_capacity = Cmax;//加油结束,更新油量 }else { // 以后的加油站油价更高,只加恰好到min_pos的油 if(tank_capacity<need){ // 油箱的油有余,须要在以后加油站加油 total_price += (need-tank_capacity)*stations[j].price;//更新消耗 tank_capacity = need;//加油结束,更新油量 } } tank_capacity -= need;//行驶结束,更新油量 j = min_pos;//达到目的地,更新加油站地位 } printf("%.2lf",total_price); return 0;}AC代码2:#include<cstdio>#include<algorithm>using namespace std;struct gasStation{ float price;//油价 float dis;//加油站到杭州的间隔 }station[500];bool cmp(gasStation g1,gasStation g2){ return g1.dis<g2.dis;}int main(){ float Cmax,D,Davg; //汽车油箱的量,杭州到起点城市的间隔,每单元汽油能够跑的间隔 int N;//加油站数目 scanf("%f %f %f %d",&Cmax,&D,&Davg,&N); for(int i=0;i<N;++i){ scanf("%f %f",&station[i].price,&station[i].dis); } sort(station,station+N,cmp); float cheapest = 0;//跑到起点的起码油钱 bool flag = true;//是否跑到起点 float maxDis = 0;//不能跑到起点的最大间隔 float tempDis = Cmax*Davg;//从以后加油站登程加满油能跑得最大间隔 int now = 0;//最近工夫一次汽车加油的加油站 float CRemain = 0;//油箱残余油量,初始为0 if(station[now].dis!=0){//以后开始的加油站间隔杭州不为0,所以加不了油,无奈发车 printf("The maximum travel distance = 0.00"); return 0; } while(now<N){ int CNeed = Cmax-CRemain;//以后汽车所能加油的最大油量 float gap = station[now].dis+tempDis;//假如加满从以后加油站跑得最远的中央间隔杭州的间隔 if(now==N-1){//以后是最初一个加油站,前面没有加油站了,就无奈再遍历,得特判 if(gap<D) {//gap<D,阐明没有跑到最终地位 maxDis = gap;//能跑得最远距离就是gap flag = false; }else{//能够跑到起点 cheapest += station[now].price*(D-station[now].dis)/Davg;//累计跑到起点的钱 } break; } if(gap>=D){//从now加满油后能够跑到起点,且以后加油站不是最初一个加油站,得判断在now到最初加油站之间是否有比now更便宜的加油站 int temp; for(temp=now+1;temp<N;++temp){ if(station[temp].price<station[now].price){ break; } } if(temp!=N){//阐明的确有,不能间接从now到起点,得先去k加油站,而后再判断 float consumed_gas = (station[temp].dis-station[now].dis)/Davg;//从now到temp所须要加的油 if(CRemain>=consumed_gas){//油箱残余油量能够达到下一加油站,就不在now加油了 CRemain -= consumed_gas; } else{//不够就加恰好到temp的油 cheapest += station[now].price*(consumed_gas-CRemain); CRemain = 0;//达到temp加油站的时候恰好为0 } now = temp;//更新下一个达到的加油站 maxDis = station[temp].dis;//更新间隔,跑到加油站temp continue;//间接进入下一次判断了,无需再找到比now高的最低油价,因为没有比now低油价的加油站间接就开到起点了 }else{//如果没有比now更低油价的加油站,间接去起点 cheapest += station[now].price*(D-station[now].dis)/Davg;//累计跑到起点的钱 break; } } int i;//保留从now加满油后,以后汽车第一个跑不到的加油站i for(i=now+1;i<N;++i){//找到以后汽车第一个跑不到的加油站i,i-1就是所能跑到的最初一个加油站 if(station[i].dis>gap){ break; } } //now+1到i-1的所有加油站就是以后汽车所能跑到的所有加油站,找到价格最近的且油价小于now的那个加油站k int k; for(k=now+1;k<i;++k){ if(station[k].price<station[now].price){ break; } } if(k==i){//阐明没有比now更低油价的加油站,则找油价最低的 k=now+1;//对k从新赋值 for(int j=now+2;j<i;++j){ if(station[j].price<station[k].price){ k = j; } } } //在有比now加油站更低油价的时候,k保留的就是比now更低油价的加油站,如果没有k保留的就是最低油价的加油站 if(station[k].price<station[now].price){//如果下一个加油站的油更便宜,只加到恰好到k加油站的油即可 float consumed_gas = (station[k].dis-station[now].dis)/Davg;//从now到k所须要加的油 if(CRemain>=consumed_gas){//油箱残余油量能够达到下一加油站,就不在now加油了 CRemain -= consumed_gas; } else{//不够就加恰好到k的油 cheapest += station[now].price*(consumed_gas-CRemain); CRemain = 0;//达到k加油站的时候恰好为0 } } else{//否则就加满 cheapest += station[now].price*CNeed;//累计加满油的钱 CRemain = Cmax-(station[k].dis-station[now].dis)/Davg;//更新达到下一个加油站的残余油量 } now = k;//更新为下一个加油站k maxDis = station[k].dis;//更新间隔,跑到加油站k } if(flag){//能跑到起点 printf("%.2f",cheapest); }else{ printf("The maximum travel distance = %.2f",maxDis); } return 0;}

October 14, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1070-Mooncake

题目粗心:现有月饼需求量为D.已知n种月饼各自的库存量和总售价,问如何销售这些月饼,使得能够取得的收益最大。求最大收益。 算法思路:此题是应用贪婪的思维来求解,个别为了取得最大收益,会首先将售价最高的月饼尽可能多的销售进来,因为在需求量肯定的状况下,单价高的月饼总售价也会更高,所以咱们对所有的月饼依照其单价从高到低进行排序,而后顺次遍历所有的月饼,如果以后月饼的数量小于等于需求量,那么就全副售出并计算收益和更新需求量,否则就卖出和需求量相等的月饼并计算收益和更新需要为0,在需求量为0的时候进行遍历即可。 留神点:1、题目说数量为负数然而没说是正整数(positive integer)所以得将amount和D设置为double,不然测试点2会呈现谬误2、在D==0的时候应该退出循环,并且不能在为0的时候就输入利润,否则测试点3谬误。 提交后果:第一次测试:测试点2谬误。谬误起因:amount定义为了int类型。改过办法:将amount定义为double,并且为了不便计算将需求量也定义为double类型 */ AC代码:#include <cstdio>#include <algorithm>using namespace std;struct MoonCake{ double price;//总价 double unit_price;//单价 double amount;//数量};bool cmpByUnitPrice(MoonCake a,MoonCake b){ return a.unit_price>b.unit_price;}int main(){ int N; double D; scanf("%d %lf",&N,&D); MoonCake moonCakes[N]; for (int i = 0; i < N; ++i) { scanf("%lf",&moonCakes[i].amount); } for (int i = 0; i < N; ++i) { scanf("%lf",&moonCakes[i].price); moonCakes[i].unit_price = moonCakes[i].price/moonCakes[i].amount; } // 依照单价从高到低排序 sort(moonCakes,moonCakes+N,cmpByUnitPrice); double profit = 0;//总利润 for (int j = 0; j < N; ++j) { if (moonCakes[j].amount<=D){ // 以后月饼全副卖出 profit += moonCakes[j].price; D -= moonCakes[j].amount; } else { // 卖出一部分 profit += moonCakes[j].unit_price*D; D = 0; } if(D==0){ // 需要为0退出循环 break; } } printf("%.2lf",profit); return 0;}

October 14, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1048-Find-Coins

题目粗心:给出n个正整数和一个正整数m,问n个数字中是否存在一对数字a和b(a$\leq$b),使得a+b=m成立。如果有多对,输入a最小的那一对。 算法思路1:应用hash散列的思维来解决每一个输出的数字,nums来记录每一个数字呈现的次数,下标为数字,其值为呈现次数。那么咱们从0开始遍历始终到1000,对于每一个数字j,首先得判断j和M-j呈现的次数是否都不为0,如果是,对于所有j!=M-j(两数字不相等)或者j==M-j并且nums[j]>1(两数字相等然而呈现了至多2次),就输入j和M-j,并且完结程序即可。如果遍历结束,输入No Solution 算法思路2:应用二分查找的思维解决该问题,首先,应用coins数组存储所有的货币,而后对其从小达到排序,遍历每一个coins[j],在数组中查找M-coins[j],如果存在,并且其地位pos不等于j,就输入coins[j]和M-coins[j],并且退出循环。如果遍历结束,输入No Solution. 算法思路3:同样应用coins数组存储所有的货币,而后对其从小达到排序。而后应用指针i指向0,指针j指向N-1,而后i向右走,j向左走,如果i和j指向的数字之和sum等于M,就输入coins[i]和coins[j]而后完结程序,如果sum<M,阐明coins[i]太小,那么就让i向右走一格,否则说coins[j]太大,就让j向左走一格。如果遍历结束,输入No Solution 留神点:1、得将nums数组写在main函数里面,不然测试点1报错。2、得思考2个数字相等的状况,不然测试点1报错。3、二分法的二分函数得写在main函数外,不然测试点1和2会运行超时。4、集体举荐算法思路3,无需思考2个数字相等 提交后果: AC代码1:#include <cstdio>using namespace std;int nums[1001];// 得写在main函数里面int main(){ int N,M; scanf("%d %d",&N,&M); int temp; for (int i = 0; i < N; ++i) { scanf("%d",&temp); ++nums[temp]; } for (int j = 0; j < 1001; ++j) { if(nums[j]!=0&&nums[M-j]!=0){ if(j!=M-j||(2*j==M&&nums[j]>1)){ printf("%d %d",j,M-j); return 0; } } } printf("No Solution"); return 0;}AC代码2:#include <cstdio>#include <algorithm>using namespace std;int binarySearch(int left,int right,int num,const int coins[]){ int mid; while (left<=right){ mid = left + (right-left)/2; if(coins[mid]==num){ return mid; } else if (coins[mid]<num){ left = mid+1; } else { right = mid-1; } } return -1;}int main(){ int N,M; scanf("%d %d",&N,&M); int coins[N]; for(int i=0;i<N;++i){ scanf("%d",&coins[i]); } sort(coins,coins+N); for (int j = 0; j < N; ++j) { //对于coins[j],查找M-coins[j]==coins[k]的k并且k!=j int mid; int pos = binarySearch(0,N-1,M-coins[j],coins); if(pos!=-1&&pos!=j){ printf("%d %d",coins[j],coins[pos]); return 0; } } printf("No Solution"); return 0;}AC代码3:#include <cstdio>#include <algorithm>using namespace std;int main(){ int N,M; scanf("%d %d",&N,&M); int coins[N]; for(int i=0;i<N;++i){ scanf("%d",&coins[i]); } sort(coins,coins+N); int i=0,j=N-1; while (i<j){ if(coins[i]+coins[j]==M){ printf("%d %d",coins[i],coins[j]); return 0; } else if(coins[i]+coins[j]<M){ ++i; } else { --j; } } printf("No Solution"); return 0;}

October 13, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1050-String-Subtraction

题目粗心:给出2个字符串,在第一个字符串中删除第二个字符串中呈现过的所有字符并且输入 算法思路:s1和s2别离记录第一和第二个字符串,应用table记录所有在s2中呈现过的字符,在遍历s1的过程中,只有以后字符在table中没有被记录就输入即可。 提交后果: AC代码:#include <unordered_map>#include <iostream>using namespace std;int main(){ string s1,s2; getline(cin,s1); getline(cin,s2); unordered_map<char,bool> table; for (int i = 0; i < s2.size(); ++i) { table[s2[i]] = true; } for (int j = 0; j < s1.size(); ++j) { if(!table[s1[j]]){ printf("%c",s1[j]); } } return 0;}

October 13, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1092-To-Buy-or-Not-to-Buy

题目粗心:给出两串珠子中每颗珠子的色彩,问第一串中是否有第二串中的所有珠子,即对每种色彩来说,第一串中该色彩珠子的个数必须不小于第二串中该色彩珠子的个数。如果是,输入“Yes"”,并输入第一串中除了用来和第二串珠子进行匹配以外,还剩多少珠子:如果不是,则输入“No",并输入第串中还少多少个珠子,能力让第一串领有第二串中的所有珠子。 算法思路:咱们应用counts记录抉择的珠子的每一个色彩的珠子呈现的次数,而后再遍历第二串珠子,对于每一个珠子咱们将其数量减一,示意已取得,如果呈现小于0的状况,阐明该色彩的珠子数量不够,令isEnough记录为false。最初对于isEnough为true的状况,咱们间接将counts数组中每一种色彩的珠子的数目进行累计,对于isEnough为false的状况,咱们只累减数目小于0的珠子。这样就失去多进去或者少的珠子数目。 留神点:1、对于珠子数目短缺的状况,能够间接应用长的珠子数目减去短的珠子数目来取得最初多进去的珠子数目。 提交后果: AC代码:#include <unordered_map>#include <iostream>using namespace std;int main(){ string s1,s2; cin>>s1>>s2; unordered_map<char,int> counts; for (int i = 0; i < s1.size(); ++i) { ++counts[s1[i]]; } bool isEnough = true;// 是否短缺 for (int j = 0; j < s2.size(); ++j) { --counts[s2[j]]; if(counts[s2[j]]<0){ isEnough = false; } } int sum = 0; unordered_map<char,int>::iterator it; if(isEnough){ //统计多进去的珠子数目 printf("Yes "); for (it=counts.begin();it!=counts.end();++it) { sum += it->second; } } else { //统计短少的珠子数目 printf("No "); for (it=counts.begin();it!=counts.end();++it) { if(it->second<0){ sum -= it->second; } } } printf("%d",sum); return 0;}

October 13, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1084-Broken-Keyboard

题目粗心:给定2个字符串,第一个字符串为输出的字符串,第二个字符串为输入字符串,依据没有输入的字符,输入坏掉的键 算法思路:咱们能够应用notBroken记录哪些字符是好的键,在输出s1和s2的时候,首先变量s2,将s2的字符全副记录为好键,notBroken[s2[i]] = true,而后遍历s1,对于在notBroken中显示s1的字符不是好键的就进行输入,然而因为只输入一次,所以在输入结束后,就将其记录为好键。 留神点:1、对于英文字符要转换为大写字母提交后果: AC代码:#include <unordered_map>#include <iostream>using namespace std;int main(){ string s1,s2; cin>>s1>>s2; unordered_map<char,bool> notBroken; for (int i = 0; i < s2.size(); ++i) { // 首先将小写字母转化为大写字母 if(s2[i]>='a'&&s2[i]<='z'){ s2[i] = s2[i] - 32; } notBroken[s2[i]] = true;//s2字符串的字符都是好键 } for (int i = 0; i < s1.size(); ++i) { // 首先将小写字母转化为大写字母 if(s1[i]>='a'&&s1[i]<='z'){ s1[i] = s1[i] - 32; } if(!notBroken[s1[i]]){ printf("%c",s1[i]); } // 示意曾经输入过了 notBroken[s1[i]] = true; } return 0;}

October 13, 2020 · 1 min · jiezi

关于算法-数据结构:算法最大子数组

问题形容:给定一只股票在某段时间内的历史价格变动曲线,找出一个可能实现收益最大化的时间段。了解:为找出最大化的收益,须要思考的是在买进和卖出时的价格变动幅度,因而从该股票的每日变动幅度来思考问题比拟适合。由此,能够将上述问题稍作变形:给定一只股票在某段时间内的每日变动幅度,找出一个适合的买进和卖出工夫,以实现收益最大化。因而,将输出数据转换如下,并试图在整个时间段中找到一个累加和最大的子区间,亦即最大子数组。暴力求解:首先可能想到的是在一个给定数组(区间)中,其子数组(子区间)的个数是C(2,n),很容易就能遍历完所有子数组从而找出最大的那个,其最坏状况渐进工夫复杂度是(n2)。假如每日变动幅度保留在数组A中(A的下标从1到n),A.length示意A的元素个数,最终后果以元组模式返回;给出伪码如下:BRUTE_FORCE(A) i = 1 sum = -infinity for i <= A.length, inc by 1 j = i last_sum = 0 for j <= A.length, inc by 1 last_sum += A[j] if last_sum > sum sum = last_sum start = i end = j return (start, end, sum)java代码实现: private static void bruteForce(int[] arr) { int start = -1, end = -1, max = 0; for (int i = 0; i < arr.length; i++) { // 定义lastSum的目标,每一轮新的循环都须要从新累计 int lastSum = 0; for (int j = i; j < arr.length; j++) { lastSum += arr[j]; if (lastSum > max) { max = lastSum; start = i; end = j; } } } System.out.println("maxSum = " + max + ", start : " + start + ", end = " + end); }分治求解:分治策略通常蕴含:合成子问题,解决子问题,合并子问题。由此能够推出大抵的解决思路:首先仍然假如数据输出如上一个办法那样,而后思考将A[1...n]拆分为规模大致相同的两个子数left[1...mid]和right[mid+1...n],其中mid=(1+n)/2向下取整,那么能够必定,最大子数组要么在这两个子数组中,要么横跨这两个子数组,因而能够别离求解这三种状况,取其中最大的子数组并返回即可。对于left/right子数组可递归求解,而对于横跨两个子数组的状况,如果可能使得该状况下的求解工夫复杂度为O(n),那么应该能让整体的最坏工夫复杂度低于(n2)。如果仅仅是通过遍历所有蕴含A[mid]和A[mid+1]的子数组来找最大子数组,那么很显然仅求解该状况就须要(n2)的工夫。能够推断横跨两个子数组的最大子数组,必须由两个别离在left/right中的子数组组成,这两个子数组在别离蕴含了A[mid]和A[mid+1]的所有子数组中是最大的;因为如果存在一个不满足上述条件的最大子数组,那么总能够用上述办法找到一个更大的子数组。根据上述思路,很容易推知求解横跨两个子数组的状况只须要O(n)的工夫。由此给出伪码如下:(1)子过程:找出横跨两个子数组的最大子数组 FIND_CROSSING_MAX_SUBARRAY(A, low, mid, high) left_sum = -infinity sum = 0 i = mid for i >= low, dec by 1 sum += A[i] if sum > left_sum left_sum = sum left_index = i right_sum = -infinity sum = 0 i = mid + 1 for i <= high, inc by 1 sum += A[i] if sum > right_sum right_sum = sum right_index = i return (left_index, right_index, left_sum+right_sum)(2)主过程:分治法找出最大子数组FIND_MAX_SUBARRAY(A, low, high) if low == high return (low, high, A[low]) else mid = down_trunc((low + high) / 2) (left_start, left_end, left_sum) = FIND_MAX_SUBARRAY(A, low, mid) (right_start, right_end, right_sum) = FIND_MAX_SUBARRAY(A, mid+1, high) (cross_start, cross_end, cross_sum) = FIND_CROSSING_MAX_SUBARRAY(A, low, mid, high) if left_sum > right_sum and left_sum > cross_sum return (left_start, left_end, left_sum) else if right_sum > left_sum and right_sum > cross_sum return (right_start, right_end, right_sum) else return (cross_start, cross_end, cross_sum)能够看出上述算法渐进工夫复杂度为(nlg(n))。java代码实现: ...

October 12, 2020 · 3 min · jiezi

关于算法-数据结构:PAT甲级1082-Read-Number-in-Chinese

题目粗心:依照中文发音规定输入一个绝对值在9位以内的整数。 算法思路:此题考查的细节次要在0和Wan的输入上,因为只有9位,那么最高位只会达到亿。那么首先应用units数组寄存每一位数字的单位(从个、十、百到亿),因为万,十万,百万,千万的单位每一个都有万字,然而在输入的时候只有该组数位中不为0数字对应的最小单位才会带上万输入,所以,这里在units[4]存储Wan,其余位只存储十,百,千。同时应用nums数组存储所有的数字对应的中文拼音,下标(int)和数字(string)为一一对应关系。因为这里因为万位数字的非凡解决关系,将数字分为三个局部: 1、个、十、百、千,也就是从右往左第一个到第四个数字 2、万、十万、百万、千万,也就是从右往左第五个到第八个数字 3、亿,也就是从右往左第九个数字那么咱们首先将数字N转存到数组digits中,其程序恰好是逆序,这样咱们从左往右依照每一个局部进行解决。对于万位数字的解决,咱们应用is_first_wan变量记录以后的万位数字是否是呈现的第一个万位数字,如果是,那么得增加Wan。这里为了更加不便的解决0的输入,得先进行预处理,首先咱们留神到,只有2个不为0的数字间隔相差大于1的不论两头有几个0存在,最初都只会输入一个0,所以,咱们应用数组non_zero和non_zero_index别离保留digits数组中不为0的数字以及其在digits中的下标地位,这里不采纳hash的办法来记录地位和对应的值是因为不同数位上的数字会呈现反复。接着就是遍历non_zero数组,解决所有的不为0的数字,应用vector<string> result保留最初的后果 个、十、百、千位的解决:1、如果不是第一位不为0的数字,并且与上一位数字的地位相差大于1,咱们首先增加0(result.push_back(nums[0]))2、如果以后位是个位,只增加对应的数字result.push_back(nums[non_zero[j]]),如果不是,得先增加单位result.push_back(units[non_zero_index[j]]) 万、十万、百万、千万位的解决:1、如果不是第一位不为0的数字,并且与上一位数字的地位相差大于1,咱们首先增加0(result.push_back(nums[0]))2、如果是第一次呈现万位数字,首先得增加一个Wan,而后让is_first_wan为false;3、如果以后位数字恰好为第五个(也就是万),那么肯定是第一次呈现的,所以只须要增加数字即可,否则就得增加上十,百,千补全单位,而后再增加数字 亿位的解决:1、如果不是第一位不为0的数字,并且与上一位数字的地位相差大于1,咱们首先增加0(result.push_back(nums[0]))2、先增加单位,而后增加数字。 从下面能够看到因为都须要解决0,于是能够将该步骤抽取进去作为公共代码局部。 符号的解决:对于正数,在最初增加一个Fu即可result的输入:逆序输入,对于i>0的状况,得输入空格。留神点:1、如果在个位增加了单位,也就是字符串数组中增加了"",会输入多与的空格,测试点0和测试点4就会呈现格局谬误2、边界数据0,该当间接输入ling,测试点3考查3、测试点1谬误的能够思考如下数据:808808808,ba Shi Wan前面不须要再输入ling,而是间接输入ba Qian 给出一组测试数据:808080808 ba Yi ling ba Bai ling ba Wan ling ba Bai ling ba-880808080 Fu ba Yi ba Qian ling ba Shi Wan ling ba Qian ling ba Shi800000008 ba Yi ling ba800000000 ba Yi80000008 ba Qian Wan ling ba80008000 ba Qian Wan ling ba Qian80000000 ba Qian Wan提交后果: AC代码:#include <cstdio>#include <string>#include <vector>using namespace std;int main(){ string units[9] = {"","Shi","Bai","Qian","Wan","Shi","Bai","Qian","Yi"}; string nums[10] = {"ling","yi","er","san","si","wu","liu","qi","ba","jiu"}; int N; int digits[10]; int index = 0;//digit的工作指针 scanf("%d",&N); int n = N; if(n<0){ n = -n; } if(n==0) { // 0特判 printf("ling"); return 0; } //将n的每一位应用digits数组存储 while (n!=0){ digits[index++] = n%10; n /= 10; } // 从左到右遍历digits数组(也即是从后往前遍历数字的每一位),记录所有不为0的数字及其地位 int non_zero[10];// 保留所有不为0的数字 int non_zero_index[10];// 保留所有不为0数字对应的下标 int non_zero_p = 0;//non_zero的工作指针 for (int i = 0; i < index; ++i) { if(digits[i]!=0){ non_zero[non_zero_p] = digits[i]; non_zero_index[non_zero_p] = i; ++non_zero_p; } } //遍历non_zero数组,解决所有的不为0的数字 vector<string> result;// 保留后果汇合 bool is_first_wan = true;//判断是否曾经增加了Wan for (int j = 0; j < non_zero_p; ++j) { // 首选解决数字0 if(j!=0&&(non_zero_index[j]-non_zero_index[j-1])>1){ // 不是个位数字,并且与上一位数字的地位相差大于1 result.push_back(nums[0]);//增加一个0 } if(non_zero_index[j]<4){ // 个、十、百、千位 if(non_zero_index[j]!=0){// 个位的单位不能增加,因为""也会输入一个空格,测试点0和4考查 result.push_back(units[non_zero_index[j]]); } result.push_back(nums[non_zero[j]]); } else if(non_zero_index[j]<8) { // 万、十万、百万、千万位 if(is_first_wan){ // 第一次呈现万位及其以上的状况 result.push_back(units[4]);//增加一个Wan is_first_wan = false; } if(non_zero_index[j]!=4){ // 十万、百万、千万位须要在万后面增加十、百、千 result.push_back(units[non_zero_index[j]]); } //最初增加数字 result.push_back(nums[non_zero[j]]); } else { // 亿位 result.push_back(units[non_zero_index[j]]); result.push_back(nums[non_zero[j]]); } } if(N<0) { result.emplace_back("Fu"); } for (int i=result.size()-1;i>=0;--i) { printf("%s",result[i].c_str()); if(i>0) printf(" "); } return 0;}

October 12, 2020 · 2 min · jiezi

关于算法-数据结构:算法最大子数组

问题形容: 给定一只股票在某段时间内的历史价格变动曲线,找出一个可能实现收益最大化的时间段。 了解: 为找出最大化的收益,须要思考的是在买进和卖出时的价格变动幅度,因而从该股票的每日变动幅度来思考问题比拟适合。由此,能够将上述问题稍作变形:给定一只股票在某段时间内的每日变动幅度,找出一个适合的买进和卖出工夫,以实现收益最大化。因而,将输出数据转换如下,并试图在整个时间段中找到一个累加和最大的子区间,亦即最大子数组。 暴力求解办法: 首先可能想到的是在一个给定数组(区间)中,其子数组(子区间)的个数是C(2,n),很容易就能遍历完所有子数组从而找出最大的那个,其最坏状况渐进工夫复杂度是(n2)。假如每日变动幅度保留在数组A中(A的下标从1到n),A.length示意A的元素个数,最终后果以元组模式返回;给出伪码如下: BRUTE_FORCE(A) i = 1 sum = -infinity for i <= A.length, inc by 1 j = i last_sum = 0 for j <= A.length, inc by 1 last_sum += A[j] if last_sum > sum sum = last_sum start = i end = j return (start, end, sum)分治求解办法: 上述办法的渐进工夫复杂度差强人意。类比于归并排序,有时采纳分治策略可能取得更好的工夫复杂度。分治策略通常蕴含分解成子问题、解决子问题、合并子问题。由此能够推出大抵的解决思路:首先仍然假如数据输出如上一个办法那样,而后思考将A[1...n]拆分为规模大致相同的两个子数组left[1...mid]和right[mid+1...n],其中mid=(1+n)/2向下取整,那么能够必定,最大子数组要么在这两个子数组中,要么横跨这两个子数组,因而能够别离求解这三种状况,取其中最大的子数组并返回即可。 对于left/right子数组可递归求解,而对于横跨两个子数组的状况,如果可能使得该状况下的求解工夫复杂度为O(n),那么应该能让整体的最坏工夫复杂度低于(n2)。如果仅仅是通过遍历所有蕴含A[mid]和A[mid+1]的子数组来找最大子数组,那么很显然仅求解该状况就须要(n2)的工夫。能够推断横跨两个子数组的最大子数组,必须由两个别离在left/right中的子数组组成,这两个子数组在别离蕴含了A[mid]和A[mid+1]的所有子数组中是最大的;因为如果存在一个不满足上述条件的最大子数组,那么总能够用上述办法找到一个更大的子数组。 根据上述思路,很容易推知求解横跨两个子数组的状况只须要O(n)的工夫。由此给出伪码如下: (1)子过程:找出横跨两个子数组的最大子数组 FIND_CROSSING_MAX_SUBARRAY(A, low, mid, high) left_sum = -infinity sum = 0 i = mid for i >= low, dec by 1 sum += A[i] if sum > left_sum left_sum = sum left_index = i right_sum = -infinity sum = 0 i = mid + 1 for i <= high, inc by 1 sum += A[i] if sum > right_sum right_sum = sum right_index = i return (left_index, right_index, left_sum+right_sum)(2)主过程:分治法找出最大子数组 ...

October 12, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1041-Be-Unique

题目粗心:给定N个数,依照读入的程序,输入第一个只呈现了一次的数,如果没有输入None 算法思路:此题次要应用hash的思维,将呈现的数字作为下标,呈现的次数作为值,而后一一对应,在输出的时候,应用num数组承受所有的值,count数组保留所有值所呈现的次数,因为最大数字为10000,所以count数组得开10001以上,而后应用j指针遍历数组num,如果count[num[j]]==1,阐明依照读入的程序有一个只呈现了一次的数字,将其输入并且退出循环即可。如果最初j==N阐明没有符合条件的数字,输入None; 留神:应用cin会超时提交后果: AC代码:#include <cstdio>using namespace std;int main(){ int N; scanf("%d",&N); int num[N]; int count[10001]; for (int i = 0; i < N; ++i) { scanf("%d",&num[i]); ++count[num[i]]; } int j; for (j = 0; j < N; ++j) { if(count[num[j]]==1){ printf("%d",num[j]); break; } } if(j==N){ // 没有符合条件的输出 printf("None"); } return 0;}

October 12, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1077-Kuchiguse

题目粗心:给定N个字符串,求它们的公共后缀,如果没有就输入nai 算法思路:求字符串的公共后缀就得想到从后往前进行遍历雷同的字符了,为此应用n记录三个字符串的最小长度,作为遍历的最大长度,而后应用i,j,k别离作为s1,s2,s3的下标指针,只有s1[i]==s2[j]&&s1[i]==s3[k]就阐明以后字符为公共字符,增加到后果字符串r中,而后--i,--j,--k,否则就间接break退出循环。退出循环后,间接逆置字符串r,如果r为空阐明没有公共后缀,输入nai,否则就输入r即可。 留神点:这里的输出字符串得用getline承受一行,cin遇到空格就进行输出了,同时在输出N后得用getchar()承受回车,防止被前面的getline承受到。 提交后果: AC代码:#include <cstdio>#include <iostream>#include <algorithm>using namespace std;int main(){ int N; cin>>N; getchar();// 承受回车 string s1,s2,s3; getline(cin,s1); getline(cin,s2); getline(cin,s3); int n = min(min(s1.size(),s2.size()),s3.size()); int i = s1.size()-1,j=s2.size()-1,k=s3.size()-1;//s1,s2,s3的下标指针 string r; for (int l = 0; l < n; ++l) { if(s1[i]==s2[j]&&s1[i]==s3[k]){ r += s1[i]; --i; --j; --k; } else{ break; } } reverse(r.begin(),r.end()); if(r.empty()){ cout<<"nai"; } else{ cout<<r; } return 0;}

October 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1035-Password

题目要求:给定n集体的用户名和明码,依照要求替换明码中的特定字符(1 (one) by @, 0 (zero) by %, l by L, and O by o),如果不存在须要批改的明码,则须要依据单复数输入There is(are) 1(n) account(s) and no account is modified 算法思路:在输出明码的时候就遍历该明码的每一个字符,应用flag标记该明码是否须要批改,如果批改过就应用vector<string> modified保留批改过的字符串(username+password),其大小能够用来判断批改过的明码的多少 ,最初依照modified.size()是否为0来进行不同的输入即可。 提交后果: AC代码:#include<cstdio>#include<string>#include<iostream>#include<vector>using namespace std;vector<string> modified;//用于保留批改过的字符串(username+password),其大小能够用来判断批改过的明码的多少 int main(){ int n;//明码总数 cin>>n; string username,password; for(int i=0;i<n;++i){ cin>>username>>password;//批改明码中的1 (one) by @, 0 (zero) by %, l by L, and O by o bool flag = false;//判断是否被批改 for(int j=0;j<password.size();++j){ if(password[j]=='1'){ password[j] = '@'; flag = true; } if(password[j]=='0'){ password[j] = '%'; flag = true; } if(password[j]=='l'){ password[j] = 'L'; flag = true; } if(password[j]=='O'){ password[j] = 'o'; flag = true; } } if(flag){//批改过了 modified.push_back(username+" "+password); } } if(modified.size()==0){ if(n>1){ cout<<"There are "<<n<<" accounts and no account is modified"; }else{ cout<<"There is 1 account and no account is modified"; } }else{ cout<<modified.size()<<endl; for(auto s:modified){ cout<<s<<endl; } } return 0;}

October 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1005-Spell-It-Right

题目要求:给定一个非负整数,将其每一位进行相加,而后对后果的每一位从左往右输入其对应的英文数字 算法思路:因为整数大小最大为10^100,得用string存储才行,对string中的每一位减去'0'后进行求和,而后再转化为string,从左往右顺次输入对应每一位的英文字母,为了不便对最终后果的每一位进行输入建设从数字到英文单词的映射numToWords,下标示意每一位数字,其值代表对应的英文单词。 提交后果: AC代码:#include<cstdio>#include<string>#include<iostream>using namespace std;string numToWords[10] = {"zero","one","two","three","four","five","six","seven","eight","nine"};int main(){ string a; cin>>a; //对每一位进行求和 int sum = 0; for(int i=0;i<a.length();++i){ sum += (a[i]-'0'); } //而后将sum转化为string不便对每一位进行操作 string s = to_string(sum); for(int i=0;i<s.length();++i){ cout<<numToWords[s[i]-'0']; if(i<s.length()-1) cout<<" "; } return 0 ;}

October 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1001-AB-Format

题目粗心:给出两个整数a,b,求a+b的值,并依照每三位用一个逗号分隔分模式输入。 算法思路:这里给出2种解题思路,第一种是通用的解法,对于任意整数都实用。第二种是利用此题的数据范畴来解题。 第一种办法:首先计算a+b的值c,对于正数先输入负号,而后对后果取绝对值,应用string r存储最初输入的后果,将c的每一位逆序增加到r中,并且每增加3位就增加一个逗号(应用index来标记增加位数),这里得特判c==0的状况,间接输入0,最初将r逆序输入即可。 第二种办法:仔细分析下就能够晓得,最大后果为-210^6和210^6,也就是最多只有7位数字局部,那么最多就2个中央可能须要增加逗号如:1,000,000首先将数字c取绝对值而后转化为string类型,用string s保留,而后将s逆置,判断是否有7位,如果abs(c)>999999的话,就阐明得增加2个逗号,如果abs(c)>999的话阐明增加1个逗号,否则不增加,每隔三位增加一个逗号 。 提交后果:第一次测试:测试点4谬误,起因在于没有判断a+b等于0的状况 AC代码:第一种办法:#include <cstdio>#include <string>#include <algorithm>using namespace std;int main(){ int a,b; scanf("%d %d",&a,&b); int c = a+b; if(c<0){ printf("-"); c = -c; } if (c==0){ printf("0"); return 0; } string r; int index = 0; while (c!=0){ if(index%3==0&&index!=0){ // 这里得排除初始index=0的状况 r += ","; } r += to_string(c%10); c /= 10; ++index; } reverse(r.begin(),r.end()); printf("%s",r.c_str()); return 0;}第二种办法:#include<string>#include<iostream>#include<algorithm>using namespace std;int main(){ int a,b; cin>>a>>b; int c = a+b; int d = abs(c); string s = to_string(d); string result = ""; if(c<0) cout<<"-"; reverse(s.begin(),s.end()); if(d>999999){ result += s.substr(0,3)+","+s.substr(3,3)+","+s.substr(6); }else if(d>999){ result += s.substr(0,3)+","+s.substr(3); }else{ result += s; } reverse(result.begin(),result.end()); cout<<result;}

October 10, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1073-Scientific-Notation

题目粗心:对于给定一个迷信计数法模式的数字,要求将其批改为一般数字示意的模式,并且要求将其无效位都保留。 算法思路:因为指数局部的绝对值不超过9999,阐明应用double无奈存储该数字了,只能应用字符串或者字符数组,这里采纳字符串的形式进行解决,迷信计数法的数字能够分为无效局部加上指数局部,其分割线为字符E。咱们应用indexOfE保留其E字符的地位,subString保留无效局部的绝对值,接下来就是依据指数的正负来判断是小数点往左还是往右挪动了,咱们应用sgn_expo保留指数的正负,true为正,应用expo保留指数的绝对值,最初对于其输入后果应用result保留,初始为保留该数字的符号。result的获取办法如下: 1、指数为正数,小数点向左挪动,也就是在左侧增加expo个0。首先增加"0.",而后再增加expo-1个0,最初再增加subString去除小数点的局部。 比方+1.23400E-03取得result的办法为,""->"0."->"0.000"->"0.000123400". 2、指数为负数,小数点向右挪动,该状况分为2种。咱们应用rest_len示意无效局部中小数点后的局部长度2.1、如果rest_len>expo,阐明小数点向右挪动后,仍然存在(小数点在expo地位),那么就先增加无效局部的第一位subString[0],再从小数点前面增加expo位字符subString.substr(2,expo) ,接着就是增加小数点,最初增加剩下来的无效局部subString.substr(2+expo)。 比方:-1.23E+1取得result的办法:"-"->"-1"->"-12"->"-12."->"-12.3"。 2.2、如果rest_len<expo,阐明小数点向右挪动后,就不存在小数点了,那么就将无效局部的小数点去掉,赋值给result后,再增加expo-rest_len个0就好。 比方:-1.2E+10取得result的办法为:"-"->"-12"->"-12000000000" 提交后果:AC代码:#include<cstdio>#include<string>#include <iostream>using namespace std;int main(){ string s; cin>>s; bool isPositive = s[0]=='+';//记录该数字的正负 //找到E的地位 int indexOfE; for(indexOfE=1;indexOfE<s.size();++indexOfE){ if(s[indexOfE]=='E') break; } //截取1到indexOfE之间的字符串 string subString = s.substr(1,indexOfE-1); // 获取指数的符号和大小 bool sgn_expo = s[indexOfE+1]=='+';//true代表为正 int expo = stoi(s.substr(indexOfE+2));// 指数的绝对值 string result = isPositive?"":"-";//后果字符串 // 依据指数的符号判断小数点向左挪动还是向右挪动 if(!sgn_expo){ //指数为正数,小数点向左挪动 result += "0."; //增加expo-1个0 for (int i = 0; i < expo - 1; ++i) { result += "0"; } //而后增加subString去除小数点的局部 result += subString[0]+subString.substr(2); } else{ // 指数为负数,小数点向右挪动 int rest_len = subString.length()-2;//小数点后的局部长度 if(rest_len>expo){ //小数点仍然存在 result += subString[0]+subString.substr(2,expo)+"."+subString.substr(2+expo); } else{ //小数点不存在,在前面补充expo-rest_len个0 result += subString[0]+subString.substr(2); for (int i = 0; i < expo-rest_len; ++i) { result += "0"; } } } cout<<result; return 0;}

October 9, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1061-Dating

题目粗心:给出4个字符串,其中前两个字符串蕴含两个信息:DAY和HH,后两个蕴含一个信息:MM。上面给出这个信息的辨认信息和转换关系:DAY:前两个字符串的第一对雷同地位的A~G的大写字母。转换关系: 大写字母是从A开始的第几个,就是星期几。HH:寻找信息DAY的地位之后的第一对雷同地位的0~9或A-N的字符。转换关系: 0-9对应0-9,A-N对应10-23。MM:后两个字符串的第一对雷同地位的A-Z或a~z的英文字母。转换关系: 该字符所在的地位(从0开始)。最初按DAY HH:MM的格局输入。 算法思路:首先为了不便输入,应用capitalLetterToWeek建设大写字母到星期的映射,hours建设0~9和A~N到0~23的映射,其初始化的办法见如下init(),而后应用s1,s2,s3,s4承受4个字符串,应用found_capital记录是否曾经找到了大写字母,在s1和s2找到雷同的字符时,首先判断found_capital是否为true,如果不是则判断以后的字符是否是大写字母A到G,如果是则输入该字符对应的星期capitalLetterToWeek[s1[j]],如果found_capital为true,则判断以后字符是否是数字0~9或者A~N,如果是则输入该字符对应的小时hours[s1[j]],最初遍历s3和s4,只有找到第一个相等的字母,输入其地位即可。 初始化:unordered_map<char,string> capitalLetterToWeek;//大写字母到星期的映射unordered_map<char,int> hours;// 0~9和A~N到0~23的映射void init(){ capitalLetterToWeek['A'] = "MON"; capitalLetterToWeek['B'] = "TUE"; capitalLetterToWeek['C'] = "WED"; capitalLetterToWeek['D'] = "THU"; capitalLetterToWeek['E'] = "FRI"; capitalLetterToWeek['F'] = "SAT"; capitalLetterToWeek['G'] = "SUN"; for (int k = 0; k < 24; ++k) { if(k<10){ hours['0'+k] = k; } else{ hours['A'+k-10] = k; } }}留神点:1、代表星期的大写字母为A到G,代表小时的大写字母为A到N,代表分钟的字符是英文字母。2、小时和分钟的格局都得保留2位整数。提交后果: AC代码:#include<cstdio>#include<string>#include <iostream>#include <unordered_map>using namespace std;unordered_map<char,string> capitalLetterToWeek;//大写字母到星期的映射unordered_map<char,int> hours;// 0~9和A~N到0~23的映射void init(){ capitalLetterToWeek['A'] = "MON"; capitalLetterToWeek['B'] = "TUE"; capitalLetterToWeek['C'] = "WED"; capitalLetterToWeek['D'] = "THU"; capitalLetterToWeek['E'] = "FRI"; capitalLetterToWeek['F'] = "SAT"; capitalLetterToWeek['G'] = "SUN"; for (int k = 0; k < 24; ++k) { if(k<10){ hours['0'+k] = k; } else{ hours['A'+k-10] = k; } }}// 判断代表星期的大写字母bool isCapitalInWeek(char c){ return c >= 'A' && c <= 'G';}// 判断代表小时的大写字母bool isLetterInHour(char c){ return (c >= 'A' && c <= 'N');}// 判断是否是字母bool isLetter(char c){ return (c >= 'A' && c <= 'Z')||(c >= 'a' && c <= 'z');}//判断是否是数字bool isNum(char c){ return (c>='0'&&c<='9');}int main(){ init();// 初始化 string s1,s2,s3,s4; cin>>s1>>s2>>s3>>s4; //首先取得s1和s2的大写字母和第二个雷同的字符 bool found_capital = false;// 记录是否曾经找到了大写字母 for (int j = 0; j < s1.size() && j < s2.size(); ++j) { if(s1[j]==s2[j]){ if (found_capital){ // 曾经找到大写字母了,当初的字符代表工夫 if(isLetterInHour(s1[j])||isNum(s1[j])){ printf("%02d:",hours[s1[j]]); break; } } else if(isCapitalInWeek(s1[j])){ printf("%s ",capitalLetterToWeek[s1[j]].c_str()); found_capital = true; } } } //而后取得s3和s4雷同字符的地位 for (int j = 0; j < s3.size() && j < s4.size(); ++j){ if(s3[j]==s4[j]&&isLetter(s3[j])){ printf("%02d",j); break; } } return 0;}

October 9, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1058-AB-in-Hogwarts

题目粗心:编写一个程序来计算A+B,其中A和B是依照“Galleon Sickle.Knut"的规范格局给出 算法思路:应用构造体保留一个货币信息,用A,B,result别离代表输出的货币和A+B的值,在A+B的加法过程中,应用carry代表从低到高产生的进位,首先计算Knut的值,其后果为(A.Knut+B.Knut)%29,而其进位为(A.Knut+B.Knut)/29,而后计算Sickle的值,其后果为(A.Sickle+B.Sickle+carry)%17,其进位为(A.Sickle+B.Sickle+carry)/17,最初计算Galleon的值,其后果为A.Galleon+B.Galleon+carry,没有进位,最初将后果依照指定格局输入即可. 提交后果: AC代码:#include<cstdio>using namespace std; struct Currency{ int Galleon; int Sickle;// 逢17进1 int Knut;// 逢29进1 };int main(){ Currency A,B,result; scanf("%d.%d.%d %d.%d.%d",&A.Galleon,&A.Sickle,&A.Knut,&B.Galleon,&B.Sickle,&B.Knut); int carry;//进位 result.Knut = (A.Knut+B.Knut)%29; carry = (A.Knut+B.Knut)/29; result.Sickle = (A.Sickle+B.Sickle+carry)%17; carry = (A.Sickle+B.Sickle+carry)/17; result.Galleon = A.Galleon+B.Galleon+carry; printf("%d.%d.%d",result.Galleon,result.Sickle,result.Knut); return 0;}

October 8, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1027-Colors-in-Mars

题目粗心:给定三个[0,168]范畴内的十进制整数,将它们转换为十三进制后按程序输入。 算法思路:该题数据范畴就确定了转化为13进制的数字最多只有2位,所以对于任意一个[0,168]范畴内的十进制整数,除以13就是十位数字,对13取余就是个位上的数字,惟一的区别在于对于大于9的数字用大写字母示意,那么应用radix数组存储13进制的每一位数字,比方radix[10]='A',代表了理论数字与显示数字的对应关系,radix[a[i]/13],radix[a[i]%13]就是对应的十位数字和个位数字上的显示数字。 提交后果: AC代码:#include<cstdio>using namespace std; int main(){ int a[3]={}; scanf("%d %d %d",&a[0],&a[1],&a[2]); char radix[13] = {'0','1','2','3','4','5','6','7','8','9','A','B','C'};//13进制数 printf("#"); for(int i=0;i<3;++i){ printf("%c%c",radix[a[i]/13],radix[a[i]%13]); } return 0;}

October 8, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1019-General-Palindromic-Number

题目粗心:给出两个整数n、b,问十进制整数n在b进制下是否是回文数,若是,则输入Yes;否则,输入No.在此之后输入n在b进制下的示意。 算法思路:先将整数N转化为b进制数,而后判断该数字是否为回文数,最初输入后果即可。 将十进制数N转化为b进制数办法:vector<int> r;//保留N的b进制数的每一位 while(N!=0){ r.push_back(N%b); N /= b;}判断该数字是否为回文数办法:for(int i=0;i<r.size()/2;++i){ if(r[i]!=r[r.size()-1-i]){ isPalindromic = false; }} 留神点:1、最初输入该数字的时候得倒序输入 提交后果: AC代码:#include<cstdio>#include<vector>using namespace std;int main(){ int N,b; scanf("%d %d",&N,&b); //将N转化为b进制数 vector<int> r;//保留N的b进制数的每一位 while(N!=0){ r.push_back(N%b); N /= b; } bool isPalindromic = true; // 判断是否为回文数 for(int i=0;i<r.size()/2;++i){ if(r[i]!=r[r.size()-1-i]){ isPalindromic = false; } } if(isPalindromic){ printf("Yes\n"); }else{ printf("No\n"); } //输入该数字 for(int i=r.size()-1;i>=0;--i){ printf("%d",r[i]); if(i>0) printf(" "); } return 0;}

October 8, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1031-Hello-World-for-U

题目粗心:将给定字符串按U形进行输入。其中n1为左侧竖线蕴含的字符数,n2为底部横线蕴含的字符数,n3为右侧竖线蕴含的字符数,且n1、n2、n3均蕴含拐角处相交的字符,于是有n1+n2+n3=N+2恒成立。此外,对n1、n2、n3有如下的限制性条件: n1=n3,即左侧竖线蕴含的字符数等于右侧竖线蕴含的字符数。n2≥n1,即底部横线蕴含的字符数总是不少于左、右单侧竖线蕴含的字符数。在满足下面两个条件的前提下,使n1尽可能大。算法思路:首先得获取n1,n2和n3,获取的办法是从小到大遍历n2,这样对于n1和n3来说就是从大到小变动,在(N+2-n2)能够整除2的状况下,第一次满足的n1<=n2必然是最大的n1。而后再依照每一行进行输入,对于第1到n1-1行,都是左右两边一个字符,两头n2-2个空格,最初一行为n2个字符。为此应用index标记最右边的待输入字符的地位,对于第1到n1-1行,第一个字符为s[index],最左边的字符则为s[N-1-index],最初一行的输入间接输入n2个s[index++]即可。 提交后果: AC代码:#include<cstdio>#include<cstring>using namespace std;int main(){ char s[90]; scanf("%s",s); int N = strlen(s);// 字符串的长度 int n1,n2,n3;//2n1+n2=N+2 ,n1为小于n2的最大值 //遍历n2获取n1,n3 for(n2=3;n2<=N;++n2){ if((N+2-n2)%2==0){ n1 = (N+2-n2)/2; n3 = n1; if(n1<=n2){ break; } } } int index = 0;//标记以后待输入字符的地位(从左往右) //输入后果,除了最初一行,其余的每一行都是输入一个字符,n2-2个空格,一个字符 for(int i=0;i<n1-1;++i){ printf("%c",s[index]); for(int j=0;j<n2-2;++j){ printf(" "); } printf("%c\n",s[N-1-index]); ++index; } //输入最初一行 for(int i=0;i<n2;++i){ printf("%c",s[index++]); } return 0;}

October 8, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1036-Boys-vs-Girls

题目粗心:给出N个同学的信息,输入女生中最高分数获得者的信息与男生中最低分数获得者的信息,并输入它们的差。如果不存在女生或者男生,则在对应获得者信息处输入Absent,同时分数差处输入NA。 算法思路:应用构造体Student同学的名字,性别,ID和分数,girl保留最高分的女生其分数初始为-1,boy保留最低分的男生其分数初始为101,对于每一次输出的信息,如果是男生,就判断以后输出的分数是否比boy的分数低,如果是,boy=student,如果是女生,判断该输出的分数是否比girl的分数高,如果是,girl=student.在最初输入的时候,对于girl.grade == -1的状况,阐明没有相应的女生,boy.grade == 101阐明没有相应的男生,对于以上2种状况之一最初都得输入NA 留神点:1、name和ID的数组长度得开到11以上。 提交后果: AC代码:#include<cstdio>using namespace std;struct Student{ char name[20]; char gender; char ID[20]; int grade;};int main(){ int N; scanf("%d",&N); Student student; Student girl;//保留最高分的女生 girl.grade = -1; Student boy;//保留最低分的男生 boy.grade = 101; for(int i=0;i<N;++i){ scanf("%s %c %s %d",student.name,&student.gender,student.ID,&student.grade); if(student.gender=='M'&&boy.grade>student.grade){ boy = student; }else if(student.gender=='F'&&girl.grade<student.grade){ girl = student; } } if(girl.grade != -1){ printf("%s %s\n",girl.name,girl.ID); }else{ printf("Absent\n"); } if(boy.grade != 101){ printf("%s %s\n",boy.name,boy.ID); }else{ printf("Absent\n"); } if(girl.grade != -1&&boy.grade != 101){ printf("%d",girl.grade-boy.grade); }else{ printf("NA"); } return 0;}

October 7, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1006-Sign-In-and-Sign-Out

题目粗心:每天第一个到机房的人要把门关上,最初一个来到的人要把门关好。现有一堆芜杂的机房签到、签离记录,请依据记录找出当天开门和关门的人。(没有人同时签到或者同时签离) 算法思路:该题目的思路大体有2种.第一种就是首先应用构造体数组student保留所有的学生,而后先依照达到工夫升序排列,数组中第一个元素就是最先达到的学生,而后依照来到工夫降序排列,数组中第一个元素就是最初来到的学生。第二种就是应用2个构造体变量a和b别离保留以后Sign_in_time最小和Sign_out_time最大的学生,在输出结束后,就能够输入对应的ID_number即可。这里采纳了第一种办法,然而第二种办法更优。 提交后果: AC代码:#include<cstdio>#include<string>#include<algorithm>using namespace std;struct Student{ string ID_number; int Sign_in_time; int Sign_out_time;};int getSeconds(int hh,int mm,int ss){ return hh*3600+mm*60+ss;}bool cmpByIn(Student a,Student b){ return a.Sign_in_time<b.Sign_in_time;}bool cmpByOut(Student a,Student b){ return a.Sign_out_time>b.Sign_out_time;}int main(){ int M; scanf("%d",&M); Student student[M]; char id[20]; int hh,mm,ss; for(int i=0;i<M;++i){ scanf("%s %d:%d:%d",id,&hh,&mm,&ss); student[i].ID_number = id; student[i].Sign_in_time = getSeconds(hh,mm,ss); scanf("%d:%d:%d",&hh,&mm,&ss); student[i].Sign_out_time = getSeconds(hh,mm,ss); } //先依照达到工夫升序排列,取得最先达到教室的人 sort(student,student+M,cmpByIn); printf("%s ",student[0].ID_number.c_str()); //而后依照来到工夫降序排列,取得最初来到教室的人 sort(student,student+M,cmpByOut); printf("%s",student[0].ID_number.c_str()); return 0;}

October 7, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1011-World-Cup-Betting

题目粗心:给出三行数据,代表三场较量。每行有三个浮点型数,从左至右别离代表W(Win)、T(Tie).L(Los).当初须要从每行的W、T、L中抉择最大的数,并输入三行各自抉择的是哪一个。之后。无妨设三行各自的最大的数为a、b、c,计算最大收益即(abc0.65-1)2 并输入。 算法思路:每次输出一行3个数字,而后获取3个数字的最大值Max,判读该最大值为w,t,l中的哪一个,而后间接输入对应的W,T,L并且得累乘Max到result上,最初输入计算结果即可。 留神点:1、留神保留2位小数 提交后果: AC代码:#include<cstdio>#include<algorithm>using namespace std;int main(){ double w,t,l; double result = 1.0; for(int i=0;i<3;++i){ scanf("%lf %lf %lf",&w,&t,&l); double Max = max(max(w,t),l); if(Max==w){ printf("W "); }else if(Max==t){ printf("T "); }else { printf("L "); } result *= Max; } printf("%.2lf",(result*0.65-1)*2); return 0;}

October 7, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1009-Product-of-Polynomials

题目粗心:给定2个多项式,求他们的乘积 算法思路:间接模仿2个多项式乘积的过程即可,应用数组A和B别离寄存2个多项式,其中下标为指数,其值为系数,而后应用result保留两者相乘的后果,对于A的每一项都与B的每一项相乘,而后只有该项不为0,就将后果累加到result对应的指数地位,最初统计result不为0的项个数并且输入后果即可。 留神点:1、留神得初始化数组2、留神空格的输入3、result的数组大小至多为2001,因为有可能有指数为2000的项。 提交后果: AC代码:#include<cstdio>using namespace std;int main(){ int K; int N;//指数 double AN;//系数 double A[1001] = {}; double B[1001] = {}; double result[2001] = {}; //先输出多项式A scanf("%d",&K); for(int i=0;i<K;++i){ scanf("%d %lf",&N,&AN); A[N] = AN; } //再输出多项式B scanf("%d",&K); for(int i=0;i<K;++i){ scanf("%d %lf",&N,&AN); B[N] = AN; } //计算A乘以B for(int i=0;i<1001;++i){ for(int j=0;j<1001;++j){ double multi = A[i]*B[j]; if(multi!=0){ result[i+j] += multi; } } } // 统计result不为0的项数 int num = 0; for(int i=0;i<2001;++i){ if(result[i]!=0){ ++num; } } // 输入后果 printf("%d",num); for(int i=2000;i>=0;--i){ if(result[i]!=0){ printf(" %d %.1lf",i,result[i]); } } return 0;}

October 7, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1002-AB-for-Polynomials

题目粗心:给出两行,每行示意一个多项式:第一个数示意该多项式中系数非零项的项数,前面每两个数示意一项,这两个数别离示意该项的幂次和系数。试求两个多项式的和,并以与后面雷同的格局输入后果。 算法思路:咱们在输出A和B的时候应用result保留A加B,让result的下标对应指数,其值为系数,而后输出A的时候暂存A,而后输出B的时候,间接对应下标相加即可。而后第一次遍历result统计不为0的项数num,而后再次从后往前遍历数组result,将其系数不为0的项输入。 留神点:1、须要保留一位小数2、空格的输入 3、result得初始化,不然对于其值是否为0来判断须要输入就行不通了。 提交后果: AC代码:#include<cstdio>using namespace std;int main(){ int K; double result[1001] = {};// result[i]保留的是i次方的系数,肯定要初始化{},不然会出错 int N; double AN;//次方和系数 for(int j=0;j<2;++j){ scanf("%d",&K); for(int i=0;i<K;++i){ scanf("%d %lf",&N,&AN); result[N] += AN; } } int num = 0;//系数不为0的项数 for(int i=1000;i>=0;--i){ if(result[i]!=0){ // 系数不为0 ++num; } } printf("%d",num); for(int i=1000;i>=0;--i){ if(result[i]!=0){ // 系数不为0 printf(" %d %.1lf",i,result[i]); } } return 0;}

October 6, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1065-AB-and-C-64bit

题目粗心:给出三个整数A,B,C,如果A+B>C就输入true,否则输入false; 算法思路:留神到题目给定的数据范畴,对于A,B,C都只能应用long long类型存储,其范畴为[-2^63,2^63),而此题考查的是溢出问题。 对于正溢出:也就是A+B>=2^63时,因为long long最大为2^63-1,那么A+B最大为2^64-2,对2^64(long long示意的数据范畴长度)取余失去-2,那么正溢出的数字在[-2^63,-2],也就是说,对于A>0,B>0,如果A+B<0就能够断定正溢出 对于负溢出:也就是A+B<-2^63时,因为long long最小值为-2^63,那么A+B最小为-2^64,对2^64(long long示意的数据范畴长度)取余失去0,那么负溢出的数字在[0,2^63),也就是说,对于A<0,B<0,如果A+B>=0就能够断定负溢出。 对于没有溢出的状况,失常判断即可。 留神点:1、必须得先进行求和而后在判断和C的大小,否则测试点1和2无奈通过 2、对于负溢出,如果没有写等于0的判断条件,测试点2出错。 提交后果: AC代码:#include<cstdio>using namespace std;int main(){ int T; scanf("%d",&T); long long A,B,C; for(int i=1;i<=T;++i){ scanf("%lld %lld %lld",&A,&B,&C); long long sum = A+B;// 测试点1和2 if(A>0&&B>0&&sum<0){ // 正溢出肯定大于C printf("Case #%d: true\n",i); }else if(A<0&&B<0&&sum>=0){// 测试点2考查等于0的状况 // 负溢出肯定小于C printf("Case #%d: false\n",i); }else{ if(A+B>C){ printf("Case #%d: true\n",i); }else{ printf("Case #%d: false\n",i); } } } return 0;}

October 6, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1046-Shortest-Distance

题目粗心:有N个结点围成一个圈,相邻两个点之间的间隔已知,且每次只能挪动到相邻点。而后给出M个询问,每个询问给出两个数字A和B即结点编号(1≤A,B≤N),求从A号结点到B号结点的最短距离。 算法思路:因为这n各点组成了一个环,那么A到B有2条门路能够走,咱们就假设依照输出的方向为顺时针方向,那么应用数组sum[N+1]保留1点到带一个顶点的间隔,那么依照顺时针方向,任意2点a和b(a<b)的间隔为sum[b-1]-sum[a-1],该环的长度为sum[N],逆时针的间隔为sum[N]-(sum[b-1]-sum[a-1]),最终的2点之间的间隔为min(sum[b-1]-sum[a-1],sum[N]-(sum[b-1]-sum[a-1])). 留神点:1、输出的查问的2点a和b,a有可能大于b,须要替换地位2、如果没有应用sum数组保留累计长度的信息,会超时,因为每次都得遍历数组提交后果: AC代码:#include<cstdio>#include<algorithm>using namespace std;int main(){ int N; scanf("%d",&N); int d[N+1];// d[i]示意i到i+1的间隔,d[N]示意N到1的间隔 int sum[N+1];// sum[i]示意从1到i+1的间隔,sum[N]就是一圈的长度 for(int i=1;i<=N;++i){ scanf("%d",&d[i]); sum[i] = sum[i-1]+d[i]; } int M; scanf("%d",&M); for(int i=0;i<M;++i){ int a,b; scanf("%d %d",&a,&b); if(a>b){ int c = a; a = b; b = c; } int dist = sum[b-1]-sum[a-1]; printf("%d\n",min(dist,sum[N]-dist)); } return 0;}

October 5, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1042-Shuffling-Machine

题目粗心:有54张牌,编号为1~ 54,初始按编号从小到大排列。另外,这些牌按初始排列给定花色,即从左至右别离为13张S、13张H、13张C、13张D、2张J,如下所示: S1, S2.., S13, H1, H2,.., H13, C1, C2,..,CI3, D1, D2,., D13,J1,J2接下来执行一种操作, 这种操作将牌的地位扭转为指定地位。例如有5张牌S3, H5, C1,D13, J2,而后给定操作序列{4,2, 5,3,1},因而把S3放到4号位、把H5放到2号位、C1放到5号位、D13 放到3号位、J2放到1号位,于是就变成了J2, H5, D13, S3, C1。当初须要将这种操作执行K次,求最初的排列后果。例如下面的例子中,如果执行第二次操作,那么序列J2, H5, D13, S3,C1就会变成C1, HS, S3, J2, D13. 算法思路:题目要求模仿洗牌过程,给定初始牌组为cards,起始下标地位从1开始,给定洗牌程序orders,和洗牌次数K,要求得出最终洗牌后果。题目比较简单,就是应用hash的思维,咱们每次洗牌就创立一个新的数组new_cards暂存最终洗牌后果,该数组的值取决于orders与cards,其对应关系为new_cards[orders[j]] = cards[j];在new_cards数组求解结束后,将new_cards数组赋值给cards即可,每次循环执行该过程,直到完结 提交后果: AC代码:#include<cstdio>#include<string>using namespace std;string cards[55]={"","S1", "S2", "S3","S4","S5","S6","S7","S8","S9","S10","S11","S12","S13", "H1","H2","H3","H4","H5","H6","H7","H8","H9","H10","H11","H12","H13", "C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13", "D1","D2","D3","D4","D5","D6","D7","D8","D9","D10","D11","D12","D13","J1","J2"};int main(){ int K;//反复次数 scanf("%d",&K); int orders[55]; for(int j=1;j<55;++j){ scanf("%d",&orders[j]); } for(int i=0;i<K;++i){ string new_cards[55]; for(int j=1;j<55;++j){ new_cards[orders[j]] = cards[j]; } // 将new_cards的值赋值给cards for(int j=1;j<55;++j){ cards[j] = new_cards[j]; } } for(int i=1;i<55;++i){ printf("%s",cards[i].c_str()); if(i<54) printf(" "); } return 0;}

October 4, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1095-Cars-on-Campus

题目粗心:给出N条记录,每条记录给出一辆车的车牌号、以后时刻以及出入校状况(入校(in)还是出校out))。而后给出K个查问,每个查问给出一个时刻,输入在这个时刻校园内的车辆数。查问结束后输入在学校内停留时间最长的车辆的车牌号(如果有多个,就一并输入)和对应的停留时间。 算法思路:该题和1016 Phone Bills的数据处理要求很类似, 这里要求给定某一个时刻,要求以后时刻有多少辆车在停车场,最直观的想法就是用一个变量(car_num)来记录车辆数目,从开始时刻到查问时刻,遇到in的记录就加一,遇到out的时候就减一,而后就失去以后查问时刻有多少车辆存在停车场了,然而该应用办法有一个前提条件,那就是所有的记录都得是无效记录,然而这里的记录有可能是有效的,所以,第一件事就是将记录所有的无效记录全副筛选进去寄存在valid_records数组中,而后对于对于每一次查问就能够依照上述办法取得车辆数目,对于最长的停车工夫长度能够在筛选无效记录的时候记录最长的停车工夫和对应的车牌号码。 排序规定1:首先依照车牌号分组,依照字典序排序,对于车牌号雷同的依照工夫先后排序,这是为了筛选无效记录。排序规定2:依照工夫先后排序。 筛选无效记录的办法:先依照排序规定一进行排序,应用指针i遍历所有的记录,这里的i最大为N-2,因为无效记录为一对,所以i<N-1即可。首先得判断i和i+1条记录为同一辆车的记录,否则无意义,而后再判断i和i+1的status别离为in和out,这样的记录才是一对无效记录,对于每一条无效记录,咱们将i和i+1增加进valid_records数组中,而后再累计该车辆的停车工夫,这里应用map<string,int> cars进行存储每一辆车对应的累计停车工夫,最初在记录最大的停车时长即可(用max_parking_time记录) 获取查问时刻停车场的车辆数目的办法:先依照排序规定2排序,而后咱们留神到查问的时刻是顺次递增的,也就是说,如果每一次查问都是从都开始查问的话,会和上一次查问的记录反复,能够采取紧接着上一次查问地位往后查问车辆进出状况的办法,咱们应用index记录上一次查问的记录地位,初始为0,应用car_num记录上一次查问停车场车辆的停留数目,只有以后index指向的记录的工夫<=查问工夫,那么就反复以下操作: 1、如果index指向记录的status为in,car_num++;2、如果index指向记录的status为out,car_num--;留神:1、对同一辆车来说,配对的on和off必须满足在把这辆车的记录按工夫顺序排列后,在它们之间不容许呈现其余on或者off的记录;否则,将被视为有效记录。2、如果每一次查问都是从00:00:00开始查问的话,会超时。 3、有可能查问工夫在所有记录时刻之后,比方查问为23:59:59,然而记录的最靠后的工夫为20:00:00,那么index得判断是否将valid_records遍历结束了。测试点1和2考查该点(本人测试失去的后果,不肯定精确) 提交后果: AC代码:#include<cstdio>#include<cstring>#include<algorithm>#include<string>#include<vector> #include<map>using namespace std;struct Record{ string plate_number ;//车牌号码 int time; bool status;// in为true,out为false };int getSeconds(int hh,int mm,int ss){ return hh*3600+mm*60+ss;}bool cmp(Record a,Record b){ if(a.plate_number!=b.plate_number){ return a.plate_number<b.plate_number; }else{ return a.time<b.time; }}bool cmpByTime(Record a,Record b){ return a.time<b.time;}int main(){ int N,K;//记录总数和查问总数 scanf("%d %d",&N,&K); vector<Record> records; Record record; char plate_number[20]; int hh,mm,ss; char status[10]; for(int i=0;i<N;++i){ scanf("%s %d:%d:%d %s",plate_number,&hh,&mm,&ss,status); record.plate_number = plate_number; record.time = getSeconds(hh,mm,ss); record.status = strcmp(status,"in")==0; records.push_back(record); } sort(records.begin(),records.end(),cmp); //筛选有效记录 map<string,int> cars;//每一辆车停车的最长工夫 int max_parking_time = -1;//最长停车工夫 vector<Record> valid_records; int begin=0,next=0; for(int i=0;i<N-1;++i){ if(records[i].plate_number==records[i+1].plate_number){ // i和i+1是同一辆车 if(records[i].status&&!records[i+1].status){ //i为in,i+1为out,i与i+1为一对无效记录 valid_records.push_back(records[i]); valid_records.push_back(records[i+1]); int parking_time = records[i+1].time-records[i].time;//停车工夫 // 更新该辆车的累计停车工夫 cars[records[i].plate_number] += parking_time; max_parking_time = max_parking_time<cars[records[i].plate_number]?cars[records[i].plate_number]:max_parking_time;//更新所有车辆最长停车工夫 } } } //依据工夫进行排序 sort(valid_records.begin(),valid_records.end(),cmpByTime); //查问开始 int index = 0;//以后查问记录的下标 int car_num = 0;//以后停车场的车辆数目 for(int i=0;i<K;++i){ scanf("%d:%d:%d",&hh,&mm,&ss); int query_time = getSeconds(hh,mm,ss); // 从index始终找到停车工夫大于query_time的后面一辆记录即可 while(index<valid_records.size()&&valid_records[index].time<=query_time){ if(valid_records[index].status){ ++car_num; }else{ --car_num; } ++index; } printf("%d\n",car_num); } //输入最长停车工夫的车牌号和工夫 map<string,int>::iterator it; for(it=cars.begin();it!=cars.end();++it){ if(it->second==max_parking_time){ printf("%s ",it->first.c_str()); } } printf("%02d:%02d:%02d",max_parking_time/3600,max_parking_time%3600/60,max_parking_time%60); return 0;}

October 3, 2020 · 1 min · jiezi

关于算法-数据结构:算法与数据结构2排序算法

抉择排序public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 0 ~ N-1 // 1~n-1 // 2 for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1 // 最小值在哪个地位上 i~n-1 int minIndex = i; for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 minIndex = arr[j] < arr[minIndex] ? j : minIndex; } swap(arr, i, minIndex); }}public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;}

September 30, 2020 · 1 min · jiezi

关于算法-数据结构:算法与数据结构1

常数工夫工夫操作常见算术运算位运算(>>,>>>,<<,<<<,|,&,^)赋值、比拟、自增、自减数组寻址工夫复杂度看最高次幂的数

September 30, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1080-Graduate-Admission

题目粗心:有N位考生,M所学校,每位考生都有K个意愿学校,每个学校也有招生人数限度。当初给出所有考生的初试问题GE、面试问题GI以及K个意愿学校的编号,要求模仿学校录取招生的过程,并输入每个学校录取的考生编号(按从小到大程序)。 排序规定:先按考生的总分(GE + GD)/2从高到低排序,总分雷同的按GE从高到低排序。如果GE依然雷同,则按排名雷同解决。 算法思路:首先得定义好数据的构造,不便解决,这里采纳构造体Applicant保留每一位考生的id,GE,GI,总分,排名和意愿信息,并且应用数组vector<Applicant> schools[M]记录每一个学校录取的学生,这里对于总分间接采纳GE+GI的解决,无需除以2。顺次将所有数据进行输出后,对所有的学生进行排序,而后获取排名,再依照排名的程序遍历每一个学生的每一个意愿,只有以后填写的意愿学校没有招满或者最初一个学生的排名等于以后学生的排名就能够录取,这里得留神录取后得退出循环不得在进行解决。最初依照要求输入,得留神每一个学校录取的考生的编号是依照从小到大进行输入的。 提交后果: AC代码:#include<cstdio>#include<vector>#include<algorithm>using namespace std;struct Applicant{ int id; int GE; int GI; int final_grade; int rank; int preferred_schools[6];};bool cmp(Applicant a,Applicant b){ if (a.final_grade!=b.final_grade){ return a.final_grade>b.final_grade; } else { return a.GE>b.GE; }}bool cmpById(Applicant a,Applicant b){ return a.id<b.id;}int main(){ int N,M,K;// 总人数,学校数目,每一个学生的意愿数目 scanf("%d %d %d",&N,&M,&K); int quota[M];// 每一个学校的名额 for (int i = 0; i < M; ++i) { scanf("%d",&quota[i]); } vector<Applicant> applicants;// 保留所有学生 Applicant applicant; for (int j = 0; j < N; ++j) { scanf("%d %d",&applicant.GE,&applicant.GI); applicant.final_grade = applicant.GE+applicant.GI; applicant.id = j; for (int i = 0; i < K; ++i) { scanf("%d",&applicant.preferred_schools[i]); } applicants.push_back(applicant); } sort(applicants.begin(),applicants.end(),cmp); // 获取排名 for (int k = 0; k < applicants.size(); ++k) { if(k==0){ applicants[k].rank = 1; } else if (applicants[k].final_grade==applicants[k-1].final_grade&&applicants[k].GE==applicants[k-1].GE){ applicants[k].rank = applicants[k-1].rank; } else { applicants[k].rank = k+1; } } vector<Applicant> schools[M];// 每一个学校录取的学生 // 解决每一个考生的意愿 for (int l = 0; l < applicants.size(); ++l) { // 遍历所有的意愿 for (int i = 0; i < K; ++i) { int preferred = applicants[l].preferred_schools[i]; // 首先判断该学校是否招满了 if(schools[preferred].size()<quota[preferred]||schools[preferred][schools[preferred].size()-1].rank==applicants[l].rank){ // 没有满,间接招或学校招满了,然而该学生和最初一名排名雷同,也招 schools[preferred].push_back(applicants[l]); break; } } } for (int m = 0; m < M; ++m) { // 招到学生才进行解决 if (schools[m].size()>0){ sort(schools[m].begin(),schools[m].end(),cmpById); for (int i = 0; i < schools[m].size(); ++i) { printf("%d",schools[m][i].id); if (i<schools[m].size()-1) printf(" "); } } printf("\n"); } return 0;}

September 30, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1083-List-Grades

题目粗心:给出N位考生的姓名、准考证号及分数,将这些信息按分数从高到低排序,并输入分数在给定区间[grade1, grade2]内的考生信息。如果不存在满足条件的考生,则输入“NONE"。 算法思路:依据题意,构造体类型Student须要寄存考生的姓名、准考证号和分数。因为所有考生的分数都不雷同,排序函数就只需依据分数进行排序即可。同时应用isExist变量来记录是否有存在[grade1,grade2]的考生,如果为false,就输入为false. 提交后果: AC代码:#include<cstdio>#include<vector>#include<algorithm>#include<string>using namespace std;struct Student{ string name; string ID; int grade;};bool cmpByGrade(Student a,Student b){ return a.grade>b.grade;}int main(){ int N;// 总人数 scanf("%d",&N); char name[10]; char id[20]; vector<Student> students; Student student; for (int i = 0; i < N; ++i) { scanf("%s %s %d",name,id,&student.grade); student.name = name; student.ID = id; students.push_back(student); } int grade1,grade2;//分数的上下界 scanf("%d %d",&grade1,&grade2); sort(students.begin(),students.end(),cmpByGrade); bool isExist = false;// 是否有学生在[grade1,grade2]区间中 for (int j = 0; j < students.size(); ++j) { if (students[j].grade>=grade1&&students[j].grade<=grade2){ isExist = true; printf("%s %s\n",students[j].name.c_str(),students[j].ID.c_str()); } } if (!isExist){ printf("NONE\n"); } return 0;}

September 29, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1075-PAT-Judge

题目粗心:有N位考生,其准考证号00001~N。共有K道题,编号为1~ K,且每道题的分值给出。而后给出M次提交记录,每个提交记录显示了该次提交所属考生的准考证号、交题的题号及所得的分值,其中分值要么是-1 (示意未通过编译),要么是0到该题满分区间的一一个整数。当初要求对所有考生按上面的规定排序: 先按K道题所得总分从高到低排序。若总分雷同,则按完满解决(即取得题目满分)的题目数量从高到低排序。若完满解决的题目数量也雷同,则按准考证号从小到大排序。算法思路:首先得设计好存储信息的构造,对于输出,要记录考生的id,题目编号和对于的分数,对于输入来说,须要取得考生的排名,总分,每一道题的最高得分。为此应用构造体Student保留考生的id,排名,总分,每一道题的最高得分及其是否提交状况,完满解题数和是否有编译通过的提交状况(简略了解就是是否须要输入)。而后就是对于每一条提交记录的分数的解决,解决规定如下: 对于每一条记录,记录其考生的user_id和problem_id的提交状况(students[user_id].isSubmited[problem_id] = true;) 判断该题的得分partial_score_obtained是否为-1,如果不是-1阐明为通过编译的提交,将students[user_id].hasSolution置为true,表明该用户存在通过编译的提交须要输入,而后判断该分数是否比现有的得分更高,如果是则更新最高分(students[user_id].s[problem_id] = partial_score_obtained;) 并且判断该最高分是否为满分,如果是则将完满解题数加一(++students[user_id].perfect;),如果是-1不必做其余解决,因为每一道题的得分曾经默认为0,无需再置为0分接下来就是初始化总分,排序,获取排名,最初输入信息即可。排序规定:因为存在不须要输入的考生,然而这里对所有考生都进行了排序,所以得将无需输入的人放在前面。也即是hasSolution不同的考生,依据hasSolution从高到低排序,再而后根据上述的排序规定排序。也能够采纳遍历所有考生,将无需输入的考生剔除的形式来进行解决。 输入规定:只输入hasSolution为true的考生,对于这些考生如果problem_id的题目isSubmited[problem_id]为true,就输入分数(默认为0,对应编译不通过的状况),否则就输入"-" 提交后果:第一次测试:测试点4谬误。第二次测试:全副正确。 留神点:hasSolution==false,示意该用户没有提交一个可通过编译的答案,不必输入该用户的信息。hasSolution==true,阐明该用户至多有一个通过编译的提交,如果isSubmited[problem_id]==false阐明该题没有提交输入"-",否则输入0对于hasSolution==false的用户不参加排序。对于测试点4考查的点次要在两个中央。第一个就是当一个用户反复提交满分代码的时候,完满解题数目不再重复记录,这里采纳了在呈现更高分的时候判断是否为满分在解决这个问题。第二个是以后记录的题目分数为-1,然而之前有通过编译的提交也不要进行解决,放弃之前的记录即可,这里通过isSubmited数组进行的解决,无需进行记录编译出错的分数为0,因为默认分数就是0,所以只需记录通过编译的状况下的分数。测试点4尽管考查的状况次要是以上2个状况,然而仍然会呈现思考了以上2种状况仍然出错的时候(比方自己(╥╯^╰╥)),这种次要是本人的代码逻辑出错,在这里记录两种情景。出错情景一:排序的时候将不该输入的人进行了排序,没有一道通过编译提交的人的总分为0,有通过编译的提交然而全副谬误的人也是0,当前者编号较小的时候,就会取得后者原本应该有的排名。 出错情景二:如果在一个用户的所有提交记录中,某一道题只提交了一次,并且在其提交记录中排在第一个地位,并且提交为-1,然而其余题目都是通过了编译的,那么依照自己的错误代码的写法就会导致没有通过编译的题目输入"-",而正确的输入是0,因为该用户对该题有提交。 情景二的错误代码: if (partial_score_obtained==-1&&students[user_id].hasSolution){ // 对于用户有其余通过编译的提交,对于本题没有通过编译,输入为0,只有没有提交记录才是 "-" students[user_id].isSubmited[problem_id] = true;}if (partial_score_obtained!=-1){ // 阐明有通过编译的提交 students[user_id].hasSolution = true; students[user_id].isSubmited[problem_id] = true; if (partial_score_obtained>students[user_id].s[problem_id]){ // 记录更高得分 students[user_id].s[problem_id] = partial_score_obtained; // 更高得分为满分的时候完满解题数目加一 if (partial_score_obtained==p[problem_id]){ ++students[user_id].perfect; } }}谬误起因:isSubmited的作用是记录题目有没有提交,而错误代码的写法增加了判断条件,使其语义含糊,实属不该。 改过后的代码:改过后的代码:students[user_id].isSubmited[problem_id] = true;if (partial_score_obtained!=-1){ // 阐明有通过编译的提交 students[user_id].hasSolution = true; if (partial_score_obtained>students[user_id].s[problem_id]){ // 记录更高得分 students[user_id].s[problem_id] = partial_score_obtained; // 更高得分为满分的时候完满解题数目加一 if (partial_score_obtained==p[problem_id]){ ++students[user_id].perfect; } }}总结:此题的细节解决较多,对于题目没有说分明的点,须要通过样例数据进行揣测验证,自己第一次写的时候也是各种踩坑,不过对于集体的逻辑解决能力有较大的帮忙。 ...

September 29, 2020 · 2 min · jiezi

关于算法-数据结构:PAT甲级1055-The-Worlds-Richest

题目粗心:给出N集体的姓名、年龄及其领有的财产值,而后进行K次查问。每次查问要求输入年龄范畴在[AgeL, AgeR]的财产值从大到小的前M人的信息。如果财产值雷同,则年龄小的优先;如果年龄也雷同,则姓名的字典序小的优先。 算法思路:总体思路就是先排序在依照要求输入后果,这里的排序能够应用排序函数,也能够应用set的自定义排序,这里抉择了第二种,无论哪一种排序办法,最终都会导致测试超时,最要害的点在于M的取值范畴,M最大为100,阐明每一个年龄的人数最大为100,那么只须要将每一个年龄的前100名增加进新的容器中,而后再进行遍历输入就不会导致超时。 提交后果:第一次测试:应用sort函数,导致测试点2运行超时。第二次测试:应用set自定义排序函数,导致测试点1超时。第三次测试:将排完序后的元素的每个年龄的前100名取出来而后遍历,测试通过。 留神点:1、如果不将每一个年龄的前100集体取出来而后再查问的话,对于应用sort函数形式会导致测试点2超时,应用set形式会导致测试点1超时。 2、寄存每个年龄的数组大小得设置为1000以上,不然会导致测试谬误 3、暴力求解可得22分。 AC代码:#include<cstdio>#include<vector>#include<set>#include<algorithm>#include<string>using namespace std;struct People{ string name; int age; int worth;};struct cmp{ bool operator()(const People &a,const People &b){ if (a.worth!=b.worth){ return a.worth>b.worth; } else if (a.age!=b.age){ return a.age<b.age; } else { return a.name<b.name; } }};int main(){ int N,K;// 总人数和查问次数 scanf("%d %d",&N,&K); set<People,cmp> peoples; char name[20]; People people; for (int i = 0; i < N; ++i) { scanf("%s %d %d",name,&people.age,&people.worth); people.name = name; peoples.insert(people); } // 将每个年龄的人中前100名保留到数组sorted_people中 vector<People> sorted_people; int age_num[100001]; // 寄存年龄为i的人数,这里的下标不能是200,得是总人数加一,不过1000就能够通过所有测试点了 for(auto &peo : peoples){ if (age_num[peo.age]<100){ ++age_num[peo.age]; sorted_people.push_back(peo); } } int M,Amin,Amax; for (int j = 0; j < K; ++j) { scanf("%d %d %d",&M,&Amin,&Amax); // 输入后果 printf("Case #%d:\n",j+1); int index = 0; bool printed = false;// 是否有输入 for (auto & peo : sorted_people) { if (peo.age>=Amin&&peo.age<=Amax){ if (index==M) break; printf("%s %d %d\n",peo.name.c_str(),peo.age,peo.worth); ++index; printed = true; } } if (!printed){ printf("None\n"); } } return 0;}

September 27, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1028-List-Sorting

题目粗心:给出N个考生的准考证号、姓名、分数,并输出参数C,要求按C的不同取值进行排序: C= 1,则按准考证号从小到大排序。C= 2,则按姓名字典序从小到大排序;若姓名雷同,则按准考证号从小到大排序。C= 3,则按分数从小到大排序;若分数雷同,则按准考证号从小到大排序。算法思路:惯例排序题,应用构造体Student存储学生信息,在排序函数中依据C的取值不同抉择不同的排序形式。排序函数如下cmp函数:bool cmp(Student a,Student b){ if (C==1){ // 依据id排序 return a.id<b.id; } else if (C==2){ // 依据name排序 if (a.name!=b.name){ return a.name<b.name; } else { return a.id<b.id; } } else { if (a.grade!=b.grade){ // 依据分数排序 return a.grade<b.grade; } else { return a.id<b.id; } }}提交后果: 留神点最初的一组数据应用cin、cout可能会超时AC代码#include<cstdio>#include<vector>#include<algorithm>#include<string>using namespace std;int N,C; // 记录数目和待排序的列struct Student{ int id; string name; int grade;};vector<Student> students;// 学生的记录汇合bool cmp(Student a,Student b){ if (C==1){ // 依据id排序 return a.id<b.id; } else if (C==2){ // 依据name排序 if (a.name!=b.name){ return a.name<b.name; } else { return a.id<b.id; } } else { if (a.grade!=b.grade){ // 依据分数排序 return a.grade<b.grade; } else { return a.id<b.id; } }}int main(){ scanf("%d %d",&N,&C); Student student; char name[20]; for (int i = 0; i < N; ++i) { scanf("%d %s %d",&student.id,name,&student.grade); student.name = name; students.push_back(student); } sort(students.begin(),students.end(),cmp); // 输入所有的学生信息 for (int j = 0; j < students.size(); ++j) { printf("%06d %s %d\n",students[j].id,students[j].name.c_str(),students[j].grade); } return 0;}

September 25, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1012-The-Best-Rank

题目粗心:现已知N个考生的3门课分数C、M、E,而均匀分数A能够由这3个分数失去。当初别离按这4个分数对n个考生从高到低排序,这样对每个考生来说,就会有4个排名且每个分数都会有一个排名。接下来会有M个查问,每个查问输出一个考生的ID, 输入该考生4个排名中最高的那个排名及对应是A、C、M、E中的哪一个。如果对不同课程有雷同排名的状况,则按优先级A>C>M>E输入;如果查问的考生ID不存在,则输入N/A。 算法思路:这里为了不便起见,应用总分代替均匀分数,每一个学生保留所有的分数曾经对应科目的排名信息,同时为了避免非法查问,应用isExist建设学生ID与学生的映射,因为要获取每一个学生所有科目排名中最好的排名,那么就得一一依照每一个科目的分数进行排序,而后获取对应科目的排名。属于惯例排序题目,这里应用数组存储分数纯正是为了防止写反复的代码。 留神点:isExist的初始化得在取得排名之后,因为输入最好排名的时候须要依据isExist获取对应的学生信息,其中就包含排名提交后果: AC代码:#include<cstdio>#include<vector>#include<algorithm>#include<unordered_map>using namespace std;struct Student{ int id; int scores[4];// A,C,M,E的分数 int rank[4];// 每一门科目的名次,顺次是A>C>M>E};unordered_map<int,Student> isExist;// 用于判断查问的学生是否存在char subject[4] = {'A','C','M','E'}; // 不便输入最初最高排名对于的科目int course_index;//示意以后须要排序的科目bool cmp(Student a, Student b){ return a.scores[course_index]>b.scores[course_index];}void printBestRank(int query) { Student student = isExist[query]; int index = 0; // 获取最好的排名 for (int i = 1; i < 4; ++i) { if(student.rank[i]<student.rank[index]){ index = i; } } printf("%d %c\n",student.rank[index],subject[index]);}int main(){ int N,M;// 学生人数,查问排名的学生人数 scanf("%d %d",&N,&M); vector<Student> students;// 学生汇合 Student stu; int c,m,e; for (int i = 0; i < N; ++i) { scanf("%d %d %d %d",&stu.id,&c,&m,&e); stu.scores[1] = c; stu.scores[2] = m; stu.scores[3] = e; stu.scores[0] = c+m+e; students.push_back(stu); } // 对所有科目进行排序 for (course_index=0;course_index<4;++course_index){ sort(students.begin(),students.end(),cmp); // 获取以后科目的排名 for (int i = 0; i < N; ++i) { if (i==0){ students[i].rank[course_index] = 1; } else if (students[i].scores[course_index] == students[i-1].scores[course_index]){ students[i].rank[course_index] = students[i-1].rank[course_index]; } else { students[i].rank[course_index] = i+1; } } } // 初始化isExist,这里得在获取完排名后在进行初始化isExit,不然会呈现输入为正数的状况 for (int j = 0; j < N; ++j) { isExist[students[j].id] = students[j]; } // 开始查问 int query;// 待查问学生的id for (int k = 0; k < M; ++k) { scanf("%d",&query); if (isExist.find(query)==isExist.end()){ // 不存在 printf("N/A\n"); } else{ printBestRank(query); } } return 0;}

September 23, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1062-Talent-and-Virtue

题目粗心:给出N个考生的准考证号、Virtue_Grade、Talent_Grade以及及格线L、优良线H,而后对这n个考生进行分类: 1. 如果Virtue_Grade和Talent_Grade中有一一个低于L,则为不及格生,即为第5类,且设上面4类均及格。2. 如果Virtue_Grade和Talent_Grade均不低于H,则为第1类。 3. 如果Virtue_Grade不低于H,Talent_Grade低于H,则为第2类。 4. 如果Virtue_Grade和Talent_Grade均低于H但Virtue_Grade不低于Talent_Grade,则为第3类。算法思路:题目没有什么难度,次要是细节得留神,在每一个考生在输出的时候直接判断属于哪一个梯队,对于低于L的考生间接不予考虑,这样保障参加排序的都是须要排序的考生。 排完序后间接依照要求输入即可。 排序规定:先进行内部排序:Virtue_Grade和Talent_Grade大于等于H的称为圣人,排在第一梯队,外部依据总分非递增排序。Virtue_Grade大于等于H然而Talent_Grade低于H的小人,排在第二梯队,外部依据总分非递增排序。Virtue_Grade和Talent_Grade小于H然而Virtue_Grade大于等于Talent_Grade的称为哲人,排在第三梯队,外部依据总分非递增排序。其余的Virtue_Grade和Talent_Grade大于等于L的称为君子,排在第四梯队,外部依据总分非递增排序。而后进行外部排序:首先依照总分非递加排序,总分雷同的依照 Virtue_Grade和Talent_Grade有一个低于60分的不参加排序提交后果:第一次测试: 测试点2,3,4答案谬误第二次测试: 全副正确,起因在于第三梯队的Virtue_Grade大于等于Talent_Grade的等于没有增加导致出错。 AC代码:#include<cstdio>#include<vector>#include<algorithm>using namespace std;struct People{ int ID_Number; int Virtue_Grade; // Virtue_Grade int Talent_Grade; // Talent_Grade int Total_Grade; // 总分 int level;// 梯队};vector<People> peoples;bool cmp(People a,People b){ if (a.level!=b.level){ return a.level < b.level; } else if (a.Total_Grade!=b.Total_Grade){ return a.Total_Grade > b.Total_Grade; } else if (a.Virtue_Grade!=b.Virtue_Grade){ return a.Virtue_Grade > b.Virtue_Grade; } else { return a.ID_Number < b.ID_Number; }}int main(){ int N,L,H;//人数,分数上限,分数下限 scanf("%d %d %d",&N,&L,&H); People people; for (int i = 0; i < N; ++i) { scanf("%d %d %d",&people.ID_Number,&people.Virtue_Grade,&people.Talent_Grade); if (people.Virtue_Grade>=L&&people.Talent_Grade>=L){ // Virtue_Grade和Talent_Grade都大于等于L的才进行排名 if (people.Virtue_Grade>=H&&people.Talent_Grade>=H){ // 圣人 people.level = 1; } else if (people.Virtue_Grade>=H&&people.Talent_Grade<H){ // 小人 people.level = 2; } else if (people.Virtue_Grade>=people.Talent_Grade){ // 哲人 people.level = 3; } else{ // 君子 people.level = 4; } people.Total_Grade = people.Talent_Grade + people.Virtue_Grade; peoples.push_back(people); } } sort(peoples.begin(),peoples.end(),cmp); printf("%d\n",peoples.size()); for (int k = 0; k < peoples.size(); ++k) { printf("%d %d %d\n",peoples[k].ID_Number,peoples[k].Virtue_Grade,peoples[k].Talent_Grade); } return 0;}

September 23, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1025PAT-Ranking

题目粗心有N个考场,每个考场有K个考生。当初给出各个考场中考生的准考证号与分数,要求将所有考生按分数从高到低排序,并按程序输入所有考生的准考证号、排名、考场号以及考场内排名。 算法思路这是一道比拟惯例的排序题目,题目要求获取每一个考场的考生本考场的排名和最终所有考生在一起的最终排名,那么就先应用vector<Student> students[N+1]来保留每一个考场的考生,而后应用vector<Student> stu保留所有考场的考生汇合,首先对于考场i的学生stuents[i]应用sort函数间接进行排序获取排名,而后将获取了本地考场排名的学生增加进stu中,而后再对stu进行排序,获取每一个考生的最终排名,最初依照题目要求进行输出即可。 获取排名的办法对于排序后第一个考生名次为1,而后对于其余考生若与前一个考生的分数雷同取得雷同排名,否则等于排在后面的人数加1。 留神点考生编号为13位须要应用字符数组或者字符串进行存储。这里应用了string类型进行存储,输出的时候先应用字符数组进行输出,而后间接赋值给string类型变量,这样在数据量较高的应用比cin输出要快,输出的时候得应用c_str()函数转化为字符数组进行输入.提交后果 实现代码#include<cstdio>#include<vector>#include<algorithm>#include<string>using namespace std;int N;//考场数目int K;//每一个考场的人数struct Student{ string registration_number;// 考生编号,13位 int total_score; // 总分 int location_number; // 考场编号,从1开始 int local_rank; // 本考场排名 int final_rank; // 最终排名};bool cmp(Student a,Student b){ if (a.total_score!=b.total_score){ return a.total_score>b.total_score; } else{ return a.registration_number<b.registration_number; }}int main(){ scanf("%d",&N); vector<Student> students[N+1]; // 所有考生的汇合,studnets[i]就是考场编号为i的考生汇合 vector<Student> stu; // 所有考生汇合 int index = 0; char registration_number[15]; for (int i = 1; i <=N; ++i) { scanf("%d",&K); for (int j = 0; j < K; ++j) { Student student; scanf("%s %d",registration_number,&student.total_score); student.registration_number = registration_number; student.location_number = i; students[i].push_back(student); } } // 首先对本考场的学生进行排序获取排名 for (int i = 1; i <= N; ++i) { sort(students[i].begin(),students[i].end(),cmp); // 计算排名 for (int j = 0; j < students[i].size(); ++j) { if (j==0){ students[i][j].local_rank = 1;// 第一个排名为1 } else if (students[i][j].total_score==students[i][j-1].total_score){ // 总分一样排名一样 students[i][j].local_rank = students[i][j-1].local_rank; } else{ // 总分不一样,排名为后面的人数加一 students[i][j].local_rank = j+1; } // 计算完本考场排名后退出stu中,获取最终排名 stu.push_back(students[i][j]); } } // 对所有考生进行排序 sort(stu.begin(),stu.end(),cmp); // 获取考生的总排名 for (int k = 0; k < stu.size(); ++k) { if (k==0){ stu[k].final_rank = 1; } else if (stu[k].total_score==stu[k-1].total_score){ stu[k].final_rank = stu[k-1].final_rank; } else{ stu[k].final_rank = k+1; } } // 输入所有考生的信息 printf("%d\n",stu.size()); for (int l = 0; l < stu.size(); ++l) { printf("%s %d %d %d\n",stu[l].registration_number.c_str(),stu[l].final_rank,stu[l].location_number,stu[l].local_rank); } return 0;}

September 22, 2020 · 1 min · jiezi

关于算法-数据结构:PAT甲级1026Table-Tennis

题目粗心:有K张乒乓球桌(编号为1-K)于8:00 21:00 凋谢,每一组球员都是抉择以后闲暇的最小编号的球桌进行训练,且训练时长最多容许2h,而如果达到时没有球桌闲暇,则排成队列期待。这K张球桌中有M张是VIP球桌,如果存在VIP球桌闲暇,且期待队列中存在VIP球员,那么期待队列中第一个VIP球员将返回编号最小的VIP球桌训练:如果存在VIP球桌闲暇,期待队列中没有VIP球员,那么VIP球桌将被调配给期待队列中第一个一般球员:而如果以后没有VIP球桌闲暇,那么VIP球员将被看作一般球员解决。当初给出每个球员的达到工夫、须要训练时长、是否是VIP球员以及给出球桌数和所有VIP球桌编号,求所有在关门前失去训练的球员的达到工夫、训练开始工夫、期待时长以及所有球桌当天的服务人数。 解题思路:该题目应该是PAT_1014和PAT_1017这类模仿问题中最为简单的了。对于期待队列中有非VIP球员和VIP球员,球桌中有VIP球桌和非VIP球桌,那么该问题实质上就是将球桌和球员一一对应调配的问题,思考所有的状况,简略点说就是,球座和球员的组合分为4种不同的状况: 第一:最早闲暇的球桌index是VIP球桌,队首球员是VIP球员,那么将index号桌子调配给他。第二:最早闲暇的球桌index是VIP球桌,然而队首球员不是VIP球员,那么首先找到期待队列中第一个VIP球员,如果该VIP球员的达到工夫在VIP球桌闲暇之前,那么将该球桌调配给队列中的第一个VIP球员。如果不是,就将该VIP球桌调配给队首队员。第三:最早闲暇的球桌index不是VIP球桌,然而队首球员是VIP球员,那么首先去查问最早闲暇的VIP球桌VIPindex,如果VIP球员的达到工夫在VIP球桌闲暇工夫之后,那么将该VIP球桌调配给该球员。否则,就将非VIP球桌index调配给该VIP球员。第四:最早闲暇的球桌index不是VIP球桌,队首球员不是VIP球员,那么将该球桌调配给该球员即可。想分明下面的逻辑后接下来就是确定数据结构存储数据了,首先对于单个球员和球座都能够采纳构造体来存储数据,球员的期待队列应用vector<Player> wait_queue来存储,因为球桌固定了编号与之对应,那么就能够应用构造体数组来存储所有的球桌,这里为了不便实现VIP球员和非VIP球员的调配应用了2个期待队列别离存储VIP球员期待队列和非VIP球员的期待队列,同时应用vip_index和non_vip_index指向以后待处理的VIP球员和非VIP球员的地位,最初将每一个应用过球桌训练的队员应用vector<Player> result来存储(因为有的队员因为工夫起因无奈取得训练机会)。 算法步骤:算法步骤: 第一步:数据的输出,依照题目要求的格局输出即可,不过得留神,对于达到工夫在完结服务工夫end_serve_time后的间接疏忽,同时这里对立将工夫以秒作为单位,须要将训练时长乘以60,并且在训练时长超过limit_time(2h)的时候须要将训练工夫压缩为2h。第二步:将期待队列中的球员依照达到工夫进行排序。第三步:遍历期待队列,将VIP用户与非VIP用户拆散开来。第四步:解决算法的球员与球桌匹配逻辑,循环次数为期待队列的长度.循环开始: 首先取得队首球员,队首队员的获取是通过比拟VIP期待队列和非VIP期待队列中待处理队员谁先达到来判断的,须要留神的是有可能VIP期待队列或者 非VIP期待队列中的队员处理完毕,所以须要进行判断(这里是该程序呈现段谬误和答案谬误的点)。 而后就是4种状况的探讨,须要留神的是队员的等待时间有可能为0,在队员达到的时候球桌就是闲暇的那么为0,否则为球桌闲暇工夫减去球员的达到工夫, 球桌的下场服务工夫能够对立为球桌开始服务工夫+队员的须要训练工夫。在程序中球员训练完结的标记就是等待时间计算结束,而后就要更新桌子的下次 闲暇工夫,并且将队员增加到result中,将vip_index或non_vip_index自增。第五步:对于result依照服务开始的工夫进行排序,而后依照格局输入result中的球员的相干信息和球座的服务人数。留神点:期待时长是须要四舍五入到整数,也就是须要加上30秒再除以60或者应用round函数。table的下标是从1开始的,对于从0开始的写法,最初会输入3 2 3的状况(是的题目没有说(╥╯^╰╥))在找到最早闲暇的桌子下标Index的时候得判断以后球桌的闲暇工夫是否在服务完结工夫之后,如果是那就间接退出循环,无需再进行遍历。提交后果:第一次提交: 测试点1,4,5答案谬误测试点3,6,7段谬误第二次提交:答案全对,谬误的起因在于须要在最开始获取队首队员的时候须要判断此时还有没有待处理的VIP队员或者非VIP队员。 具体代码实现如下:#include<cstdio>#include<vector>#include<algorithm>using namespace std;int N;//球员数量int K,M;//K张桌子,M张VIP桌子int begin_serve_time = 8*3600;//开始服务工夫int end_serve_time = 21*3600;//完结服务工夫int limit_time = 2*3600;//最长服务工夫struct Player{ int arriving_time;// 达到工夫 int playing_time;// 须要服务工夫 int waiting_time;// 等待时间 int serve_time; // 开始服务的时刻 bool isVIP;//是否是VIP};vector<Player> wait_queue;//期待队列int vip_index = 0;//期待队列工作指针,指向第一个待处理的VIP用户int non_vip_index = 0;//期待队列工作指针,指向第一个待处理的非VIP用户vector<Player> vip_wait_queue;//VIP期待队列vector<Player> non_vip_wait_queue;//非VIP用户期待队列vector<Player> result;//依照解决完结的程序保留每一个曾经解决的队员struct Table{ int begin_time = begin_serve_time;//开始服务工夫 int number;//服务的球员数目 bool isVIP;//是否是VIP桌子}table[110];int getTime(int hh,int mm,int ss){ return hh*3600+mm*60+ss;}bool cmpByArriveTime(Player a,Player b){ return a.arriving_time<b.arriving_time;}bool cmpByServeTime(Player a,Player b){ return a.serve_time<b.serve_time;}int main(){ scanf("%d",&N); for(int i=0;i<N;++i){ int hh,mm,ss,time,vip; scanf("%d:%d:%d %d %d",&hh,&mm,&ss,&time,&vip); int arrive_time = getTime(hh,mm,ss); if(arrive_time<end_serve_time){ Player player; // 在完结服务之前达到 player.arriving_time = arrive_time; // 最长服务工夫是2小时 player.playing_time = time*60>limit_time?limit_time:time*60; player.isVIP = vip == 1; wait_queue.push_back(player); } } scanf("%d %d",&K,&M); // 标记VIP桌子 for(int i=0;i<M;++i){ int index; scanf("%d",&index); table[index].isVIP = true; } int n = wait_queue.size(); // 依照达到工夫进行排序 sort(wait_queue.begin(),wait_queue.end(),cmpByArriveTime); // 将VIP用户与非VIP用户拆散开来 for(int i=0;i<n;++i){ if(wait_queue[i].isVIP){ vip_wait_queue.push_back(wait_queue[i]); }else{ non_vip_wait_queue.push_back(wait_queue[i]); } } int vip_number = vip_wait_queue.size(); int non_vip_number = non_vip_wait_queue.size(); // 解决n个用户 for(int i=0;i<n;++i){ // 第二次提交批改的代码局部: //队首球员 bool flag;//判断队首球员是否是VIP球员 if(vip_index<vip_number&&non_vip_index<non_vip_number){ // VIP队员和非VIP队员都存在,抉择先达到的作为队首队员 flag = vip_wait_queue[vip_index].arriving_time < non_vip_wait_queue[non_vip_index].arriving_time; }else { // 存在VIP队员就是true,没有就是false flag = vip_index < vip_number; } // 队首队员 Player player = flag ? vip_wait_queue[vip_index] : non_vip_wait_queue[non_vip_index]; // 首先找到服务工夫最早完结并且编号最小的桌子 int minTime = 0x3fffffff; int index; for(int j=1;j<=K;++j){ if(minTime>table[j].begin_time){ minTime = table[j].begin_time; index = j; } } if(minTime>=end_serve_time){ //最早服务完结的桌子在21点当前就不进行服务了 break; } if(table[index].isVIP){ // 如果是VIP桌子 if(player.isVIP){ // 队首球员也是VIP,将该VIP球座调配给VIP球员 if(table[index].begin_time>=player.arriving_time){ // 球员到的时候球桌还没有闲暇 player.waiting_time = table[index].begin_time - player.arriving_time; player.serve_time = table[index].begin_time; // 更新球员的开始服务工夫 }else{ // 球员到的时候球桌曾经闲暇,没有等待时间,开始服务工夫就是达到工夫 player.waiting_time = 0; player.serve_time = player.arriving_time;// 球员开始服务工夫就是球员达到工夫 } table[index].begin_time = player.serve_time + player.playing_time;//更新该桌子的开始服务工夫 ++table[index].number;//累计该桌子为用户提供服务的人数 result.push_back(player); //队首球员是 VIP ++vip_index; }else{ // 队首球员不是VIP,判断第一个VIP球员是否在球座闲暇之前达到。 if(vip_index<vip_number&&vip_wait_queue[vip_index].arriving_time<=table[index].begin_time){ // 在闲暇前达到,调配给该VIP球员. vip_wait_queue[vip_index].waiting_time = table[index].begin_time - vip_wait_queue[vip_index].arriving_time; vip_wait_queue[vip_index].serve_time = table[index].begin_time; table[index].begin_time += vip_wait_queue[vip_index].playing_time;//更新该桌子的开始服务工夫 ++table[index].number;//累计该桌子为用户提供服务的人数 result.push_back(vip_wait_queue[vip_index]); ++vip_index; }else{ // 没有VIP球员或者不在桌子闲暇前达到,调配给队首球员 if(table[index].begin_time>=player.arriving_time){ // 球员到的时候球桌还没有闲暇 player.waiting_time = table[index].begin_time - player.arriving_time; player.serve_time = table[index].begin_time; // 更新球员的开始服务工夫 }else{ // 球员到的时候球桌曾经闲暇,没有等待时间,开始服务工夫就是达到工夫 player.waiting_time = 0; player.serve_time = player.arriving_time;// 球员开始服务工夫就是球员达到工夫 } table[index].begin_time = player.serve_time + player.playing_time;//更新该桌子的开始服务工夫 ++table[index].number;//累计该桌子为用户提供服务的人数 result.push_back(player); //队首球员不是VIP ++non_vip_index; } } }else { // 桌子不是VIP if(player.isVIP){ //队首球员是VIP,查找最早闲暇的VIP球座 minTime = 0x3fffffff; int vipIndex = -1; for(int j=1;j<=K;++j){ if(minTime>table[j].begin_time&&table[j].isVIP){ minTime = table[j].begin_time; vipIndex = j; } } if(vipIndex!=-1&&table[vipIndex].begin_time<=player.arriving_time){ // 找到一张VIP桌子并且在队首球员达到之后就是闲暇的,将该VIP球桌调配给队首球员 player.waiting_time = 0;//没有等待时间 player.serve_time = player.arriving_time;//开始服务工夫就是达到工夫 table[vipIndex].begin_time = player.serve_time + player.playing_time;//更新该桌子的下次服务工夫 ++table[vipIndex].number; result.push_back(player); //队首球员是 VIP ++vip_index; }else { // 没有VIP桌子或者队首球员达到时没有闲暇,将index非VIP桌子调配给队首球员 if(table[index].begin_time>=player.arriving_time){ // 球员到的时候球桌还没有闲暇 player.waiting_time = table[index].begin_time - player.arriving_time; player.serve_time = table[index].begin_time; // 更新球员的开始服务工夫 }else{ // 球员到的时候球桌曾经闲暇,没有等待时间,开始服务工夫就是达到工夫 player.waiting_time = 0; player.serve_time = player.arriving_time;// 球员开始服务工夫就是球员达到工夫 } table[index].begin_time = player.serve_time + player.playing_time;//更新该桌子的开始服务工夫 ++table[index].number;//累计该桌子为用户提供服务的人数 result.push_back(player); //队首球员是 VIP ++vip_index; } }else { // 球员不是VIP,间接将该桌子调配给该球员 if(table[index].begin_time>=player.arriving_time){ // 球员到的时候球桌还没有闲暇 player.waiting_time = table[index].begin_time - player.arriving_time; player.serve_time = table[index].begin_time; // 更新球员的开始服务工夫 }else{ // 球员到的时候球桌曾经闲暇,没有等待时间,开始服务工夫就是达到工夫 player.waiting_time = 0; player.serve_time = player.arriving_time;// 球员开始服务工夫就是球员达到工夫 } table[index].begin_time = player.serve_time + player.playing_time;//更新该桌子的开始服务工夫 ++table[index].number;//累计该桌子为用户提供服务的人数 result.push_back(player); //队首球员不是VIP ++non_vip_index; } } } // 依据开始服务的工夫进行排序 sort(result.begin(),result.end(),cmpByServeTime); //输入每一个用户的达到工夫,开始服务工夫和等待时间 int r = result.size(); for(int i=0;i<r;++i){ printf("%02d:%02d:%02d %02d:%02d:%02d %d\n",result[i].arriving_time/3600,result[i].arriving_time%3600/60,result[i].arriving_time%60, result[i].serve_time/3600,result[i].serve_time%3600/60,result[i].serve_time%60, (result[i].waiting_time+30)/60); } //输入每一张桌子服务的人数 for(int i=1;i<=K;++i){ printf("%d",table[i].number); if(i<K) printf(" "); } return 0;}

September 21, 2020 · 3 min · jiezi

关于算法-数据结构:一周刷完剑指offer19顺时针打印矩阵

顺时针打印矩阵1. 题目形容输出一个矩阵,依照从内向里以顺时针的程序顺次打印出每一个数字 2. 示例[[ 1, 2, 3, 4], [ 5, 6, 7, 8], [ 9, 10, 11, 12], [13, 14, 15, 16]] 则顺次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 3. 解题思路顺时针走,即向右->向下->向左->向上,一共要走(长*宽)步。 遇到边界就改变方向,当向上碰到顶的时候,四个边界都放大。思路简略,一个循环即可! 4. Java实现import java.util.ArrayList;public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix) { ArrayList<Integer> res = new ArrayList(); int rows = matrix.length; // 行的大小 int cols = matrix[0].length; // 列的大小 if (rows == 0 || cols == 0){ return res; } // 从左到右开始遍历,right最左边【列】,从上到下,bottom最底部【行】 int left = 0, right = cols-1, top = 0, bottom = rows -1; while(left <= right && top <= bottom){ // 从左到右开始遍历 for(int i = left; i <= right; i++){ res.add(matrix[top][i]); } // 从上到下开始遍历 for (int i = top+1; i <= bottom; i++){ res.add(matrix[i][right]); } // 从下往上开始遍历 if (top != bottom){ for (int i = right-1; i>= left; i--){ res.add(matrix[bottom][i]); } } //从 右往左开始遍历 if (left != right){ for (int i = bottom-1; i > top; i--){ res.add(matrix[i][left]); } } left++; right--; top++; bottom--; } return res; } }5. Python实现# -*- coding:utf-8 -*-class Solution: # matrix类型为二维列表,须要返回列表 def printMatrix(self, matrix): if matrix == None: return rows = len(matrix) columns = len(matrix[0]) start = 0 while rows > start * 2 and columns > start * 2: self.PrintMatrixInCircle(matrix, columns, rows, start) start += 1 print('') def PrintMatrixInCircle(self, matrix, columns, rows, start): endX = columns - 1 - start endY = rows - 1 - start # 从左到右打印一行 for i in range(start, endX+1): number = matrix[start][i] print(number, ' ', end='') # 从上到下打印一行 if start < endY: for i in range(start+1, endY+1): number = matrix[i][endX] print(number, ' ', end='') # 从右到左打印一行 if start < endX and start < endY: for i in range(endX-1, start-1, -1): number = matrix[endY][i] print(number, ' ', end='') # 从下到上打印一行 if start < endX and start < endY-1: for i in range(endY-1, start, -1): number = matrix[i][start] print(number, ' ', end='')如果您感觉本文有用,请点个“在看” ...

September 17, 2020 · 2 min · jiezi

关于算法-数据结构:一周刷完剑指offer13调整数组顺序使奇数位于偶数前面

调整数组程序使奇数位于偶数后面1. 题目形容输出一个整数数组,实现一个函数来调整该数组中数字的程序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半局部,并保障奇数和奇数,偶数和偶数之间的绝对地位不变。 2. 示例[1, 3, 4, 5, 8, 10]转变成:[1, 3, 5, 4, 8, 10] 3. 解题思路绝对地位不变--->放弃稳定性; 奇数位于后面,偶数位于前面 --->存在判断,移动元素地位; 这些都和外部排序算法类似,思考到具备稳定性的排序算法不多,例如插入排序,归并排序等;这里采纳插入排序的思维实现。 4. Java实现public class Solution { public void reOrderArray(int [] array) { int index = 0; // 用于保留奇数地位 int size = array.length; for (int i =0; i< size; i++){ if (array[i] % 2 == 1){ // 如果是奇数的话 int j = i; while (j > index){ // 循环替换奇数和偶数地位,偶数地位绝对不变 int temp = array[j]; array[j] = array[j-1]; array[j-1] = temp; j--; } index++; // 更新奇数地位 } } }}5. Python实现新建数组空间 ...

September 17, 2020 · 1 min · jiezi

关于算法-数据结构:一周刷完剑指offer12数值的整数次方

数值的整数次方1. 题目形容给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保障base和exponent不同时为0 2. 示例简略,无 3. 解题思路求base的exponent次方 首先,判断exponent 是否为正数,如果是则标记,并转为正整数循环相乘判断 exponent 的标记,如果是正数,则取倒数4. Java实现public class Solution { public double Power(double base, int exponent) { double res = 1.0; if(base == 0) return 0; if(exponent == 0) return res; boolean flag = true; if(exponent < 0){ flag = false; exponent = -exponent; } for(int i = 0; i< exponent; i++){ res *= base; } return flag ? res : 1/ res; }}5. Python实现# -*- coding:utf-8 -*-class Solution: def Power(self, base, exponent): # write code here # 能够不必转换 base 为正整数 res = 1.0 flag = True if base == 0: return 0 if exponent == 0: return 1 if exponent < 0: flag = False exponent = -exponent for i in range(exponent): res *= base return res if flag else 1/res如果您感觉本文有用,请点个“在看” ...

September 17, 2020 · 1 min · jiezi

关于算法-数据结构:一周刷完剑指offer11二进制中-1-的个数

二进制中 1 的个数1. 题目形容输出一个整数,输入该数32位二进制示意中1的个数。其中正数用补码示意。 2. 示例无 3. 解题思路先理解一下计算机的编码模式: 计算机中,正数以其正值的补码模式表白 什么叫补码呢?这得从原码,反码说起。 原码:一个整数,依照绝对值大小转换成的二进制数,称为原码。比方 00000000 00000000 00000000 00000101 是 5的 原码。 反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。取反操作指:原为1,得0;原为0,得1。(1变0; 0变1) 比方:将00000000 00000000 00000000 00000101每一位取反, 得11111111 11111111 11111111 11111010。 称:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。 反码是互相的,所以也可称:11111111 11111111 11111111 11111010 和00000000 00000000 00000000 00000101 互为反码。 补码:反码加1称为补码。也就是说,要失去一个数的补码,先失去反码,而后将反码加上1,所得数称为补码。 比方:00000000 00000000 00000000 00000101的反码 是:11111111 11111111 11111111 11111010。 那么,补码为:11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011 ...

September 17, 2020 · 2 min · jiezi

关于算法-数据结构:一周刷完剑指offer10矩形覆盖

矩形笼罩1. 题目形容咱们能够用2 1的小矩形横着或者竖着去笼罩更大的矩形。请问用n个2 1的小矩形无重叠地笼罩一个2*n的大矩形,总共有多少种办法? 2. 示例比方n=3时,2*3的矩形块有3种笼罩办法: 3. 解题思路还是找法则的一道题,归根结底还是 斐波那契数列 如果是n=1 -> 1种 如果是n=2 -> 2种 如果是n=3 -> 3种 如果是n=4 -> 5种 留神,这里Python应用递归办法也是不能通过的,超过运行所要求的的工夫 4. Java实现public class Solution { public int RectCover(int target) { if (target <= 0) return 0; if (target == 1) return 1; if (target == 2) return 2; return (RectCover(target - 1) + RectCover(target - 2)); }}5. Python实现# -*- coding:utf-8 -*-class Solution: def rectCover(self, number): # write code here if number <= 0: return 0 if number == 1: return 1 if number == 2: return 2 a, b = 1, 2 for i in range(number-2): a, b = b, a+b return b如果您感觉本文有用,请点个“在看” ...

September 17, 2020 · 1 min · jiezi

关于算法-数据结构:一周刷完剑指offer7斐波那契数列

1. 题目形容大家都晓得斐波那契数列,前两项相加为第三项的值 如:0, 1, 1, 2, 3, 5, 8, 13...... 2. 示例当初要求输出一个整数n,请你输入斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n<=39 3. 解题思路第一种办法:应用递归的模式 第二种办法:应用非递归的模式,不同与python,【a, b = b, a+b】, java须要应用一个两头变量 4. Java实现应用递归public class Solution { public int Fibonacci(int n) { if (n <= 0) return 0; if (n == 1) return 1; return (Fibonacci(n-2) + Fibonacci(n-1)); }}非递归:public class Solution { public int Fibonacci(int n) { int a = 0, b = 1; int temp; for (int i = 0; i < n; i++){ temp = a; a = b; b = temp + b; } return a; }}5. Python实现第一种办法:递归# -*- coding:utf-8 -*-class Solution: def Fibonacci(self, n): # write code here a, b = 0, 1 if n == 0: return 0 if n == 1: return 1 return self.Fibonacci(n-1) + self.Fibonacci(n-2) 二种办法:非递归# -*- coding:utf-8 -*-class Solution: def Fibonacci(self, n): # write code here a, b = 0, 1 if n == 0: return 0 for i in range(n): a, b = b, a+b return a 如果您感觉本文有用,请点个“在看” ...

September 17, 2020 · 1 min · jiezi

关于算法-数据结构:数据结构与算法学习链表及其常用方法封装

概念链表和数组一样,能够用于存储一系列的元素,然而链表和数组的实现机制齐全不同,链表相似于火车:有一个火车头,火车头会连贯一个节点,节点上有乘客(相似于数据),并且这个节点会连贯下一个节点,以此类推 链表和数组的区别数组:要存储多个元素,数组(或称为列表)可能是最罕用的数据结构咱们之前说过,简直每一种编程语言都有默认实现数组构造然而数组也有很多毛病:数组的创立通常须要申请一段间断的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当以后数组不能满足容量需要时,须要扩容(个别状况下是申请一个更大的数组,而后将元素组中的元素赋值过来)而且在数组结尾或两头地位插入数据的老本很高,须要进行大量元素的位移只管咱们学过的js的Array类办法能够帮咱们做这些事,但背地的原理仍然是这样的 链表的劣势要存储多个元素,另外一个抉择就是链表但不同数组,链表的元素在内存中不用是间断的空间链表的每个元素由一个存储元素自身的节点和一个指向下一个元素的援用(有些语言成为指针或者连贯)组成。内存空间不是必须间断的,能够充沛利用计算机的内存,实现灵便的内存动静治理链表不用在创立时就确定大小,并且大小能够有限的延长上来链表再插入和删除数据时,工夫复杂度能够达到O(1),绝对数组效率高很多 链表的毛病链表拜访任何一个地位的元素时,都须要从头开始拜访(无奈跳过第一个元素去拜访任何一个元素)无奈通过下标间接拜访元素,须要从头一个个拜访,直到找到对应的元素 链表的罕用办法append(data):向列表尾部增加一个新的项insert(position,data):向列表的特定地位插入一个新的项get(position):获取对应地位的元素indexOf(data):返回元素在列表中的索引。如果列表中没有该元素则返回-1update(positon,data):批改某个地位的元素removeAt(position):从列表的特定地位移除一项remove(data):从列表中移除一项isEmpty():如果链表中不蕴含任何元素,返回true,如果链表长度大于0则返回falseSize():返回链表蕴含的元素个数。与数组的length属性类似toString():将链表中的数据转换为字符串 实现链表的罕用办法class CreateData { constructor(data) { this.data = data; this.next = null }}class LinkedList { constructor() { this.head = ''; this.length = 0; } append(data) { let newData = new CreateData(data); if (this.head === '') { newData.next = this.head; this.head = newData; } else { let current = this.head; while (current.next) { current = current.next } current.next = newData } this.length++; } insert(position, data) { if (position < 0 || position > this.length) return false let newData = new CreateData(data); if (position === 0) { newData.next = this.head; this.head = newData } else { let index = 0; let current = this.head; let prev = null; while (index++ < position) { prev = current current = current.next; } prev.next = newData; newData.next = current } this.length++ } get(position) { if (position < 0 || position >= this.length) return null; let current = this.head; let index = 0; while (index++ < position) { current = current.next } return current.data; } indexOf(data) { let current = this.head; let index = 0; while (current.data !== data) { if (!current.next) return -1 current = current.next; index += 1; } return index } update(position, data) { if (position < 0 || position >= this.length) return false; let newData = new CreateData(data); let current = this.head; let index = 0; while (index++ < position) { current = current.next; } current.data = data } removeAt(position) { if (position < 0 || position >= this.length) return false; if (position === 0) { let current = this.head; this.head = current.next } else { let current = this.head; let index = 0; let prev = null; while (index++ < position) { prev = current; current = current.next; } prev.next = current.next } this.length -= 1; } remove(data) { let i = this.indexOf(data); this.removeAt(i) } isEmpty() { return this.length > 0 ? false : true; } size() { return this.length } toString() { let current = this.head; let str = ''; while (current) { str += current.data + ','; current = current.next } return str.slice(0, str.length - 1) }}测试下面封装链表的办法let linkedList = new LinkedList();linkedList.append('小强');linkedList.append('小红');linkedList.append('小花');console.log('链表向最初增加一项', linkedList)linkedList.insert(1, '小李');console.log('链表向指定地位插入一项', linkedList)console.log('依据索引获取某一项的值', linkedList.get(3))console.log('依据某一项值获取对应索引', linkedList.indexOf('小花'))linkedList.update(2, '小明')console.log('依据索引替换传进去的值', linkedList)linkedList.removeAt(2)console.log('依据索引删除某一项',linkedList)linkedList.remove('小强')console.log('依据某一项值删除数据',linkedList)console.log('查看是否为空', linkedList.isEmpty())console.log('查看链表元素的个数', linkedList.size())console.log('将链表内的元素转化成字符串', linkedList.toString())天行健,小人以自暴自弃 ...

September 17, 2020 · 2 min · jiezi

关于算法-数据结构:算法的加密

1. 算法品种* 单向加密* 对称加密* 非对称加密1.1 单向加密-------- 即加密之后不能解密,个别用于数据验证 1) Base64Base64 编码是从二进制到字符的过程,用 64 个字符来示意任意的二进制数据,罕用于在 HTTP 加密,图片编码传输等。 可打印字符:在ASCII码中规定,0~31、128 这33个字符属于控制字符,32~127这95个字符属于可打印字符 转换形式:在 HTTP 协定下传输二进制数据时须要将其转换为字符数据,而网络传输只能传输可打印字符(95 个),不能转换的就须要应用 Base64 进行转换。 转换方法: 1 字节(byte) = 8 比特位(bit)Base64 定义了 64 (2^6)个可打印字符示意二进制的办法,也就是说 6 个 bit 的二进制数据能够用对应的字符代替示意对于间断多个二进制数据,每 3 个字节一组进行转换,3个字节 24 bit,而后将其分为 4 局部(3×8 = 4×6),每个局部刚好 6 bit,将 6 bit 二进制转换为 Base64 定义的字符即实现转换例, 6 bit 二进制是 000000,那么对应的字符就是 A,如果 6 bit 二进制是 110011,那么对应的字符就是 z若二进制数据字节数不是 3 的倍数,Base64 就将剩下的二进制数据补 0 至 3 的倍数,全 0 的用字符 “=” 代替2) MD5Message Digest algorithm 5,信息摘要算法,MD5 ...

September 15, 2020 · 2 min · jiezi

关于算法-数据结构:排序算法总结Java实现

排序算法分类排序算法依据解决数据应用到的存储设备可分为两大类,别离是外部排序和内部排序。 外部排序:将须要解决的数据都加载到内存中进行排序内部排序:因为数据量过于宏大,单靠内存无奈实现,须要借助内部存储进行排序外部排序又可细分,如下图所示 插入排序1.间接插入排序基本思路:首先先从数组中抉择一个数x放到一个数组里,从遍历以后数组,和新数组的每一个元素进行比拟,从而决定它放在什么地位,只能以后数组的每个元素都放进去了举个例子,数组{7,5,9,3,12,8,20,18},总共须要7趟,假如咱们抉择7作为第一个数初始状态:(7),5,9,3,12,8,20,18,从下标1开始找,放到新数组里(7)第1趟:(5,7),9,3,12,8,20,18第2趟:(5,7,9),3,12,8,20,18第3趟:(3,5,7,9),12,8,20,18第4趟:(3,5,7,9,12),8,20,18第5趟:(3,5,7,8,9,12),20,18第6趟:(3,5,7,8,9,12,20),18第7趟:(3,5,7,8,9,12,18,20) 代码如下:public static void insertSort(int[] arr){ if (arr == null || arr.length <= 0 ) return; int len = arr.length; for (int i = 1; i < len; i ++){ //保留以后地位的值 int insertValue = arr[i]; //找到以后地位的前一个地位 int insertIndex = i - 1; //和前一个地位进行比拟,只有比前一个小,就把前一个地位的值赋给以后地位 //始终循环上来,直到本人成为了第一个数或者找到了一个比本人还小的数 while (insertIndex >= 0 && insertValue < arr[insertIndex]){ arr[insertIndex + 1] = arr[insertIndex]; insertIndex--; } //把之前保留的数和赋给以后找到的地位 arr[insertIndex + 1] = insertValue; } }2.希尔排序基本思路:先将整个待排序的记录宰割成若干子序列,别离对这些子序列进行排序,待整个残缺的序列根本有序时,再对整体记录进行顺次间接插入排序举个例子:比方对于数组{9,12,8,98,36,29,190,54} ...

September 15, 2020 · 6 min · jiezi

关于算法-数据结构:面向-offer-学算法笔面试大杀器-单调栈

目录前言枯燥栈初入茅庐小试牛刀打怪降级出师试炼前言枯燥栈是一种比拟简略的数据结构。尽管简略,但在某些题目中能施展很好的作用。 最近很多大厂的口试、面试中都呈现了枯燥栈的题目,而还有不少小伙伴连枯燥栈是什么都不理解,因而老汪专门写了这篇文章,心愿对你们有所帮忙。 老规矩,先上一道题给大家看看枯燥栈能解决什么样的问题,这题是 2020 年猿辅导(K12 教育的独角兽,研发岗白菜价 40W 起步,不加班高福利,想要内推的能够私信老汪)的一道面试题。 给定一个二叉搜寻树,并给出他的前序序列,要求输入中序序列,工夫复杂度O(n),并给出证实。枯燥栈是什么:枯燥栈是一种具备枯燥性的栈,任何时刻从栈顶到栈底的元素都是枯燥增/减的。干什么: 枯燥栈能够找到从左/右遍历第一个比它大/小的元素的地位。枯燥栈也能够将某个元素左/右边所有比它小/大的元素按升/降序输入。怎么做:应用栈构造,在入栈的时候放弃 id 和 val 的枯燥性即可。翻译成大白话,但凡遇到题目中间接或间接要求查找某个元素左/左边第一个大于/小于它的元素,或者要求将某个元素左/左边所有小/大于它的元素按升/降序输入,就能够应用枯燥栈来解决。 具体怎么做你可能还有些迷糊,上面咱们间接通过做题来加深对枯燥栈的了解。十分钟包教包会,当天就能够和面试官对线。 初入茅庐给定数组 a,要求输入这样的数组 b,b[i] 是 a[i] 右边第一个比 b[i] 大的元素,若不存在则 b[i] = a[i]。最暴力的解法,就是对每一个 a[i],遍历其右边的所有元素,这样所需的工夫复杂度是 O(n^2)。如果应用枯燥栈,工夫复杂度能够优化到 O(n)。 这是最根本、最直白的枯燥栈问题,所有枯燥栈问题都是在该问题上进行延长、变种得来的,把握了这个问题的解决办法,所有枯燥栈的问题都能迎刃而解。 因为本问题过于简略、直白,就不多做解说,间接上代码: public int[] solve(int[] a){ if(a == null) return null; //容错解决,越是简略的题越要留神细节 int n = a.length; int[] b = new int[n]; Stack<Integer> stack = new Stack(); //枯燥栈当然要用栈构造啦 for(int i = 0; i < n; i++){ while(!stack.isEmpty() && stack.peek() < a[i]) stack.pop(); //所有比 a[i] 小的元素都出栈,保障了从栈顶到栈底元素是枯燥增的,并且栈顶的元素是 a[i] 右边第一个比 a[i] 大的元素 if(stack.isEmpty()) stack.push(a[i]); b[i] = stack.peek(); } return b;}这代码也是枯燥栈问题的根本构造,所有枯燥栈问题的代码都基于此进行变种、扩大。小伙伴们能够多花两分钟好好消化下面的代码。 ...

September 9, 2020 · 2 min · jiezi

关于算法-数据结构:二叉树的序列化与反序列化

二叉树的序列化与反序列化前言    几个月前,笔者加入了一次面试。面试的最初,面试官要求手写“二叉树的序列化与反序列化”。其实咱们在把握了二叉树的算法套路之后,这应该是比较简单的一道题。接下来咱们就来看看如何解决它吧! 讲讲序列化    序列化。一个经常出现的名词,咱们都晓得Java Bean个别都须要继承Serializable接口,还须要定义一个serialVersionUID。新多人虽这么用过却不晓得为什么要这么用。所以为了关照新人,咱们先来讲讲序列化是什么,以及为什么须要序列化。     首先援用Wikipedia的一段形容 序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格局(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在雷同或另一台计算机环境中,能复原原先状态的过程。按照序列化格局从新获取字节的后果时,能够利用它来产生与原始对象雷同语义的正本。对于许多对象,像是应用大量援用的简单对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。    我想维基百科曾经解释的十分具体了,那咱们想想,为什么须要序列化呢?咱们晓得序列化能够将一个对象"存起来",那么,咱们就能够运行时动静的对象进行网络传输、跨平台存储、转储、RPC等操作。序列化技术能够说是很多技术的祭祀,咱们耳熟能详的RPC就是其中一个。 二叉树与序列化    当初咱们曾经对序列化有了一个根本的理解。咱们无妨思考一下序列化的过程。咱们会发现,将一个数组对象序列化很简略,咱们能够将 int[] arr = new int[3];序列化成"0,0,0"。将一个对象序列化也很简略,譬如JSON,就是最好的对象形容格局之一。那二叉树该怎么序列化呢?咱们怎么把一个二维的“树”转换成一个一围的“串”呢?     这就须要用到二叉树的遍历,一说到遍历大家就会感觉序列化也没那么难了,因为都晓得二叉树的遍历是比拟死板的.那么到底选用前序还是中序,或者后序遍历呢?答案是"轻易"。咱们只须要保障序列化和反序列化用的是同一种遍历办法即可。那么接下来,咱们就借着Leetcode中的297. 二叉树的序列化与反序列化来给大家演示一下序列化与反序列化操作。     首先还是回顾一下遍历操作,三种不同的遍历形式区别只在于逻辑代码和递归代码的绝对地位。咱们就以前序遍从来演示。 // 二叉树前序遍历套路。public static void preOrder(TreeNode root){ if( root == null ) return; // 前序遍历 逻辑代码 doSomething(); preOrder(root.left); preOrder(root.right);}    基于这个骨架,咱们来看看二叉树的序列化代码应该怎么写。依照咱们方才的框架,咱们首先应该想到的代码是这样的。 class Codec { final String NULL = "null"; // 用'null'代替示意空节点; final String SEP = ","; StringBuilder res = new StringBuilder(); public void serialize(TreeNode root) { if ( root == null ) { res.append(NULL); res.append(SEP); } else { res.append(String.valueOf(root.val)); res.append(SEP); serialize(root.left); serialize(root.right); } }}    然而咱们会发现,明明一个简略的递归,写进去却很简单。而且力扣297给的办法签名是这个样子的。String serialize(TreeNode root)。那这样一变,可能萌新们变得无从下手,不会套框架了。咱们来看看怎么在递归中正确的返回咱们想要的后果。 ...

August 11, 2020 · 1 min · jiezi

关于算法-数据结构:前端学数据结构与算法一复杂度分析

前言兜兜转转了这么久,数据结构与算法始终是逃不过命题。曾几何时,前端学习数据结构与算法,想必会被认为不务正业,但现今想必大家已有耳闻与经验,面试遇到链表、树、爬楼梯、三数之和等题目曾经不足为奇。想进靠谱大厂算法与数据结构应该不止是提上日程那么简略,可能当初曾经是火烧眉毛。这次决定再写一个系列也只是作为我这段时间的学习报告,也不相对不会再像我之前的vue原理解析那般断更了,欢送大家监督~ 学数据结构与算法的最好机会是十年前,其次就是当初。什么是数据结构与算法?例如书店里的书能够以年代作为辨别摆放,能够以作家集体为辨别摆放,也能够以类型作为辨别摆放,这么摆的目标就是为了高效的找到心仪的书,这就是数据结构;又例如你借了一摞书筹备走出书店,其中有一本忘了注销,如何疾速找出那本书?你能够一本本的尝试,也能够每一次间接就检测半摞书,再剩下的半摞书仍然如此,这样12本书,你只用4次即可,而这就是算法。 再举个例子,计算数字从1到100之和,应用循环咱们可能会写出这样的程序: let res = 0for (let i = 1; i <= 100; i++) { res += i}return res如果这里的100变成了十万、百万,那么这里计算量同样也会随之减少,然而如果应用这样一个求和的公式: 100 * (100 + 1) / 2 无论数字是多大,都只须要三次运算即可,算法可真秒!同样数据结构与算法是相互依存的,数据结构为什么这么存,就是为了让算法能更快的计算。所以首先须要理解每种数据结构的个性,算法的设计很多时候都须要基于以后业务最合适的数据结构。 为什么要学习数据结构与算法?谈谈我集体的见解,首先当然是环境的逼迫,大厂都再考这些,人人又想进大厂,而大厂又为了减少筛选的效率。其次学习之后能够宽阔解决问题的眼界,多指针、二分查找、动静布局;树、链表、哈希表,这一坨数据为什么要用这么麻烦的形式的存储?这些都能够拓展咱们编写程序的下限。当然所有的这些都指向同一个问题: 如何高效且节约存储空间的实现计算工作当初才明确,原来代码不全是写的越短越简洁效率就越高;原来同样一个问题,不同的解法效率可能有成千盈百倍的差距;原来工夫和空间不可兼得,总得就义其中的一个性能。 最初就是简单利用对数据结构和算法的利用,集体学习中所知的,如九宫格输入法的拼音匹配、编辑器里括号的匹配、浏览器历史记录后退和后退的实现、vue组件keep-alive的LRU缓存策略等,这些都须要对数据结构与算法有理解才行。可能日常的开发中所用甚少,不过减少对简单问题的形象与了解总是有好处的,毕竟程序员的价值体现就是解决问题。 如果评判一段程序运行工夫?例如咱们再leetcode上解题,当获取一个通过时,你编写的题解的用时与内存超过的百分比是越高越好,那为什么一段程序有那么大的区别?这个时候咱们就要了解评断程序执行效率的规范,毕竟不能等每段程序都运行完了之后去看执行工夫来评判,执行之前咱们要用肉眼粗略能看出个大略。 大O复杂度表示法能够看接下来这段代码: function test (n) { const a = 1 const b = 2 let res = 0 for (let i = 0; i < n; i++) { res += i } } 站在程序执行的角度来看,这段程序会别离执行一次a = 1以及b = 2,接下来会执行一段循环,for循环执行了两次n遍的运算(i++以及res += i),这段程序一共执行了2n + 2次,如果用大O表示法,这段程序的工夫复杂度能够示意为O(2n + 2),(大O的具体实践及公式网上很多,大家可自行搜寻)。简略来说, 大O表示法的含意是代码执行工夫随数据规模增长而增长的趋势, 也就是这段代表的执行运行耗时,常数级别的操作对趋势的影响甚少,会被间接忽视。所以下面的O(2n + 2)能够间接看成是O(n),因为只有n影响到了趋势。接下里再看一段代码: ...

August 8, 2020 · 2 min · jiezi

关于算法-数据结构:js算法与数据结构队列

一、队列的定义 二、队列办法 三、js实现办法如下//队列 一种先进先出的数据结构var Queue = function(){ //用数组来模仿 首元素为队头,尾元素为队尾 var items = [] //入队 this.enqueue = function(element){ items.push(element) } //出队 this.dequeue = function(){ return items.shift() } //查看队列头 this.front = function(){ return items[0] } //队列长度 this.size = function(){ return items.length } //判断是否为空 this.isEmpty= function(){ return items.length == 0 }}四、实战演练1.击鼓传花有多个玩家进行击鼓传花,每位玩家顺次开始击鼓,击一次鼓传花一次,并且计数加1(从0开始),当有玩家击鼓后,计数为3时,则淘汰该玩家,计数归0,持续进行上述过程,直到残余一名玩家为胜利者,游戏完结。 code//击鼓传花var setFlowerParams = function(user, number){ var passFlower = new Queue()//初始化队列for(var i = 0 ; i < user.length ; i++){ passFlower.enqueue(user[i])}//循环队列while( passFlower.size() > 1){ for(var i = 0; i < number - 1; i++){ passFlower.enqueue(passFlower.dequeue()) } var taotai = passFlower.dequeue() console.log('淘汰' + taotai) } return console.log('最终玩家' + passFlower.front())} var user = []运行后果 ...

July 31, 2020 · 1 min · jiezi

关于算法-数据结构:javascript算法与数据结构栈

一、栈的定义一种后进先出的数据结构。 栈顶是个闭口,能够放入元素即push(),移除元素即pop()。栈底关闭,不能操作元素。 二、栈的办法 三、js实现栈的办法//创立一个函数结构器,用来创建对象var Stack = function(){ //能够以数组模仿栈,首元素为栈底,尾元素为栈顶 var items = [] //入栈 从栈顶进入一个元素 this.push = function(element){ return items.push(element) } //出栈 从栈顶移除一个元素 this.pop = function(){ return items.pop() } //peek 查看栈顶元素 this.peek = function(){ return items[items.length - 1] } //查看栈有多少个元素 this.size = function(){ return items.length } //判断栈是否为空 this.isEmpty = function(){ return items.length == 0 } //清空栈 this.clear = function(){ items = [] } //查看数组 this.getItems = function(){ return items }}三、实例演战将十进制转换为二进制代码如下: ...

July 30, 2020 · 1 min · jiezi

关于算法-数据结构:javascript算法与数据结构栈

一、栈的定义一种后进先出的数据结构。 栈顶是个闭口,能够放入元素即push(),移除元素即pop()。栈底关闭,不能操作元素。 二、栈的办法 三、js实现栈的办法//创立一个函数结构器,用来创建对象var Stack = function(){ //能够以数组模仿栈,首元素为栈底,尾元素为栈顶 var items = [] //入栈 从栈顶进入一个元素 this.push = function(element){ return items.push(element) } //出栈 从栈顶移除一个元素 this.pop = function(){ return items.pop() } //peek 查看栈顶元素 this.peek = function(){ return items[items.length - 1] } //查看栈有多少个元素 this.size = function(){ return items.length } //判断栈是否为空 this.isEmpty = function(){ return items.length == 0 } //清空栈 this.clear = function(){ items = [] } //查看数组 this.getItems = function(){ return items }}三、实例演战将十进制转换为二进制代码如下: ...

July 30, 2020 · 1 min · jiezi

关于算法-数据结构:LeetCode001两数之和easy

题目:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标你能够假如每种输出只会对应一个答案。然而,数组中同一个元素不能应用两遍 示例: 给定 nums = [2, 7, 11, 15], target = 9因为 nums[0] + nums[1] = 2 + 7 = 9所以返回 [0, 1]办法一:暴力解法 思路:将数组中的所有可能的两两组合进行相加,而后和指标数字比拟,如果相等,那么这两个元素所对应的下标就是咱们想要的后果。置信聪慧的你曾经想到通过双层for循环来解决class LeetCode0001{ public function twoSum($nums, $target) { $len = count($nums); for($i = 0; $i< $len; $i++) { for ($j=$i+1; $j<$len;$j++) { if ($nums[$i] + $nums[$j] == $target) { return [$i, $j]; } } } }}工夫复杂度为:O(n^2) 空间复杂度:O(1) 办法二:数组翻转 思路: A + B = targetA和B是数组中的元素,targer为目标值换种思路,既然数组中两个元素的和为指标数,也就是说 ...

July 24, 2020 · 1 min · jiezi

关于算法-数据结构:一文吃透时间复杂度和空间复杂度

学习数据结构和算法的第一步 工夫复杂度最常见的工夫复杂度有哪几种O(1):Constant Complexity 常数复杂度O(log n):Logarithmic ComPlexity 对数复杂度O(n):Linear ComPlexity 线性工夫复杂度O(n^2):N square ComPlexity 平方O(n^3):N cubic ComPlexity 立方O(2^n):Exponential Growth 指数O(n!):Factorial 阶乘剖析工夫复杂度的时候是不思考前边的系数的,比如说O(1)的话,并不代表它的复杂度是1,也能够是2、3、4...,只有是常数次的,都用O(1)示意 如何来看一段代码的工夫复杂度?最罕用的形式就是间接看这段代码,它依据n的不同状况会运行多少次 O(1)$n=100000;echo 'hello';O(?)$n=100000;echo 'hello1';echo 'hello2';echo 'hello3';第一段代码,不论n是多少,都只执行一次,所以它的工夫复杂度就是O(1)。第二个其实也同理,咱们不关怀系数是多少。尽管第二段代码会执行3次echo输入,然而不论n是多少,它都只执行3次,因而它的工夫复杂度也是常数复杂度,也就是O(1) 在看下边两段代码: O(n)for($i = 1; $i <= $n; $i++) { echo 'hello';}O(n^2)for($i = 1; $i <= $n; $i++) { for($j = 1; $j <= $n; $j++) { echo 'hello'; }}这两段代码都是随着n的不同,它执行的次数也在发生变化,第一段代码执行的次数和n是线性关系的,所以它的工夫复杂度是O(n)。 第二段代码是一个嵌套循环,当n为100的状况下,里边的输入语句就会执行10000次,因而它的工夫复杂度就是O(n^2)。如果第二段代码中的循环不是嵌套的,而是并列的,那么它的工夫复杂度应该是O(2n),因为前边的常数系数咱们不关注,因而它的工夫复杂度就是O(n) O(log n)for($i = 1; $i <= $n; $i = $i*2) { echo 'hello';}O(k^2)fib($n) { if ($n < 2) { return $n; } return fib($n-1) + fib($n-2);}第一段代码,当n=4时,循环执行2次,所以循环外部执行的次数和n的关系为log2(n),因而工夫复杂度为对数复杂度O(logn)。第二段是一个Fibonacci(斐波拉契)数列,这里是用了递归的这种模式,这就牵扯到了递归程序在执行的时候,如何计算它的工夫复杂度,它的答案是k^n,k是一个常数,是一个指数级的,所以简略的通过递归的形式求Fibonacci数列是十分慢的,它是指数级的工夫复杂度。具体指数级的工夫复杂度是怎么失去的,后边会具体阐明。下边看一下各种工夫复杂度的曲线 ...

July 23, 2020 · 1 min · jiezi

关于算法-数据结构:二分查找允许重复元素的有序数组

带反复元素的二分查找例题:旋转数组的最小数字起源:力扣(LeetCode)链接:https://leetcode-cn.com/probl... 例题把一个数组最开始的若干个元素搬到数组的开端,咱们称之为数组的旋转。输出一个递增排序的数组的一个旋转,输入旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 示例示例1: 输出:[3,4,5,1,2]输入:1示例2: 输出:[2,2,2,0,1]输入:0题解高兴的一天从力扣的每日一题开始。发现明天难度是easy,于是决定做进去再去吃早饭。 点开题目一看,这不是爆搜吗hhhh。但咱们是有幻想有谋求的人,这么简略的题目怎么能忍耐工夫复杂度为O(n)的搜寻。于是我将二分查找的板子砸了下来: public class Solution { public int minArray(int[] numbers) { int len = numbers.length; int l = 0, r = len-1; int ans; while (l <= r) { int mid = l + ((r-l) >> 1); if (numbers[mid] > numbers[l]) { l = mid + 1; } else if (numbers[mid] < numbers[l]) { r = mid - 1; } else { ans = numbers[mid]; break; } } return ans; }}而后: ...

July 22, 2020 · 1 min · jiezi

关于算法-数据结构:二叉树的四种遍历方式

1.二叉树的遍历算法1.1三种遍历示例先序遍历:ABCDEFGH中序遍历:CBEDFAHG后续遍历:CEFDBHGA 能够用如下形式进行遍历:假设有个指针p沿着图中蓝色箭头游历整个二叉树。标号1示意第一次通过节点,2示意第二次通过节点,3示意第三次通过节点,别离对应了前序遍历(1)、中序遍历(2)、后序遍历(3)的后果:1.2二叉树的存储构造: struct BTNode { int val; TreeNode *lchild; TreeNode *rchild; };1.3先序遍历 void preorder(BTNode *p){ if(p!=null){ Visit(p);//能够是打印 preorder(p->lchild);//先序遍历左子树 preorder(p->lchild);//先序遍历右子树 }}1.4中序遍历 void inorder(BTNode *p){ if(p!=null){ preorder(p->lchild); Visit(p); preorder(p->lchild); }}1.5后序遍历 void postorder(BTNode *p){ if(p!=null){ preorder(p->lchild); preorder(p->lchild); Visit(p); }}2.档次遍历要进行档次遍历,须要创立一个循环队列。先将二叉树头节点入队列,而后出队列,拜访该节点,如果它有左子树,左子树入队;如果它有右子树,右子树入队。而后出队,对出队节点拜访,如此重复,直到队列为空为止。 void level(BTNode *p){ int front=0,rear=0; BTNode *que[maxSize]; BTNode *p; if(p!=Null){ rear=(rear+1)%maxSize; que[rear]=p;//根节点入队 while(front!=rear){//队列非空 front=(front+1)%maxSize; q=que[front];//队头出队 Visit(q);//访问队头 if(q->lchild!=Null){//左子树非空 rear=(rear+1)%maxSize;//左子树入队 que[rear]=q->lchild; } if(q->rchild!=Null){//右子树非空 rear=(rear+1)%maxSize;//右子树入队 que[rear]=q->rchild; } } }}

July 22, 2020 · 1 min · jiezi

算法与数据结构学习链表

链表数组须要一块间断的内存空间来存储,对内存的要求比拟高。如果咱们申请一个 100MB 大小的数组,当内存中没有间断的、足够大的存储空间时,即使内存的残余总可用空间大于 100MB,依然会申请失败。而链表恰恰相反,它并不需要一块间断的内存空间,它通过“指针”将一组零散的内存块串联起来应用,所以如果咱们申请的是 100MB 大小的链表,基本不会有问题。 它跟数组一样,也是十分根底、十分罕用的数据结构。不过链表要比数组略微简单,从一般的单链表衍生进去好几种链表构造,比方双向链表、循环链表、双向循环链表。和数组相比,链表更适宜插入、删除操作频繁的场景,查问的工夫复杂度较高。不过,在具体软件开发中,要对数组和链表的各种性能进行比照,综合来抉择应用两者中的哪一个。 单链表 循环链表 双向链表 双向循环链表 应用链表实现一个LRU缓存保护一个有序单链表,越凑近链表尾部的结点是越早之前拜访的。当有一个新的数据被拜访时,咱们从链表头开始程序遍历链表。 如果此数据之前曾经被缓存在链表中了,咱们遍历失去这个数据对应的结点,并将其从原来的地位删除,而后再插入到链表的头部。如果此数据没有在缓存链表中,又能够分为两种状况: 如果此时缓存未满,则将此结点直接插入到链表的头部;如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。这样咱们就用链表实现了一个 LRU 缓存

July 10, 2020 · 1 min · jiezi

算法与数据结构学习数组

数组数组(Array)是一种线性表数据结构。它用一组间断的内存空间,来存储一组具备雷同类型的数据。 咱们晓得,计算机会给每个内存单元调配一个地址,计算机通过地址来拜访内存中的数据。当计算机须要随机拜访数组中的某个元素时,它会首先通过上面的寻址公式,计算出该元素存储的内存地址: a[i]_address = base_address + i * data_type_size 其中 data_type_size 示意数组中每个元素的大小。 很多人都答复说,“链表适宜插入、删除,工夫复杂度 O(1);数组适宜查找,查找时间复杂度为 O(1)”。实际上,这种表述是不精确的。数组是适宜查找操作,然而查找的工夫复杂度并不为 O(1)。即使是排好序的数组,你用二分查找,工夫复杂度也是 O(logn)。所以,正确的表述应该是,数组反对随机拜访,依据下标随机拜访的工夫复杂度为 O(1)。 容器可能代替数组Java ArrayList 无奈存储根本类型,比方 int、long,须要封装为 Integer、Long 类,而 Autoboxing、Unboxing 则有肯定的性能耗费,所以如果特地关注性能,或者心愿应用根本类型,就能够选用数组。如果数据大小当时已知,并且对数据的操作非常简单,用不到 ArrayList 提供的大部分办法,也能够间接应用数组。还有一个是我集体的爱好,当要示意多维数组时,用数组往往会更加直观。比方 Object[][] array;而用容器的话则须要这样定义:ArrayList<ArrayList<object> > array。总结,对于业务开发,间接应用容器就足够了,省时省力。毕竟损耗一丢丢性能,齐全不会影响到零碎整体的性能。但如果你是做一些十分底层的开发,比方开发网络框架,性能的优化须要做到极致,这个时候数组就会优于容器,成为首选。 数组为什么是从0开始编号,而不是1呢?从数组存储的内存模型上来看,“下标”最确切的定义应该是“偏移(offset)”。后面也讲到,如果用 a 来示意数组的首地址,a[0]就是偏移为 0 的地位,也就是首地址,a[k]就示意偏移 k 个 type_size 的地位。然而,如果数组从 1 开始计数,那咱们计算数组元素 a[k]的内存地址就会变为: a[k]_address = base_address + (k-1)*type_size 比照两个公式,咱们不难发现,从 1 开始编号,每次随机拜访数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。 下面解释得再多其实都算不上压倒性的证实,说数组起始编号非 0 开始不可。最次要的起因可能是历史起因。 C 语言设计者用 0 开始计数数组下标,之后的 Java、JavaScript 等高级语言都效仿了 C 语言,或者说,为了在肯定水平上缩小 C 语言程序员学习 Java 的学习老本,因而持续沿用了从 0 开始计数的习惯。实际上,很多语言中数组也并不是从 0 开始计数的,比方 Matlab。甚至还有一些语言反对正数下标,比方 Python。 ...

July 9, 2020 · 1 min · jiezi

算法与数据结构学习时间复杂度分析

复杂度分析大O表示法、时间复杂度分析O(1) int i = 8; int j = 6; int sum = i + j;O(logn)、O(nlogn)i=1; while (i <= n) { i = i * 2; }通过 2x=n求解x=$\log_2n$,所以,这段代码的时间复杂度就是 O($\log_2n$)。$\log_2n$ 不管是以2为底,还是3为底...统一表示成O(logn) O(m+n)、O(m*n)int cal(int m, int n) { int sum_1 = 0; int i = 1; for (; i < m; ++i) { sum_1 = sum_1 + i; } int sum_2 = 0; int j = 1; for (; j < n; ++j) { sum_2 = sum_2 + j; } return sum_1 + sum_2;}最好情况时间复杂度、最坏情况时间复杂度、平均时间复杂度// n表示数组array的长度int find(int[] array, int n, int x) { int i = 0; int pos = -1; for (; i < n; ++i) { if (array[i] == x) { pos = i; break; } } return pos;}最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。就像我们刚刚讲到的,在最理想的情况下,要查找的变量 x 正好是数组的第一个元素,这个时候对应的时间复杂度就是最好情况时间复杂度。最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。就像刚举的那个例子,如果数组中没有要查找的变量 x,我们需要把整个数组都遍历一遍才行,所以这种最糟糕情况下对应的时间复杂度就是最坏情况时间复杂度。平均情况时间复杂度 要查找的变量 x 在数组中的位置,有 n+1 种情况:在数组的 0~n-1 位置中和不在数组中。我们把每种情况下,查找需要遍历的元素个数累加起来,然后再除以 n+1,就可以得到需要遍历的元素个数的平均值,即 这个公式简化之后,得到的平均时间复杂度就是 O(n)。我们知道,要查找的变量 x,要么在数组里,要么就不在数组里。这两种情况对应的概率统计起来很麻烦,为了方便你理解,我们假设在数组中与不在数组中的概率都为 1/2。另外,要查找的数据出现在 0~n-1 这 n 个位置的概率也是一样的,为 1/n。所以,根据概率乘法法则,要查找的数据出现在 0~n-1 中任意位置的概率就是 1/(2n)。因此,前面的推导过程中存在的最大问题就是,没有将各种情况发生的概率考虑进去。如果我们把每种情况发生的概率也考虑进去,那平均时间复杂度的计算过程就变成了这样: 引入概率之后,前面那段代码的加权平均值为 (3n+1)/4。用大 O 表示法来表示,去掉系数和常量,这段代码的加权平均时间复杂度仍然是 O(n)。 ...

July 8, 2020 · 2 min · jiezi

三十张图助你看清红黑树的前世今生

微信公众号:小超说这是查找算法系列的第三篇 : 三十张图助你看清红黑树的前世今生 在《算法》(第4版)中,红黑树的实现直接采用了左倾红黑树 (LLRB) 的方法,左倾红黑树可以用更少的代码量实现红黑树,在本文中我也使用他的方法理解。相比于经典红黑树,增加了一个限制红节点一定是父节点的左子节点,但是实现却容易不少一、红黑树的定义1.每个节点要么是黑色要么是红色; 2.根节点是黑色; 3.每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据; 4.任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; 5.每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; 这就是红黑树的定义,但你看完肯定会一脸懵,我也是一样。我会想:红黑树的来源是什么?为什么要区分红色和黑色节点呢?这些性质是怎么来的,或者有什么作用?不着急,你听我慢慢道来,我希望,你能够通过这篇文章对红黑树有一个清晰的认识,包括它的来历,意义以及各种操作。 二、平衡二叉查找树首先,还记得咱们上次文章中介绍的二叉查找树吗?尽管它具有不错的性能,但是仍然无法避免极端的情况。一般的二叉查找树的生成和数据插入的顺序密切相关,我们来看一组情况: 我们依次插入[B,A,C,G,R,Z],最后得到的树无疑已经退化到接近链表,这时,性能会受到很大地影响。我们的任务就是试图构造一种新的数据结构来解决这种问题,即构建平衡二叉查找树: 平衡二叉查找树的严格定义是:每个节点的左子树和右子树的高度差至多等于1。但我们不必严格死扣定义,我们只需要知道,平衡二叉查找树中“平衡”的意思,其实就是让整棵树左右看起来比较“对称”、比较“平衡”,不要出现左子树很高、右子树很矮的情况。这样就能让整棵树的高度相对来说低一些,相应的插入、删除、查找等操作的效率高一些。 三、2-3树我们不着急直接介绍红黑树,请跟随我的思路先学习一下2-3树,循序渐进地走向红黑树。 2-3树的定义一棵2-3树由以下节点组成: 2-节点,含有一个键(及其对应的值)和两条链接,左连接指向的2-3树中的键都小于该节点,右链接指向的2-3树中的键都大于该节点。3-节点,含有两个键(及其对应的值)和三条链接,左链接指向的2-3树中的键都小于该节点,中链接指向的2-3树中的键都位于该节点的两个键之间,右链接指向的2-3树中的键都大于该节点。 如果将一棵2-3树进行中序遍历,得到的是一个有序的序列,比如我们对下图进行中序遍历,会得到[A,C,F,J,K,M,O,Q,R,S,T]。 2-3树的创建我们首先给出两条原则【融合】与【拆分】: 原则1. 加入新节点时,不会往空的位置添加节点,而是添加到最后一个叶子节点上(使2-节点变为3-节点,3-节点变为4-节点)原则2. 如果出现4-节点,要将它分解成三个2-节点组成的树,并且分解后新树的根节点需要向上和父节点融合(父节点变成3-节点或者4-节点)接下来我们一起看一下2-3树的生长过程: 可以发现,虽然我们的插入顺序是[A->C->F->J->K->M->O->],是升序的,但是我们构造的2-3树却始终保持平衡。 那么,如何实现这种高效的数据结构呢?上面的一些操作我们描述清楚是一回事,但是实现又是另外一回事。我们选择红黑二叉查找树这种数据结构来实现它,简称红黑树。 红黑树与2-3树的等价红黑树背后的思想是用标准的二叉查找树(完全由2-节点构成)和一些额外的信息(替换3-节点)来表示2-3树。我们将树中的链接分为两种类型:一种是红链接(左倾),一种是黑链接。其中红链接将两个2-节点连接起来构成一个3-节点,黑节点则是2-3树中的普通节点。为了方便,我们把红链接指向的节点标记为红色,其他节点标记为黑色,这就是红黑树的由来,具体见下图所示: 我们再重新审视一下红黑树定义要求的各种性质: (1)每个节点要么是黑色要么是红色;(这个就不多说了,很自然) (2)根节点是黑色; 根节点只有两种情况,第一种可能是2-节点,此时对应着黑色;第二种可能是3-节点,此时根节点也是黑色的,你可以结合上图理解。 (3)每个叶子节点都是黑色的空节点(NIL),也就是说,叶子节点不存储数据; 这一条主要是为了简化红黑树的实现而设置的,严格来说,我们之前画的还不完整。 (4)任何相邻的节点都不能同时为红色,也就是说,红色节点是被黑色节点隔开的; 我们假设存在同时为红色的两个相邻节点,那么会怎么样呢?见下图: (5)每个节点,从该节点到达其可达叶子节点的所有路径,都包含相同数目的黑色节点; 这条要求保证了红黑树的性能。我们随意选一条从根节点到叶子节点的路径,由于性质4,任何相邻的节点不能同时为红色,所以每存在一个红节点,至少对应了一个黑节点,即红色节点个数<=黑色节点个数,我们再结合2-3树,每个黑色节点对应着一个2-节点或一个3-节点;根据2-3树的性质,其节点<=log(N),可以推出黑色节点<=log(N),那么加上红色节点不会大于2log(N)。 总结如下就是: 红色节点个数<=黑色节点个数=====>每一个黑色节点对应2-3树的每一个节点====>2-3树节点<=log(N)====>黑色节点<=log(N)====>红黑树节点<=2log(N)我们将2-3树染色后称为红黑树,然后给出了一些颜色的规则,这些规则能够帮助我们快速的构建使用红黑树。这使得我们以后只需要将注意力集中在颜色上面,只需要去维护上述规定的性质即可。我想,这大概就是红黑树存在的意义! 红黑树的节点表示代码如下: private static final boolean RED = true;private static final boolean BLACK = false;private class Node{ int key;//键 String value;//值 Node left,right;//左右子树 boolean color;}//构造函数Node(int key,String value,boolean color){ this.key=key; this.value=value; this.color=color;}//判断节点x的颜色private boolean isRed(Node x){ if(x==null) return false; return x.color==RED;}四、红黑树的创建首先,我们回顾一下2-3树的创建过程, ...

July 6, 2020 · 2 min · jiezi

腾讯T4曰想要更进一步这些技术可能你还需要精进一下

今天在某乎上看到一篇文章,说的是非科班的Java程序员该如何补充计算机基础知识,需要看哪些书? 声明:我是科班出身,大学也算努力,系统的掌握了计算机知识。迄今为止,也工作了很多年了。但老实说,无论是从在实际开发中,还是个人成长,回头看,大学学习的很多课程,其中有很多,要么“用处”不大,要么完全可以换个方式,更高效的学习。 其中的原因,一方面是实际开发更关注知识的“实用”,另一方面,我们的大学教学也确实与产业有点脱钩。 因此,本文,我就从实用出发,简单介绍下哪些领域的知识有很大的学习必要性,以及如何有重点的学习。 数据结构毫无疑问,数据结构对一名程序员来说非常重要,不是有句话说“程序 = 数据结构 + 算法”。从某个角度看,这种说法即使现在依然成立。这也说明数据结构的重要性。 但大部分的数据结构课程,关注的重点都在如何从数学上实现一个数据结构(例如堆栈、链表)。这从研究上来说,没有错。 但在实际开发中,大部分主流语言(例如Java、C#)都已经内置了常用数据结构。而且即使没有内置的,我们也常可以在第三方库中找到现成的实现。而且这些实现,大都经过实践检验,无论是稳定性还是性能都有保证。 也就是说,对大部分程序员来说,在实际开发中,很难有需求从头实现一个数据结构。因此,就完全没必要像科班生那样,从数学源头来学习数据结构,而只需做到下面几点: 熟悉常用数据结构的概念(例如数组、堆栈、链表、Map等)。了解常用数据结构不同实现的差异(例如ArrayList和LinkList的差异)。关注常用数据结构的外围算法(例如如何对List和Map进行查找)。关注数据结构使用中容易出错的地方(例如是否线程是否安全等)。… 当然也完全没必要阅读大部分的著作,而只需了解关注的重点是什么,然后再到网上搜索专题文章学习即可。至于关注的重点,可以参考《xxx面试大全》中的数据结构章节。 关注公众号:Java架构师联盟,每日更新技术好文,后台回复1000,获取资料 算法这个要具体问题具体分析了。以我接触的领域来说,大部分普通的业务系统都不会涉及到太复杂的算法,因此就没必要专门在算法上投入时间。 但在一些特殊的领域,如果算法跟不上,可能“寸步难行”,例如图形处理领域,无论是图像的变化还是增强,无一例外都要用到矩阵变换,因此就必然涉及到线性代数的内容,顺藤摸瓜,往纵深学,就必然会牵出更多的东西。 因此,对非科班生(尤其是数学不够好的),对算法学习我是持“劝退”态度的。因为,从职业发展来说,这实在是一条太“曲折”的路线。 一方面,目前的开发越来越趋专业化,算法一般由专门的算法团队负责,普通软件工程师只负责算法转换。 以我为例,虽然是科班出身,也系统的学习过算法,但也常有力有不逮的时候。复杂的算法既实现不了,甚至是理解不了。很多时候,我干脆就不做实现,直接请算法工程师告诉我思路,甚至是伪代码,而我负责转化为正式代码(例如Java)。实践证明,这种做法不仅是可行的,而且也是高效的,正所谓“术业有专攻”。 另一方面,人的精力是有限的,你完全可以把精力投入到自己更擅长的方面,例如设计、产品、架构上,从而取得“差异化”的成功。 当然,如果你“心气”很高,想去一些大厂或者想在算法行业有所精进,那么算法还是对你相当重要的 关注公众号:Java架构师联盟,每日更新技术好文,后台回复1000,获取资料 设计模式设计模式,我认为是初中级程序员,向高级程序员提升的关键点。 在实践中,我见过太多程序员,前期冲劲十足,但后继乏力,最终泯然于众生。我不敢说所有的人如此,但有不少都是吃了设计模式的亏。 在工作的前几年,大部分程序员都是处于熟悉语言层面的阶段,也就是处于“技”的阶段。这个阶段,如果人还算靠谱,大概在2到3年就会过去,接下来就要进入“术”的阶段。在编程领域,“术”的最典型代表就是“设计模式”。因此,设计模式的重要性再怎么强调都不为过。 要学习设计模式,最经典的读物依然是GOF的《设计模式:可复用面向对象软件的基础》,精炼、深刻,没有一句废话。但这本书对初学者来说,读起来太艰涩,一方面是作者极度追求语言的凝练,一方面代码是用C++描述的。因此,我推荐大家阅读《Head First设计模式》,生动有趣,而且是用Java描述的。 当然,学习设计模式,不仅要读书,更要从实践中学习。例如学习Spring框架的过程,如果你有思考,就会发现其中有太多设计模式可供借鉴。 学习设计模式,就是从实践到理论,然后再从理论到实践,反复实践,反复思索,反复总结的过程。当然,这也是从一个“码农”转变成“工程师”的过程。 关注公众号:Java架构师联盟,每日更新技术好文,后台回复1000,获取资料 软件工程实现一个软件系统的过程,不仅只有编码。还涉及到项目安排,团队协调等一系列非技术因素。而作为一名程序员,如果想往上更进一步,独当一面,成为team leader或者开发经理等管理职务。则软件工程一定要跟上。 当然,软件工程这么多年也一直在进步,从原来的瀑布开发,到现在流行的敏捷开发,但无论怎么变,有些经典的东西还是不变的。下面我就推荐几本我认为现在依然值得深读的书: 《人月神话》《人件》《Scrum敏捷软件开发》当然,关于软件工程,最好的学习方法依然是观察。观察你所在的团队、所在的公司是如何处理工程问题,然后思索,阅读,最终形成自己的方法观。 架构 & 设计写出一个好程序,有几个维度,从下到上,也是一个程序员逐步升级的过程。 第一阶段,首先要保证基本功扎实,最简单的说,要做到语法熟练、基本框架熟练,成为一个功夫精熟的“码农”。 第二阶段,从“技”到“术”,从“码农”到“工程师”。这个阶段的关键技术是设计模式。在局部上,不仅追求实现功能,更关注实现的好,关注功能之外的维度,例如健壮性、低耦合、可扩展等指标。对主流框架(例如Spring),不仅会用,更有深刻的理解。 第三阶段,从“术”到“道”。 这个阶段,不仅在局部上追求一个模块的好坏,而且还要从整个系统层面去掌控程序,例如保证整个程序不出现系统腐败,如何安排资源的优先级等。这个时候就不是单一的维度,单一的技术能够保证了。 那么,到底怎么才能成为一名架构师的,很难用一句话来回答。 按照某个学习方法,执行下去,你一定能成为一名优秀的软件工程师,但至于如何成为一名架构师,我想除了努力,运气肯定也很重要。但无论如何,有机遇,不是还得有准备嘛? 一名架构师日常的工作,所靠的肯定不是单一的维度,也不是但靠纯粹的读书能获得的。但是,有些经典书的阅读,确实给日常的工作带来了巨大的帮助,下面就是我认为开卷有益的: 关注公众号:Java架构师联盟,每日更新技术好文,后台回复1000,获取资料 最后作为一名程序员,从技术菜鸟到大拿的路径有很多,其中最核心的因素就是坚持和努力。学习的过程,就好像登山的过程,以我的经验,大概有80%的人在攀登的过程中,会因为这样那样的原因而掉队。 但无论如何,我相信,只要目标明确,努力加坚持,即使是一个非科班生,也完全可以登顶。 关注公众号:Java架构师联盟,每日更新技术好文,后台回复1000,获取资料

June 20, 2020 · 1 min · jiezi

图解并查集附赠几道-Leetcode-练手题

前言并查集是一种非常有用且高效的数据结构,千万不要被这个极具专业性的名字吓到了,它的算法思想和代码实现都非常简单,不需要花太大力气就可以轻松掌握。下面就通过画图等方式为大家介绍一下这种神奇的数据结构。 一、 图解并查集并查集有两个英文名:1、Disjoint Set,2、Union Find。它的作用就是把一个数据集分成若干个子集,每个子集内部数据可以互联互通,而子集之间则不具有连通性。并查集的底层结构类似于堆(不熟悉堆的同学赶紧去复习一下堆排序,面试频率很高哦),也是用数组描述一种树结构,但不同的是,堆是一棵独立的二叉树,并查集的树是多叉树,而且可能不止一棵树(也就是森林)。在并查集中,我们把相互独立的数据集称为连通分量,连通分量在逻辑上可以形象地描述为一棵树,每棵树都有一个根节点,其他的元素都会直接或间接指向根节点。 比如下图这个并查集,我们维护一个parent数组,每个元素初始化为对应的数组下标,代表自己是独立的一棵树,且是树根。以第一棵树为例,在后续数据处理过程中,我们把与所有与"2"同属一个连通分量的元素都连到"2"上,并把数组对应下标的元素赋值为2,其中"5"先连接到了"1"上,"1"又连接到了"2"上。最后,数组每个元素都代表其指向的父节点。 并查集底层结构知道了并查集的底层结构,那我们该如何去构建这个结构并进行查找操作呢?并查集有两个最重要的方法:union 和 find,这里先给出UnionFind 最完善的 API 框架伪代码: public class UnionFind { public UnionFind(int N) {} // 构造方法 public int find(int x) {} //查找某个元素的根节点 public void union(int x, int y) {} // 为x和y建立联系 public boolean connected(int x, int y) {} //判断x和y是否相连(在同一棵树也就是连通分量中) public int count() {} // 返回连通分量的个数,也就是多少棵树}1. find 方法实现find方法的目的是寻找某个元素所在树的根节点,代码如下: public int find(int x) { while (x != parent[x]) { x = parent[x]; } return x;}解释一下这短短几行代码做了什么,前面的介绍我们已经知道,根节点元素的数组值就是自身的下标,也就是parent[x] = x; 那么当数组元素值不等于其下标时,说明它不是根节点,就一直循环找下去,直到找到根节点。 如下图是寻找5的根节点的过程: ...

June 17, 2020 · 4 min · jiezi

旋转链表面试官你确定要让手写这个吗

前言:今天练习了一道关于单链表的算法题 《旋转链表》,由于之前写过一篇 《单链表反转?面试官你确定要问这个吗?》 的文章,然后今天又碰到了这道有关单链表的算法,就想着再 “水篇文章” 吧(带引号的哈),可以证明我没偷懒,按时写作业了。 嘿嘿 . . . . . . . . .接下来,①、首先回忆下单链表的数据结构 ;②、详解描述下什么是旋转链表(题目描述); ③、图解旋转链表代码 数据结构:1. 单链表的数据结构:单链表是一种线性结构,它是由一个个 节点(Node) 组成的。并且每个节点(Node)是由一块 数据域(data) 和一块 指针域(next) 组成的。      ①、节点(Node)结构图如下: 节点的数据域:data数据域一般就是用来存放数据的 。(注:data域需要指定类型,只能存放指定类型的数据,不能什么东西都放,是不是呀; 那代码中是怎么实现的呢? 使用 泛型 。)节点的指针域:next指针域一般就是存放的指向下一个节点的指针;这个指针其实是一个内存地址,因为Node节点对象是存放在JVM中的堆内存中,所以节点的next指针域中存放就是下一个节点在堆内存中的地址;而在代码中对象的内存地址是赋值给其引用变量了,所以指针域中存放的是下一个节点对象的引用变量。 ②、单链表结构图如下:(下图是由三个节点构成的单链表) 若有所思,en en en . . . . . . 好像单链表的知识在脑海中清晰了些呀;那要不我们快马加鞭,赶紧把单链表的数据结构代码弄出来,然后再思索下怎么实现旋转操作, en en en. . . .. . . 嘿嘿! 2. 节点类Node代码:创建Node节点类,节点类中并且额外提供了两个方法(单链表的创建方法、单链表的遍历打印方法);注意:单链表的创建方法 createLinkedList( ):Node节点的插入方式为 尾插法, 其实还有 头插法 方式; 扩展:链表中节点的插入方式还在 HashMap 中使用到了,在 JDK 1.7 时是头插法,JDK 1.8时是尾插法; ...

June 7, 2020 · 3 min · jiezi