Leetcode 刷题 day03

因为很久没有刷过题了,打算这段时间把每种类型的过一遍熟悉一下——好了下面进入刷题模式(学习模式)

继续上次的会议室练习,这次我们换用贪心算法——扫描线来解决问题

leetcode上扫描线的概念如下所示: 

该题用到的是扫描线技巧。(学习howard大佬的思想,其原文链接在这:. - 力扣(LeetCode)

本题题意求的是会议冲突数量的最大值,就是某一时刻,最多有多少个会议在同时进行。

如图,我们想象有个时间线从左往右走扫描,同时维护一个变量num_of_ongoing_meeting,表示当前正在进行的会议数量。

每遇到一个会议的起始端点,就将num_of_ongoing_meeting加1
每遇到一个会议的结束端点,就将num_of_ongoing_meeting减1
num_of_ongoing_meeting的最大值就是本题的答案。

(不过看了代码感觉空间占用很多...)


//贪心算法——扫描线
//寻求会议冲突数量的最大值,即在某一时刻,最多会有多少个会议在同时进行。
class Solution{
public:
    int minMeetingRooms(vector<vector<int>>& intervals){
        int last_end = 0;
        //starts[i]:有多少个会议在i时刻开始
        //由本题条件:0<=starti<endi<=10^6
        //可得,会议的结束时间最晚维10^6
        int starts[1000001]={0};
        int ends[1000001]={0};
        for(const auto& interval:intervals){
            starts[interval.at(0)]++;
            ends[interval.at(1)]++;
            last_end = std::max(last_end,interval.at(1));
            //记录最晚的结束时间点
        }
        //所需会议室的最少数量
        int min_num_of_room = 0;
        //当前时刻有多少个会议正在进行
        int num_of_ongoing_meeting = 0;
        for(int i=0;i<=last_end;i++){
            num_of_ongoing_meeting += starts[i];
            num_of_ongoing_meeting -= ends[i];
            min_num_of_room = std::max(min_num_of_room,num_of_ongoing_meeting);
        }
        return min_num_of_room;
    }
};

 接下来是跳跃游戏一系列题目, 首先是跳跃游戏1

方法一:贪心
我们可以用贪心的方法解决这个问题。

(官方解题)设想一下,对于数组中的任意一个位置 y,我们如何判断它是否可以到达?根据题目的描述,只要存在一个位置 x,它本身可以到达,并且它跳跃的最大长度为 x+nums[x],这个值大于等于 y,即 x+nums[x]≥y,那么位置 y 也可以到达。

换句话说,对于每一个可以到达的位置 x,它使得 x+1,x+2,⋯,x+nums[x] 这些连续的位置都可以到达。

这样以来,我们依次遍历数组中的每一个位置,并实时维护 最远可以到达的位置。对于当前遍历到的位置 x,如果它在 最远可以到达的位置 的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 x+nums[x] 更新 最远可以到达的位置。

在遍历的过程中,如果 最远可以到达的位置 大于等于数组中的最后一个位置,那就说明最后一个位置可达,我们就可以直接返回 True 作为答案。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。

以题目中的示例一:

[2, 3, 1, 1, 4]

为例:

我们一开始在位置 0,可以跳跃的最大长度为 2,因此最远可以到达的位置被更新为 2;

我们遍历到位置 1,由于 1≤2,因此位置 1 可达。我们用 1 加上它可以跳跃的最大长度 3,将最远可以到达的位置更新为 4。由于 4 大于等于最后一个位置 4,因此我们直接返回 True。

代码:

//贪心算法
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        int rightmost = 0;
        for(int i=0;i<n;++i){
            if(i<=rightmost){
                rightmost = max(rightmost,nums[i]+i);
                if(rightmost >= n-1){
                    return true;
                }
            }
        }
        return false;
    }
};

 

方法一:正向查找可到达的最大位置

方法一虽然直观,但是时间复杂度比较高,有没有办法降低时间复杂度呢?

如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。

例如,对于数组 [2,3,1,2,4,2,3],初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。

从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。

在具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1。

在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。

