算法优化:LeetCode第122场双周赛解题策略与技巧

news2025/1/14 18:39:51

接下来会以刷常规题为主 ,周赛的难题想要独立做出来还是有一定难度的,需要消耗大量时间

比赛地址 

3011. 判断一个数组是否可以变为有序

public class Solution {
    public int minimumCost(int[] nums) {
        if (nums.length < 3) {
            // 数组长度小于3时,无法分割成3个子数组
            return -1;
        }

        int minCost = Integer.MAX_VALUE;
        int n = nums.length;

        // 第一个分割点至少在索引1,第二个分割点至少在索引2
        for (int i = 1; i < n - 1; i++) {
            for (int j = i + 1; j < n; j++) {
                int cost = nums[0] + nums[i] + nums[j];
                minCost = Math.min(minCost, cost);
            }
        }

        return minCost;
    }
}

100164. 通过操作使数组长度最小

冒泡排序

class Solution {
    
    public boolean canSortArray(int[] nums) {
   
        int n = nums.length;

        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (Integer.bitCount(nums[j]) == Integer.bitCount(nums[j + 1]) && nums[j]>nums[j + 1]) {
                    // 如果前一个元素的1的数量大于后一个元素的1的数量,交换它们
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                }
            }
        }

        // 遍历完后,检查数组是否有序
        for (int i = 0; i < n - 1; i++) {
            if (nums[i] > nums[i + 1]) {
                return false;
            }
        }
        
        return true;
    }
}

100181. 将数组分成最小总代价的子数组 I

当 x<y 时,x  mod  y=x 因此如果选择数组中的两个不相等的元素,则可以删除较大元素,保留较小元素。

用 minNum表示数组 nums 中的最小元素

用 minCount 表示数组 nums 中的 minNum 的出现次数

分别考虑 minCount=1 和 minCount>1 的情况。

  • 如果 minCount=1

则可以每次选择 minNum  和另一个元素,由于 minNum 一定小于另一个元素,因此总是可以删除另一个元素,保留 minNum,直到数组 nums  中只有一个元素 minNum,数组 nums的最小长度是 1。

  • 如果 minCount>1 
  1. 如果数组 nums 中存在一个元素 num 满足 num mod minNum≠0 ,记 newNum= (num  mod  minNu)
  2. 则必有 0<newNum<minNum 可以在一次操作中选择 num 和 minNum ,删除这两个元素并添加元素newNum。
  3. 由于 newNum < minNum ,因此 newNum 成为数组 nums 中的新的最小元素且最小元素唯一,之后可以每次选择 newNum 和另一个元素,其效果是删除另一个元素,保留 newNum ,直到数组 nums 中只有一个元素 newNum ,数组 nums 的最小长度是 1
  4. 如果数组 nums 中不存在元素 num 满足 num  mod  minNum ≠ 0 ,则无法通过操作得到小于 minNum 的元素,因此在将所有大于 minNu 的元素删除之后,剩余 minCount 个元素 minNum 。由于每次可以选择 2 个元素 minNum 执行操作得到元素 0 无法继续操作,因此 minCount 个元素 minNum 的最多操作次数可以根据count_min的奇偶性判断
class Solution:
    def minimumArrayLength(self, nums: List[int]) -> int:
        min_val = min(nums)
        count_min = nums.count(min_val)

        for num in nums:
            if num % min_val != 0:
                return 1  # 产生了新的更小值

        # 没有产生新的最小值,计算最小值的数量
        return (count_min ) // 2 +1 if count_min % 2 != 0 else count_min // 2

 

100178. 将数组分成最小总代价的子数组 II

一、直接用滑动窗口求解

这种方法会超时

class Solution:
    def minimumCost(self, nums: List[int], k: int, dist: int) -> int:
        
        first = nums[0]  # 初始元素的代价
        window_size = dist + 1  # 窗口大小
        minimumCost = float('inf')  # 初始化最小代价为无穷大

        # 遍历数组,寻找除第一个和最后一个元素之外的最小的 k-1 个元素
        for start in range(1, len(nums) - window_size + 1):
            window = nums[start:start + window_size]
            sorted_window = sorted(window)
            # 获取除第一个的 k-1 个最小元素的和
            window_cost = sum(sorted_window[:k-1])
            # 更新最小代价
            minimumCost = min(minimumCost, window_cost)

        # 最终的最小总代价是第一个元素的代价加上最小窗口代价
        return first + minimumCost 

