2023年2月期每日一题
- 第一天 (2325. 解密消息)
- 第十六天(2341. 数组能形成多少数对)
- 第十七天 (1139. 最大的以 1 为边界的正方形)
- 第十八天 (1237. 找出给定方程的正整数解)
- 第十九天 (1792. 最大平均通过率)
- 第二十天(2347. 最好的扑克手牌)
- 第二十一天 (1326. 灌溉花园的最少水龙头数目)
- 第二十四天(2357. 使数组中所有元素都等于零)
- 第二十五天 (1247. 交换字符使得字符串相同)
- 第二十七天 (1144. 递减元素使数组呈锯齿状)
- 第二十八天 (2363. 合并相似的物品)
第一天 (2325. 解密消息)
问题
代码
class Solution {public String decodeMessage(String key, String message) {/** 根据key串出现的字符顺序 对应 字母表 生成对应的哈希表将明文根据哈希表解密*/char cur = 'a';Map<Character, Character> rules = new HashMap<Character, Character>(); // 用于保存根据key生成的对应关系for(int i=0; i<key.length(); i++) {// 生成哈希表char c = key.charAt(i); // 拿到对应的字符if(c!=' ' && !rules.containsKey(c)) {// 当前字符未对应字母表的字符时rules.put(c, cur);cur++; // 字母表指针后移}}// 根据生成的哈希表解密messageStringBuilder jm = new StringBuilder();for(int i=0; i<message.length(); i++) {char c = message.charAt(i);if(c!=' ') {// c不等于空格时,获取解密后的字符c = rules.get(c);}jm.append(c);}* return jm.toString();}
}
第十六天(2341. 数组能形成多少数对)
问题
代码
class Solution {public int[] numberOfPairs(int[] nums) {/** 可利用哈希表存储遍历到的每个数作为键位, 当某键位值为2时 */Map<Integer, Integer> cnt = new HashMap<Integer, Integer>(); // 用于记录每个数字出现的次数int count = 0; // 用于记录出现成对的数字次数for(int num:nums) {cnt.put(num, cnt.getOrDefault(num,0) + 1); // 当该键位已有时获取(没有时获取0) 再加1if(cnt.get(num) % 2 == 0) {// 成对出现一定为偶数count++;}}return new int[] {count, nums.length - count*2};}
}
第十七天 (1139. 最大的以 1 为边界的正方形)
问题
思想
/**
做题思想:
利用前缀和的方法来做,因为使用0/1来表示是否链接, 所以当(0,0)到(0,5)这个横线连通的话前缀和就是5了
利用两个二维度的数组, 分别用来计算 从左侧(i,0)到右侧(i,j)的前缀和,以及从上侧(0,j)到下侧(i,j)的前缀和
然后用一个二重循环分别测试以(i,j)点为左上点坐标时,
*/
代码
class Solution {public int largest1BorderedSquare(int[][] grid) {/** 做题思想: 利用前缀和的方法来做,因为使用0/1来表示是否链接, 所以当(0,0)到(0,5)这个横线连通的话前缀和就是5了利用两个二维度的数组, 分别用来计算 从左侧(i,0)到右侧(i,j)的前缀和,以及从上侧(0,j)到下侧(i,j)的前缀和然后用一个二重循环分别测试以(i,j)点为左上点坐标时,*/int m=grid.length, n = grid[0].length; // m表示行,n表示列int maxEdge = Math.max(m,n); // 最大的长度边为mxn的较小一个int [][] rowSum = new int [m][n+1]; // 横向统计前缀和int [][] colSum = new int [m+1][n]; // 竖向统计前缀和for(int i=0; i<m; i++) {for(int j=0; j<n; j++) {rowSum[i][j+1] = rowSum[i][j]+grid[i][j]; // 横向计算前缀和colSum[i+1][j] = colSum[i][j]+grid[i][j]; // 竖向计算前缀和}}int ans = 0; // 用于记录最大的正方形边界// 遍历判断,以(i,j)为左上角点 找最大的正方形for(int i=0; i<m; i++) {for(int j=0; j<n; j++) {for(int edge=ans+1; edge<=maxEdge; edge++) {// ans为记录当前探测到的最大的正方形长度边, 又因为又用1表示连接// 所以edge从ans+1开始表示用(i+edge-1,j)减去(i,j)位置前缀,可得出这两点间的实际距离,如果不等于edge,说明中间不连接if(i+edge-1>=m || j+edge-1>=n) {// 说明越界了break;}int left = colSum[i+edge][j]-colSum[i][j]; // 左侧边长度int top = rowSum[i][j+edge] - rowSum[i][j]; // 上侧边长度if(left!=edge || top!=left) {// 假如 左侧或者上侧 长度不等于edge, 说明不连续break;}int right = colSum[i+edge][j+edge-1] - colSum[i][j+edge-1]; // 右侧边长度int bottom = rowSum[i+edge-1][j+edge] - rowSum[i+edge-1][j]; // 底侧边长度if(right==edge && bottom==edge) {// 底侧与右侧边也要等于edge时,说明又找到更大的正方形了ans=edge;}}}}return ans*ans; // ans为边,以ans为边的正方形包括元素为ans的平方个}
}
第十八天 (1237. 找出给定方程的正整数解)
问题
代码
class Solution {public List<List<Integer>> findSolution(CustomFunction customfunction, int z) {/** 通过二分查找, 调用给定函数,添加符合题意得数对 */List<List<Integer>> res = new ArrayList<List<Integer>>();for(int x=1; x<1000; x++) {int yleft = 1, yright = 1000;while(yleft <= yright) {int ymid = (yleft + yright) / 2;if(customfunction.f(x,ymid) == z) {// 找到符合规定得数List<Integer> pair = new ArrayList<Integer>();pair.add(x);pair.add(ymid);res.add(pair);}if(customfunction.f(x,ymid)>z) {// 说明ymid大了,调整高位yright = ymid-1;}else {// 说明ymid低了,调整低位yleft = ymid + 1;} }}return res;}}
第十九天 (1792. 最大平均通过率)
问题
思想·
代码
class Solution {public double maxAverageRatio(int[][] classes, int extraStudents) {PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a,b)->{ //PriorityQueue每次会取出优先级最小的long val1 = (long)(b[1]+1)*b[1]*(a[1]-a[0]);long val2 = (long)(a[1]+1)*a[1]*(b[1]-b[0]);if(val1 == val2) {return 0;}return val1<val2?1:-1;});for(int[] c:classes) {pq.offer(new int[] {c[0], c[1]});}for(int i=0; i<extraStudents; i++) {// 将给定必过的学生分配给权值最小的班级int[] arr = pq.poll();int pass = arr[0], total = arr[1];pq.offer(new int[] {pass+1, total+1});}double res = 0;// 计算最大平均通过率for(int i=0; i<classes.length; i++) {int[] arr = pq.poll();int pass = arr[0], total = arr[1];res += 1.0 * pass / total;}return res/classes.length;}
}
第二十天(2347. 最好的扑克手牌)
问题
代码
class Solution {public String bestHand(int[] ranks, char[] suits) {/**根据 题目规则编写函数即可*/Set<Character> suitsSet = new HashSet<Character>();for(char suit : suits) {// 遍历一遍花色,加入到集合中suitsSet.add(suit);}// 因为集合相同得元素只能保存一次if(suitsSet.size() == 1) {return "Flush"; // 当花色相同时,size为1}// 用于保存出现数字的个数Map<Integer,Integer> ranksMap = new HashMap<>();for(int rank : ranks) {ranksMap.put(rank, ranksMap.getOrDefault(rank, 0) + 1);}// 假如有五个数字都不一样if(ranksMap.size()==5) {return "High Card";}// 判断是否有三张花色以上相同的for(Map.Entry<Integer,Integer> entry : ranksMap.entrySet()) {if(entry.getValue()>2) {return "Three of a Kind";}}// 不满足上面的情况,只能是两张数字一样了return "Pair";}
}
第二十一天 (1326. 灌溉花园的最少水龙头数目)
问题
思想
/**
思想:
可将本题看成将没个i处可覆盖的区域为一个小区间, 求最小覆盖的区间数
*/
代码
class Solution {public int minTaps(int n, int[] ranges) {/**思想:可将本题看成将没个i处可覆盖的区域为一个小区间, 求最小覆盖的区间数*/int [][] intervals = new int[n+1][]; // 创建保存每个i可覆盖的区间for(int i=0; i<=n; i++) {// 将这些小区间保存为一些数组// 确保这些区间不会超过(0-n)这个数值范围int start= Math.max(0, i-ranges[i]); // 区间开头int end = Math.min(n,i+ranges[i]); // 区间结尾intervals[i] = new int[] {start, end};}// 将每个小区间的开始端按升序排序Arrays.sort(intervals, (a,b) -> a[0]-b[0]);int[] dp = new int[n+1]; // 创建动态规划dp,记录到第i个区间覆盖[0-i]的最小区间数Arrays.fill(dp, Integer.MAX_VALUE);dp[0] = 0;for(int [] interval : intervals) {int start = interval[0], end = interval[1];if(dp[start] == Integer.MAX_VALUE) {return -1; // 当dp[start] 等于 int上限时,说明当前区间覆盖不完整个区域}for(int j=start; j<=end; j++) {dp[j] = Math.min(dp[j], dp[start]+1);}}return dp[n];}
}
第二十四天(2357. 使数组中所有元素都等于零)
问题
代码
class Solution {public int minimumOperations(int[] nums) {/** 思想:读懂题目后,会发现,每次都会减去最小的操作数因此操作数不同的个数,就是要减去最少的非零元素(注意不要统计0)*/int ans = 0;// 用于保存值HashSet<Integer> hashSet = new HashSet<>();for(int num : nums) {if(!hashSet.contains(num) && num != 0) {// 只有当前遍历到的值不在set中,并且不为零,才统计ans++;}hashSet.add(num);}return ans;}
}
第二十五天 (1247. 交换字符使得字符串相同)
问题
思想
/**
思想:
出现交换的情况无非 s1为x,s2为y (称之为xy)或者s1为y,s2为x(称之为yx)
我们记录这些不对称的情况, 可以通过交换去消除
消除时如图所示,可通过一次交换,消除 两次xy情况 “或者” 消除两次yx情况
也可以通过两次交换消除 一次xy情况与一次yx情况
因此。xy情况加yx情况一定是偶数次才有可能通过交换换成两个相同字符串
*/
代码
class Solution {public int minimumSwap(String s1, String s2) {/**思想:出现交换的情况无非 s1为x,s2为y (称之为xy)或者s1为y,s2为x(称之为yx)我们记录这些不对称的情况, 可以通过交换去消除消除时如图所示,可通过一次交换,消除 两次xy情况 “或者” 消除两次yx情况也可以通过两次交换消除 一次xy情况与一次yx情况因此。xy情况加yx情况一定是偶数次才有可能通过交换换成两个相同字符串*/int xy=0,yx=0; // 记录对应位置情况的个数int n = s1.length(); // 字符串长度相等for(int i=0; i<n; i++) {char a = s1.charAt(i), b = s2.charAt(i); // 获取两个字符串相同位置i的字符if(a=='x' && b=='y') { // xy情况xy++;}if(a=='y' && b=='x') { // yx情况yx++;}}if((xy+yx)%2==1) { // 交换次数为奇数时,换不成一样的串return -1;}/** xy/2或者yx/ 2 是一次交换消除两次不对称的情况; xy%2或者yx%2 一定是最后两次交换消两种情况各一次的时候 */return xy/2 + yx/2 + xy%2 + yx%2; }
}
第二十七天 (1144. 递减元素使数组呈锯齿状)
问题
思想
/**
思想:
变成锯齿状, 无非就是将偶数下标处或者奇数下标处变成小于两侧的值
注意,小于两侧时,只需要小于两侧较小的数即可。
换句话的意思就是 当前值分别减两侧值,得到最大的数+1即可,
当当前值已经小于两侧时, 减去两侧值时就是负数,初始记录res=0,则取大值就是0
*/
代码
class Solution {public int movesToMakeZigzag(int[] nums) {/** 思想:变成锯齿状, 无非就是将偶数下标处或者奇数下标处变成小于两侧的值注意,小于两侧时,只需要小于两侧较小的数即可。换句话的意思就是 当前值分别减两侧值,得到最大的数+1即可, 当当前值已经小于两侧时, 减去两侧值时就是负数,初始记录res=0,则取大值就是0*/int res0 = 0, res1 = 0;for(int i=0; i<nums.length; i++) {int a = 0; // 临时计算减去数if(i>0) { // i>0就是满足与左侧比较的条件a = Math.max(a, nums[i] - nums[i-1] + 1);}if(i<nums.length-1) { // 满足与右侧比较的条件a = Math.max(a, nums[i] - nums[i+1] + 1);}if(i%2==0) { // 偶数为低位res0+=a;}else { // 偶数为低位res1+=a;}}return res0>res1?res1:res0; // 返回小的}
}
第二十八天 (2363. 合并相似的物品)
问题
思想
/**
思想: 遍历两个价值重量列表,将相同价值的作为键重量和为值保存到map中
再遍历map,以键为数组第一个元素,值为数组第二个元素
*/
代码
class Solution {public List<List<Integer>> mergeSimilarItems(int[][] items1, int[][] items2) {/** 思想: 遍历两个价值重量列表,将相同价值的作为键重量和为值保存到map中再遍历map,以键为数组第一个元素,值为数组第二个元素*/// 创建一个MapMap<Integer, Integer> hashMap = new HashMap<>(); // 用于记录价值-重量和for(int i=0; i<items1.length; i++) { // 统计第一个二维数组的重量和hashMap.put(items1[i][0], hashMap.getOrDefault(items1[i][0], 0) + items1[i][1]);}for(int i=0; i<items2.length; i++) { // 统计第二个二维数组的重量和hashMap.put(items2[i][0], hashMap.getOrDefault(items2[i][0], 0) + items2[i][1]);}// 创建保存返回值的二维列表List<List<Integer>> ret = new ArrayList<List<Integer>>();//遍历map,将[键,值]加入二维列表for(Map.Entry<Integer, Integer> entry : hashMap.entrySet()) {List<Integer> listT = new ArrayList<>();// 将键值加入小集合listT.add(entry.getKey());listT.add(entry.getValue());// 将小集合加入大集合ret.add(listT);} // 排序!Collections.sort(ret, (a,b)->a.get(0)-b.get(0));return ret;}