首页 > 其他分享 >20天 hot 100 速通计划-day16

20天 hot 100 速通计划-day16

时间:2023-08-24 21:44:05浏览次数:45  
标签:20 速通 nums int 位置 到达 中位数 hot 区间

295. 数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

  • 例如 arr = [2,3,4] 的中位数是 3
  • 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5

实现 MedianFinder 类:

  • MedianFinder() 初始化 MedianFinder 对象。
  • void addNum(int num) 将数据流中的整数 num 添加到数据结构中。
  • double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

示例 1:

输入
["MedianFinder", "addNum", "addNum", "findMedian", "addNum", "findMedian"]
[[], [1], [2], [], [3], []]
输出
[null, null, null, 1.5, null, 2.0]

解释
MedianFinder medianFinder = new MedianFinder();
medianFinder.addNum(1);    // arr = [1]
medianFinder.addNum(2);    // arr = [1, 2]
medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2)
medianFinder.addNum(3);    // arr[1, 2, 3]
medianFinder.findMedian(); // return 2.0

提示:

  • -105 <= num <= 105
  • 在调用 findMedian 之前,数据结构中至少有一个元素
  • 最多 5 * 104 次调用 addNumfindMedian

数据结构构造,纯技巧题,主要是堆的应用:使用一个大顶堆和一个小顶堆来维护整个数据结构。大顶堆用来保存较小一半的元素,小顶堆用来保存较大一半的元素。通过这种方式,中位数可以在常数时间内计算得出。

要求中位数,可以将列表拆分成两个区间,看作求小区间最大值和大区间最小值的中间值

  • 用大顶堆维护小区间元素,保证常数时间可以得到小区间最大值
  • 用小顶堆维护大区间元素,保证常数时间可以得到大区间最小值
  • 保证 | 小区间 | >= | 大区间 |,求中位数
    • 小区间最大值 + 大区间最小值 / 2 = 中位数 ( | 小区间 | == | 大区间 |,列表长度为偶数时)
    • 小区间最大值 = 中位数 ( | 小区间 | > | 大区间 |,列表长度为奇数时)

抽象成两个操作

  • 增加元素:每当向数据结构中添加一个元素时,我们将元素插入到适当的堆中,并平衡两个堆的大小,以保持中位数的计算正确。
  • 计算中位数:当需要计算中位数时,如果大顶堆的大小大于小顶堆的大小,中位数为大顶堆的堆顶元素;否则,中位数为大顶堆和小顶堆的堆顶元素的平均值。
class MedianFinder {
private:
    priority_queue<int> maxHeap;  // 保存较小一半的元素,大顶堆
    priority_queue<int, vector<int>, greater<int>> minHeap;  // 保存较大一半的元素,小顶堆

public:
    MedianFinder() {
        // 构造函数,初始化最大堆和最小堆
    }

    void addNum(int num) {
        // 向数据结构中添加一个元素
        if (maxHeap.empty() || num <= maxHeap.top()) {
            // 如果大顶堆为空或者元素num小于等于大顶堆堆顶元素,将元素num插入大顶堆
            maxHeap.push(num);
            // 平衡堆的大小
            if (maxHeap.size() > minHeap.size() + 1) {
                // 如果大顶堆的大小超过小顶堆的大小1个,将大顶堆堆顶元素移动到小顶堆
                minHeap.push(maxHeap.top());
                maxHeap.pop();
            }
        } else {
            // 元素num大于大顶堆堆顶元素,将元素num插入小顶堆
            minHeap.push(num);
            // 平衡堆的大小
            if (minHeap.size() > maxHeap.size()) {
                // 如果小顶堆的大小超过大顶堆的大小,将小顶堆堆顶元素移动到大顶堆
                maxHeap.push(minHeap.top());
                minHeap.pop();
            }
        }
    }

    double findMedian() {
        // 找到数据结构中的中位数
        if (maxHeap.size() > minHeap.size()) {
            // 如果大顶堆的大小大于小顶堆的大小,中位数为大顶堆的堆顶元素
            return maxHeap.top();
        } else {
            // 否则,大顶堆和小顶堆的大小相等,中位数为大顶堆和小顶堆的堆顶元素的平均值
            return (maxHeap.top() + minHeap.top()) / 2.0;
        }
    }
};

贪心算法

121. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 104

只能买进一次和卖出一次(无状态),低买高卖就是最优解(单步决策最优解唯一),而贪心本质上也算低买高卖。我们总是选择当前最优的,即在当前价格之前找到最小的价格作为买入价格,然后在当前价格之后找到最大的价格作为卖出价格。这样就能保证我们选择的价格对能够获得最大利润的贡献最大。

贪心的目标就是扫一遍出结果,过程就是丢芝麻捡西瓜

目标是扫一次出结果

→ 结构固定,关键在于明确自己想要贪什么

→ 要贪利润,而利润 = 当前价格 - 买入价格,要保证利润最大,就要控制买入价格尽量小,保证利润尽量大(双变量)