二、引入堆的代码实现

效率和之前的方法相差无几

class Solution:
    def minimumCost(self, nums: List[int], k: int, dist: int) -> int:
        first = nums[0]
        n = len(nums)
        minimumCost = float('inf')

        for start in range(1, n - dist):
            # 维护一个大小为 dist + 1 的最小堆
            min_heap = nums[start:start + dist + 1]
            heapq.heapify(min_heap)

            window_cost = 0
            # 弹出最小的 k-1 个元素并计算它们的和
            for _ in range(k-1):
                if min_heap:
                    window_cost += heapq.heappop(min_heap)

            minimumCost = min(minimumCost, window_cost)

        return first + minimumCost

三、大小顶堆、延迟删除、滑动窗口

这道题目的思路是利用滑动窗口结合两个堆(优先队列)来找出序列中指定数量(`k-1`)的最小数的和,它们是从序列的某个区间(该区间长度由`dist`决定)中选择出来的。这个序列中的第一个数 (`nums[0]`) 是固定的,所以总是被包含在结果中。

下面是详细的解题步骤:

  1. 初始化两个堆:一个小顶堆 small 来保存当前窗口中的最小的 k-2 个数,以及一个大顶堆 big 来保存窗口内剩余的数。

  2. 使用 HashMap 进行延迟删除:为了实现有效地从堆中删除特定的非堆顶元素,创建两个 HashMap (smallMark 和 bigMark) 来标记堆中元素是否已经被 "删除"。该删除实际上是延迟执行的,即直到这个元素出现在堆顶时才真正被排除。

  3. 填充初始窗口:从 nums 数组的第二个元素开始,将 dist+1 长度内的元素放入 big 堆。

  4. 从 big 中取出 k-2 个最小元素:这 k-2 个元素是将要加入 small 的,记录这 k-2 个数的和作为窗口的当前总和。

  5. 滑动窗口:在数组中滑动窗口,并动态维护这两个堆以保持正确的最小 k-2 个数的总和。

  6. 调整堆:当窗口滑动导致元素移出窗口时,更新 small 堆以保持其有效性,并进行相应的调整。如果移出的元素当前在 small 中,则它需要被标记为已删除;如果它在 big 中,则直接标记为已删除。

  7. 处理新进入窗口的元素:窗口滑动时,可能会有新的元素进入。这些新元素需要加入到 big 堆中。从 big 中取出的最小元素会放入 small 堆,并更新当前窗口总和(sum)。

  8. 求解最终结果:在滑动窗口过程中,每次窗口更新后,计算此时的窗口总和加上 nums[0](固定加入)。所有窗口中总和的最小值即为所求问题的答案。

class Solution {
    // small是小顶堆 维护前k-2小的数
    // big是大顶堆 维护窗口内剩下的数

    PriorityQueue<Integer> small, big;
    // 标记当前元素是否已经被删除以及被删除的个数
    HashMap<Integer, Integer> smallMark, bigMark;
    // samll和big当前未被删除的元素个数
    int smallSiz, bigSiz;
    long sum;

