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

Leetcode - 周赛443

目录

  • 一、3502. 到达每个位置的最小费用
  • 二、3503. 子字符串连接后的最长回文串 I
  • 三、3504. 子字符串连接后的最长回文串 II
  • 四、3505. 使 K 个子数组内元素相等的最少操作数

一、3502. 到达每个位置的最小费用

题目链接
在这里插入图片描述
本题是一道脑筋急转弯,实际就是计算前缀最小值,画个图理解一下:
在这里插入图片描述
代码如下:

class Solution {
    public int[] minCosts(int[] cost) {
        int n = cost.length;
        int[] ans = new int[n];
        ans[0] = cost[0];
        for(int i = 1; i < n; i++){
            ans[i] = Math.min(ans[i-1], cost[i]);
        }
        return ans;
    }
}

二、3503. 子字符串连接后的最长回文串 I

题目链接
在这里插入图片描述
本题数据范围较小,直接暴力,代码如下:

class Solution {
    public int longestPalindrome(String s, String t) {
        int n = s.length();
        int m = t.length();
        int ans = 1;
        for(int i = 0; i < n; i++){
            for(int j = i; j < n; j++){
                if(check(s.substring(i,j+1))){
                    ans = Math.max(ans, j-i+1);
                }
                for(int x = 0; x < m; x++){
                    for(int y = x; y < m; y++){
                        if(check(s.substring(i,j+1) + t.substring(x,y+1))){
                            ans = Math.max(ans, j-i+1+y-x+1);
                        }
                        if(check(t.substring(x,y+1))){
                            ans = Math.max(ans, y-x+1);
                        }
                    }
                }
            }
        }
        return ans;
    }
    boolean check(String s){
        int l = 0, r = s.length() - 1;
        while(l < r){
            if(s.charAt(l) != s.charAt(r))
                return false;
            l++;
            r--;
        }
        return true;
    }
}

三、3504. 子字符串连接后的最长回文串 II

题目链接
在这里插入图片描述
本题要构造一个最长的回文串,可以将它分成三个部分 —— ABA,它有两种情况:

  • 情况一:s = "???AB??", t = "???A??",A1 与 A2 互相回文,B 自身是一个回文串
  • 情况二:s = "???A???", t = "??BA??",A1 与 A2 互相回文,B 自身是一个回文串
  • 注:A,B 长度都可以为 0

举一个例子 s = "???abcac??", t = "???ba??",这里 A1 = “ab”,B = “cac”,A2 = “ba”,可以将它拆分成两个部分:

  • A1 与 A2,这里换一个视角,将 A2 翻转过来,A1 就等于 A2,所以可以将 字符串t 反转一下,这部分实际上就变成了一个求 s 与 resT 的最长公共子串问题,可以使用 dp 来解决。
    • 定义 f [ i ] [ j ] f[i][j] f[i][j]:以 i i i 结尾的字符串 s s s 与 以 j j j 结尾的字符串 r e s T resT resT 的最长公共子串
    • s [ i ] s[i] s[i] != r e s T [ j ] resT[j] resT[j] f [ i ] [ j ] = 0 f[i][j] = 0 f[i][j]=0
    • s [ i ] s[i] s[i] == r e s T [ j ] resT[j] resT[j] f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + 1 f[i][j] = f[i-1][j-1]+1 f[i][j]=f[i1][j1]+1
  • B 这部分可以使用中心扩展法来枚举,由于回文串的长度可奇可偶,对于一个长度为 n n n 的字符串,需要枚举的中心点就有 n + ( n − 1 ) n + (n - 1) n+(n1) 个(奇数中心有 n n n 个,偶数中心有 n − 1 n-1 n1 个),可以从 0 枚举到 2 ∗ n − 2 2 * n - 2 2n2,此时可以直接得到 l = i / 2 , r = ( i + 1 ) / 2 l=i/2, r=(i+1)/2 l=i/2,r=(i+1)/2,然后向两边扩展就行。
  • 此时的答案等于: r − l + 1 + 2 ∗ m a x ( f [ l − 1 ] ) r-l+1+2*max(f[l-1]) rl+1+2max(f[l1])

代码如下:

