目录
一,3222. 求出硬币游戏的赢家
二,3223. 操作后字符串的最短长度
三,3224. 使差值相等的最少数组改动次数
四,3225. 网格图操作后的最大分数
一,3222. 求出硬币游戏的赢家
本题就是一道模拟题,每个人每次只能拿走1枚价值为75的硬币和4枚价值为10的硬币,代码如下:
class Solution {
public String losingPlayer(int x, int y) {
int t = -1;
while(x >= 1 && y >= 4){
x -= 1;
y -= 4;
t *= -1;
}
return t==-1?"Bob":"Alice";
}
}
//进阶版本
class Solution {
public String losingPlayer(int x, int y) {
int min = Math.min(x, y/4);
return min%2==0?"Bob":"Alice";
}
}
二,3223. 操作后字符串的最短长度
本题求字符串s的最短长度,我们可以先统计每个字符出现的次数,然后暴力,计算出每一个字符最终剩下的次数。题目中所说的删除,实际上是说,如果一个字符出现次数 >= 3,那么可以将其减去2,直到该字符出现次数 < 3。
代码如下:
class Solution {
public int minimumLength(String s) {
int[] cnt = new int[26];
for(char c : s.toCharArray()){
cnt[c-'a']++;
}
int ans = 0;
for(int x : cnt){
while(x >= 3){
x -= 2;
}
ans += x;
}
return ans;
}
}
//简化循环版本
class Solution {
public int minimumLength(String s) {
int[] cnt = new int[26];
for(char c : s.toCharArray()){
cnt[c-'a']++;
}
int ans = 0;
for(int x : cnt){
if(x > 0)
ans += x%2==0?2:1;
}
return ans;
}
}
三,3224. 使差值相等的最少数组改动次数
本题如果使用暴力的话,我们可以枚举X的值,再求出将每个差值变成X所需要的最小操作次数,需要知道对于每对(nums[i],nums[n-i-1]),它们什么时候需要操作0次,1次,2次。分情况讨论,(令 p = nums[i],q = nums[n-i-1] 且 p < q):
- q - p = X,操作 0 次
- q - p > X,操作 1 次
- q - p < X && max(q,k-p) >= X,操作 1 次
- q - p < X && max(q,k-p) < X,操作 2 次
注:max(q,k-p)表示如果只修改1次,那么X的值只能在[0,max(q,k-p)],否则必须要修改2次
为了简化时间复杂度,我们可以进行预处理,使得在枚举X时可以直接得出对于所有(p,q)的差值变成X需要操作的次数。这里有两种做法:
- 可以使用两个数组,分别统计不需要操作的对数,以及需要操作两次的对数。枚举每一对(p,q),cnt1[q-p]+=1,即如果X=q-p,那么这对数不需要操作;cnt2[max(q,k-q)]+=1,即对于(p,q)如果只能操作1次,那么X最多只能到max(q,k-q),只要X>max(q,k-q),那么这对数就必须要操作两次。得到上述两个数组后,枚举X,最终所需要的操作数就是 n/2 - cnt1[X] + (cnt2[0] + ... + cnt2[X-1])
class Solution {
public int minChanges(int[] nums, int k) {
int n = nums.length;
int[] cnt1 = new int[k+1];
int[] cnt2 = new int[k+1];
for(int i=0; i<n/2; i++){
int x = nums[i], y = nums[n-i-1];
int mn = Math.min(x, y);
int mx = Math.max(x, y);
cnt1[mx-mn] += 1;
cnt2[Math.max(mx, k-mn)] += 1;
}
int ans = n, pre = 0;
for(int x=0; x<=k; x++){
//cnt1[x]:表示刚好等于x,不用修改
//n/2-cnt1[x]:表示至少需要修改一次的(p,q)数量
//pre:表示需要修改两次的(p,q)数量
ans = Math.min(ans, n/2-cnt1[x]+pre);
pre += cnt2[x];
}
return ans;
}
}
- 差分数组,简单来说差分数组就是使用简单的操作来实现一段区域内数字的同增或同减,支持所有操作完成后再查询;不支持边操作边查询;
- 对于本题来说,对于每对(p,q),令 x = p-q,如果X属于[0,x-1],那么需要操作一次,要将[0,x-1]区域全部加1;如果X属于[x+1,mx],也只需要操作一次,要将[x+1,mx]区域全部加1;如果X属于[mx+1,k],那么需要操作2次,要将[mx+1,k]区域全部加2
- 如果要计算每个X对应的操作次数,就是算差分数组的前缀和
class Solution {
public int minChanges(int[] nums, int k) {
int n = nums.length;
int[] d = new int[k+2];
for(int i=0; i<n/2; i++){
int p = nums[i], q = nums[n-i-1];
if(p > q){
int t = p;
p = q;
q = t;
}
int x = q - p;
int mx = Math.max(q, k-p);
//[0, x-1]范围所有数 +1
d[0]++;
d[x]--;
//[x+1, mx]范围所有数 +1
d[x+1]++;
d[mx+1]--;
//[mx+1, k]范围所有数 +2
d[mx+1]+=2;
}
int ans = n, pre = 0;
for(int v : d){
pre += v;
ans = Math.min(ans, pre);
}
return ans;
}
}
四,3225. 网格图操作后的最大分数
本题讲O(n^4)的解法,我们可以将原问题转换成一个与原问题相同的子问题,原问题:求 j 列网格图的最大分数;子问题:在知道最后一列黑格子的高度后,那么剩下的就是求 j-1 列网格图的最大分数,依次类推....
我们要求当前第 j 列所能得到的分数,必须要知道第 j+1 列和 j-1 列的黑格子的高度,所以可以这样定义dfs(j,cur,pre):在第 j 列黑格子高度为 cur,第 j+1 列黑格子高度为 pre 时,[0,j]列网格图的最大分数。还剩下第 j-1 列黑格子的高度,我们可以在dfs中枚举。
代码如下:
class Solution {
public long maximumScore(int[][] grid) {
int n = grid.length;
long[][] preSum = new long[n+1][n];//每列的前缀和
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
preSum[i+1][j] = preSum[i][j] + grid[i][j];
}
}
memo = new long[n+1][n+1][n+1];
for(int i=0; i<n+1; i++){
for(int j=0; j<n+1; j++){
Arrays.fill(memo[i][j], -1);
}
}
long ans = 0;
//枚举n-1列的高度
for(int i=0; i<=n; i++){
ans = Math.max(ans, dfs(n-1,i,0,preSum));
}
return ans;
}
long[][][] memo;
//dfs(j列, j列的高度, j+1列的高度)
long dfs(int j, int cur, int pre, long[][] preSum){
if(j == 0) return pre > cur ? preSum[pre][0]-preSum[cur][0]:0;
if(memo[j][cur][pre] != -1) return memo[j][cur][pre];
int n = preSum.length-1;
long res = 0;
//枚举j-1列的高度
for(int nxt=0; nxt<=n; nxt++){
long s = Math.max(nxt, pre) > cur ? preSum[Math.max(nxt,pre)][j]-preSum[cur][j] : 0;
res = Math.max(res, dfs(j-1,nxt,cur,preSum)+s);
}
return memo[j][cur][pre] = res;
}
}
在此贴一个O(n^3)的解法:
class Solution {
public long maximumScore(int[][] grid) {
int n = grid.length;
long[][] preSum = new long[n+1][n];
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
preSum[i+1][j] = preSum[i][j] + grid[i][j];
}
}
memo = new long[n+1][n+1][2];
for(int i=0; i<n+1; i++){
for(int j=0; j<n+1; j++){
Arrays.fill(memo[i][j], -1);
}
}
long ans = 0;
//枚举n-1列的高度
for(int i=0; i<=n; i++){
ans = Math.max(ans, dfs(n-2,i,0,preSum));
}
return ans;
}
long[][][] memo;
//dfs(j列, j+1列的高度, j+1列的高度 < j+2列的高度)
long dfs(int j, int pre, int dec, long[][] preSum){
if(j < 0) return 0;
if(memo[j][pre][dec] != -1) return memo[j][pre][dec];
int n = preSum.length-1;
long res = 0;
//枚举j-1列的高度
for(int cur=0; cur<=n; cur++){
if(cur == pre){
res = Math.max(res, dfs(j-1,cur,0,preSum));
}else if(cur < pre){
res = Math.max(res, dfs(j-1,cur,1,preSum)+preSum[pre][j]-preSum[cur][j]);
}else if(dec == 0){
res = Math.max(res, dfs(j-1,cur,0,preSum)+preSum[cur][j+1]-preSum[pre][j+1]);
}else if(pre == 0){
res = Math.max(res, dfs(j-1,cur,0,preSum));
}
}
return memo[j][pre][dec] = res;
}
}