四个题目难度分别为简单、中等、中等、困难;解法不一定最优,能AC,欢迎交流。
5742.将句子排序
题意:有一个类似“is2 sentence4 This1 a3”,长度不超过200的字符串,单词后面跟着数字,单词用空格隔开,要求按数字升序重新拼接字符串并去掉数字,变成“This is a sentence”。
思路:单词数不超过9,只需要1位来表示单词应该存在的位置;按空格分割字符串,提取数字放到新单词数组的指定位置,最后拼接时去掉数字。
/** 5742.将句子排序 * 按空格分割字符串,提取数字放到新单词数组的指定位置,最后拼接时去掉数字。 */ public String sortSentence(String s) { //分割 String[] a=s.split(" "); int n=a.length; String[] ans=new String[n]; //每个字符串的最后一个是数字,落到新字符串中 for(int i=0;i<n;i++){ int idx=a[i].charAt(a[i].length()-1)-'0'; ans[idx-1]=a[i]; } //拼接排序后的字符串,注意中间的空格数比单词数少1 String res=""; for(int i=0;i<n;i++){ if(i==0) res=res+ans[i].substring(0,ans[i].length()-1); else res=res+" "+ans[i].substring(0,ans[i].length()-1); } return res; }
5743. 增长的内存泄露
题意:有两个内存条,剩余内存分别为m1和m2;现有一个程序在消耗内存,第i秒消耗i内存;每秒钟,如果两个内存条剩余内存大者被程序消耗,相同则消耗第一个内存条;两个内存条的剩余内存都不满足程序消耗则程序奔溃;问程序何时奔溃;
思路:模拟程序运行的消耗,按条件减少内存条的剩余内存,不满足条件时返回;
/** 5743. 增长的内存泄露 * 模拟程序运行的消耗,按条件减少内存条的剩余内存,不满足条件时返回; */ public static int[] memLeak(int m1, int m2) { int[] res=new int[3]; int t=1; //需要占用的内存比两个内存条都大,则程序奔溃 while(!(t>m1 && t>m2)){ if(m1>=m2) m1-=t; else m2-=t; t++; } res[0]=t; res[1]=m1; res[2]=m2; return res; }
5744. 旋转盒子
题意:现有m*n的字符矩阵,字符代码石头、固定物、空位置;将矩阵顺时针旋转90°,石头如果悬空会因为惯性落下;求最后的字符矩阵;
思路:
1.不管三七二十一,先顺时针旋转90°;
2.对于每一列,从下往上遍历,找到两个固定物之间的信息,固定物包含地底和天花板;
3.计算上下固定物之间的石头数,从固定物下方堆砌石头,剩余的空间用空位置填充;
4.注意点:地底和天花板作为固定物的表示
时空复杂度都为(n*m)
/** 5744. 旋转盒子 * */ public char[][] rotateTheBox(char[][] box) { int n=box.length,m=box[0].length; char[][] res=new char[m][n]; //正常旋转90 for(int i=0;i<n;i++){ for(int j=0;j<m;j++) res[j][n-1-i]=box[i][j]; } n=res.length; m=res[0].length; //石头落下去,每一列从下往上,分为4种情况[地底,障碍物][地底,天上][障碍物,障碍物][障碍物,天上] for(int j=0;j<m;j++){ //up表示上方固定物,down表示下方固定物,初始化为天花板和地底,num表示(up,down)之间的石头数 int up=-1,down=n,num=0; for(int i=n-1;i>=0;i--){ if(res[i][j]=='#'){//石头 num++; }else if(res[i][j]=='*'){//固定物 if(num==0){ //由于是从下往上,所以固定物先确定下方 down=i; }else { //上下固定物区间确定了,开始石头掉落的过程模拟 up=i; change(res,up,down,num,j); //清空石头数,并更新下方固定物位置 num=0; down=i; } } } //上方固定物为天花板 if(num>0){ up=-1; change(res,up,down,num,j); } } return res; } //j列中(up,down)之间有num块石头,从下往上堆,剩余空间用空位置填充 public static void change(char[][] res,int up,int down,int num,int j){ for(int i=down-1;i>up;i--){ if(num>0){ res[i][j]='#'; num--; }else{ res[i][j]='.'; } } }
5212. 向下取整数对和
题意:有长度为100000的数组,返回对所有下标0<=i,j<n的floor(a[i]/a[j])结果之和,答案对1e9+7求模;floor()函数返回整数部分;
样例:
输入[2,5,9] 输出10
floor(2 / 5) = floor(2 / 9) = floor(5 / 9) = 0 floor(2 / 2) = floor(5 / 5) = floor(9 / 9) = 1 floor(5 / 2) = 2 floor(9 / 2) = 4 floor(9 / 5) = 1
(0+1+2+4+1)%(1000000007)=10;
思路:
1.确定方向
对于floor(x/y)函数,假设y一定,则有多个x使得floor(x/y)函数值相同;例如y=9,x∈[9,17]时函数值都是1,x∈[18,26]时函数值都是2, x∈[27,35]时函数值都是3;显然需要进行区间计数,线段树、树状数组、前缀和是常规操作,这里选择前缀和即可;
2.看数据范围
数组长度和元素大小都是105,可以直接开大数组num;
先计数:num[i]表示元素i的个数;
后变成前缀和数组:num[i]表示[0,i]的元素个数总和,通过num[i]-num[i-1]则表示元素i的个数;
这里最大元素maxx作为数组长度,不需要每次都开满数组长度为105,节省空间;
3.倍数关系
对于元素i,每次找一段区间内的元素总个数,计算函数值之和;
倍数区间形如[i,i×2-1]、[i×2,i×3-1]、[i×3,i×4-1] ... [i×(j-1),i×j-1];
倍数×区间内的元素总个数 = 元素i在该段区间的函数值总和;
元素i的个数×倍数×区间内的元素总个数 = 所有i在该段区间的函数值总和;
再对多段区间进行累加即可;
4.越界情况
极限数据105×105=1010会导致int溢出,计算过程用long变量,最后转int;
当i*j>maxx时直接使用i*j作为数组下标会使得数组越界;
/** 5212. 向下取整数对和 * 求所有floor(a[i] / a[j])之和 * 前缀和 */ public int sumOfFlooredPairs(int[] a) { long res=0,p=1000000007; int n=a.length,maxx=0; for(int i=0;i<n;i++){ maxx=Math.max(maxx,a[i]); } int[] num=new int[maxx+1]; //计数 for(int i=0;i<n;i++) num[ a[i] ]++; //前缀和 for(int i=1;i<=maxx;i++) num[i]+=num[i-1]; for(int i=1;i<=maxx;i++){ //x表示数字i的个数 long x=num[i]-num[i-1]; if(x==0) continue; //找区间,[i,i*2-1]、[i*2,i*3-1]、[i*3,i*4-1],每次+i for(int j=i;j<=maxx;j=j+i){ //y表示区间的个数,如果j+i-1>maxx则取maxx即可,防止数组下标越界 long y=num[Math.min(j+i-1,maxx)]-num[j-1]; res=(res+(j/i)*y*x)%p; } } return (int)res; }