备战蓝桥杯国赛【Day 18】

📌 写在前面 :今天的3道题全部来自蓝桥杯算法赛真题 ,难度梯度递进,核心考点包括:分离排序思想、贪心拼接策略、归并排序求逆序对、多关键字排序。这些题目看似简单,但暗藏精妙设计,是检验排序思维深度的绝佳试金石。


📚 今日刷题清单

题号 题目 来源 类型 难度 核心考点
1 奇偶排序 蓝桥杯17022 分离排序 ⭐⭐ 列表推导式、分离排序、稳定合并
2 二进制王国 蓝桥杯17035 贪心+自定义排序 ⭐⭐⭐⭐ 拼接比较、贪心证明、字典序最小
3 星际争霸 蓝桥杯5092 归并排序+逆序对 ⭐⭐⭐⭐⭐ 归并排序、逆序对计数、多关键字排序

一、奇偶排序 ⭐⭐

1.1 题目描述

给定 n 个整数,要求将所有奇数 按升序排在前面,所有偶数按升序排在后面。

输入n 和数组 a
输出:排序后的数组(奇数在前,偶数在后,各自升序)

1.2 关键思路:分离+分别排序+合并

暴力思路 :遍历数组,奇数放前面,偶数放后面,然后各自排序。时间 O(n log n)
更优思路:利用 Python 列表推导式,一行分离奇偶,再分别排序合并。

核心洞察

  • 奇数和偶数的相对顺序不需要交错,可以完全分离
  • 分离后各自排序,再拼接即可

1.3 推演验证

复制代码
输入: n=6, a=[3, 1, 4, 1, 5, 9, 2, 6]

分离:
奇数: [3, 1, 1, 5, 9]
偶数: [4, 2, 6]

各自排序:
奇数: [1, 1, 3, 5, 9]
偶数: [2, 4, 6]

合并: [1, 1, 3, 5, 9, 2, 4, 6]

等等,样例输出应该是?
假设输入: [3, 1, 4, 1, 5, 9, 2, 6]
奇数排序: [1, 1, 3, 5, 9]
偶数排序: [2, 4, 6]
输出: 1 1 3 5 9 2 4 6

1.4 题解

python 复制代码
n = int(input())
a = list(map(int, input().split()))

# 分离奇数和偶数
odd = [x for x in a if x % 2 == 1]   # 奇数
even = [x for x in a if x % 2 == 0]  # 偶数

# 各自排序后合并输出
print(*(sorted(odd) + sorted(even)))

复杂度 :时间 O(n log n)(排序),空间 O(n)

1.5 关键细节

坑点 说明
x % 2 == 1 判断奇数 Python中负数取模结果可能为负,但题目数据通常为正整数
* 解包输出 print(*(list)) 等价于 print(list[0], list[1], ...),去掉方括号
列表推导式效率 O(n) 遍历,比 filter 更 Pythonic
稳定排序 sorted() 是稳定排序,相等元素相对顺序不变

二、二进制王国 ⭐⭐⭐⭐

2.1 题目描述

给定 n 个二进制字符串,将它们拼接起来,求字典序最小的拼接结果。

输入nn 个二进制字符串
输出:字典序最小的拼接字符串

2.2 关键思路:自定义比较器贪心

暴力思路 :枚举所有排列,拼接后比较字典序。n! 种排列,不可接受。
优化思路 :贪心策略------a 应该在 b 前面,当且仅当 a+b < b+a(字符串拼接后字典序比较)。

为什么 a+b < b+a 是对的?

证明思路(交换论证):

  • 假设最优排列中,存在相邻两个字符串 a, b,满足 a+b > b+a
  • 交换它们得到 ..., b, a, ...
  • 比较两种排列:... + a + b + ... vs ... + b + a + ...
  • 由于 a+b > b+a,交换后字典序更小,与"最优"矛盾!
  • 所以最优排列中,任意相邻两个都满足 a+b ≤ b+a

传递性:这种比较关系满足传递性(可以严格证明),所以可以用排序实现。

2.3 推演验证

复制代码
输入: n=3, s=["10", "1", "11"]

所有排列及拼接结果:
["10","1","11"] → "10111"
["10","11","1"] → "10111"
["1","10","11"] → "11011"
["1","11","10"] → "11110"
["11","1","10"] → "11110"
["11","10","1"] → "11101"

字典序最小: "10111"

用贪心策略验证:
比较 "10" 和 "1":
"10"+"1" = "101", "1"+"10" = "110"
"101" < "110",所以 "10" 应该在 "1" 前面

比较 "10" 和 "11":
"10"+"11" = "1011", "11"+"10" = "1110"
"1011" < "1110",所以 "10" 在 "11" 前面

比较 "1" 和 "11":
"1"+"11" = "111", "11"+"1" = "111"
相等,相对顺序不变(稳定排序)

排序结果: ["10", "1", "11"] 或 ["10", "11", "1"]
拼接: "10111" ✓

2.4 题解

python 复制代码
from functools import cmp_to_key