class Solution {
    int dfs(String S, String T){
        char[] s = S.toCharArray();
        char[] t = T.toCharArray();
        int n = s.length, m = t.length;
        int[][] f = new int[n+1][m+1];
        int[] mx = new int[n+1]; // 统计以 i-1 结尾的字符串S 与 字符串T 的最长公共子串长度
        int ans = 0;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(s[i] == t[j]){
                    f[i+1][j+1] = f[i][j] + 1;
                    mx[i+1] = Math.max(f[i+1][j+1], mx[i+1]);
                }
            }
            ans = Math.max(ans, 2 * mx[i+1]); // B 为 0 的情况
        }
        for(int i = 0; i < 2 * n - 1; i++){ // 中心扩展
            int l = i / 2;
            int r = (i + 1) / 2;
            while(l >= 0 && r < n && s[l] == s[r]){
                l--;
                r++;
            }
            if(l+1 <= r-1) // 判断 s[l+1, r-1] 不为空 
                ans = Math.max(ans, r - l - 1 + 2 * mx[l+1]);
        }
        return ans;
    }
    public int longestPalindrome(String s, String t) {
        String resT = new StringBuilder(t).reverse().toString();
        // 分别计算 AB与A,A与BA 两种情况
        return Math.max(dfs(s, resT), dfs(resT, s)); 
    }
}

四、3505. 使 K 个子数组内元素相等的最少操作数

题目链接
在这里插入图片描述
本题是一道综合题:

  • 长度为 x x x 的子数组,说明要使用 滑动窗口
  • 求子数组中所有元素相等的最小操作次数,肯定是变成中位数操作次数最小,求中位数
  • 求包含 k k k 个长度 恰好 为 x 的不重叠子数组(每个子数组中的所有元素都相等)所需要的最少操作数,这是 dp

先来解决如何计算滑窗中位数,使用对顶堆解决,准备两个堆,一个大根堆 left,一个小根堆right

  • 由特殊到一般,如果给定一个无序数组,如何求它的中位数(不排序做法)
    • 中位数只会出现在有序数组的中间位置,所以可以使用 left 来统计数组的前半段,right 来统计数组的后半段,保证 left.size() >= right.size(),此时中位数一定就是 left的堆顶元素(1/2个)。明确了大概的方向,接下来就是如何动态维护元素入堆
    • 对于元素 x,如何判断它是进入 left 还是 right,分情况讨论:
      • 如果 left.size() <= right.size(),那么一定要把x放入left,但是这里会出现一个问题,x 可能比rgiht.peek()还要大,如果直接把 x 放入left,那么有序性就被打破,所以最好的做法是先将 x 放入right,然后把 right.poll() 放入 left 中
      • 如果 left.size() > right.size(),那么一定要把x放入right,但是这里也会出现一个问题,x 可能比left.peek()还要小,如果直接把 x 放入right,那么有序性就被打破,所以最好的做法是先将x放入left,然后把 left.poll() 放入 right 中
  • 上述做法已经把入堆的操作讲完了,接下来就是在滑窗过程中如何出元素,假设该元素为 out
    • 先判断 out 在那个堆当中
    • 如果 out <= left.peek(),说明它在left,需要将它从left移除。但是仅仅这样还不行,它还会出现一个问题如果移除之后 left.size() < rgiht.size(),那么就不符合我们上述的定义了,还需要将 right.poll() 放入 left
    • 如果 out > left.peek(),说明它在right,需要将它从right移除。同理这样也不行,它会出现一个问题如果移除之后 left.size() > rgiht.size() + 1,那么也不符合我们上述的定义,需要将 left.poll() 放入 right
    • 最后,left的堆顶元素(1/2个) 就是该滑窗的中位数

滑窗中位数有了,接下来就是计算——将窗口中元素全部变为中位数所需的操作次数,画个图好理解:
在这里插入图片描述
得到 r e s res res 数组(res[i]:将 [i,i+k-1] 所有数变成中位数的操作次数)之后,剩下的就是枚举选哪 k k k 个不重叠的子数组,直接使用 dp 来做,定义 f[i][j]:在前 j 个数中,选择 i 个长度为 x 的不重叠子数组所需的最小操作次数

  • 对于f[i][j],即以下标为 j-1 结尾的子数组有选或不选两种情况:
  • 不选,它直接从 f[i][j-1] 转移过来,f[i][j] = f[i][j-1]
  • 选,题目要求它不能重叠,所以选的子数组下标是[j-k,j-1],需要从 f[i-1][j-k](这里j-k表示的是前 j-k 个数,也就是 [0,j-k-1] 选 i-1 个数的最小操作次数) 转移过来,f[i][j] = f[i-1][j-k] + res[j-k]

代码如下:

