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

Leetcode - 双周赛153

目录

  • 一、3498. 字符串的反转度
  • 二、3499. 操作后最大活跃区段数 I
  • 三、3500. 将数组分割为子数组的最小代价
  • 四、3501. 操作后最大活跃区段数 II

一、3498. 字符串的反转度

题目连接
在这里插入图片描述
本题直接模拟,代码如下:

class Solution {
    public int reverseDegree(String s) {
        int ans = 0;
        for(int i = 0; i < s.length(); i++){
            int c = 'z' - s.charAt(i) + 1;
            ans += (i + 1) * c;
        }
        return ans;
    }
}

二、3499. 操作后最大活跃区段数 I

题目连接
在这里插入图片描述
本题就是一道阅读理解题,大概意思:至多执行一次操作,在一次操作中,可以将 0011100 这种把连续的 1 包围的 0 (左右两边都要有0)转换成 1,问最终 1 出现的最大次数。实际上就是计算操作一次最多可以将多少个 0 翻转成 1,而这就是计算相邻两段连续的 0 出现的最大次数,然后再加上原本 1 的个数就是答案。

代码如下:

class Solution {
    public int maxActiveSectionsAfterTrade(String s) {
        int n = s.length();
        int i = 0, ans = 0, mx = 0, pre = 0;
        while(i < n){
            while(i < n && s.charAt(i) == '1'){
                ans++;// 统计 1 出现的次数
                i++;
            }
            int j = i;// 记录 0 出现的位置
            while(i < n && s.charAt(i) == '0'){
                i++;
            }
            // 0: [j, i)
            // i - j 表示连续的 0 的出现次数 
            if(pre > 0 && i - j > 0){
                mx = Math.max(pre + i - j, mx); 
            }
            pre = i - j; // 更新相邻 0 出现次数
        }
        return ans + mx;
    }
}

三、3500. 将数组分割为子数组的最小代价

题目连接
在这里插入图片描述
本题如果直接使用 dp 做的话,需要分别枚举 (l,r,i),也就是说时间复杂度是O(n^3)的,这明显会超时,所以需要进行优化。

将代价公式进行优化:

  • ( n u m s [ 0 ] + n u m s [ 1 ] + . . . + n u m s [ r ] + k ∗ i ) ∗ ( c o s t [ l ] + . . . + c o s t [ r ] ) (nums[0] + nums[1]+...+nums[r] +k*i)*(cost[l]+...+cost[r]) (nums[0]+nums[1]+...+nums[r]+ki)(cost[l]+...+cost[r])
  • 用前缀和化简一下,得到: ( p r e N [ r + 1 ] + k ∗ i ) ∗ ( p r e C [ r + 1 ] − p r e C [ l ] ) (preN[r+1]+k*i)*(preC[r+1]-preC[l]) (preN[r+1]+ki)(preC[r+1]preC[l])
  • 明确 (l,r) 是不可能优化的,所以实际上就是如何把 i 给优化掉,公因式分解,得到: p r e N [ r + 1 ] ∗ ( p r e C [ r + 1 ] − p r e C [ l ] ) + k ∗ i ∗ ( p r e C [ r + 1 ] − p r e C [ l ] ) preN[r+1]*(preC[r+1]-preC[l]) + k * i *(preC[r+1]-preC[l]) preN[r+1](preC[r+1]preC[l])+ki(preC[r+1]preC[l])
  • 前一段已经没有i了,主要看 k ∗ i ∗ ( p r e C [ r + 1 ] − p r e [ l ] ) k*i*(preC[r+1]-pre[l]) ki(preC[r+1]pre[l])
  • 假设 n u m s nums nums 数组分成 m m m 段,从前到后每一段的和依次为 {A,B,C,...,M},将 k 提出来,剩下的和就变成了A + 2B + 3C + .... + mM,这时可以将其重新划分(A+...+M)+(B+...+M)+(C+...+M)+...+(M),这样一看 i 就已经被等价成计算后缀和了,即 (preC[n]-preC[l1])+(preC[n]-preC[l2])+...
  • 最终代价公式可以等价为 preN[r+1] * (preC[r+1] - preC[l]) + k * (preC[n] - preC[l])

此时在使用 dp 去计算,定义 f[i]: 以 i-1 结尾得到的最小总代价,对于 i 来说:

  • 它可以从 i 之前任意一个状态转移过来,假设它从 f[j] 转移过来,转移方程就是 f[i] = max(f[i], f[j] + preN[i+1] * (preC[i+1] - preC[j]) + k * (preC[n] - preC[j]))

代码如下:

class Solution {
    public long minimumCost(int[] nums, int[] cost, int k) {
        int n = nums.length;
        long[] preN = new long[n + 1];
        long[] preC = new long[n + 1];
        for(int i = 0; i < n; i++){
            preN[i + 1] = preN[i] + nums[i];
            preC[i + 1] = preC[i] + cost[i];
        }
        long[] f = new long[n+1];//以 i-1 结尾的最小总代价
        Arrays.fill(f, Long.MAX_VALUE);
        f[0] = 0;
        //preN[r] * (preC[r] - preC[l]) + k * (preC[n] - pre[l])
        for(int i = 0; i < n; i++){
            for(int j = 0; j <= i; j++){
                f[i+1] = Math.min(f[i+1], f[j] + preN[i+1] * (preC[i+1] - preC[j]) + k * (preC[n] - preC[j]));
            }
        }
        return f[n];
    }
}

四、3501. 操作后最大活跃区段数 II

题目链接
在这里插入图片描述
本题是T2的进阶,变化的点在于本题限定了操作的范围,问每次查询只能操作 [L,R] 区间时,所能得到的最多 1 的个数,与 T2 的思路一致,还是计算出 [L,R] 区间相邻两段连续的 0 出现的最大次数,唯一的问题在于如何快速计算出相邻两段连续的 0 出现的最大次数