n = int(input())
s = []
for _ in range(n):
    s.append(input().strip())

def compare(a, b):
    """
    比较函数:a+b 和 b+a 的字典序
    返回 -1: a 排在 b 前面(a+b < b+a)
    返回  1: a 排在 b 后面(a+b > b+a)
    返回  0: 相等
    """
    if a + b < b + a:
        return -1
    elif a + b > b + a:
        return 1
    else:
        return 0

# 自定义排序
s.sort(key=cmp_to_key(compare))

# 拼接输出
print(''.join(s))

复杂度 :时间 O(n log n × L)L 为字符串平均长度

2.5 关键细节

坑点 说明
a+b < b+a 不是 a < b 直接比较字符串本身是错误的!必须用拼接后比较
cmp_to_key 用法 Python3 标准写法,将比较函数转为 key 函数
稳定排序 a+b == b+a 时返回0,保持原有相对顺序
字符串拼接复杂度 每次比较 O(L),总复杂度 O(n log n × L)
输入读取 input().strip() 去除首尾空白,避免换行符干扰

三、星际争霸 ⭐⭐⭐⭐⭐

3.1 题目描述

星际争霸中,每个士兵有一个战斗力字符串 (由 0-9 组成)。定义一个字符串的混乱度 为:将其转换成列表后,逆序对的个数

现在给定 n 个士兵的战斗力字符串,按以下规则排序:

  1. 混乱度升序(逆序对数小的在前)
  2. 混乱度相同,字符串长度升序
  3. 长度也相同,字符串本身字典序升序

输入nn 个字符串
输出:排序后的字符串列表

3.2 关键思路:归并排序求逆序对 + 多关键字排序

第一步:求逆序对个数

逆序对定义 :数组中 i < ja[i] > a[j] 的对数。

归并排序求逆序对

  • 归并排序的分治过程中,当 arr[i] > arr[j] 时,arr[i...m] 都大于 arr[j],贡献 m-i+1 个逆序对
  • 时间 O(n log n)

第二步:多关键字排序

排序规则:

  1. 第一关键字:逆序对数(升序)
  2. 第二关键字:字符串长度(升序)
  3. 第三关键字:字符串本身(字典序升序)

Python 实现:arr.sort(key=lambda x: (x[0], len(x[1]), x[1]))

3.3 推演验证

复制代码
输入: n=3
s = ["21", "123", "321"]

计算逆序对:
"21" → [2,1] → 逆序对: (2,1) → 1个
"123" → [1,2,3] → 逆序对: 0个
"321" → [3,2,1] → 逆序对: (3,2),(3,1),(2,1) → 3个

排序:
("123", 0, 3) → 第一
("21", 1, 2)  → 第二
("321", 3, 3) → 第三

输出: 123, 21, 321

3.4 题解

python 复制代码
def merge_sort(arr, l, r):
    """
    归并排序,同时计算逆序对个数
    arr: 待排序数组(整数列表)
    l, r: 左右边界(闭区间)
    返回: 逆序对个数
    """
    if l >= r:
        return 0
    
    m = (l + r) // 2
    left = merge_sort(arr, l, m)      # 左半部分逆序对
    right = merge_sort(arr, m + 1, r) # 右半部分逆序对
    return left + right + merge(arr, l, m, r)  # 跨区间逆序对

def merge(arr, l, m, r):
    """
    合并两个有序区间 [l,m] 和 [m+1,r]
    同时计算跨区间逆序对
    """
    temp = [0] * (r - l + 1)  # 临时数组
    i = l          # 左半部分指针
    j = m + 1      # 右半部分指针
    idx = 0        # temp数组指针
    res = 0        # 逆序对计数
    
    # 双指针合并
    while i <= m and j <= r:
        if arr[i] <= arr[j]:
            # 左边小,直接放入
            temp[idx] = arr[i]
            i += 1
        else:
            # 右边小,说明 arr[i...m] 都大于 arr[j]
            # 贡献 m - i + 1 个逆序对
            res += m - i + 1
            temp[idx] = arr[j]
            j += 1
        idx += 1
    
    # 处理剩余元素
    while i <= m:
        temp[idx] = arr[i]
        i += 1
        idx += 1
    while j <= r:
        temp[idx] = arr[j]
        j += 1
        idx += 1
    
    # 复制回原数组
    arr[l:r+1] = temp
    return res

# 主程序
n = int(input())
arr = []

for _ in range(n):
    s = input().strip()
    # 将字符串转为整数列表,计算逆序对
    num_list = list(s)  # 如 "21" → ['2', '1']
    inversion_count = merge_sort(num_list, 0, len(num_list) - 1)
    # 保存 (逆序对数, 原字符串)
    arr.append((inversion_count, s))

# 多关键字排序:
# 1. 逆序对数升序
# 2. 字符串长度升序
# 3. 字符串字典序升序
arr.sort(key=lambda x: (x[0], len(x[1]), x[1]))

# 输出结果
for info in arr:
    print(info[1])

