【Python LeetCode Patterns】刷力扣,15 个学习模式总结

news2025/3/30 20:08:37

  • 1. 前缀和(Prefix Sum)—— 查询子数组中元素和
    • 303. 区域和检索 - 数组不可变
    • 304. 二维区域和检索 - 矩阵不可变
  • 2. 双指针(Two Pointers)—— 移向彼此或远离彼此
  • 3. 滑动窗口(Sliding Window)—— 找到满足特定条件的子数组或子字符串
  • 4. 快慢指针(Fast and Slow Pointers)—— 快甩慢一圈时相遇
  • 5. 原地翻转链表(Linked List In-place Reversal)—— 不使用额外空间重新排列链表
  • 6. 单调栈(Monotonic Stack)—— 查找数组中下一个更 × 的元素
    • 496. 下一个更大元素 I
    • 503. 下一个更大元素 II
  • 7. 前 K 个元素(Top K Elements)—— 查找 K 个最 × 的元素
  • 8. 重叠间隔(Overlapping Intervals)—— 可能重叠的间隔或范围
  • 9. 修改二进制搜索(Modified Binary Search)——
  • 10. 二叉树遍历
  • 11. 深度优先搜索
  • 12. 广度优先搜索
  • 13. 矩阵遍历
  • 14. 回溯(Backtracking)
    • 473. 火柴拼正方形
    • 2305. 公平分发饼干(剪枝)
    • 78. 子集
  • 15. 动态规划

15 Patterns

在这里插入图片描述

1. 前缀和(Prefix Sum)—— 查询子数组中元素和

在这里插入图片描述

如果要 查询数组 nums 中从下标 ij 的数组元素和,那么大量这种查询,应该怎么做呢?

核心思想:

  • 构建一个长度为 len(nums)+1 的数组 prefix_sum假设左边界 prefix_sum[0] = 0
  • 这样 prefix_sum 下标比 num 数组大 1,所以:prefix_sum[i+1] = nums[0] + nums[1] + ... + nums[i]
  • 任何区间 [l, r] 的和就可以通过:sum = prefix_sum[r+1] - prefix_sum[l] 快速得到!
  • 时间复杂度:构建 prefix_sum 的复杂度是 O(n),之后 每次查询都是 O(1)
  • 空间复杂度:可以使用 nums 数组本身构建前缀和,就不需要额外空间了。

303. 区域和检索 - 数组不可变

在这里插入图片描述

class NumArray:

    def __init__(self, nums: List[int]):
        # 初始化前缀和数组,长度为 len(nums) + 1,前缀和[0] = 0
        self.prefix_sum = [0] * (len(nums) + 1)
        for i in range(len(nums)):
            self.prefix_sum[i + 1] = self.prefix_sum[i] + nums[i]

    def sumRange(self, left: int, right: int) -> int:
        return self.prefix_sum[right + 1] - self.prefix_sum[left]  # 区间和

# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(left,right)

304. 二维区域和检索 - 矩阵不可变

在这里插入图片描述

构建一个 前缀和矩阵(sum_matrix) 来快速 查询任意子矩形区域的总和

在这里插入图片描述

在这里插入图片描述

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        rows, cols = len(matrix), len(matrix[0])
        # 创建前缀和矩阵,注意多出一行一列,初始化为0
        self.prefix_sum = [[0] * (cols + 1) for _ in range(rows + 1)]

        # 构建二维前缀和矩阵
        for r in range(rows):
            for c in range(cols):
                self.prefix_sum[r + 1][c + 1] = (
                    matrix[r][c]
                    + self.prefix_sum[r][c + 1]
                    + self.prefix_sum[r + 1][c]
                    - self.prefix_sum[r][c]
                )

    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        # 使用二维前缀和公式求子矩阵和
        return (
            self.prefix_sum[row2 + 1][col2 + 1]
            - self.prefix_sum[row1][col2 + 1]
            - self.prefix_sum[row2 + 1][col1]
            + self.prefix_sum[row1][col1]
        )

# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)

2. 双指针(Two Pointers)—— 移向彼此或远离彼此

在这里插入图片描述

在这里插入图片描述

时间复杂度在这里插入图片描述

刷题参考:面试经典 150 题——双指针

3. 滑动窗口(Sliding Window)—— 找到满足特定条件的子数组或子字符串

