蓝桥杯Python赛道备赛——Day1:基础算法

   本博客就蓝桥杯中的基础算法(这一部分说是算法,但更是一些简单的操作)进行罗列,包括:枚举、模拟、前缀和、差分、二分查找、进制转换、贪心、位运算和双指针。

   每一个算法都在给出概念解释的同时,给出了示例代码,以供低年级师弟师妹们学习和练习。

   前序知识:
(1)Python基础语法
(2)Python OOP(面向对象编程)


一、枚举

算法解释:
   枚举就是像“穷举”一样,把所有可能性都列出来,挨个检查是否符合条件。就像你忘记密码时,从0000到9999逐个尝试。

核心要点:

  • 暴力遍历:用循环把所有可能情况都试一遍
  • 剪枝优化:提前排除不可能的情况(比如检查到中间发现已经不可能,直接跳过后续判断)
  • 适用场景:当数据范围较小(比如三位数、四位数)时可用

代码示例:找出所有3位水仙花数

# 枚举算法
# 水仙花数定义:一个3位数,各位数字立方和等于它本身(例如153 = 1³ + 5³ + 3³)
for num in range(100, 1000):  # 遍历所有3位数
    # 分解各位数字(数学方法)
    a = num // 100         # 获取百位数字:整除100(例如153//100=1)
    b = (num // 10) % 10   # 获取十位数字:先整除10得15,再取余10得5
    c = num % 10           # 获取个位数字:直接取余10(153%10=3)
    
    # 判断是否符合条件
    if a**3 + b**3 + c**3 == num:
        print(num)  # 输出符合条件的数字

二、模拟

算法解释:
   实际上就是"仿真"一个过程,严格按照题目描述的步骤来实现程序。就像用乐高积木一步步搭建出场景模型。

核心要点:

  • 流程还原:必须完全按照题目要求的步骤来写代码
  • 边界处理:特别注意开始、结束、空数据等特殊情况

代码示例:银行排队时间计算(3个窗口,5个客户)

# 模拟算法
# 问题描述:客户按顺序到达,选择最早空闲的窗口办理业务,计算总等待时间
n = 3  # 银行窗口数量
k = 5  # 客户数量
times = [2,5,3,4,1]  # 每个客户办理业务需要的时间(假设同时到达)

windows = [0] * n  # 记录每个窗口当前业务的结束时间(初始都是0)
total_wait = 0     # 总等待时间统计

for t in times:  # 遍历每个客户
    # 找到最早可用的窗口(窗口结束时间最小的)
    min_time = min(windows)  # 获取所有窗口中的最早空闲时间
    idx = windows.index(min_time)  # 找到这个窗口的索引位置
    
    # 计算当前客户的等待时间(窗口空闲时间 - 到达时间)
    # 假设所有客户都是0时刻到达,所以等待时间就是窗口之前的结束时间
    wait_time = max(0, min_time - 0)  # 如果窗口空闲,等待时间为0
    total_wait += wait_time  # 累加到总等待时间
    print(f"客户{times.index(t)+1}选窗口{idx}, 等待时间:{wait_time} 分钟")
    # 更新窗口的结束时间(原结束时间 + 当前客户办理时间)
    windows[idx] = min_time + t  

print(f"总等待时间:{total_wait} 分钟")

三、前缀和

算法解释:
   预先计算数组前N项的和,使得后续求任意区间和的时间复杂度降为O(1)。

代码示例:

# 前缀和算法
# 原始数组(索引从0开始)
arr = [1, 3, 5, 7, 9]

# 创建前缀和数组(比原数组多一个元素,pre_sum[0]=0)
pre_sum = [0] * (len(arr) + 1)  # 初始化全0数组

# 计算前缀和数组
for i in range(len(arr)):
    # pre_sum[i+1] 表示原数组前i个元素的和(i从0开始计数)
    pre_sum[i+1] = pre_sum[i] + arr[i]  
    # 例如:pre_sum[1] = arr[0], pre_sum[2] = arr[0]+arr[1]

# 查询区间和:获取原数组索引2到4的和(对应元素5+7+9=21)
# 注意题目中的区间定义:如果题目说区间[2,4]指原数组第2到第4个元素(从0开始)
left = 2  # 原数组索引2(第三个元素)
right = 4 # 原数组索引4(第五个元素)
sum_range = pre_sum[right+1] - pre_sum[left]  # pre_sum[5] - pre_sum[2]
print(f"区间和:{sum_range}")  # 输出21

四、差分

算法解释:
   差分是前缀和的逆运算,用于快速对数组的某个区间进行增减操作。

代码示例:

# 差分数组算法
# 原始数组(假设初始全0)
arr = [0] * 5  # 索引0-4

# 创建差分数组(长度比原数组多1)
diff = [0] * (len(arr) + 1)  # 索引0-5

# 操作:对原数组的区间[1,3](索引1到3)统一加2
l = 1  # 起始索引(包含)
r = 3  # 结束索引(包含)
val = 2

# 差分数组操作(重点!)
diff[l] += val    # 影响从l开始的所有元素
diff[r+1] -= val  # 在r+1位置抵消影响

print("原始差分数组:", diff)

# 应用差分数组得到最终结果
for i in range(0, len(arr)):  # 注意从1开始
    diff[i+1] += diff[i]  # 计算前缀和得到每个位置的增量
    previous = arr[i]  # 原数组当前值
    arr[i] += diff[i]   # 将增量应用到原数组(注意索引偏移)
    print(f"原数组索引{i}的增量:{diff[i]},原数组值:{previous}")

print("最终数组:", arr)

五、二分查找

算法解释:
   就和朋友猜某个商品的价格一样,先猜一个中间的价格,朋友告诉你是高了还是低了,再根据这个信息再往高猜或者往低了猜。

核心要点:

  • 有序性:必须在已排序的数据上使用
  • 边界处理:注意while循环的终止条件和mid计算方式
  • 模板记忆:掌握两种写法(找第一个/最后一个匹配项)

代码示例:在有序数组中查找数字5

# 二分查找算法
def binary_search(nums, target):
    # 初始化左右指针(表示搜索范围的边界)
    left = 0
    right = len(nums) - 1  # 数组最后一个元素的索引
    
    while left <= right:  # 当左指针<=右指针时继续搜索
        mid = (left + right) // 2  # 计算中间位置(取整)
        
        if nums[mid] == target:
            return mid  # 找到目标,返回索引位置
        elif nums[mid] < target:
            # 中间值太小,说明目标在右半区
            left = mid + 1  # 将左指针移到mid右侧
        else:
            # 中间值太大,说明目标在左半区
            right = mid - 1  # 将右指针移到mid左侧
    
    return -1  # 没找到返回-1

# 测试用例(注意数组必须是有序的!)
nums = [1, 3, 5, 7, 9]
print(binary_search(nums, 5))  # 输出2(索引位置)
print(binary_search(nums, 6))  # 输出-1(未找到)

# 易错点提醒:
# 1. mid计算如果用(left+right)//2可能导致整数溢出(Python不用考虑)
# 2. 循环条件必须是<=,若写成<会漏查元素

六、进制转换

算法解释:
   就像用不同规格的盒子装东西,比如十进制是每满10进1,二进制是每满2进1。

核心要点:

  • 除基取余法:用目标进制不断除以数值,记录余数
  • 逆序输出:最后要把余数倒序排列
  • 字母处理:超过9的数字用A-F表示(16进制)

代码示例:将255转换为十六进制

# 进制转换
def convert_base(n, base):
    if base < 2 or base > 16:
        return "错误:仅支持2-16进制"
    
    # 数字符号库(超过9用字母表示)
    digits = "0123456789ABCDEF"
    result = []  # 存储余数
    
    # 特殊情况处理:输入为0时直接返回0
    if n == 0:
        return "0"
    
    # 循环计算余数
    while n > 0:
        remainder = n % base  # 获取当前余数
        result.append(digits[remainder])  # 将余数转为对应符号
        n = n // base  # 更新n为商
    
    # 余数是倒序排列的,需要反转
    return ''.join(reversed(result))

print(convert_base(255, 16))  # 输出FF
print(convert_base(10, 2))    # 输出1010

# 执行过程演示(255转16进制):
# 255 ÷ 16 = 15 余 15 → 对应F
# 15 ÷ 16 = 0 余 15 → 对应F
# 结果反转得到FF

七、贪心

算法解释:
   像吃自助餐时每次都选最贵的食物,希望最终总价值最高(不一定全局最优,但追求局部最优)。

核心要点:

  • 贪心选择:每一步都选择当前最优解
  • 无后效性:当前选择不影响后续选择

代码示例:最多能参加的会议(选择不重叠区间)

# 贪心算法
intervals = [[1,3], [2,4], [4,12], [3,6], [5,7], [6,9], [7,10], [8,11]]
# 问题描述:选择最多数量的互不重叠的时间段

# 关键步骤:按结束时间排序(早结束的能给后面留更多时间)
intervals.sort(key=lambda x: x[1])  # 按每个元素的第二个值排序

count = 0  # 统计能选择的数量
last_end = -float('inf')  # 初始化上一个选择的结束时间(负无穷)

intervals_list = []

for start, end in intervals:
    if start >= last_end:  # 当前活动开始时间不冲突
        count += 1
        intervals_list.append([start, end])
        last_end = end  # 更新最后结束时间

print(f"最多可以参加{count}个会议, 会议时间分别为:{intervals_list}")  # 输出3(选[1,3],[3,6]会冲突吗?)

八、位运算

算法解释:
   直接操作二进制位,就像用显微镜观察数据的二进制结构。

核心要点:

操作效果示例(二进制)
x & 1判断奇偶1101 & 1 = 1
x << 1乘以25<<1=10 (101→1010)
x & (x-1)消除最后一个11100 & 1011=1000
x ^ x = 0相同数异或得00110 ^ 0110 = 0000

代码示例:计算数字的二进制中1的个数

# 位运算 (二进制)
def count_ones(n):
    count = 0
    while n:
        # n & (n-1) 会消除二进制中最后的一个1
        # 例如:n=13(1101), n-1=12(1100) → 1101 & 1100 = 1100(消去最后一个1)
        n = n & (n - 1)
        count += 1  # 每消去一个1就计数
    return count

print(count_ones(13))  # 输出3(1101有3个1)
print(count_ones(7))   # 输出3(0111)

# 执行过程演示(n=13):
# 初始值:1101 → count=0
# 第一次循环:1101 & 1100 = 1100 → count=1
# 第二次循环:1100 & 1011 = 1000 → count=2
# 第三次循环:1000 & 0111 = 0000 → count=3

九、双指针

算法解释:
   就像两个人协同工作,一个快一个慢,或者从两头向中间移动。

核心要点:

  • 快慢指针:判断链表是否有环
  • 左右指针:有序数组的二分查找、两数之和
  • 数组合并:将两个独立的数组合并为新的数组

代码示例1:快慢指针法(Floyd判圈算法)

  • 快指针每次移动 2步,慢指针每次移动 1步
  • 若链表有环,快指针最终会追上慢指针(相遇)
  • 若快指针遇到 None,说明无环
# 双指针(快慢指针)
# 定义链表节点类
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def hasCycle(head: ListNode) -> bool:
    # 处理空链表或单节点无环的情况
    if not head or not head.next:
        return False
    
    # 初始化双指针
    slow = head   # 慢指针,每次走1步
    fast = head   # 快指针,每次走2步
    
    # 循环移动指针(注意循环条件)
    while fast and fast.next:  # 保证fast能安全移动两步
        # 先移动指针,再判断是否相遇
        fast = fast.next.next  # 快指针移动两步
        slow = slow.next       # 慢指针移动一步
        
        if fast == slow:       # 相遇说明有环
            return True
    
    return False  # 循环结束未相遇,说明无环

# 测试用例
# 构造一个带环的链表
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node5 = ListNode(5)
node6 = ListNode(6)

node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node2  # 关键:形成环(6指向2)

print(hasCycle(node1))  # 输出 True

# 构造无环链表
nodeA = ListNode(1)
nodeB = ListNode(2)
nodeA.next = nodeB
print(hasCycle(nodeA))  # 输出 False

代码示例2: 左右指针

# 双指针
def merge(nums1, m, nums2, n):
    # nums1有足够空间(总长度m+n),前m个是有效元素
    # 从后往前填充,避免覆盖未处理的元素
    p1 = m - 1  # nums1有效部分的最后索引
    p2 = n - 1  # nums2的最后索引
    tail = m + n - 1  # 合并后的最后位置
    
    while p2 >= 0:  # 当nums2还有元素需要处理时
        # 如果nums1还有元素,且nums1当前值更大
        if p1 >= 0 and nums1[p1] > nums2[p2]:
            nums1[tail] = nums1[p1]
            p1 -= 1
        else:
            nums1[tail] = nums2[p2]
            p2 -= 1
        tail -= 1  # 向前移动填充位置
    return nums1

# 测试用例
nums1 = [1,3,4,5,7,0,0,0]
nums2 = [2,4,6]
print(merge(nums1, 5, nums2, 3))  # 输出[1,2,3,4,5,6]

# 执行过程演示:
# 初始状态:nums1=[1,3,5,0,0,0], nums2=[2,4,6]
# p1=2(指向5), p2=2(指向6), tail=5
# 第一轮:5<6 → 填入6,tail=4, p2=1
# 第二轮:5>4 → 填入5,tail=3, p1=1
# 第三轮:3<4 → 填入4,tail=2, p2=0
# 第四轮:3>2 → 填入3,tail=1, p1=0
# 第五轮:1<2 → 填入2,tail=0, p2=-1
# 最终nums1变为[1,2,3,4,5,6]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值