→ 维护一个最小的买入价格和一个最大利润,每次扫描都实时更新最小买入和最大利润

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int minVal = prices[0]; // 初始化最小值为第一个价格
        int maxProfit = 0; // 初始化最大利润为0

        // 遍历数组
        for (int i = 1; i < prices.size(); i++) {
            // 如果当前的价格比最小值小,更新最小值
            if (prices[i] < minVal) {
                minVal = prices[i];
            }
            // 如果当前的价格减去最小值的差值大于最大利润,更新最大利润
            else if (prices[i] - minVal > maxProfit) {
                maxProfit = prices[i] - minVal;
            }
        }

        return maxProfit;
    }
};

55. 跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 105

说实话,本题的贪心特征不明显(判断可行性,可以转换为最值问题,但而非直接求最值)

目标是扫一次出结果

→ 结构固定,关键在于明确自己想要贪什么

→ 要贪距离:我们要尽可能地贪距离,即在每一步都选择能够到达最远距离的位置。

→ 维护一个最远可以到达的位置:我们使用一个变量farthest来表示当前最远可以到达的位置。
每次遍历更新最远可以到达的位置:在遍历过程中,我们每次都更新farthest的值,以保证它表示的是当前最远可以到达的位置。

  • 若最远可以到达的位置达到或超过数组末尾:如果最远可以到达的位置达到或超过数组末尾,说明可以成功到达末尾,返回true。
  • 遍历结束后仍未到达末尾:如果遍历结束时,最远可以到达的位置仍未达到末尾,说明无法成功到达末尾,返回false。
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size(); // 获取数组长度
        int farthest = 0; // 最远可以到达的位置

        for (int i = 0; i < n; ++i) { // 遍历数组
            if (i <= farthest) { // 如果当前位置能够到达
                farthest = max(farthest, i + nums[i]); // 更新最远可以到达的位置
                if (farthest >= n - 1) { // 如果最远可以到达的位置达到或超过数组末尾
                    return true; // 返回true表示可以到达末尾
                }
            }
        }
        return false; // 遍历结束后仍未到达末尾,返回false表示无法到达末尾
    }
};

45. 跳跃游戏 II

给定一个长度为 n0 索引整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

示例 1:

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

输入: nums = [2,3,0,1,4]
输出: 2

提示:

  • 1 <= nums.length <= 104
  • 0 <= nums[i] <= 1000
  • 题目保证可以到达 nums[n-1]

相较于上一题,本题保证了一定能到,要求最小跳跃步数,本题相较于上一题的贪心性质更明显(求最值)

目标是扫一次出结果

→ 结构固定,关键在于明确自己想要贪什么

→ 要贪距离:因为一定能到结尾,我们要尽可能地贪距离,即在每一步都选择能够到达最远距离的位置,以保证步数最小

→ 维护一个最远可以到达的位置:我们使用一个变量 maxPosition 来表示当前最远可以到达的位置。扫描过程中记录跳的步数

int jump(vector<int>& nums) {
    int n = nums.size();
    int end = 0;  // 当前能够到达的最远位置
    int jump = 0;  // 跳跃的步数

    int maxPosition = 0;  // 下一步能够到达的最远位置
    for (int i = 0; i < n - 1; ++i) {
        maxPosition = max(maxPosition, i + nums[i]);
        if (i == end) {
            end = maxPosition;
            ++jump;
        }
    }

    return jump;
}

763. 划分字母区间

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

返回一个表示每个字符串片段的长度的列表。

示例 1:

输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 

示例 2:

输入:s = "eccbbbbdec"
输出:[10]

提示:

  • 1 <= s.length <= 500
  • s 仅由小写英文字母组成

看似出现了两次循环,但是贪心的目标仍然是扫一遍出结果,结构仍然固定

之所以出现两次循环是因为需要将过程划分为代价记录和贪心执行两个过程是可行的。

首先,代价记录过程(cost recording process)是为了记录每个字符的最后出现位置。我们使用一个长度为26的数组last来存储每个字符的最后出现位置。遍历字符串s,对于每个字符s[i],将其最后出现的位置记录在last数组中的对应位置last[s[i] - 'a']。通过代价记录过程,我们能够得到每个字符的最后出现位置。

然后,贪心执行过程(greedy execution process)是根据最后出现位置来划分字母区间。我们使用两个指针startend来表示当前字母区间的起始位置和结束位置。初始时,将startend都设置为0。接下来,我们遍历字符串s,对于每个字符s[i],更新end的值为s[i]的最后出现位置last[s[i] - 'a']和当前end的较大值。如果当前遍历到的字符s[i]就是当前字母区间的结束位置,即i == end,则说明找到了一个字母区间,将该字母区间的长度end - start + 1加入到结果数组partitions中,并更新starti + 1,开始下一个字母区间的搜索。通过贪心执行过程,我们能够得到所有的字母区间。

最后,将代价记录过程和贪心执行过程结合起来,即可得到划分字符串的函数partitionLabels。该函数返回一个整数数组partitions,其中存储了划分字符串s的所有字母区间的长度。

注:贪心的思想在于每次都使得当前的字母区间尽量长,从而最大化划分出的区间数目。

