首页 > 编程语言 >代码随想录算法训练营第三十四天|134. 加油站 135. 分发糖果 860.柠檬水找零 406.根据身高重建队列

代码随想录算法训练营第三十四天|134. 加油站 135. 分发糖果 860.柠檬水找零 406.根据身高重建队列

时间:2024-10-14 20:47:08浏览次数:8  
标签:10 return gas candyVec 随想录 第三十四 找零 加油站 身高


134. 加油站

在一条环路上有 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 可为起始索引。

示例 2: 输入:

  • gas = [2,3,4]

  • cost = [3,4,3]

  • 输出: -1

  • 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。

思路:需要承认没有贪心的思路,甚至看完解析对本题贪心的思路也一知半解,没能完全理解。先自己实现一遍暴力,然后附上解析思路供回头学习。

暴力代码实现如下:(时间超限A不了)

class Solution:

    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:

        length = len(gas)

        if sum(gas) < sum(cost):

            return -1

        for i in range(length):

            cur = (i+1) % length

            cur_sum = gas[i] - cost[i]

            while cur_sum > 0 and cur != i:     # 如果条件>=0无法通过,可能是答案不唯一了。

                cur_sum += gas[cur] - cost[cur]

                cur = (cur+1) % length

            #if cur == i:                          # 剩油量应为>=0

            if cur == i and cur_sum >= 0:      

                return i

        return -1

解析思路:

首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

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

区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择其实位置了。

那么局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优,找不出反例,试试贪心!

规范代码:

class Solution:

    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:

        curSum = 0  # 当前累计的剩余油量

        totalSum = 0  # 总剩余油量

        start = 0  # 起始位置

        

        for i in range(len(gas)):

            curSum += gas[i] - cost[i]

            totalSum += gas[i] - cost[i]

            

            if curSum < 0:  # 当前累计剩余油量curSum小于0

                start = i + 1  # 起始位置更新为i+1

                curSum = 0  # curSum重新从0开始累计

        

        if totalSum < 0:

            return -1  # 总剩余油量totalSum小于0,说明无法环绕一圈

        return start

全局最优思路:

class Solution:

    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:

        curSum = 0  # 当前累计的剩余油量

        minFuel = float('inf')  # 从起点出发,油箱里的油量最小值

        

        for i in range(len(gas)):

            rest = gas[i] - cost[i]

            curSum += rest

            if curSum < minFuel:

                minFuel = curSum

        

        if curSum < 0:

            return -1  # 情况1:整个行程的总消耗大于总供给,无法完成一圈

        

        if minFuel >= 0:

            return 0  # 情况2:从起点出发到任何一个加油站时油箱的剩余油量都不会小于0,可以从起点出发完成一圈

        

        for i in range(len(gas) - 1, -1, -1):

            rest = gas[i] - cost[i]

            minFuel += rest

            if minFuel >= 0:

                return i  # 情况3:找到一个位置使得从该位置出发油箱的剩余油量不会小于0,返回该位置的索引

        

        return -1  # 无法完成一圈

135. 分发糖果

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

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

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

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

示例 1:

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

示例 2:

  • 输入: [1,2,2]
  • 输出: 4
  • 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

思路:自己没做出来。

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

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

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

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

如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1

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

遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?

因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。

如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。

确定左孩子大于右孩子的情况一定要从后向前遍历!

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

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

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

所以就取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,也比右边candyVec[i + 1]的糖果多

规范代码:

class Solution:

    def candy(self, ratings: List[int]) -> int:

        candyVec = [1] * len(ratings)

        

        # 从前向后遍历,处理右侧比左侧评分高的情况

        for i in range(1, len(ratings)):

            if ratings[i] > ratings[i - 1]:

                candyVec[i] = candyVec[i - 1] + 1

        

        # 从后向前遍历,处理左侧比右侧评分高的情况

        for i in range(len(ratings) - 2, -1, -1):

            if ratings[i] > ratings[i + 1]:

                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1)

        

        # 统计结果

        result = sum(candyVec)

        return result

