当前位置: 首页 > news >正文

Leetcode - 周赛448

目录

  • 一,3536. 两个数字的最大乘积
  • 二,3537. 填充特殊网格
    • 方法一:递归
    • 方法二:循环
  • 三,3538. 合并得到最小旅行时间
  • 四,3539. 魔法序列的数组乘积之和

一,3536. 两个数字的最大乘积

题目列表
在这里插入图片描述
本题就是求最大值和次大值的乘积,代码如下:

class Solution {public int maxProduct(int n) {int[] mx = new int[2];while(n > 0){int x = n % 10;if(mx[0] < x){mx[1] = mx[0];mx[0] = x;}else if(mx[1] < x){mx[1] = x;}n /= 10;}return mx[0] * mx[1];}
}

二,3537. 填充特殊网格

题目列表
在这里插入图片描述
本题将一个 2 n ∗ 2 n 2^n * 2^n 2n2n 的网格,不断均等的分成四个部分,且四个部分都有要求:

  • 右上角象限中的所有数字都小于右下角象限中的所有数字
  • 右下角象限中的所有数字都小于左下角象限中的所有数字
  • 左下角象限中的所有数字都小于左上角象限中的所有数字
  • 每个象限也都是一个特殊网格

方法一:递归

可以发现这就是递归,不断的将 2 n ∗ 2 n 2^n * 2^n 2n2n 的网格分成 2 n − 1 ∗ 2 n − 1 2^{n-1} * 2^{n-1} 2n12n1 的网格,即将原问题变成子问题来解决。根据上述条件,我们的计算顺序:右上 — 右下 —— 左下 —— 左上.

代码如下:

class Solution {public int[][] specialGrid(int n) {int[][] ans = new int[1<<n][1<<n];dfs(0, 1<<n, 0, 1<<n, ans);return ans;}int k = 0;void dfs(int l, int r, int u, int d, int[][] ans){if(r - l == 1){ans[u][l] = k++;return;}int m = (r - l) / 2;dfs(l+m, r, u, d-m, ans); // 右上dfs(l+m, r, u+m, d, ans); // 右下dfs(l, r-m, u+m, d, ans); // 左下dfs(l, r-m, u, d-m, ans); // 左上}
}

方法二:循环

在这里插入图片描述

代码如下:

class Solution {public int[][] specialGrid(int n) {int[][] ans = new int[1<<n][1<<n];for(int k = 0; k < n; k++) {for(int i = 0; i < (1 << k); i++) {for(int j = (1 << n) - (1 << k); j < (1 << n); j++) {ans[i+(1<<k)][j] = ans[i][j] + (1 << (2*k));ans[i+(1<<k)][j-(1<<k)] = ans[i+(1<<k)][j] + (1 << (2*k));ans[i][j-(1<<k)] = ans[i+(1<<k)][j-(1<<k)] + (1 << (2*k));}}}return ans;}
}

三,3538. 合并得到最小旅行时间

题目列表
在这里插入图片描述
本题大致意思:将长度为 l 的公里分成 n 段,position[i]:表示路标的位置(从0到i的总距离),可以将这 n 段公路进行合并(相邻合并),问恰好k次合并后从0-l的最小旅行时间。time[i]:表示从position[i]到position[i+1]每公里所需时间

这么大致一看,就是划分型DP,不过有一点需要注意,它们的时间合并与路段合并的方式并不相同,举个例子:

  • position:[0,3,8,10],time = [5,8,3,6]
  • 不合并,它的最终结果是(3 - 0) * 5 + (8 - 5) * 8 + (10 - 8) * 3
  • 合并1,2下标的路段后,position:[0,8,10],time = [5,11,6]
  • 它的结果是(8 - 0) * 5 + (10 - 8) * 11
  • 可以发现,合并前它第一个段路每公里所需时间是 5,合并后它第一个段路每公里所需时间依然是 5,也就是合并时间只会对后面的路段造成影响。(这点在状态转移时非常重要!!!!)

由于需要在O(1)的时间求出每段路径的速度,所以需要先对time进行前缀和的处理,定义dfs(i,j,k):[i,n-1] 这段路恰好分成 k 段所需的最短旅行时间,对于当前所在的position[i] 路标来说,枚举下一段路的终点 position[nxt]

