代码随想录算法训练营——贪心算法:122.买卖股票的最佳时机 II;55. 跳跃游戏;45.跳跃游戏 II;1005.K次取反后最大化的数组;34. 加油站;135. 分发糖果

122.买卖股票的最佳时机 II

力扣题目链接(opens new window)

给定一个数组,它的第  i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

  • 输入: [7,1,5,3,6,4]
  • 输出: 7
  • 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

我的算法:

分析:分类讨论所有情况!

int maxProfit(int* prices, int pricesSize) {
    int flag=0;//表示手上有没有股票
    int comeinprice;//表示成本
    int profit=0;
    if(pricesSize==1) return 0;
    
    for (int i=1;i<pricesSize;i++){
        int line=prices[i]-prices[i-1];
        
        if(line>=0 && flag==0){//下一阶段股票涨了,且手上没有股票——可以买
            comeinprice=prices[i-1];
            flag=1;
        }
        //下一阶段涨了,且手上有股票——不需要操作
        //下一阶段跌了,手上有股票——卖 
        else if(line <0 && flag==1 ){
            flag=0;
            profit+= prices[i-1] - comeinprice;
        }
        //下一阶段跌了,手上没股票——不需要操作 

        if ( i==pricesSize-1 && flag==1){//已经到最后一天了++手上有股票必须卖掉
            profit+=prices[i]-comeinprice;
        }

    }
    return profit;
    
}

 算法2:

使用贪心的时候 尽量从最最最最局部的开始考虑

因为实际上可以分成每一天为一段!只考虑收集正的段,就不用考虑我的方法中对于flag和最后一天情况的讨论了!

int maxProfit(int* prices, int pricesSize) {
    int profit=0;
    if(pricesSize==1) return 0;
    
    for (int i=1;i<pricesSize;i++){
        if(prices[i]-prices[i-1]>0 ){
            profit+=prices[i]-prices[i-1];
        }
        

    }
    return profit;
    
}

 

55. 跳跃游戏

力扣题目链接(opens new window)

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例  1:

  • 输入: [2,3,1,1,4]
  • 输出: true
  • 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

分析:

找最大的覆盖范围

bool canJump(int* nums, int numsSize) {
    int max=0;//记录能跳到的最大位置——也就是反映能跳到的范围
    int i=0;
    while(i<=max && i<=numsSize-2){//在能跳到的范围内进行遍历
        max=fmax(max, nums[i]+i);
        i++;
    }
    
    if(max>=numsSize-1 )return true;
    return false;
    
}                   

45.跳跃游戏 II

力扣题目链接(opens new window)

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

  • 输入: [2,3,1,1,4]
  • 输出: 2
  • 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳  1  步,然后跳  3  步到达数组的最后一个位置。
  • 说明: 假设你总是可以到达数组的最后一个位置。

分析:

直觉来说需要往最远的地方跳——为什么不行呢?——往最远的地方跳了之后,可能中间路途上跳过了一个非常大的值,而后面的地方反而值非常的小——需要维护最远可到达位置

这轮遍历——找到下一轮最远可到达位置

从这轮终点——再到刚刚找到的最远可到达位置(直到最远可到达位置超过终点)

……

**注意:记录的内容是什么,各个循环遍历等号到底是怎么取

int jump(int* nums, int numsSize) {
    if(numsSize==1) return 0;
    //遍历当前范围,找当前范围内最大的下一个范围
    int start=0;//记录这一轮的开始结点
    int cur=nums[0];//当前最远可以到哪个结点
    int max_next_move=0;//下一次最远可以到哪个结点
    int ans=1;

    while (true){//因为必然可以跳到,所以可以while true
        int i;
        for (i=start;i<=cur;i++){
            max_next_move=fmax(max_next_move,i+nums[i]);
            if(i>=numsSize-1) return ans;
        }
        //这轮没有能够达到最后一个位置,去下一轮
        cur=max_next_move;
        start=i;//从上一个失败的i往后,到max next move
        ans++;
        if(cur>= numsSize -1) return ans;
    }

    return ans;
}

1005.K次取反后最大化的数组

力扣题目链接(opens new window)

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

以这种方式修改数组后,返回数组可能的最大和。

示例 1:

  • 输入:A = [4,2,3], K = 1
  • 输出:5
  • 解释:选择索引 (1) ,然后 A 变为 [4,-2,3]。
  • 输入:A = [3,-1,0,2], K = 3
  • 输出:6
  • 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。

分析:

贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

局部最优可以推出全局最优。

那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。

那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

虽然这道题目大家做的时候,可能都不会去想什么贪心算法,一鼓作气,就AC了。

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小(很妙!)
  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K--
  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
  • 第四步:求和
int cmp(const void *a,const void *b)
{
    return abs(*(int *)b)-abs(*(int *)a);
}

int largestSumAfterKNegations(int* nums, int numsSize, int k) {
    qsort(nums, numsSize, sizeof(int),cmp);
    int sum=0;
    for (int i=0;i<numsSize;i++){
        if(k>0 && nums[i]<0) {
            nums[i]=-nums[i];
            k--;
        }
        while(k>0 && i==numsSize-1 ){
            nums[i]=-nums[i];
            k--;
        }

        sum=sum+nums[i];
    }
    return sum;
}

34. 加油站

力扣题目链接(opens new window)

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明:

  • 如果题目有解,该答案即为唯一答案。
  • 输入数组均为非空数组,且长度相同。
  • 输入数组中的元素均为非负数。

示例 1: 输入:

  • gas = [1,2,3,4,5]
  • cost = [3,4,5,1,2]

输出: 3 解释:

  • 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
  • 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
  • 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
  • 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
  • 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
  • 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
  • 因此,3 可为起始索引。

分析:

尝试用累积的方法求解

如果总油量减去总消耗大于等于零那么一定可以跑完一圈。——sum

说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {
    int sum=0;//求差值,如果sum<0,肯定不能跑完一圈
    int record_start;
    int zonal_sum=-1;//通过zonal_sum找到能累积起最大值的起点
    for(int i=0;i<gasSize;i++){
        if(zonal_sum<0){
            zonal_sum=0;
            record_start=i;
        }

        gas[i]=gas[i]-cost[i];
        sum+=gas[i];
        zonal_sum+=gas[i];
        
    }
    if(sum<0) return -1;
    return record_start;
}

135. 分发糖果

力扣题目链接(opens new window)

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

  • 输入: [1,0,2]
  • 输出: 5
  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

分析:

采用了两次贪心的策略:

  • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
  • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。

这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。

这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼

先确定右边评分大于左边的情况(也就是从前向后遍历)

此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果

局部最优可以推出全局最优。

**注意对相等情况的处理

再确定左孩子大于右孩子的情况(从后向前遍历)

如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。

那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

局部最优可以推出全局最优。

代码:

int candy(int* ratings, int ratingsSize) {
    int *c=malloc(sizeof(int)*ratingsSize);
    c[0]=1;
    for (int i=0;i<ratingsSize-1;i++){
        int l=ratings[i];
        int r=ratings[i+1];
        if(r>l) c[i+1]=c[i]+1;
        else c[i+1]=1;
    }
    int sum=c[ratingsSize-1];
    for (int i=ratingsSize-1;i>=1;i--){
        int l=ratings[i-1];
        int r=ratings[i];
        if(l>r) c[i-1]=fmax(c[i]+1,c[i-1]);
        sum+=c[i-1];
    }
    return sum;

}

  • 10
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值