当前位置: 首页 > news >正文

【Python算法】基础语法、算法技巧模板、二分、DFS与BFS

建议全部跟着手敲一遍!!!

import sys
from math import gcd
from bisect import bisect_left
from collections import defaultdict, deque
import heapq
from itertools import permutations, combinations
from functools import lru_cache
from typing import List

# ===================== 输入输出优化 =====================
# 重新定义输入函数,使用sys.stdin.readline().strip()实现快速输入
# 这是因为sys.stdin.readline()比普通的input()函数读取速度更快
input = lambda: sys.stdin.readline().strip()  

# 示例:读取n和n个数字
def read_data():
    # 读取一行输入,并按空格分割成字符串列表
    data = input().split()              
    # 将列表的第一个元素转换为整数,作为数字的数量n
    n = int(data[0])
    # 将列表中从第二个元素开始的n个元素转换为整数,并存储在列表nums中
    nums = list(map(int, data[1:n+1]))   
    return n, nums

# ===================== 格式化输出 =====================
# 定义圆周率π的值
pi = 3.1415926
# 使用f-string格式化输出,保留两位小数
print(f"{pi:.2f}")  # 保留两位小数 → 3.14
# 使用f-string格式化输出,将整数5补零到3位
print(f"{5:03d}")   # 补零到3位 → 005

# ===================== 数据结构技巧 =====================
# 列表初始化
# 创建一个长度为100的一维数组,所有元素初始化为0
arr = [0] * 100                         
# 正确创建二维数组(3行5列)
# 使用列表推导式创建一个3行5列的二维数组,所有元素初始化为0
matrix = [[0] * 5 for _ in range(3)]    

# 字典默认值处理
# 创建一个默认值为0的字典
# 当访问字典中不存在的键时,会自动创建该键并将其值初始化为0
d = defaultdict(int)                    
# 对字典中键为"key"的值加1,无需判断key是否存在
d["key"] += 1                           

# 字典排序(按值降序)
# 对字典d的键值对进行排序,按照值的降序排列
# 使用lambda函数指定排序的键为值的相反数,实现降序排序
sorted_dict = dict(sorted(d.items(), key=lambda x: -x[1]))

# 双端队列(BFS常用)
# 创建一个双端队列对象
q = deque()
# 在队尾插入元素1
q.append(1)         
# 从队首移除并返回元素
q.popleft()         