  • positon[i] 到 position[nxt] 所消耗的时间 (pre[i+1] - pre[j]) * (position[nxt] - position[i])
  • 下一个状态就变成 dfs(nxt,?,?)
    • 上面说了合并时间只会对后面的路段造成影响,所以j -> i+1(因为前缀和的原因所以+1)
    • k -> k - (nxt - i - 1)[i,nxt] 说明原本有 nxt-i 段路,直接合并成了一段,所以 k - (nxt - i - 1)
  • 转移方程 dfs(i,j,k) = min(dfs(nxt, i+1, k-(nxt-i-1)))

代码如下:


// dfs 写法
class Solution {public int minTravelTime(int l, int n, int k, int[] position, int[] time) {int[] pre = new int[n+1];for(int i = 0; i < n; i++){pre[i+1] = pre[i] + time[i];}memo = new int[n-1][n-1][k+1];return dfs(0, 0, k, pre, position);}int[][][] memo;int dfs(int i, int j, int leftK, int[] pre, int[] position){int n = position.length;if(i == n - 1) return leftK == 0 ? 0 : Integer.MAX_VALUE/2;if(memo[i][j][leftK] > 0) return memo[i][j][leftK];int t = pre[i+1] - pre[j];int res = Integer.MAX_VALUE / 2;for(int nxt = i + 1; nxt < Math.min(n, leftK + i + 2); nxt++){res = Math.min(res, dfs(nxt, i+1, leftK-(nxt-i-1), pre, position) + t * (position[nxt] - position[i]));}return memo[i][j][leftK] = res;}
}// 递推写法
class Solution {public int minTravelTime(int l, int n, int k, int[] position, int[] time) {int[] pre = new int[n+1];for(int i = 0; i < n; i++){pre[i+1] = pre[i] + time[i];}int[][][] f = new int[n][n][k+1];for(int[][] a : f){for(int[] b : a){Arrays.fill(b, Integer.MAX_VALUE / 2);}}for(int j = 0; j < n; j++){f[n-1][j][0] = 0;}for(int i = n - 2; i >= 0; i--){for(int j = 0; j <= i; j++){int t = pre[i + 1] - pre[j];for(int leftK = 0; leftK < k + 1; leftK++){for(int nxt = i + 1; nxt < Math.min(n, leftK + i + 2); nxt++){f[i][j][leftK] = Math.min(f[i][j][leftK], f[nxt][i+1][leftK-(nxt-i-1)] + t * (position[nxt] - position[i]));}}}}return f[0][0][k];}
}

四,3539. 魔法序列的数组乘积之和

题目列表
在这里插入图片描述
本题题意:从 nums 数组中选择 m 个数(可重复选),且要求 s u m ( 2 i ) sum(2^{i}) sum(2i)(i 表示nums的下标)二进制形式中1的个数为 k,求所有可能组合的 nums[i] 的乘积之和。