在这里插入图片描述

在这里插入图片描述

时间复杂度:在这里插入图片描述 → \rightarrow 在这里插入图片描述

刷题参考:面试经典 150 题——滑动窗口

4. 快慢指针(Fast and Slow Pointers)—— 快甩慢一圈时相遇

在这里插入图片描述

刷题参考:面试经典 150 题——快慢指针

5. 原地翻转链表(Linked List In-place Reversal)—— 不使用额外空间重新排列链表

在这里插入图片描述

核心思想: 使用三个指针,precurnext

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        prev = None
        current = head
        
        while current is not None:
            next = current.next
            current.next = prev
            prev = current
            current = next
        return prev

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 单调栈(Monotonic Stack)—— 查找数组中下一个更 × 的元素

在这里插入图片描述

栈(stack),先进后出,符合某些问题的特点,比如说函数调用栈。单调栈 实际上就是栈,只是利用了一些巧妙的逻辑,使得 每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。

单调栈用于处理一类典型的问题,比如 「下一个更大元素」,「上一个更小元素」 等。

在这里插入图片描述

时间复杂度:在这里插入图片描述 → \rightarrow O(n)

496. 下一个更大元素 I

在这里插入图片描述

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        n1, n2 = len(nums1), len(nums2)
        greater = [-1] * 10001

        stack = [10**4+1]
        for i in range(n2-1, -1, -1):
            while nums2[i] >= stack[-1]:
                stack.pop()
            if stack[-1] != 10**4 + 1:
                greater[nums2[i]] = stack[-1]
            stack.append(nums2[i])

        ans = [-1] * n1
        for i in range(n1):
            ans[i] = greater[nums1[i]]
        return ans

503. 下一个更大元素 II

在这里插入图片描述

from collections import defaultdict
class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        n = len(nums)
        max_index = nums.index(max(nums))
        
        ans = [-1] * n
        stack = [(max_index, nums[max_index])]
        for i in range(max_index + 1, n):
            while nums[i] > stack[-1][1]:
                ans[stack[-1][0]] = nums[i]
                stack.pop()
            stack.append((i, nums[i]))
        
        for i in range(0, max_index+1):
            while nums[i] > stack[-1][1]:
                ans[stack[-1][0]] = nums[i]
                stack.pop()
            stack.append((i, nums[i]))

        return ans

7. 前 K 个元素(Top K Elements)—— 查找 K 个最 × 的元素

在这里插入图片描述

在这里插入图片描述

时间复杂度: O(n logn) → \rightarrow O(n log K)

8. 重叠间隔(Overlapping Intervals)—— 可能重叠的间隔或范围

在这里插入图片描述

9. 修改二进制搜索(Modified Binary Search)——

在这里插入图片描述

10. 二叉树遍历

在这里插入图片描述

11. 深度优先搜索

在这里插入图片描述

12. 广度优先搜索

在这里插入图片描述

13. 矩阵遍历

在这里插入图片描述

14. 回溯(Backtracking)

在这里插入图片描述

回溯算法的核心在于通过不断尝试所有可能的选择,然后在发现当前路径无法达到目标时“回退”,换一种选择继续探索。

“试错—撤销—重新尝试” 的过程就是回溯算法的精髓。

回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成

参考:回溯总结
在这里插入图片描述

def backtrack(path, choices):
    if is_solution(path):   # 终止条件
        return True

    for choice in choices:    # 遍历当前状态下所有的选择
        if is_valid(choice, path):
            path.append(choice)  # 1. 做出选择,将 choice 加入路径中
            
            updated_choices = update_choices(choices, choice)
            if backtrack(path, updated_choices):   # 2. 递归调用,继续在剩余选择中搜索
                return True
                
            path.pop()  # 3. 回溯:撤销选择
    
    return False  # 所有选择均尝试过后未能得到解决方案

473. 火柴拼正方形