可以预处理相邻两段连续的 0 出现的次数,会出现以下三种情况:
在这里插入图片描述
对于情况一的第二种情况,就是维护一个区间最大值,可以使用 ST表 或者 线段树 去维护,对于第一/三种情况,可以手动计算。此时还有一个问题,需要找到 [L,R] 区间中最前/最后的完整的连续 0 段落,这可以使用二分来查找。

代码如下:

class Solution {
    private record Pair(int l, int r) { // 左闭右开
    }

    private static class SparseTable {
        private final int[][] st;

        SparseTable(List<Pair> a) {
            int n = a.size() - 1;
            int sz = 32 - Integer.numberOfLeadingZeros(n);
            // st[i][j]: 区间 [i, i+2^j) 的最大值 
            st = new int[n][sz];
            for (int i = 0; i < n; i++) {
                st[i][0] = a.get(i).r - a.get(i).l + a.get(i + 1).r - a.get(i + 1).l;
            }
            for (int j = 1; j < sz; j++) {
                for (int i = 0; i + (1 << j) <= n; i++) {
                    st[i][j] = Math.max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
                }
            }
        }

        // 查询区间最大值,[l,r) 左闭右开
        int query(int l, int r) {
            if (l >= r) {
                return 0;
            }
            int k = 32 - Integer.numberOfLeadingZeros(r - l) - 1;
            return Math.max(st[l][k], st[r - (1 << k)][k]);
        }
    }

    public List<Integer> maxActiveSectionsAfterTrade(String S, int[][] queries) {
        char[] s = S.toCharArray();
        int n = s.length;
        int total1 = 0;
        List<Pair> a = new ArrayList<>();
        a.add(new Pair(-1, -1)); // 哨兵
        int start = 0;
        for (int i = 0; i < n; i++) {
            if (i == n - 1 || s[i] != s[i + 1]) {
                if (s[i] == '1') {
                    total1 += i - start + 1;
                } else {
                    a.add(new Pair(start, i + 1)); // 左闭右开
                }
                start = i + 1;
            }
        }
        a.add(new Pair(n + 1, n + 1)); // 哨兵

        SparseTable st = new SparseTable(a);
        List<Integer> ans = new ArrayList<>(queries.length);
        for (int[] query : queries) {
            int ql = query[0];
            int qr = query[1] + 1; // 左闭右开

            // a 中没有重复的区间左右端点,可以直接用库函数二分
            // 找第一个区间,左端点 >= ql
            int i = Collections.binarySearch(a, new Pair(ql, 0), (p, q) -> p.l - q.l);
            if (i < 0) i = ~i;
            // 找最后一个区间,右端点 <= qr
            int j = Collections.binarySearch(a, new Pair(0, qr + 1), (p, q) -> p.r - q.r);
            if (j < 0) j = ~j;
            j--;

            int mx = 0;
            if (i <= j) { // [ql,qr) 中有完整的区间
                int full = st.query(i, j); // 相邻完整区间的长度之和的最大值
                int sl = merge(a.get(i - 1).r - ql, a.get(i).r - a.get(i).l);
                int sr = merge(qr - a.get(j + 1).l, a.get(j).r - a.get(j).l);
                mx = Math.max(full, Math.max(sl, sr));
            } else if (i == j + 1) {
                mx = merge(a.get(i - 1).r - ql, qr - a.get(j + 1).l);
            }
            ans.add(total1 + mx);
        }
        return ans;
    }
    private int merge(int x, int y) {
        return x > 0 && y > 0 ? x + y : 0;
    }
}
http://www.dtcms.com/a/112873.html

相关文章:

  • 失眠睡不着运动锻炼贴士
  • Mac强制解锁APP或文件夹
  • Java的Selenium常用的元素操作API
  • 【图像处理基石】什么是AWB?
  • 扩展库Scrapy:Python网络爬虫的利器
  • 【Rust学习】Rust数据类型,函数,条件语句,循环
  • 实战打靶集锦-38-inclusiveness
  • pyTorch框架使用CNN进行手写数字识别
  • AI比人脑更强,因为被植入思维模型【43】蝴蝶效应思维模型
  • 多模态智能体框架MM-StoryAgent:跨模态叙事视频生成的技术突破
  • 九、重学C++—类和函数
  • QGIS中第三方POI坐标偏移的快速校正-百度POI
  • C#编程基础知识点介绍
  • 亚马逊系统异常48小时:这3类商品退货政策有变
  • 开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案)
  • SpringWebFlux路由函数:RouterFunction与HandlerFunction
  • 简单多状态dp问题 + 总结(一)
  • 【新能源汽车研发测试数据深度分析:从传感器到智能决策的硬核方法论】
  • 照片to谷歌地球/奥维地图 - 批量导出GPS数据到CSV文件
  • (小白0基础) 租用AutoDL服务器进行deepseek-8b模型微调全流程(Xshell,XFTP) —— 准备篇
  • 2025.4.4总结
  • THE UNIVERSITY OF MANCHESTER-NUMERICAL ANALYSIS 1-4.1数值代数方程组求解
  • 【Android】UI开发:XML布局与Jetpack Compose的全面对比指南
  • 什么是边缘计算?
  • 使用Visual Studio
  • 36、web前端开发之JavaScript(五)
  • 如何部署DeepSeek企业知识库:
  • 单片机学习笔记8.定时器
  • 针对Ansible执行脚本时报错“可执行文件格式错误”,以下是详细的解决步骤和示例
  • Java异常处理与全局异常处理全面指南