//贪心算法一:正向查找可到达的最大位置
class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size(),step=0,maxPos=0,end=0;
        for(int i=0;i<n-1;++i){
            if(maxPos >= i){
                maxPos = max(maxPos,i+nums[i]);
                if(i==end){
                    end = maxPos;
                    ++step;
                }
            }
        }
        return step;
    }
};

(绝佳思路解释:

1、如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点。 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新。

2、如果从这个 起跳点 起跳叫做第 1 次 跳跃,那么从后面 3 个格子起跳 都 可以叫做第 2 次 跳跃。

3、所以,当一次 跳跃 结束时,从下一个格子开始,到现在 能跳到最远的距离,都 是下一次 跳跃 的 起跳点。
     对每一次 跳跃 用 for 循环来模拟。

     跳完一次之后,更新下一次 起跳点 的范围。
     在新的范围内跳,更新 能跳到最远的距离。
4、记录 跳跃 次数,如果跳到了终点,就得到了结果。

from 作者:Ikaruga
链接:https://leetcode.cn/problems/jump-game-ii/solutions/1/45-by-ikaruga/ )

//代码2
class Solution {
public:
    int jump(vector<int>& nums) {
        int start = 0,end = 1,ans = 0;
        while(end<nums.size()){
            int maxPos = 0;
            for(int i=start;i<end;i++){
                maxPos = max(maxPos,i+nums[i]);
            }
            start = end;
            end = maxPos+1;
            ans++;
        }
        return ans;
    }
};

其实就是每一个下标,都对应了两条路,选择了每一条路之后,接着各自又有两个分岔口......由此循环往复,直到找到了对应元素为0的点。这就可以联想到深度优先搜索和广度优先搜索。

方法一:广度优先搜索

//广度优先搜索bfs
class Solution {
public:
    bool canReach(vector<int>& arr, int start) {
        if(arr[start]==0){
            return true;
        }
        int n=arr.size();
        vector<bool> used(n);
        queue<int> q;
        q.push(start);
        used[start] = true;
        while(!q.empty()){
            int u = q.front();
            q.pop();
            if(u+arr[u]<n && !used[u+arr[u]]){
                if(arr[u+arr[u]]==0){
                    return true;
                }
                q.push(u+arr[u]);
                used[u+arr[u]] = true;
            }
            if(u-arr[u]>=0 && !used[u-arr[u]]){
                if(arr[u-arr[u]]==0){
                    return true;
                }
                q.push(u-arr[u]);
                used[u-arr[u]] = true;
            }
        }
        return false;
    }
};

解决方法:动态规划+前缀和

class Solution{
public:
    bool canReach(string s,int minJump,int maxJump){
        int n = s.size();
        vector<int> f(n),pre(n);
        f[0] = 1;
        for(int i=0;i<minJump;++i){
            pre[i] = 1;
        }
        for(int i=minJump;i<n;i++){
            int left = i-maxJump,right=i-minJump;
            if(s[i]=='0'){
                int total = pre[right]-(left<=0?0:pre[left-1]);
                f[i] = (total!=0);
            }
            pre[i] = pre[i-1] + f[i];
        }
        return f[n-1];
    }
};

前缀和(第一次听到这个方法+相应的解题,学到了!)

哈希表的简单学习

 代码如下所示:(即把所有可能的数字从大到小排列,如果当前的num比valueSymbols大,则结果增添相应字符串)

//模拟 即//贪心——哈希表
const pair<int,string> valueSymbols[]={
    {1000,"M"},
    {900,  "CM"},
    {500,  "D"},
    {400,  "CD"},
    {100,  "C"},
    {90,   "XC"},
    {50,   "L"},
    {40,   "XL"},
    {10,   "X"},
    {9,    "IX"},
    {5,    "V"},
    {4,    "IV"},
    {1,    "I"}
};

class Solution {
public:
    string intToRoman(int num) {
        string roman;
        for(const auto &[value,symbol] : valueSymbols){
            while(num>=value){
                num-=value;
                roman+=symbol;
            }
            if(num==0){
                break;
            }
        }
        return roman;
    }
};

  • 14
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值