在这里插入图片描述

  • 状态定义:用一个数组 sides 来表示当前四条边的长度。回溯函数的状态可以用当前正在放置的火柴棒的索引 index 以及四条边当前的累加长度来表示。

  • 递归的基本思想

    • 选择过程:对每根火柴棒,我们有四个选择——将它放到正方形的四条边中的任意一条,只要该边放上后不超过目标边长。
    • 递归调用:放置火柴棒后,递归处理下一根火柴棒。如果某条路径最终使得所有火柴棒都被放置,并且每条边的长度都正好等于目标边长,那么说明找到了一种解法。
  • 剪枝(回退)

    • 不符合条件时回退:如果当前某个选择导致某条边的长度超过目标边长,则直接放弃该选择(剪枝),不会继续递归下去。
    • 撤销选择:在递归返回后,将之前放置的火柴棒“撤销”,使状态回到上一步,从而尝试其他可能的放置方式。
  • 终止条件:当所有火柴棒都尝试完毕(即 index == len(matchsticks)),检查四条边是否都达到目标边长。如果是,返回 True;否则返回 False。

优化技巧

  • 排序:将火柴棒按长度降序排序,可以让较大的火柴棒先被尝试,能更快发现不可能的路径,从而提前剪枝,减少不必要的递归计算。
  • 重复状态避免:有时候还可以进一步利用对称性等手段避免重复计算,不过在这个问题中,降序排序已经是一个有效的剪枝策略。
class Solution:
    def makesquare(self, matchsticks: List[int]) -> bool:
        total = sum(matchsticks)
        if total % 4 != 0:   # 火柴长度和不是 4 的倍数
            return False
        
        side = total // 4  # 正方形边长
        matchsticks.sort(reverse=True)
        
        sides = [0] * 4
        def backtrace(idx):
            # 终止条件:所有火柴都已使用,检查是否都等于边长
            if idx == len(matchsticks):
                return sides[0] == sides[1] == sides[2] == sides[3] == side

            # 遍历所有边,可以将这根火柴放入哪个边
            for i in range(4):
                if sides[i] + matchsticks[idx] <= side:
                    sides[i] += matchsticks[idx]  # 做选择:将当前火柴放入该边
                    if backtrace(idx + 1):
                        return True
                    sides[i] -= matchsticks[idx]  # 回溯

            return False
        
        return backtrace(0)   

2305. 公平分发饼干(剪枝)

在这里插入图片描述
类似火柴拼正方形,

class Solution:
    def distributeCookies(self, cookies: List[int], k: int) -> int:
        n = len(cookies)
        childs = [0] * k
        ans = 800001

        def backtrace(idx):
            nonlocal ans
            # 终止条件,分发完零食了
            if idx == n:
                unfair = max(childs)
                ans = min(ans, unfair)
                return 
            
            # 遍历所有选择
            for i in range(k):
                childs[i] += cookies[idx]  # 做选择:当前零食发给第 i 个孩子
                backtrace(idx + 1)  # 递归,继续分发下一包零食
                childs[i] -= cookies[idx]  # 撤销选择,回溯
            return 
        backtrace(0)
        return ans

但是会超时,画出回溯树查看:

在这里插入图片描述

从上面的图可以发现,第一个零食包不管给哪个小朋友,所开启的回溯树都一样(因为本题不要求顺序),所以首个零食包只要给第一个小朋友就行了,这样的回溯树只有一个根节点(一个回溯树),否则有 k k k 个回溯树。

一开始对数组进行排序,先发放饼干较多的包,这样可以减少平均回溯深度。

class Solution:
    def distributeCookies(self, cookies: List[int], k: int) -> int:
        n = len(cookies)
        cookies.sort(reverse=True)  # 从大到小排序,便于剪枝
        childs = [0] * k
        ans = 800001

        def backtrace(idx):
            nonlocal ans  # ans 记录最小不公平程度,可视作全局变量

            # 终止条件,分发完零食了
            if idx == n:
                unfair = max(childs)
                ans = min(ans, unfair)
                return 
            
            # 遍历所有选择
            for i in range(k):
                if childs[i] + cookies[idx] >= ans:  # 剪枝:比 ans 还大的不公平程度就不用看了
                    continue
                if i > 0 and childs[i] == childs[i-1]:  # 剪枝:当前零食包分给哪个孩子都一样,已经分给前一个孩子了就不要再重复给当前孩子了
                    continue
                childs[i] += cookies[idx]  # 做选择:当前零食发给第 i 个孩子
                backtrace(idx + 1)  # 递归,继续分发下一包零食
                childs[i] -= cookies[idx]  # 撤销选择,回溯
                
            return 
        backtrace(0)
        return ans

78. 子集