class Solution {
    public long minOperations(int[] nums, int x, int k) {
        long[] res = medianSlidingWindow(nums, x);
        //[i, i + x - 1]
        int n = nums.length;
        long[][] f = new long[k+1][n+1];
        // f[i][j] = min(f[i][j-1], f[i-1][j-x] + res[j-x])
        for(int i = 1; i <= k; i++){
            f[i][i*x-1] = Long.MAX_VALUE;
            for(int j = i * x; j <= n - (k - i) * x; j++){
                f[i][j] = Math.min(f[i][j-1], f[i-1][j-x] + res[j-x]);
            }
        }
        return f[k][n];
    }
    public long[] medianSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        long[] ans = new long[n-k+1];
        LazyHeap left = new LazyHeap((x, y) -> Integer.compare(y, x));
        LazyHeap right = new LazyHeap((x, y) -> Integer.compare(x, y));
        for(int l = 0, r = 0; r < n; r++){
            int in = nums[r];
            // 入堆操作
            if(left.size() == right.size()){
                left.push(right.pushPop(in));
            }else{
                right.push(left.pushPop(in));
            }
            if(r < k - 1) continue;
            int x = left.top();
            // 计算变成中位数所需操作次数
            ans[l] = (long) x * (k % 2) + right.sum() - left.sum();
            int out = nums[l];
            // 出堆操作
            if(out <= left.peek()){
                left.remove(out);
                if(left.size() < right.size()){
                    left.push(right.pop());
                }
            }else{
                right.remove(out);
                if(left.size() > right.size() + 1){
                    right.push(left.pop());
                }
            }
            l++;
        }
        return ans;
    }
}
// 手写的懒删除堆(否则会超时)
class LazyHeap extends PriorityQueue<Integer> {
    //统计当前每个元素需要删除次数
    private final Map<Integer, Integer> removeCnt = new HashMap<>();
    //实际堆的大小
    private int size = 0;
    private long sum = 0;
    public LazyHeap(Comparator<Integer> comparator){
        super(comparator);
    }
    public int size(){
        return size;
    }
    public long sum(){
        return sum;
    }
    
    //懒删除操作
    public void remove(int x){
        removeCnt.merge(x, 1, Integer::sum);
        size--;
        sum -= x;
    }

    //实际执行删除操作
    private void applyRemove(){
        while(removeCnt.getOrDefault(peek(), 0) > 0){
            removeCnt.merge(poll(), -1, Integer::sum);
        }
    }

    //查看推顶元素
    public int top(){
        applyRemove();
        return peek();
    }

    //出堆
    public int pop(){
        applyRemove();
        size--;
        sum -= peek();
        return poll();
    }

    //入堆
    public void push(int x){
        int c = removeCnt.getOrDefault(x, 0);
        if(c > 0){
            removeCnt.put(x, c - 1);
        }else{
            offer(x);
        }
        sum += x;
        size++;
    }

    //push(x), pop()
    public int pushPop(int x){
        applyRemove();
        sum += x;
        offer(x);
        sum -= peek();
        return poll();
    }
}
http://www.dtcms.com/a/120453.html

相关文章:

  • C++中的 友元关系
  • Python 序列构成的数组(当列表不是首选时)
  • SearXNG
  • Docker面试全攻略(一):镜像打包、容器运行与高频问题解析
  • mybatis的第五天学习笔记
  • 多模态大模型重塑自动驾驶:技术融合与实践路径全解析
  • @linux系统SSL证书转换(Openssl转换PFX)
  • 前端网页开发学习(HTML+CSS+JS)有这一篇就够!
  • 3dmax中VRay的3d导出glb的模型是黑白的,没有带贴图
  • K8s 老鸟的配置管理避雷手册
  • Android Input——输入系统介绍(一)
  • 【小沐杂货铺】基于Three.JS绘制三维数字地球Earth(GIS 、WebGL、vue、react,提供全部源代码)
  • 机器学习中的聚类分析算法:原理与应用
  • vue总结
  • XCode集成第三方framework步骤
  • 海阳科技IPO:业务独立性、业绩稳定性、财务规范性存致命缺陷
  • CentOs系统部署DNS服务
  • 【经典DP】三步问题 / 整数拆分 / 不同路径II / 过河卒 / 下降路径最小和 / 地下城游戏
  • 认识vue中的install和使用场景
  • python 常用的6个爬虫第三方库
  • 23种设计模式-行为型模式-观察者
  • Photoshop2025最新版v26超详细图文安装教程(附安装包)
  • 【大模型深度学习】提示学习:Prefix tuning 、P-tuning v2、P-tuning 到底有什么区别?
  • 【Python】Python 100题 分类入门练习题 - 新手友好
  • 模板引擎Freemarker使用教程
  • LabVIEW真空度监测与控制系统
  • 【RH124】第六章 管理本地用户和组
  • 启山智软的b2c前端页面设计
  • 【kind管理脚本-1】便捷使用 kind 创建、删除、管理集群脚本
  • CANoe CAPL——CANoe IL函数