  • 假设 m = 10, nums = [2,9],如果选择了 4 个 2,6 个 9 这种选法合法,那么它有 10 ! 4 ! 6 ! \frac{10!}{4!6!} 4!6!10! 种排列组合,这种选择的贡献就是 10 ! 4 ! 6 ! ∗ n u m s [ 0 ] 4 ∗ n u m s [ 1 ] 6 \frac{10!}{4!6!}*nums[0]^4*nums[1]^6 4!6!10!nums[0]4nums[1]6,将它的形式稍作变换 10 ! ∗ n u m s [ 0 ] 4 4 ! ∗ n u m s [ 1 ] 6 6 ! 10!*\frac{nums[0]^4}{4!}*\frac{nums[1]^6}{6!} 10!4!nums[0]46!nums[1]6,也就是说对于每个nums[i],可以将它单独拆分成 n u m s [ i ] j j ! \frac{nums[i]^j}{j!} j!nums[i]j 的形式来计算,最终再乘以 m!
  • 此时可以使用 d f s dfs dfs 来计算,它的参数一定包含
    • i: nums的下标
    • m: 剩余的选择次数
    • s: sum(2^i),用来计算二进制中1的个数是否为 k,但是这么计算的话可能会超出long的数据范围,所以不能将它作为参数
      • 从二进制的加法性质出发,可以发现,低位的二进制先进行加法计算的话,它只会影响高位的二进制运算结果,对于低于它的二进制运算结果没有任何影响。也就是说,我们只需要记录 s>>i 就行,不过这样记录的话就还需要额外使用一个参数leftK,来记录还需要有几个1
  • 定义 dfs(i,m,s,leftK): 在上述情况下,剩余元素的贡献值
  • 对于 nums[i] 来说,枚举选择 j = 0,1,2,...,mnums[i],接下来问题变成了
    • 当前下标为 i+1
    • 剩余可以选择的次数为 m - j
    • ( S + j ∗ 2 i ) > > ( i + 1 ) = > ( s + j ) > > 1 (S + j * 2^i) >> (i+1) => (s + j) >> 1 (S+j2i)>>(i+1)=>(s+j)>>1S 表示 [0,i] 的总和
    • 剩余二进制 1 的个数为 leftK - (s + j) & 1
    • dfs(i+1,m-j,(s+1)>>1,leftK-(s+j)&1)
  • d f s ( i , m , s , l e f t K ) = ∑ 0 m d f s ( i + 1 , m − j , ( s + j ) > > 1 , l e f t K − ( s + j ) m o d 2 ) ∗ n u m s [ i ] j j ! dfs(i,m,s,leftK) = \sum_{0}^{m} dfs(i+1,m-j,(s+j)>>1,leftK-(s+j)mod2) * \frac{nums[i]^j}{j!} dfs(i,m,s,leftK)=0mdfs(i+1,mj,(s+j)>>1,leftK(s+j)mod2)j!nums[i]j
  • 答案为 d f s ( 0 , m , 0 , k ) ∗ m ! dfs(0,m,0,k)*m! dfs(0,m,0,k)m!

代码如下:

class Solution {private static final int MOD = 1_000_000_007;private static final int MX = 31;private static final long[] fac = new long[MX]; // n!private static final long[] inv_f = new long[MX]; // 1/n!// 预处理static {fac[0] = 1;for(int i = 1; i < MX; i++){fac[i] = fac[i-1] * i % MOD;}// 1/n! * n = 1/(n-1)!inv_f[MX-1] = (int)pow(fac[MX-1], MOD-2);for(int i = MX - 2; i >= 0; i--){inv_f[i] = inv_f[i+1] * (i+1) % MOD;}}static long pow(long x, int y){long res = 1;while(y > 0){if(y % 2 == 1){res = res * x % MOD;}x = x * x % MOD;y /= 2;}return res;}public int magicalSum(int m, int k, int[] nums) {int n = nums.length;int[][] powV = new int[n][m+1];// nums[i]^jfor(int i = 0; i < n; i++){powV[i][0] = 1;for(int j = 1; j <= m; j++){powV[i][j] = (int)((long)powV[i][j-1] * nums[i] % MOD);}}memo = new long[n][m+1][k+1][m/2+1];for(long[][][] a : memo){for(long[][] b : a){for(long[] c : b){Arrays.fill(c, -1L);}}}return (int)(dfs(0, m, 0, k, powV) * fac[m] % MOD);}long[][][][] memo;long dfs(int i, int m, int s, int k, int[][] powV){int cnt = Integer.bitCount(s);if(cnt + m < k) return 0; // 之后没有 k 个 1,直接返回 0 if(i == powV.length) return m == 0 && cnt == k ? 1 : 0; // 选了 m 个数且 1 的个数为 k,返回 1;否则返回 0if(memo[i][m][k][s] != -1) return memo[i][m][k][s];long res = 0;for(int j = 0; j <= m; j++){int bit = (s + j) & 1; // 计算当前二进制位为0/1if(k >= bit)res = (res + dfs(i+1, m-j, (s+j)>>1, k-bit, powV) * powV[i][j] % MOD * inv_f[j] % MOD) % MOD;}return memo[i][m][k][s] = res;}
}

相关文章:

  • PostgreSQL数据库的array类型
  • 密码工具类-生成随机密码校验密码强度是否满足要求
  • GPS定位方案
  • 使用阿里AI的API接口实现图片内容提取功能
  • three.js通过GEO数据生成3D地图
  • 2025年5月HCIP题库(带解析)
  • 基于计算机视觉的试卷答题区表格识别与提取技术
  • js var a=如果ForRemove=true,是“normal“,否则为“bold“
  • 网页版部署MySQL + Qwen3-0.5B + Flask + Dify 工作流部署指南
  • 自定义SpringBoot Starter-笔记
  • 当K8S容器没有bash时高阶排查手段
  • Github上如何准确地搜索开源项目
  • (二)毛子整洁架构(CQRS/Dapper/DomianEvent Handler)
  • 8.软考高项(信息系统项目管理师)-沟通管理
  • 作为主动唤醒的节点,ECU上电如何请求通讯
  • String、StringBuilder、StringBuffer的区别
  • 翻转二叉树(简单)
  • 使用原生javascript手动实现一个可选链运算符
  • 牛客——暴力、技巧、字符与数组的使用(强强联合、字符数量)
  • 【工具】解析URL获取实际图片地址下载原始FFHQ图像
  • 百济首次实现季度营业利润扭亏,泽布替尼销售额近57亿元
  • 老人刷老年卡乘车被要求站着?公交公司致歉:涉事司机停职
  • 综合治理食品添加剂滥用问题,国务院食安办等六部门联合出手
  • 退休11年后,71岁四川厅官杨家卷被查
  • 南方地区强降雨或致部分河流发生超警洪水,水利部部署防范
  • 黔西游船倾覆事故84名落水人员已全部找到,10人不幸遇难