其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。

采用了两次贪心的策略:

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

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

860.柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:

  • 输入:[5,5,5,10,20]
  • 输出:true
  • 解释:
    • 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
    • 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
    • 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
    • 由于所有客户都得到了正确的找零,所以我们输出 true。

示例 2:

  • 输入:[5,5,10]
  • 输出:true

示例 3:

  • 输入:[10,10]
  • 输出:false

示例 4:

  • 输入:[5,5,10,10,20]
  • 输出:false
  • 解释:
    • 前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
    • 对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
    • 对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
    • 由于不是每位顾客都得到了正确的找零,所以答案是 false。

提示:

  • 0 <= bills.length <= 10000
  • bills[i] 不是 5 就是 10 或是 20

思路:

付钱有三种情况:5-得到一张5元,10-得到一张10元且失去一张5元,20-失去【一张10和一张5】或【三张5】(且得到一张20,20不起作用不计数)

所以我们需要记录5和10的张数,当获得付钱的时候根据以上规则进行加减,如果付钱之后,5元或者10元不够还钱,那么返回False,如果全部成功遍历,返回True

代码实现如下:

class Solution:

    def lemonadeChange(self, bills: List[int]) -> bool:

        five = 0

        ten = 0

        for i in range(len(bills)):

            if bills[i] == 5:

                five += 1

            elif bills[i] == 10:

                if five == 0:

                    return False

                ten += 1

                five -= 1

            elif bills[i] == 20:

                if five == 0 or (ten == 0 and five < 3):

                    return False

                elif ten > 0:

                    five -= 1

                    ten -= 1

                else:

                    five -= 3

        return True

规范 代码:

class Solution:

    def lemonadeChange(self, bills: List[int]) -> bool:

        five = 0

        ten = 0

        twenty = 0

        

        for bill in bills:

            # 情况一:收到5美元

            if bill == 5:

                five += 1

            

            # 情况二:收到10美元

            if bill == 10:

                if five <= 0:

                    return False

                ten += 1

                five -= 1

            

            # 情况三:收到20美元

            if bill == 20:

                # 先尝试使用10美元和5美元找零

                if five > 0 and ten > 0:

                    five -= 1

                    ten -= 1

                    #twenty += 1

                # 如果无法使用10美元找零,则尝试使用三张5美元找零

                elif five >= 3:

                    five -= 3

                    #twenty += 1

                else:

                    return False

        

        return True

406.根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

  • 输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
  • 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
  • 解释:
    • 编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
    • 编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
    • 编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
    • 编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
    • 编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

  • 输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
  • 输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length

题目数据确保队列可以被重建

思路: 自己没想出来,还是看了文字解析,复述一下思路。

首先按数组里的身高进行由高到低的排序,如果相等则按另一属性由低到高排序(需要自己根据题意理解一下)。此时,按照身高由高到低的顺序,以此遍历people数组,按照前面有多少人,将其插入到新数组对应的下标中去。这是因为我们希望得到的新数组里,先被插入的元素先满足了条件,且后插入的元素不会影响先插入的元素在数组里的条件,将身高低的元素插入在旧元素的前面是不影响其条件的。

规范解释:

如果两个维度一起考虑一定会顾此失彼。

对于本题相信大家困惑的点是先确定k还是先确定h呢,也就是究竟先按h排序呢,还是先按照k排序呢?

如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。

那么按照身高h来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。

此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!

那么只需要按照k为下标重新插入队列就可以了,为什么呢?

按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

所以在按照身高从大到小排序后:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性

局部最优可推出全局最优,找不出反例,那就试试贪心。

规范代码:

class Solution:

    def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]:

     # 先按照h维度的身高顺序从高到低排序。确定第一个维度

        # lambda返回的是一个元组:当-x[0](维度h)相同时,再根据x[1](维度k)从小到大排序

        people.sort(key=lambda x: (-x[0], x[1]))

        que = []

