力扣刷题记录&整理——(十五)Greedy


前言

整理力扣刷题思路。

  • 语言:python
  • 题库:来自neetcode: link

一、预备知识

贪心算法

贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。贪心算法并不保证会得到最优解,但在某些问题中,贪心却能得到最优解。

贪心算法的特点:

  1. 局部最优选择:在每个阶段,算法会做出一个看似最优的决定,而不考虑更大的问题。
  2. 无后效性:一旦作出这些决策,就不再重新考虑。
  3. 问题分解:如果一个问题可以通过贪心法解决,通常意味着整个问题的最优解可以通过组合局部最优解得到。

举个简单的例子,如在找零问题中,假设你是售货员,需要给客户找零n元,而钱箱中有无限个1元、5元、10元、20元、50元、100元人民币。你希望让客户拿到的纸币数量尽可能少。在这个问题中,一个有效的贪心策略是从最大的纸币开始,尽可能多地使用,然后依次减小纸币面额,直到找完所有的零钱。


二、解题思路

1.贪心算法

53.maximum-subarray

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。
link

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        #直接在原数组上修改
        for i in range(1,len(nums)):
            nums[i] += max(nums[i-1],0)
        return max(nums)

55.jump-game

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

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

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        '''
        贪心算法:维护可到达的最远位置mx,只要能到达mx,那一定也能到达mx之前的所有位置
        '''
        if len(nums) == 1:
            return True

        mx = nums[0]
        for i in range(1,len(nums)):
            #如果最远也到达不了当前位置,则更加到不了最后的位置
            if mx<i:
                return False
            else:
                #更新最远可到达的位置
                mx = max(mx,i+nums[i])
                #如果已经可以到达最后位置,提前返回True结束遍历
                if mx>=len(nums)-1:
                    return True

45.jump-game-ii

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

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

  • 0 <= j <= nums[i]
  • i + j < n
    返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。
    link
class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums)==1:
            return 0
        
        #维护步数、当前位置及之前可到达的最远位置、当前步数下可到达的最远位置
        step,mx,right = 0,0,0
        for i in range(len(nums)-1):
            mx = max(mx,i+nums[i])
            #若已到达当前步数下可到的最远端,则步数加1,更新最远位置
            if i==right:
                step += 1
                right = mx
        return step
class Solution:
    def jump(self, nums: List[int]) -> int:
        #[l,r]是当前步数所能到达的位置区间
        l=r=step=0
        while r<len(nums)-1:
            farest = 0
            for i in range(l,r+1):
                farest = max(farest,i+nums[i])
            l = r+1
            r = farest
            step += 1
        return step

134.gas-station

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

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

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。
link

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        '''
        本题的逻辑是:
        1.如果gas总量小于cost总量,一定无法环一圈
        2.如果gas总量大于或等于cost总量,一定可以环一圈
        '''
        if sum(gas)<sum(cost):
            return -1
        
        start = 0
        rest = 0
        for i in range(len(gas)):
            rest += gas[i]-cost[i]
            if rest<0:
                start = i+1
                rest = 0
        return start

763.partition-labels

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

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

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

class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        dict = {}
        #字典只存储每个字符最后出现的位置
        for i in range(len(s)):
            dict[s[i]] = i

        cnt = 0
        boundary = dict[s[0]]
        ans = []
        for i in range(len(s)):
            cnt += 1
            if dict[s[i]]>boundary:
                boundary = dict[s[i]]
            if i==boundary:
                ans.append(cnt)
                cnt = 0
        return ans

678.valid-parenthesis-string

给你一个只包含三种字符的字符串,支持的字符类型分别是 ‘(’、‘)’ 和 ‘*’。请你检验这个字符串是否为有效字符串,如果是有效字符串返回 true 。

有效字符串符合如下规则:

任何左括号 ‘(’ 必须有相应的右括号 ‘)’。
任何右括号 ‘)’ 必须有相应的左括号 ‘(’ 。
左括号 ‘(’ 必须在对应的右括号之前 ‘)’。
‘*’ 可以被视为单个右括号 ‘)’ ,或单个左括号 ‘(’ ,或一个空字符串。
一个空字符串也被视为有效字符串。
link

class Solution:
    def checkValidString(self, s: str) -> bool:
        '''
        总共可分为三种情况:(=),(>),(<)
        '''
        cnt1=cnt2=0
        #正序遍历,将*看成(,确保有足够的(
        for i in s:
            cnt1 += -1 if i==')' else 1
            if cnt1<0:
                return False
        #逆序遍历,将*看成),确保有足够的)
        for i in reversed(s):
            cnt2 += -1 if i=='(' else 1
            if cnt2<0:
                return False
        
        return True

846.hand-of-straights

Alice 手中有一把牌,她想要重新排列这些牌,分成若干组,使每一组的牌数都是 groupSize ,并且由 groupSize 张连续的牌组成。

给你一个整数数组 hand 其中 hand[i] 是写在第 i 张牌上的数值。如果她可能重新排列这些牌,返回 true ;否则,返回 false 。
link

class Solution:
    def isNStraightHand(self, hand: List[int], groupSize: int) -> bool:
        if len(hand)%groupSize != 0:
            return False
        
        dic = {}
        for i in hand:
            dic[i] = 1+dic.get(i,0)
        
        #每次取最小牌,去掉对应的顺子
        for key in sorted(dic.keys()):
            if dic[key]>0:
                tmp = dic[key]
                for i in range(key,key+groupSize):
                    if i not in dic or dic[i]<tmp:
                        return False
                    dic[i] -= tmp
        return True

1899.merge-triplets-to-form-target-triplet

三元组 是一个由三个整数组成的数组。给你一个二维整数数组 triplets ,其中 triplets[i] = [ai, bi, ci] 表示第 i 个 三元组 。同时,给你一个整数数组 target = [x, y, z] ,表示你想要得到的 三元组 。

为了得到 target ,你需要对 triplets 执行下面的操作 任意次(可能 零 次):

选出两个下标(下标 从 0 开始 计数)i 和 j(i != j),并 更新 triplets[j] 为 [max(ai, aj), max(bi, bj), max(ci, cj)] 。
例如,triplets[i] = [2, 5, 3] 且 triplets[j] = [1, 7, 5],triplets[j] 将会更新为 [max(2, 1), max(5, 7), max(3, 5)] = [2, 7, 5] 。
如果通过以上操作我们可以使得目标 三元组 target 成为 triplets 的一个 元素 ,则返回 true ;否则,返回 false 。
link

class Solution:
    def mergeTriplets(self, triplets: List[List[int]], target: List[int]) -> bool:
        a=b=c=False
        for i,j,k in triplets:
            if i>target[0] or j>target[1] or k>target[2]:
                continue
            if i==target[0]:
                a = True
            if j==target[1]:
                b = True
            if k==target[2]:
                c = True
        return a and b and c
  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值