【春招冲刺班-7天刷题挑战】(二)

目录

九、LC 124. 二叉树中的最大路径和

9.1 题求

9.2 求解

十、LC 17. 电话号码的字母组合

10.1 题求

10.2 求解

十一、LC 486. 预测赢家 ☆

11.1 题求

11.2 求解

十二、LC 5. 最长回文子串

12.1 题求

12.2 求解

十三、LC 121. 买卖股票的最佳时机

13.1 题求

13.2 求解

十四、LC 100. 相同的树

14.1 题求

14.2 求解

十五、LC 468. 验证 IP 地址

15.1 题求

15.2 求解


九、LC 124. 二叉树中的最大路径和

9.1 题求

9.2 求解

法一:DFS

# 72ms - 89.25%
class Solution:
    def maxPathSum(self, root: TreeNode) -> int:

        def dfs(node):
            nonlocal max_path_sum
            # 最大左、右子树路径
            left_val = dfs(node.left) if node.left else float('-inf')
            right_val = dfs(node.right) if node.right else float('-inf')
            max_path = max(left_val, right_val)
            max_val = max(node.val, max_path, node.val + left_val + right_val)
            # 若当前子树超过最大值, 则更新
            if max_val > max_path_sum:
                max_path_sum = max_val
            # 返回当前子树的最大路径
            return node.val + max(0, max_path)
        
        max_path_sum = float('-inf')    
        final = dfs(root)    
        return max(max_path_sum, final)

官方说明

# 72ms - 89.25%
class Solution:
    def __init__(self):
        self.maxSum = float("-inf")

    def maxPathSum(self, root: TreeNode) -> int:
        def maxGain(node):
            if not node:
                return 0

            # 递归计算左右子节点的最大贡献值
            # 只有在最大贡献值大于 0 时,才会选取对应子节点
            leftGain = max(maxGain(node.left), 0)
            rightGain = max(maxGain(node.right), 0)
            
            # 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
            self.maxSum = max(self.maxSum, node.val + leftGain + rightGain)
        
            # 返回节点的最大贡献值
            return node.val + max(leftGain, rightGain)
   
        maxGain(root)
        return self.maxSum

参考资料:

力扣

力扣


十、LC 17. 电话号码的字母组合

10.1 题求

10.2 求解

法一:回溯

# 28ms - 90.13%
class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        if not digits:
            return []

        hashmap = {'2': "abc", '3': "def", '4': "ghi", '5': "jkl",
                   '6': "mno", '7': "pqrs", '8': "tuv", '9': 'wxyz'}
        res = [] 
        
        def traverse(arr, idx):
            if len(arr) == len(digits):
                res.append(''.join(arr))
                return
    
            idx += 1
            for c in hashmap[digits[idx]]:
                arr.append(c)
                traverse(arr, idx)
                arr.pop()
        
        traverse([], -1)
        return res

参考资料:

力扣


十一、LC 486. 预测赢家 ☆

11.1 题求

11.2 求解

官方说明

# 5460ms - 7.26%
class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        
        def total(start: int, end: int, turn: int) -> int:
            if start == end:
                return nums[start] * turn
            
            scoreStart = nums[start] * turn + total(start + 1, end, -turn)
            scoreEnd = nums[end] * turn + total(start, end - 1, -turn)
            return max(scoreStart * turn, scoreEnd * turn) * turn
        
        return total(0, len(nums)-1, 1) >= 0

# 36ms - 78.35%
class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        length = len(nums)
        
        # dp[i,j] 表示下标范围 [i, j] 中, 当前玩家与另一玩家的分数之差的最大值
        dp = [[0] * length for _ in range(length)]
        for i, num in enumerate(nums):
            # i = j 时当前玩家必须选该唯一数字
            dp[i][i] = num
            
        # 之所以从 len-2 开始, 是因为 dp[len-1][len-1] 的值已经确定为 num[len-1]
        for i in range(length-2, -1, -1):  # 先左
            for j in range(i+1, length):   # 后右
                # 状态转移 - 选左 nums[i] - dp[i+1][j] - 选右 nums[j] - dp[i][j-1]
                # 之所以是 -dp[][] 是因为假设玩家 1 相对于玩家 2 的分数差为 d,
                # 则玩家 2 相对于玩家 1 的分数差为 -d, 可见二者是互为相反数的关系
                dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1])  # ☆
        
        # 先手玩家 (针对全范围 [0, len-1] 选择) 相对于后手玩家的分数之差不小于 0, 则先手玩家获胜
        return dp[0][length-1] >= 0