vector<int> partitionLabels(string s) {
    vector<int> last(26); // 用于记录每个字母最后出现的位置
    int length = s.length(); // 字符串的长度
    // 遍历字符串,统计每个字母最后出现的位置
    for (int i = 0; i < length; i++) {
        last[s[i] - 'a'] = i;
    }
    vector<int> partitions; // 用于存储每个划分区间的长度
    int start = 0; // 当前划分区间的起始索引
    int end = 0; // 当前划分区间的结束索引
    // 遍历字符串,根据最后出现的位置来划分字母区间
    for (int i = 0; i < length; i++) {
        end = max(end, last[s[i] - 'a']); // 更新当前区间的结束索引
        if (i == end) { // 当当前索引与结束索引相等时,表示已经到达当前划分区间的末尾
            partitions.push_back(end - start + 1); // 将当前划分区间的长度添加到结果数组中
            start = i + 1; // 更新下一个划分区间的起始索引
        }
    }
    return partitions; // 返回结果数组
}

标签:20,速通,nums,int,位置,到达,中位数,hot,区间
From: https://www.cnblogs.com/ba11ooner/p/17655245.html

相关文章

  • 56th 2023/7/4 模拟赛总结40
    额,这场比赛应该打得算认真,虽然最后因为一些奇怪的因素导致没有拿到所想的排名,但是总体可以首先先思考了很久,T2T3都挺接近正解的,但是因为一些知识点的欠缺二没有打下来如:T3的缩点,还有T2的一部分结论然后当时是把T2暴力拉满,还想哈希卡常过的,结果是低估了数据的强度,被卡的死死的,拿......
  • 『题解』JOISC2022B 京都観光 (Sightseeing in Kyoto)
    AtCoder题目链接Luogu题目链接观察题目,不自觉地想到了dp,但是再一看\(\text{1e5}\)数据范围,意识到大概是\(2^{\text{1e5}}\)的复杂度,绝望了……然后就很自然地想到了最优策略。(思路很巧妙但是我当时没想到。)考虑有三行(或三列),分别记为\(i,j,k\),如果\(j>i\landj>......
  • 2023.8.24 LGJ Round
    A有\(n(n\le750)\)个正整数\((a_i\le10^9)\),你需要删除一些数,使得剩下的数两两加起来都不为质数。若\(a_i+a_j\in\text{prime}\)(这里使用Miller-Rabin即可),将\(i\)和\(j\)连边。我们就是要求一个最大独立集。一般图是求最大独立集是NP问题。但是我们发现去掉所......
  • 2023年8月24日
    1.一个简单的手机号注册JS表单校验的案例为了突出这部分的代码,就不给出样式、图片代码了<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>小兔鲜儿-新鲜惠民快捷!</title><metahttp-equiv="X-UA-Compatible"cont......
  • 网易一面:单节点2000Wtps,Kafka怎么做的?
    文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录博客园版为您奉上珍贵的学习资源:免费赠送:《尼恩Java面试宝典》持续更新+史上最全+面试必备2000页+面试必备+大厂必备+涨薪必备免费赠送:《尼恩技术圣经+高并发系列PDF》,帮你实现技术自由,完成职业升级,薪......
  • Windows Server2008R2 服务器Paged Pool占用过高的问题
    这台服务器一直运行的好好的,但最近发现经常内存占用了99%,重启后过几天内存又涨到99%。运行的应用软件占的内存并不高,任务管理器所有进程占用内存加起来也远远不到99%。下载了RamMap,发现是PagedPool占用了绝大多数的内存; 下载poolmon.exe,终端中运行poolmon.exe-p-b,再按下......
  • 「SDOI2016」排列计数tj(附压行代码)
    现在求有多少种长度为n的序列A,满足以下条件:1~n这n个数在序列中各出现了一次若第i个数A[i]的值为i,则称i是稳定的。序列恰好有m个数是稳定的满足条件的序列可能很多,序列数对10^9+7取模。输入第一行一个数T,表示有T组数据。接下来T行,每行两个整数n、......
  • NOIP 2023 周赛 3 题解
    A-Permutationsummarization构造一个\(1\dotsn\)的排列使\(\prod\limits_{i=1}^n\operatorname{lcm}(p_i,p_{(i\bmodn)+1})\)最大。solution不难发现上式最大为\(\prod\limits_{i=1}^ni^2\),即让所有\(\operatorname{lcm}(x,y)=x\timesy\),那么只要使相邻两个数互质......
  • 2065:【例2.2】整数的和
    2065:【例2.2】整数的和时间限制:1000ms      内存限制:65536KB提交数:69280   通过数:58746【题目描述】求3个整数的和。输入a、b、c这3个整数,求它们的和。【输入】3个整数。【输出】三个数的和。【输入样例】123【输出样例】6#includ......
  • 2066:【例2.3】买图书
    2066:【例2.3】买图书时间限制:1000ms      内存限制:65536KB提交数:87778   通过数:51324【题目描述】已知小明有n元,他买了一本书,这本书原价为m元,现在打8折出售。求小明还剩多少钱(保留2位小数)。【输入】输入n,m。【输出】小明还剩多少钱(保留2......