    public long minimumCost(int[] nums, int k, int dist) {
        // k个 除掉第一个 还要选k-1个
        // 枚举第2个 nums[i] nums[i+1]... nums[i+dist] 里选k-2个最小的数
        // nums[i+1] nums[i+k-2]
        small = new PriorityQueue<>(Collections.reverseOrder());
        smallSiz = 0;
        smallMark = new HashMap<>();
        big = new PriorityQueue<>();
        bigSiz = 0;
        bigMark = new HashMap<>();
        // 当前小顶堆的和 也就是前k-2小的和
        sum = 0;
        int n = nums.length;
        // 把nums[1+1]...nums[1+dist]里的数加入到big里
        for (int i = 2; i <= Math.min(n-1, dist+1); i++) {
            big.add(nums[i]);
            bigSiz++;
        }
        // 取出前k-2小的数放入small
        for (int i = 0; i < k-2; i++ ) {
            int tmp = big.poll();
            bigSiz--;
            sum += tmp;
            small.add(tmp);
            smallSiz++;
        }
        long res = nums[0] + nums[1] + sum;
        // 枚举第二个数的位置
        // 枚举的位置从i-1变成i时 nums[i]离开了窗口 nums[i+dist]进入了窗口
        for (int i = 2; i + k-2 < n; i++) {
            // 移除nums[i]
            // 因为要访问small.peek() 为了确保small.peek()是未被删除的元素 需要先更新small
            updateSmallPeek();
            // nums[i]在前k-2小里
            if (smallSiz > 0 && small.peek() >= nums[i]) {
                // 因为nums[i] 是可能小于small.peek()的 我们没法直接删除nums[i] 所以要标记一下
                smallMark.merge(nums[i], 1, Integer::sum);
                // 从small里删除nums[i]
                smallSiz--;
                sum -= nums[i];
            } else {
                // nums[i]不在前k-2小里 
                bigMark.merge(nums[i], 1, Integer::sum);
                bigSiz--;
                // 这里是为了使得small的数量变成k-3个 也就是还差一个才够k-2个
                // 是为了方便后面的操作
                // 从small里选一个放到big里
                int tmp = small.poll();
                smallSiz--;
                sum -= tmp;
                big.add(tmp);
                bigSiz++;
            }
            // 先放到big里 然后从big里面拿一个放到small就刚好k-2个
            if (i+dist < n) {
                big.add(nums[i+dist]);
                bigSiz++;
            }
            // 要从big里拿一个 访问big.peek()之前要先更新big
            updateBigPeek();
            int tmp = big.poll();
            bigSiz--;
            sum += tmp;
            small.add(tmp);
            smallSiz++;

            res = Math.min(res, nums[i] + nums[0] + sum);
        }
        return res;

    }

    // 每次访问small.peek()之前都要先更新small
    public void updateSmallPeek() {
        // 如果small.peek()已经被删除了 那么就把它从small里移除 直到small.peek()是未被删除的元素
        while (smallSiz > 0 && smallMark.getOrDefault(small.peek(), 0) > 0) {
            int tmp = small.poll();
            smallMark.merge(tmp, -1, Integer::sum);
        }
    }

    public void updateBigPeek() {
        while (bigSiz > 0 && bigMark.getOrDefault(big.peek(), 0) > 0) {
            int tmp = big.poll();
            bigMark.merge(tmp, -1, Integer::sum);
        }
    }
}

这个方法高效地使用了堆结构来保持每次窗口移动后,都能快速地选择出当前窗口中的k-2个最小数,而HashMap的标记删除机制则可以绕过优先队列不支持直接删除的限制。通过这个算法,你可以在移动窗口的过程中,不断更新当前窗口的最小值和,最终得到包含`nums[0]`在内的最小成本和。

思考1:为什么要用大顶堆只用小顶堆会怎么样?

因为小顶堆只能让您迅速访问堆中的最小值,而不是最大值。因此,如果窗口中有一个更小的数字需要加入到已满的小顶堆中(这时候我们需要替换掉小顶堆中最大的数字),您需要一种方式来找到小顶堆中的最大值,而大顶堆允许我们做到这一点。 

思考2:bigMark.merge(tmp, -1, Integer::sum)这个是干什么

在Java中的 PriorityQueue 并没有提供直接删除特定元素的操作,而是只提供了删除堆顶元素的操作。为了解决这个问题,bigMark 的用途是实现“延迟删除”,这个技巧通常在优先队列中删除非顶部元素时使用。

  • bigMark 是一个 HashMap,它的键是元素值,值是该元素被标记删除的次数。
  • 当我们要从优先队列 big 中删除一个元素时,我们不能直接删除它,因为它可能不在堆顶。
  • 所以我们在 bigMark 中对这个元素的删除次数加一。这个标记表示元素已经被逻辑上删除,尽管它仍在优先队列中。
  • merge 方法是一个合并函数,它会检查 HashMap 中是否存在键 tmp
    • 如果存在,它会使用提供的合并函数 Integer::sum 将当前值与给定值相加。
    • 如果没有找到键 tmp,它会插入键值对 tmp -> -1
  • 在这个场景中,merge 方法用 -1 更新 tmp 的删除次数。每次 tmp 出现在堆顶时,这个标记都会被检查。如果标记表示该元素被删除(即删除计数大于零),这个元素将会从堆中弹出,同时更新它在 bigMark 中的标记。