复杂度

  • 计算逆序对:O(L log L)L 为字符串长度
  • 总时间:O(n × L log L + n log n)
  • 空间:O(L)(归并排序临时数组)

3.5 关键细节

坑点 说明
归并排序中的 arr[i] <= arr[j] 必须是 <= 不是 <,保证稳定性,避免重复计数
res += m - i + 1 arr[i] > arr[j] 时,arr[i...m] 都大于 arr[j],共 m-i+1
字符串转列表 list(s) 得到字符列表,merge_sort 按字符 ASCII 比较
多关键字排序 key=lambda x: (x[0], len(x[1]), x[1]) 元组逐元素比较
临时数组大小 temp = [0] * (r - l + 1),注意是闭区间,长度 r-l+1

3.6 归并排序求逆序对原理

复制代码
数组: [3, 1, 4, 2]  (索引0~3)

分治过程:
[3,1,4,2] → [3,1] 和 [4,2]
[3,1] → [3] 和 [1]
  合并: 3 > 1,res += 1-0+1 = 1(3的逆序对)
  结果: [1,3], res=1
[4,2] → [4] 和 [2]
  合并: 4 > 2,res += 1-0+1 = 1(4的逆序对)
  结果: [2,4], res=1

合并 [1,3] 和 [2,4]:
i=0(1), j=2(2): 1 <= 2, 放入1, i=1
i=1(3), j=2(2): 3 > 2, res += 2-1+1 = 2(3和2, 3后面的...等等)

等等,m=1(左半部分最后一个索引)
i=1(3), j=2(2): 3 > 2, res += m - i + 1 = 1 - 1 + 1 = 1
放入2, j=3
i=1(3), j=3(4): 3 <= 4, 放入3, i=2(左半部分结束)
放入4

总逆序对: 1 + 1 + 1 = 3
验证: (3,1), (3,2), (4,2) → 3个 ✓

🎯 今日复盘总结

题目 核心技巧 思维路径 易错点 国赛价值
奇偶排序 分离排序 奇偶分离→各自排序→合并 *解包输出、列表推导式 基础技巧
二进制王国 贪心拼接排序 相邻交换论证→a+b<b+a→自定义排序 比较器返回值、字符串拼接方向 经典贪心
星际争霸 归并排序+逆序对 分治求逆序对→多关键字排序 <=稳定性、m-i+1计数、多关键字 综合应用

💡 今日核心收获

  1. 分离排序思想:当数据可以清晰分类时,先分离再各自排序,最后合并,往往比复杂比较器更简洁。
  2. 贪心拼接策略a+b < b+a 是解决"拼接最值"类问题的通用贪心策略,记住交换论证的证明方法。
  3. 归并排序求逆序对 :分治过程中统计跨区间逆序对,时间 O(n log n),是处理"顺序对/逆序对"问题的标准算法。
  4. 多关键字排序 :Python 中用元组 key=lambda x: (key1, key2, key3),逐元素比较,简洁高效。
  5. 稳定排序的重要性:当比较相等时,稳定排序保持原有顺序,这在多轮排序或复杂规则中至关重要。

📌 排序算法复杂度对比

算法 平均时间 最坏时间 空间 稳定性 Python实现
冒泡排序 O(n²) O(n²) O(1) ---
选择排序 O(n²) O(n²) O(1) ---
插入排序 O(n²) O(n²) O(1) ---
归并排序 O(n log n) O(n log n) O(n) sorted() / sort()
快速排序 O(n log n) O(n²) O(log n) ---
堆排序 O(n log n) O(n log n) O(1) heapq
Timsort O(n log n) O(n log n) O(n) Python内置

Python 的 sort()sorted() 使用 Timsort (归并排序+插入排序的混合),是稳定排序

记得 点赞收藏,算法路上不迷路!有问题评论区见 👇

相关推荐
Gerardisite5 小时前
企业微信消息回调接口
python·机器人·企业微信
拽着尾巴的鱼儿5 小时前
国密算法 Spring Boot 实战:SM2/SM3/SM4 完整集成指南
spring boot·后端·算法
210Brian5 小时前
蓝桥杯单片机学习笔记(十二):V2026 大模板构建(上)
单片机·学习·蓝桥杯
RSTJ_16255 小时前
PYTHON+AI LLM DAY FIFITY-ONE
开发语言·人工智能·python
qingfeng154155 小时前
企业微信定时群发实战:API 如何实现批量消息自动发送?
java·开发语言·python·自动化·企业微信
qingfeng154155 小时前
企业微信 API 可以做什么?
java·开发语言·python·自动化·企业微信
Hesionberger5 小时前
LeetCode105:前序中序构建二叉树(三解法)
java·数据结构·python·算法·leetcode·深度优先
@小柯555m5 小时前
算法(移动零)
数据结构·算法·leetcode
Chase_______5 小时前
【Java杂项】为什么 long 可以自动转 float?宽化基本类型转换与精度丢失详解
java·开发语言·python