在这里插入图片描述

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        subset = []
        sub = []

        def backtrace(nums):
            subset.append(sub.copy())  # NOTE: sub.copy()
            # 终止条件
            if len(nums) == 0:
                return 
            
            # 遍历选择
            for i in range(len(nums)):
                sub.append(nums[i])  # 做选择
                backtrace(nums[i+1:])  # 递归
                sub.pop()  # 撤销选择,回溯
            return 
        
        backtrace(nums)
        return subset

15. 动态规划

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2322728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯单片机刷题——串口发送显示

设计要求 通过串口接收字符控制数码管的显示&#xff0c;PC端发送字符A&#xff0c;数码管显示A&#xff0c;发送其它非法字符时&#xff0c;数码管显示E。 数码管显示格式如下&#xff1a; 备注&#xff1a; 单片机IRC振荡器频率设置为12MHz。 串口通信波特率&#xff1a;…

探索抓包利器ProxyPin,实现手机APP请求抓包,支持https请求

以下是ProxyPin的简单介绍&#xff1a; - ProxyPin是一个开源免费HTTP(S)流量捕获神器&#xff0c;支持 Windows、Mac、Android、IOS、Linux 全平台系统- 可以使用它来拦截、检查并重写HTTP(S)流量&#xff0c;支持捕获各种应用的网络请求。ProxyPin基于Flutter开发&#xff0…

文献学习:单细胞+临床+模型构建 | 一篇Molecular Cancer文献如何完整解读CDK4/6i耐药机制

&#x1f44b; 欢迎关注我的生信学习专栏~ 如果觉得文章有帮助&#xff0c;别忘了点赞、关注、评论&#xff01; &#x1f4cc;一、研究背景&#xff1a;CDK4/6i 是不是“万无一失”&#xff1f; HR/HER2- 是最常见的乳腺癌亚型&#xff0c;占比超过70%。近年来&#xff0c;随…

网盘解析工具更新,解决了一些bug

解析工具v1.2.1版本更新&#xff0c;本次是小版本更新&#xff0c;修复了一些bug。 之前小伙伴反应的网盘进入文件后不能返回上一级&#xff0c;现在这个bug修复了&#xff0c;已经可以点击了。 点击资源后会回到资源那一级目录&#xff0c;操作上是方便了不少。 增加了检查自…

5种生成模型(VAE、GAN、AR、Flow 和 Diffusion)的对比梳理 + 易懂讲解 + 代码实现

目录 1 变分自编码器&#xff08;VAE&#xff09;​ 1.1 概念 1.2 训练损失 1.3 VAE 的实现 2 生成对抗网络&#xff08;GAN&#xff09;​ 2.1 概念 2.2 训练损失 a. 判别器的损失函数 b. 生成器的损失函数 c. 对抗训练的动态过程 2.3 GAN 的实现 3 自回归模型&am…

计算机期刊推荐 | 计算机-人工智能、信息系统、理论和算法、软件工程、网络系统、图形学和多媒体, 工程技术-制造, 数学-数学跨学科应用

Computers, Materials & Continua 学科领域&#xff1a; 计算机-人工智能、信息系统、理论和算法、软件工程、网络系统、图形学和多媒体, 工程技术-制造, 数学-数学跨学科应用 期刊类型&#xff1a; SCI/SSCI/AHCI 收录数据库&#xff1a; SCI(SCIE),EI,Scopus,知网(CNK…

【教学类-58-14】黑白三角拼图12——单页1页图。参考图1页6张(黑白、彩色)、板式(无圆点、黑圆点、白圆点)、宫格2-10、张数6张,适合集体操作)

背景需求&#xff1a; 基于以下两个代码&#xff0c;设计一个单页1页黑白三角、彩色三角&#xff08;包含黑点、白点、无点&#xff09;的代码。 【教学类-58-12】黑白三角拼图10&#xff08;N张参考图1张操作卡多张彩色白块&#xff0c;适合个别化&#xff09;-CSDN博客文章…

C++项目:高并发内存池_下

目录 8. thread cache回收内存 9. central cache回收内存 10. page cache回收内存 11. 大于256KB的内存申请和释放 11.1 申请 11.2 释放 12. 使用定长内存池脱离使用new 13. 释放对象时优化成不传对象大小 14. 多线程环境下对比malloc测试 15. 调试和复杂问题的调试技…

消息队列性能比拼: Kafka vs RabbitMQ