// 假设堆中有一个元素值为 5,现在我们要删除它:
int tmp = 5;
bigMark.merge(tmp, 1, Integer::sum); // 标记 tmp 为已删除

// 当我们后续从堆中得到堆顶元素时:
updateBigPeek(); // 在访问堆顶前更新堆

// updateBigPeek 的实现会检查堆顶元素是否被标记为已删除,如果是,就将其从堆中移除,
// 并在 bigMark 中更新其计数:
public void updateBigPeek() {
    while (bigSiz > 0 && bigMark.getOrDefault(big.peek(), 0) > 0) {
        int tmp = big.poll(); // 弹出堆顶元素
        bigMark.merge(tmp, -1, Integer::sum); // 更新 bigMark,减少删除计数
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

即插即用篇 | AKConv:具有任意采样形状和任意参数数量的卷积核

基于卷积操作的神经网络在深度学习领域取得了显著的成果,但标准卷积操作存在两个固有缺陷。一方面,卷积操作受限于局部窗口,无法捕捉其他位置的信息,而其采样形状是固定的。另一方面,卷积核的大小固定为kk,呈固定的正方形形状,而参数数量往往随大小呈平方增长。显然,不…

章鱼网络 Community Call #17|打造全新 Omnity 跨链协议

香港时间2024年1月8日12点&#xff0c;章鱼网络举行第17期 Community Call。 对于 Octopus Community 而言&#xff0c;2023年是一个分水岭。我们如期兑现我们的承诺&#xff0c;成功上线了包括 $NEAR Restaking 和 Adaptive IBC 在内的完整的 Octopus 2.0。 自从我们在2023年…

编曲学习:Cubase12导入Cubasis工程的方法!

Steinberg 发布 Cubasis 3 项目导入器&#xff0c;可将 Cubasis 的项目导入到 Cubase 使用https://m.midifan.com/news_body.php?id35635 我偶然看到这个文章&#xff0c;不过发现Cubase12默认好像没有这个选项&#xff0c;心想着要是移动端能和PC端同步&#xff0c;感觉会挺…

腾讯云服务器价格查询,2024更新

腾讯云服务器租用优惠价格表&#xff1a;轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;2核4G5M带宽756元三年、轻量4核8G12M服务器646元15个月&#xff1b;云服务器CVM S5实例2核2G配置280.8元一年、2核4G…

力扣509. 斐波那契数

动态规划 思路&#xff1a; 斐波那契数通式&#xff1a;F(n) F(n - 1) F(n - 2)&#xff1b;以此为状态转移方程&#xff0c;对其进行动态规划&#xff1b;边界条件&#xff1a; F(0) 0F(1) 1使用两个变量来存储上一组结果&#xff1b; class Solution { public:int fib(…

触摸屏监控双速电动机-PLC I/O电路设计

PLC的输入接线电路图 PLC的输入接线电路如图1-21所示。24VDC电源选用0.7mm2的棕色和蓝色软铜导线&#xff0c;弱电信号线用0.5~0.7mm2的黑色或者白色软铜导线。 PLC输入接线图 PLC的输出接线电路图 PLC的输出接线电路如图1-22所示。AC220V接触器型号为CJX2-12&#xff0c;线…

Kubernetes实战(十八)-Pod配置污点和容忍

1 污点 1.1 污点简介 亲和性调度的方式都是站在Pod的角度上&#xff0c;通过在Pod上增加属性来将Pod调度到到指定的节点上&#xff0c;其实也可以站在Node节点的角度上&#xff0c;通过给Node节点设置属性&#xff0c;来决定是否允许Pod调度过来&#xff0c;这就是污点。 No…

数据结构代码实现 —— 单链表【Java】

单链表的概述及性质等在篇不做赘述&#xff0c;有需要可移步以下文章&#xff1a; 《数据结构 C语言版 严蔚敏 第2版》&#xff1a;线性表https://blog.csdn.net/weixin_43551213/article/details/134048025 以下仅展示使用 Java 实现单链表 结点结构定义&#xff1a; publ…

国外邮箱服务:功能、作用与价格全面解析

不同于我们熟悉的国内邮箱服务&#xff08;如163、QQ邮箱等&#xff09;&#xff0c;国外邮箱通常指的是海外提供商所提供的电子邮件服务&#xff0c;如谷歌的Gmail、微软的Outlook、雅虎的Yahoo Mail等&#xff0c;当然也有特殊的比如Zoho Mail邮箱&#xff0c;国内外双版本&a…

Docker(四)操作容器

作者主页&#xff1a; 正函数的个人主页 文章收录专栏&#xff1a; Docker 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01; 操作 Docker 容器 容器是 Docker 又一核心概念。 简单的说&#xff0c;容器是独立运行的一个或一组应用&#xff0c;以及它们的运行态环境…

算法专题[递归-搜索-回溯-2-DFS]

算法专题[递归-搜索-回溯-2-DFS] 一.计算布尔二叉树的值&#xff1a;1.思路一&#xff1a;2.GIF题目解析 二.求根节点到叶子节点的数字之和1.思路一&#xff1a;2.GIF题目解析 三.二叉树剪枝1.思路一&#xff1a;2.GIF题目解析 四.验证二叉搜索树1.思路一&#xff1a;2.GIF题目…

C++学习笔记——指针

1&#xff0c;指针的基本概念 指针的作用&#xff1a;可以通过指针间接访问内存 内存的编号是从0开始记录的&#xff0c;一般用十六进制数字表示可以利用指针变量保存地址 上图中的p就是a变量的指针&#xff0c;也可以记作*a 2&#xff0c;指针变量的定义和使用 指针变量定…

AI大模型开发架构设计(3)——如何打造自己的大模型

文章目录 如何打造自己的大模型1 新时代职场人应用AIGC的5重境界2 人人需要掌握的大模型原理职场人都能听懂的大语音模型的训练过程职场人都能听得懂的大语言模型的Transformer推理过程 3 如何构建自己的大模型需要具备三个方面的能力LangChain是什么&#xff1f;LangChain主要…

【精选】中间件 tomcat漏洞复现

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【python】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏…

在腾讯云买了云服务器和域名如何快速建网站?

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com分享使用腾讯云服务器建站教程&#xff0c;…

基于SQL的可观测性现状观察

本文字数&#xff1a;8975&#xff1b;估计阅读时间&#xff1a;23 分钟 作者&#xff1a;Ryadh Dahimene 审校&#xff1a;庄晓东&#xff08;魏庄&#xff09; 本文在公众号【ClickHouseInc】首发 1375年的加泰罗尼亚地图所展示的地中海地区。通商媒介语&#xff08;Lingua F…

深度学习记录--正则化(regularization)

什么是正则化&#xff1f; 正则化(regularization)是一种实用的减少方差(variance)的方法&#xff0c;也即避免过度拟合 几种正则化的方法 L2正则化 又被称为权重衰减(weight dacay) 在成本函数中加上正则项&#xff1a; 其中 由于在w的更新过程中会递减&#xff0c;即权…

STL---Stack和Queue

一、stack的介绍和使用 &#xff08;1&#xff09;介绍 翻译: &#xff08;1&#xff09;stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。 &#xff08;2&#xff09; stack是作为容器…

【2020】百度校招Java研发工程师笔试卷(第二批)算法题

贴一下我去年9月份写的博客 三道编程题&#xff0c;一道数学题&#xff0c;两道图论&#xff0c;哎嘿嘿&#xff0c;我就是不会做&#xff0c;哎嘿嘿&#xff0c;哭了。。。 一.最小值 牛牛给度度熊出了一个数学题&#xff0c;牛牛给定数字n,m,k&#xff0c;希望度度熊能找到…

【小沐学GIS】基于C#绘制三维数字地球Earth(OpenGL)

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第二期3【小沐学GIS】…