# 根据每个元素的第二个维度k,贪心算法,进行插入

        # people已经排序过了:同一高度时k值小的排前面。

        for p in people:

            que.insert(p[1], p)

        return que

标签:10,return,gas,candyVec,随想录,第三十四,找零,加油站,身高
From: https://blog.csdn.net/weixin_47681529/article/details/142928593

相关文章

  • 代码随想录算法训练营第三十二天|122.买卖股票的最佳时机 II 55. 跳跃游戏 45.跳跃游
    122.买卖股票的最佳时机II给定一个数组,它的第 i个元素是一支给定股票第i天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。示例1:输入:[7,1,5......
  • 代码随想录算法训练营第三十一天|455.分发饼干 376. 摆动序列 53. 最大子序和
    455.分发饼干假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干j,都有一个尺寸s[j] 。如果s[j] >=g[i],我们可以将这个饼干j分配给孩子i,......
  • 代码随想录算法训练营第八天(1)|哈希表理论基础
    文档讲解:代码随想录难度:有一点哈希表理论基础哈希表首先什么是哈希表,哈希表(英文名字为Hashtable,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hashtable就可以了)。哈希表是根据关键码的值而直接进行访问的数据结构。这么官方的解释可能有点懵,其实......
  • 代码随想录2
    题目:移除元素:给你一个数组nums和一个值val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用O(1)额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。示例1:给定nums=......
  • 代码随想录1
    一个简单的二分查找题。CPP代码。二分查找需要注意的地方就是区间的问题。如果是while(left<right)。就代表着区间定义是[left,right),即右边界取不到。因此当right缩小至middle时候只需要:while(left<right){...if(nums[middle]<target)right=middle;...}如果是两边并区间......
  • 代码随想录算法训练营 | 198.打家劫舍,213.打家劫舍II,337.打家劫舍III
    198.打家劫舍题目链接:198.打家劫舍文档讲解︰代码随想录(programmercarl.com)视频讲解︰打家劫舍日期:2024-10-13想法:dp[i]到第i个房子时能偷的最多的钱;递推公式:是上上一栋房子的dp[i-2]加上这栋房子的钱nums[i]大还是上一家邻居偷的钱dp[i-1]的大;初始化因为有i-2;所以初始化......
  • 【代码随想录Day41】动态规划Part10
    300.最长递增子序列题目链接/文章讲解:代码随想录视频讲解:动态规划之子序列问题,元素不连续!|LeetCode:300.最长递增子序列_哔哩哔哩_bilibilipublicintlengthOfLIS(int[]nums){//获取数组的长度intn=nums.length;//创建一个用于存储以每个元素结......
  • 代码随想录Day24 | LeetCode 122. 买卖股票的最佳时机 II、LeetCode 55. 跳跃游戏、Le
    LeetCode122.买卖股票的最佳时机IIclassSolution:defmaxProfit(self,prices:List[int])->int:res=0foriinrange(1,len(prices)):res+=max(0,prices[i]-prices[i-1])returnresLeetCode55.跳跃游戏class......
  • 代码随想录算法训练营 | 322. 零钱兑换,279.完全平方数,139.单词拆分
    322.零钱兑换题目链接:322.零钱兑换文档讲解︰代码随想录(programmercarl.com)视频讲解︰零钱兑换日期:2024-10-12想法:完全背包,注意初始化除dp[0]外都要置为Integer.MAX_VALUE,才能后面选出最小值,还有判断dp[j-coins[i]]!=Integer.MAX_VALUE,不成立的化代表除去coins[i]后,没有......
  • 代码随想录算法训练营第十天|Day10栈与队列
    232.用栈实现队列题目链接/文章讲解/视频讲解:https://programmercarl.com/0232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97.html思路这是一道模拟题,不涉及到具体算法,使用栈来模拟队列的行为,如果仅仅用一个栈,是一定不行的,所以需要两个栈一个输入栈,一个输出......