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++; // 字母表指针后移
}
}
// 根据生成的哈希表解密message
StringBuilder 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) 再加1
if(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,以键为数组第一个元素,值为数组第二个元素
*/
// 创建一个Map
Map<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;
}