# ===================== 排序与查找算法 =====================
# 快速排序(分治思想)
def quick_sort(arr):
    # 如果数组长度小于等于1,直接返回该数组
    if len(arr) <= 1:
        return arr
    # 选择中间元素作为基准
    pivot = arr[len(arr)//2]            
    # 小于基准的元素组成的列表
    left = [x for x in arr if x < pivot]
    # 等于基准的元素组成的列表
    middle = [x for x in arr if x == pivot]
    # 大于基准的元素组成的列表
    right = [x for x in arr if x > pivot]
    # 递归地对左右子数组进行排序,并合并结果
    return quick_sort(left) + middle + quick_sort(right)  

# 二分查找左边界(寻找第一个≥target的位置)
def bisect_left_custom(arr, target):
    # 左指针初始化为0
    l, r = 0, len(arr)
    # 当左指针小于右指针时,继续循环
    while l < r:
        # 计算中间位置
        mid = (l + r) // 2
        # 如果中间元素小于目标值,将左指针移动到mid+1
        if arr[mid] < target:
            l = mid + 1
        # 否则,将右指针移动到mid
        else:
            r = mid
    # 返回插入位置
    return l  

# ===================== 数学与数论 =====================
# 埃拉托斯特尼筛法(素数筛选)
def sieve(n):
    # 创建一个长度为n+1的布尔数组,初始值都为True,表示所有数都是素数
    is_prime = [True] * (n+1)
    # 0和1不是素数,将其标记为False
    is_prime[0] = is_prime[1] = False
    # 从2开始遍历到根号n
    for i in range(2, int(n**0.5)+1):
        # 如果i是素数
        if is_prime[i]:
            # 标记i的倍数为非素数
            for j in range(i*i, n+1, i):
                is_prime[j] = False
    # 返回所有标记为True的数,即素数
    return [i for i, prime in enumerate(is_prime) if prime]

# 最大公约数与最小公倍数
# 计算12和18的最大公约数
gcd_val = gcd(12, 18)    # 6
# 根据公式计算最小公倍数
lcm_val = 12 * 18 // gcd_val  # 36

# 组合数计算(模运算优化)
# 定义模数
MOD = 10**9 + 7
def comb(n, k):
    # 初始化结果为1
    res = 1
    # 从1到k进行循环
    for i in range(1, k+1):
        # 根据递推公式计算组合数
        res = res * (n - k + i) // i  # 递推公式避免浮点
    # 返回结果对MOD取模的值
    return res % MOD

# ===================== 图论算法 =====================
# 邻接表建图(无向图)
# 创建一个默认值为列表的字典,用于存储图的邻接表
graph = defaultdict(list)
# 定义图的边
edges = [[1,2], [2,3], [1,3]]
# 遍历每条边
for u, v in edges:
    # 将v添加到u的邻接表中
    graph[u].append(v)
    # 将u添加到v的邻接表中,因为是无向图
    graph[v].append(u)

# Dijkstra最短路径(堆优化)
def dijkstra(graph, start):
    # 创建一个最小堆,初始元素为(0, start),表示从起点到起点的距离为0
    heap = [(0, start)]
    # 初始化距离字典,起点到自身的距离为0
    dist = {start: 0}
    # 当堆不为空时,继续循环
    while heap:
        # 从堆中取出距离最小的节点及其距离
        d, u = heapq.heappop(heap)
        # 如果取出的距离大于当前记录的最短距离,跳过该节点
        if d > dist.get(u, float('inf')):
            continue
        # 遍历该节点的所有邻居节点
        for v, w in graph[u]:
            # 如果通过当前节点到达邻居节点的距离更短
            if dist.get(v, float('inf')) > d + w:
                # 更新邻居节点的最短距离
                dist[v] = d + w
                # 将邻居节点及其最短距离加入堆中
                heapq.heappush(heap, (dist[v], v))
    # 返回距离字典
    return dist

# ===================== 动态规划 =====================
# 01背包问题(空间优化版)
def knapsack(weights, values, capacity):
    # 创建一个长度为capacity+1的数组,用于存储最大价值
    dp = [0] * (capacity + 1)
    # 遍历每个物品
    for w, v in zip(weights, values):
        # 逆序更新dp数组,避免重复选取物品
        for j in range(capacity, w-1, -1):  
            # 选择放入或不放入当前物品的最大价值
            dp[j] = max(dp[j], dp[j - w] + v)
    # 返回背包容量为capacity时的最大价值
    return dp[capacity]

# 最长上升子序列(贪心+二分)
def lengthOfLIS(nums):
    # 创建一个空列表,用于存储上升子序列的末尾元素
    tails = []
    # 遍历数组中的每个元素
    for num in nums:
        # 找到元素num在tails列表中应该插入的位置
        idx = bisect_left(tails, num)  
        # 如果插入位置等于tails列表的长度,说明num比tails中的所有元素都大
        if idx == len(tails):
            # 将num添加到tails列表的末尾
            tails.append(num)
        else:
            # 否则,将tails列表中该位置的元素替换为num
            tails[idx] = num  
    # 返回上升子序列的长度
    return len(tails)

# ===================== 回溯算法 =====================
# 全排列生成
def permute(nums):
    def backtrack(path):
        # 如果当前路径的长度等于数组的长度,说明已经生成了一个全排列
        if len(path) == len(nums):
            # 将当前路径的副本添加到结果列表中
            res.append(path.copy())
            return
        # 遍历数组中的每个元素
        for num in nums:
            # 如果元素不在当前路径中
            if num not in path:    
                # 将元素添加到当前路径中
                path.append(num)
                # 递归调用backtrack函数,继续生成排列
                backtrack(path)
                # 回溯,移除最后一个元素
                path.pop()         
    # 初始化结果列表
    res = []
    # 调用backtrack函数,从空路径开始生成排列
    backtrack([])
    # 返回结果列表
    return res

# ===================== 前缀和与差分 =====================
# 前缀和数组
# 初始化前缀和数组,第一个元素为0
prefix = [0]
# 遍历数组[1, 2, 3, 4]
for num in [1, 2, 3, 4]:
    # 计算前缀和,将前一个前缀和加上当前元素的值
    prefix.append(prefix[-1] + num)  # prefix = [0,1,3,6,10]

# 差分数组(区间更新)
# 创建一个长度为5的差分数组,所有元素初始化为0
diff = [0] * 5
# 差分数组的第一个元素设为1
diff[0] = 1
# 示例差分,计算差分数组的其他元素
for i in range(1, 5):
    diff[i] = (i+1) - i  # 示例差分

# ===================== 其他实用技巧 =====================
# 排列组合生成
# 生成[1,2,3]中取2个元素的所有排列,并打印结果
print(list(permutations([1,2,3], 2)))  # 排列
# 生成[1,2,3]中取2个元素的所有组合,并打印结果
print(list(combinations([1,2,3], 2)))  # 组合

# 堆操作(默认小顶堆)
# 创建一个空堆
heap = []
# 向堆中插入元素2
heapq.heappush(heap, 2)
# 向堆中插入元素1
heapq.heappush(heap, 1)
# 从堆中取出最小的元素并打印
print(heapq.heappop(heap))  # 1

# 大顶堆技巧(存入负数)
# 向堆中插入元素-3
heapq.heappush(heap, -3)
# 从堆中取出最大的元素(因为存入的是负数,所以取反)
max_val = -heapq.heappop(heap)

# 位运算技巧
# 定义一个二进制数
n = 0b1010
# 清除最低位的1,并打印结果
print(n & (n-1))    # 清除最低位的1 → 0b1000
# 计算2的3次方,并打印结果
print(1 << 3)       # 计算2^3 → 8
# 判断5的奇偶性,奇数返回0,偶数返回1
print(~5 & 1)       # 判断奇偶(5是奇→0,偶→1)

# 记忆化搜索(斐波那契数列)
# 使用lru_cache装饰器实现记忆化搜索
@lru_cache(maxsize=None)
def fib(n):
    # 如果n小于2,返回n
    if n < 2: 
        return n
    # 递归计算斐波那契数列的值
    return fib(n-1) + fib(n-2)

# ===================== 高频考题模板 =====================
# 和为K的子数组个数(前缀和+哈希)
def subarraySum(nums: List[int], k: int) -> int:
    # 创建一个默认值为0的字典,用于存储前缀和及其出现的次数
    prefix_sum = defaultdict(int)
    # 前缀和为0的情况出现1次
    prefix_sum[0] = 1
    # 初始化当前前缀和为0
    current_sum = count = 0
    # 遍历数组中的每个元素
    for num in nums:
        # 更新当前前缀和
        current_sum += num
        # 计算需要的前缀和
        count += prefix_sum.get(current_sum - k, 0)
        # 更新当前前缀和的出现次数
        prefix_sum[current_sum] += 1
    # 返回和为k的子数组的个数
    return count

# 滑动窗口模板
def sliding_window(nums, k):
    # 左指针初始化为0
    left = 0
    # 右指针从0开始遍历数组
    for right in range(len(nums)):
        # 当窗口大小超过k时,收缩窗口
        while right - left + 1 > k:
            left += 1
        # 处理当前窗口
        print(nums[left:right+1])

BFS

下面的解答都是非力扣式,而是自行写输入输出

在一个二维网格中找到由 1 组成的最大连通区域的面积。
求岛屿的最大面积问题:

from collections import deque
grid = []
m,n = map(int,input().split())
for _ in range(m):
    row = list(map(int, input().split()))
    grid.append(row)  # 输入m行n列
    
m = len(grid)
n = len(grid[0])
direc = [(0,1),(0,-1),(-1,0),(1,0)]

def bfs(i,j):
    ans=1
    q = deque([(i,j)]) # 入队
    grid[i][j]=0 # 标记为已经访问过了
    while q:
        x,y =q.popleft()
        for dx,dy in direc:
            nx = x+dx
            ny = y+dy
            if 0<=nx<m and 0<=ny<n and grid[nx][ny]:
                q.append((nx,ny))
                grid[nx][ny]=0  # 添加进队列后都要标记为已经访问过了
                ans+=1
    return ans

res=0
for i,row in enumerate(grid):
    for j,x in enumerate(row):
        if x==1:
            res = max(res,bfs(i,j))

print(res)

力扣1926题
在这里插入图片描述

from collections import deque
grid = []
# 输入行数和列数
m, n = map(int, input().split())
# 逐行输入网格数据
for _ in range(m):
    row = list(map(str, input().split()))
    grid.append(row)
# 输入入口坐标
i, j = map(int, input().split())

direc = [(0, 1), (0, -1), (-1, 0), (1, 0)]
ans = -1  # 初始化结果为 -1,表示未找到出口
q = deque([(i, j, 0)])
grid[i][j] = '+'  # 入队后标记为已经访问过了

while q:
    x, y, steps = q.popleft()
    # 检查是否到达边界且不是入口
    if (x == 0 or x == m - 1 or y == 0 or y == n - 1) and (x, y) != (i, j):
        ans = steps
        break
    # 遍历四个方向
    for dx, dy in direc:
        nx, ny = x + dx, y + dy
        # 检查新坐标是否合法且未访问过
        if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == '.':
            q.append((nx, ny, steps + 1))
            grid[nx][ny] = '+'

print(ans)

力扣994题
在这里插入图片描述

from collections import deque
import sys

# 输入行数和列数
m, n = map(int, input().split())
grid = []
fresh = 0
q = deque()

# 读取网格并初始化
for i in range(m):
    row = list(map(int, input().split()))
    grid.append(row)
    for j in range(n):
        if grid[i][j] == 1:
            fresh += 1
        elif grid[i][j] == 2:
            q.append((i, j, 0))

# 特殊情况处理:如果没有新鲜橘子,直接返回0
if fresh == 0:
    print(0)
    sys.exit()

directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
minutes = 0

# BFS处理腐烂过程
while q:
    x, y, time = q.popleft()
    minutes = time
    for dx, dy in directions:
        nx, ny = x + dx, y + dy
        if 0 <= nx < m and 0 <= ny < n and grid[nx][ny] == 1:
            grid[nx][ny] = 2
            fresh -= 1
            q.append((nx, ny, time + 1))

# 输出结果
print(minutes if fresh == 0 else -1)

力扣934题:最短的桥
在这里插入图片描述
DFS+BFS:

   解决最短桥问题的完整方案
    算法步骤:
    1. 使用DFS找到并标记第一座岛屿(标记为2)
    2. 使用多源BFS从第一座岛屿扩展,寻找第二座岛屿
    3. 当BFS遇到第二座岛屿时,返回当前扩展的步数
from collections import deque

# 读取网格大小
n = int(input())
# 读取网格数据,转换为二维整数列表
grid = [list(map(int, input().split())) for _ in range(n)]
# 定义四个移动方向:右、左、下、上
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
# 初始化BFS队列
q = deque()

# ========== 第一阶段:标记第一座岛屿 ==========
found = False  # 标记是否已找到第一座岛屿

# 遍历整个网格寻找第一座岛屿的起点
for i in range(n):
    if found:
        break
    for j in range(n):
        # 当找到第一个陆地单元格(值为1)
        if grid[i][j] == 1:
            # 使用栈实现迭代式DFS(避免递归深度限制)
            stack = [(i, j)]
            # 将起始点标记为2(表示属于第一座岛屿)
            grid[i][j] = 2
            # 开始DFS遍历
            while stack:
                x, y = stack.pop()
                # 将当前岛屿点加入BFS队列,初始步数为0
                q.append((x, y, 0))
                # 检查四个方向
                for dx, dy in directions:
                    nx, ny = x + dx, y + dy
                    # 确保新坐标在网格内且是未访问的陆地(值为1)
                    if 0 <= nx < n and 0 <= ny < n and grid[nx][ny] == 1:
                        # 标记为属于第一座岛屿
                        grid[nx][ny] = 2
                        # 加入栈中继续DFS
                        stack.append((nx, ny))
            # 标记已找到第一座岛屿,跳出循环
            found = True
            break

# ========== 第二阶段:多源BFS寻找第二座岛屿 ==========
while q:
    x, y, steps = q.popleft()  # 取出队列中的点和当前步数
    # 检查四个方向
    for dx, dy in directions:
        nx, ny = x + dx, y + dy
        # 确保新坐标在网格内
        if 0 <= nx < n and 0 <= ny < n:
            # 如果遇到值为1的单元格,说明找到第二座岛屿
            if grid[nx][ny] == 1:
                # 输出当前步数(即需要翻转的最少水域数)
                print(steps)
                # 直接退出程序
                exit()
            # 如果是水域(值为0)
            if grid[nx][ny] == 0:
                # 标记为已访问(-1表示已探索的水域)
                grid[nx][ny] = -1
                # 加入队列,步数+1
                q.append((nx, ny, steps + 1))

print(-1)

DFS

力扣086题:分割回文串
DFS+BFS

s = input().strip()  # 读取输入并去除首尾空格
res = []
path = []

def dfs(start):
    """使用深度优先搜索查找所有回文分割方案 
    Args:
        start: 当前搜索的起始位置
    """
    if start == len(s):
        res.append(path.copy())
        return
    
    for end in range(start + 1, len(s) + 1):
        substring = s[start:end]
        if substring == substring[::-1]:  # 判断是否为回文
            path.append(substring)
            dfs(end)
            path.pop()  # 回溯

dfs(0)  # 从字符串起始位置开始搜索
print(res)

二分

力扣793题:阶乘后函数K个零
在这里插入图片描述
由于阶乘末尾零的数量随着数字的增加而单调递增,我们可以利用二分查找来高效地定位这些数字。
尾随的零 ≤k 的那个最大的数

使用下面的二分模板:

while left <= right:
    mid = (left + right) // 2
    if count_trailing_zeros(mid) <= k:
        left = mid + 1   # 保持"left左侧都满足"
    else:
        right = mid - 1  # 保持"right右侧都不满足"
# 结束时 right == 最后一个满足条件的值,即最大满足条件的数

upper​​ 定位到 k 个零的区间的右端点。
​​lower​​ 定位到 k-1 个零的区间的右端点。
两者的差值 upper - lower 直接给出了恰好有 k 个零的数字数量。
完整代码:

def count_trailing_zeros(x):
    """计算x!末尾有多少个零"""
    zeros = 0
    while x > 0:
        x = x // 5
        zeros += x
    return zeros

def find_max_num_with_k_zeros(k):
    """找到最大的n使得n!的尾随零<=k"""
    left, right = 0, 5 * (k + 1)  # 足够大的上界
    
    while left <= right:
        mid = (left + right) // 2  # 注意这里不需要+1
        zeros = count_trailing_zeros(mid)
        if zeros <= k:
            left = mid + 1  # 尝试更大的数
        else:
            right = mid - 1  # 尝试更小的数
    
    return right  # 注意返回的是right而不是left

# 输入处理
k = int(input())

# 计算恰好有k个零的数字数量
if k == 0:
    print(0)
else:
    upper = find_max_num_with_k_zeros(k)
    lower = find_max_num_with_k_zeros(k - 1)
    print(upper - lower)

相关文章:

  • selenium快速入门
  • 如何实现H5端对接钉钉登录并优雅扩展其他平台
  • 《计算机视觉度量:从特征描述到深度学习》—深度学习工业检测方案评估
  • 人工智能在医疗信息化设备上为医疗行业带来了诸多变革
  • vscode 连不上 Ubuntu 18 server 的解决方案
  • MySQL:日志
  • TDEngine 配置
  • Spring AI应用:利用DeepSeek+嵌入模型+Milvus向量数据库实现检索增强生成--RAG应用(超详细)
  • 根据 PID 找到对应的 Docker 容器
  • 【专题】图论
  • Docker 容器内运行程序的性能开销
  • 使用SQL查询ES数据
  • 考研单词笔记 2025.04.10
  • Ansible:Playbook-template模板详解
  • STM32F103C8T6单片机开发:简单说说单片机的外部GPIO中断(标准库)
  • 基于landsat与Modis影像的遥感技术的生态环境质量评价
  • 纯键盘操作电脑,丢弃鼠标!!!
  • 小程序租赁系统源码功能分享
  • 汽车氛围灯400V浪涌防护方案之SD3002P4-3的应用实践
  • Python语言的网络安全
  • 单页网站制作程序/百度推广的定义
  • 网页制作属于前端吗/上海seo公司哪家好
  • 苏州公司网站制作公司/长沙企业seo服务
  • 怎么给幼儿园做网站/北京搜索优化推广公司
  • 淄博网站建设培训学校/seo诊断站长
  • 网页制作q元素/网站推广优化公司