# 32ms - 89.17%
class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        length = len(nums)
        
        dp = [0] * length
        for i, num in enumerate(nums):
            dp[i] = num
            
        for i in range(length - 2, -1, -1):
            for j in range(i + 1, length):
                # 更新本轮的 dp[j] 需要上一轮的 dp[j] 和本轮的 dp[j-1]
                dp[j] = max(nums[i] - dp[j], nums[j] - dp[j-1])  # 先后顺序一定要正确
                
        return dp[length-1] >= 0  # 右边界

参考资料:

力扣

力扣


十二、LC 5. 最长回文子串

12.1 题求

12.2 求解

法一:动态规划

# 6240ms - 37.71%
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
 
        # 最长回文串的左、右下标
        left = right = 0
        # dp[i][j]:表示 s[i: j+1] 是否为回文串
        dp = [[False for _ in range(n)] for _ in range(n)]
        # i == j 的单个字符串总为回文串
        for i in range(n):
            dp[i][i] = True

        for i in range(n-2, -1, -1):
            for j in range(i+1, n):
                # 当前长度
                length = j - i
                # 状态转移
                if s[i] == s[j]:
                    dp[i][j] = True if length < 3 else dp[i+1][j-1]
                # 最长更新
                if dp[i][j] and length > right-left:
                    left, right = i, j
 
        return s[left: right+1]

官方说明 

# 7620ms - 19.26%
class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
        
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        # i == j 的单个字符串总为回文串
        for i in range(n):
            dp[i][i] = True
        
        # 枚举子串长度 L
        for L in range(2, n+1):
            # 枚举左边界 i
            for i in range(n):
                # 由 子串长度 L 和 左边界 i 确定右边界 j (闭区间)
                j = i + L - 1
                # 右边界越界, 则退出当前层循环
                if j >= n:
                    break
                    
                if s[i] != s[j]:
                    dp[i][j] = False 
                else:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立, 就表示子串 s[i..L] 是回文
                # 此时记录最大回文串长度 max_len 及其起始位置 begin
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
                    
        return s[begin: begin+max_len]

# 740ms - 80.6%
class Solution:
    def expandAroundCenter(self, s, left, right):
        # 下标在范围内, 且两端始终对称
        while left >= 0 and right < len(s) and s[left] == s[right]:
            left -= 1
            right += 1
        # 返回当前最大回文串边界
        return left + 1, right - 1

    def longestPalindrome(self, s: str) -> str:
        # 最长回文串左、右边界
        start, end = 0, 0
        # 枚举左边界/起点 i
        for i in range(len(s)):
            # 边界条件 - 单字符 (奇数回文串)
            left1, right1 = self.expandAroundCenter(s, i, i)  
            # 边界条件 - 双字符 (偶数回文串)
            left2, right2 = self.expandAroundCenter(s, i, i+1)
            # 比较与更新
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 - left2 > end - start:
                start, end = left2, right2
        # 返回整体最大回文串
        return s[start: end+1]

参考资料:

力扣

力扣


十三、LC 121. 买卖股票的最佳时机

13.1 题求

13.2 求解

法一:动态规划 - 基本

# 2252ms - 5.08%
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        
        n = len(prices)
        # 持有与否 k:0/1  |  最大允许交易次数 j:0/1  |  交易天数 i: n+1
        dp = [[[[] for _ in range(2)] for _ in range(2)] for _ in range(n+1)]
        #### 初始状态 - 规律 第 0 天一开始就持有股票是不可能的,一开始不持有且应利润为 0
        dp[0][0][0] = 0                  # 第 0 天, 不持有股票, 利润 0
        dp[0][0][1] = float('-inf')      # 第 0 天, 自带持有股票, 不可能
        for i in range(n+1):
            dp[i][1][0] = 0              # 第 i 天, 尚未交易, 利润 0
            dp[i][1][1] = float('-inf')  # 第 i 天, 尚未交易便持有股票, 不可能
        # 状态转移
        for i in range(1, n+1):
            dp[i][0][0] = max(dp[i-1][0][0], dp[i-1][0][1]+prices[i-1])
            dp[i][0][1] = max(dp[i-1][0][1], dp[i-1][1][0]-prices[i-1])

        return dp[n][0][0]

法二:动态规划 - 降维

        由于第 i 天仅和第 i-1 天有关,因此可使用变量代替天数)

# 340ms - 15.16%
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        # 持有与否 k:0/1  |  最大允许交易次数 j:0/1 
        dp = [[[] for _ in range(2)] for _ in range(2)]
        # 初始状态
        dp[0][0] = 0
        dp[0][1] = float('-inf')
        dp[1][0] = 0
        dp[1][1] = float('-inf')
        # 状态转移
        for i in range(n):
            # 顺序不能搞反, 先使用到昨天的 dp[0][1], 再更新今天的 dp[0][1]
            dp[0][0] = max(dp[0][0], dp[0][1]+prices[i])
            dp[0][1] = max(dp[0][1], dp[1][0]-prices[i])
        return dp[0][0]