本内容是对知名性能评测博主 Anton Putra Kafka vs RabbitMQ Performance 内容的翻译与整理, 有适当删减, 相关数据和结论以原作结论为准。 简介 在本视频中&#xff0c;我们将首先比较 Apache Kafka 和传统的 RabbitMQ。然后&#xff0c;在第二轮测试中&#xff0c;会将 Kaf…

AP 场景架构设计(一) :OceanBase 读写分离策略解析

说明&#xff1a;本文内容对应的是 OceanBase 社区版&#xff0c;架构部分不涉及企业版的仲裁副本功能。OceanBase社区版和企业版的能力区别详见&#xff1a; 官网链接。 概述​ 当两种类型的业务共同运行在同一个数据库集群上时&#xff0c;这对数据库的配置等条件提出了较高…

Java 大视界 -- Java 大数据在智能金融区块链跨境支付与结算中的应用(154)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

手把手教你在linux服务器部署deepseek,打造专属自己的数据库知识库

第一步&#xff1a;安装Ollama 打开官方网址&#xff1a;https://ollama.com/download/linux 下载Ollama linux版本 复制命令到linux操作系统执行 [rootpostgresql ~]# curl -fsSL https://ollama.com/install.sh | sh在Service中增加下面两行 [rootlocalhost ~]# vi /etc/…

C++ 继承:面向对象编程的核心概念(一)

文章目录 引言1. 继承的基本知识1.1 继承的关键词的区别1.2 继承类模版 2. 基类和派生类间的转换3. 继承中的作用域4. 派生类的默认成员函数4.1 默认成员函数的规则4.2 自己实现成员函数4.3 实现一个不能被继承的基类&#xff08;基本不用&#xff09; 引言 在C中&#xff0c;…

蓝桥杯 临时抱佛脚 之 二分答案法与相关题目

二分答案法&#xff08;利用二分法查找区间的左右端点&#xff09; &#xff08;1&#xff09;估计 最终答案可能得范围 是什么 &#xff08;2&#xff09;分析 问题的答案 和 给定条件 之间的单调性&#xff0c;大部分时候只需要用到 自然智慧 &#xff08;3&#xff09;建…

【算法day22】两数相除——给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。

29. 两数相除 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#x…

关于服务器只能访问localhost:8111地址,局域网不能访问的问题

一、问题来源&#xff1a; 服务器是使用的阿里云的服务器&#xff0c;服务器端的8111端口没有设置任何别的限制&#xff0c;但是在阿里云服务器端并没有设置相应的tcp连接8111端口。 二、解决办法&#xff1a; 1、使用阿里云初始化好的端口&#xff1b;2、配置新的阿里云端口…

基于ADMM无穷范数检测算法的MIMO通信系统信号检测MATLAB仿真,对比ML,MMSE,ZF以及LAMA

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 ADMM算法 4.2 最大似然ML检测算法 4.3 最小均方误差&#xff08;MMSE&#xff09;检测算法 4.4 迫零&#xff08;ZF&#xff09;检测算法 4.5 OCD_MMSE 检测算法 4.6 LAMA检测算法 …

Linux 配置时间服务器

一、同步阿里云服务器时间 服务端设置 1.检查chrony服务是否安装&#xff0c;设置chrony开机自启&#xff0c;查看chrony服务状态 [rootnode1-server ~]# rpm -q chrony # rpm -q 用于查看包是否安装 chrony-4.3-1.el9.x86_64 [rootnode1-server ~]# systemctl enable --n…

可视化web组态开发工具

BY组态是一款功能强大的基于Web的可视化组态编辑器&#xff0c;采用标准HTML5技术&#xff0c;基于B/S架构进行开发&#xff0c;支持WEB端呈现&#xff0c;支持在浏览器端完成便捷的人机交互&#xff0c;简单的拖拽即可完成可视化页面的设计。可快速构建和部署可扩展的SCADA、H…

C++笔记-模板初阶,string(上)

一.模板初阶 1.泛型编程 以往我们要交换不同类型的两个数据就要写不同类型的交换函数&#xff0c;这是使用函数重载虽然可以实现&#xff0c;但是有以下几个不好的地方&#xff1a; 1.重载的函数仅仅是类型不同&#xff0c;代码复用率比较低&#xff0c;只要有新类型出现时&a…