法三:迭代

# 202ms - 71.71%
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        min_price = float('inf')  # 最小买入价格 (初始为无限大)
        max_profit = 0  # 最大利润 = 最大卖出价格 - 最小买入价格
        for price in prices:
            # 由于必须在未来卖出股票     
            max_profit = max(max_profit, price-min_price)  # 故先比较和确定最大利润
            min_price = min(min_price, price)  # 再比较和确定最小买入价格
        return max_profit

参考资料:

力扣


十四、LC 100. 相同的树

14.1 题求

14.2 求解

法一:递归 

# 36ms - 49.88%
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        if not p and not q:
            return True
        elif not p or not q or p.val != q.val:
            return False   
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)

法二:迭代

# 24ms - 97.74%
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        
        deque_p = collections.deque()
        deque_p.append(p)
        deque_q = collections.deque()
        deque_q.append(q)
        
        while deque_p and deque_q:
            nums = len(deque_p)
            if nums != len(deque_q):
                return False
            
            for _ in range(nums):
                p = deque_p.popleft()
                q = deque_q.popleft()
                
                if not p and not q:
                    continue 
                    
                if not p or not q or p.val != q.val:
                    return False

                deque_p.append(p.left)
                deque_p.append(p.right)
                deque_q.append(q.left)
                deque_q.append(q.right)
                
        return True

参考资料:

力扣


十五、LC 468. 验证 IP 地址

15.1 题求

15.2 求解

法一:迭代

# 28ms - 87.44%
class Solution:
    def validIPAddress(self, IP: str) -> str:
        
        if '.' in IP:
            ipv4 = IP.split('.')
            if len(ipv4) != 4:
                 return "Neither"
            for add in ipv4:
                if (not add) or (add[0] == '0' and len(add) > 1) or (not add.isdigit()) or (int(add) > 255):
                    return "Neither"
            return "IPv4"
        
        elif ':' in IP:
            ipv6 = IP.split(':')
            if len(ipv6) != 8:
                return "Neither"
            hex_digits = '0123456789abcdefABCDEF'
            for add in ipv6:
                if (not add) or (len(add) > 4) or not all(d in hex_digits for d in add):
                    return "Neither"
            return "IPv6"
        
        else:
            return "Neither"

参考资料:

力扣


十六、LC 638. 大礼包

16.1 题求

16.2 求解

法一:递归

# 132ms - 33.89%
class Solution:
    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:

        def dfs(need, prices):
            # 需求全满足, 判断最小价格并返回
            if not any(need):
                nonlocal lowest_prices
                lowest_prices = min(lowest_prices, prices)
                return 
            
            # 优惠包购买
            for sp in special:
                cur_need = need.copy()
                buy_sp = True  # 是否适合购买优惠包
                for i in range(len(cur_need)):
                    # 购买后未超出需求
                    if sp[i] <= cur_need[i]:
                        cur_need[i] -= sp[i]
                    # 超出需求, 不适合购买, 结束判断
                    else:
                        buy_sp = False
                        break
                if buy_sp:     
                    dfs(cur_need, prices+sp[-1])
                
            # 单买 - 即便存在优惠包, 也要尝试单独买
            for i in range(len(need)):
                prices += price[i] * need[i]
                need[i] = 0
            dfs(need, prices)    

        lowest_prices = float('inf')
        dfs(needs, 0)
        return lowest_prices   

官方说明 

记忆化递归 

# 52ms - 93.33%
class Solution:
    def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:
        from functools import lru_cache
        @lru_cache(None)
        def dfs(cur_need):
            res = 0
            
            # 单买
            for i, c in enumerate(cur_need):
                res += price[i] * c
    
            # 买包
            for sp in special:
                # 当前需求列表
                cur = []

                for i, num in enumerate(sp[:-1]):
                    # 大于需求 - 停止判断
                    if num > cur_need[i]:
                        break
                    # 满足需求 - 加入列表
                    cur.append(cur_need[i] - num)

                # for 正常结束, else 执行; for 由 break 退出, else 不执行
                else:
                    #针对当前需求, 比较单买还是买包好
                    res = min(res, dfs(tuple(cur)) + sp[-1])
            return res

        # 由于使用了 LRU Cache, 需用 hashable 类型 tuple 取代 unhashable 类型 list
        return dfs(tuple(needs))

参考资料:

力扣

力扣

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值