利用Duckdb求解Advent of Code 2025第5题 自助餐厅

原题地址

--- 第 5 天:自助餐厅 ---

当叉车打通墙壁时,精灵们高兴地发现,另一边确实有一个自助餐厅。

你能听到厨房传来的喧闹声。"照这样下去,我们就没有时间在餐厅挂花环了!"你决心继续你的任务,于是前去查看。

"要是我们没有在圣诞节前换用新的库存管理系统就好了!"另一位精灵大声说道。你询问发生了什么事。

厨房里的精灵解释了情况:由于他们复杂的新库存管理系统,他们无法分辨哪些食材是新鲜的,哪些是变质的。当你询问系统如何工作时,他们给了你一份他们的数据库副本(你的谜题输入)。

该数据库基于食材ID运作。它由一个新鲜食材ID范围 列表、一个空行和一个可用食材ID列表组成。例如:

复制代码
3-5
10-14
16-20
12-18

1
5
8
11
17
32

新鲜ID范围是包含性 的:范围 3-5 意味着食材ID 3、4 和 5 都是新鲜的。范围也可以重叠;如果一个食材ID位于任何范围内,它就是新鲜的。

精灵们试图确定哪些可用食材ID是新鲜的。在这个例子中,判断如下:

  • 食材ID 1 是变质的,因为它不在任何范围内。
  • 食材ID 5 是新鲜的,因为它在范围 3-5 内。
  • 食材ID 8 是变质的。
  • 食材ID 11 是新鲜的,因为它在范围 10-14 内。
  • 食材ID 17 是新鲜的,因为它在范围 16-20 内,同时也在范围 12-18 内。
  • 食材ID 32 是变质的。

所以,在这个例子中,3 个可用食材ID是新鲜的。

处理来自新库存管理系统的数据库文件。有多少个可用食材ID是新鲜的?

--- 第二部分 ---

精灵们开始把变质的库存搬到厨房后面的垃圾滑道。

为了让它们在收到新库存时不再烦你,精灵们想知道所有被新鲜食材ID范围认为是新鲜的ID。如果一个食材ID位于任何范围内,它仍然被认为是新鲜的。

现在,数据库的第二部分(可用食材ID)已经无关紧要。以下是来自上面示例的新鲜食材ID范围:

复制代码
3-5
10-14
16-20
12-18

这些范围认为新鲜的食材ID是 3, 4, 5, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20。因此,在这个例子中,新鲜食材ID范围认为总共有 14 个食材ID是新鲜的。

再次处理数据库文件。根据新鲜食材ID范围,有多少个食材ID被认为是新鲜的?

解题方法:

第一部分很简单,就是求哪些食材在新鲜食材范围中,用数据库exists就行了。

代码如下

sql 复制代码
with t as (select '3-5
10-14
16-20
12-18

1
5
8
11
17
32' t),
a as(select b::bigint a from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')=0 and b<>''),
r as(select substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')>0)
select count(*) from a where exists(select 1 from r where a.a between l and r);

第二部分涉及新鲜范围中的食材总数,因为正式数据很大,所以不可能把它们都展开再去重计数,而要采取范围上下限差值加总的办法,但要求这些范围都是彼此不重叠的。

SQL没有简单的操作可以实现范围合并,于是请教DeepSeek,他提供了如下代码

python 复制代码
def merge_intervals(intervals):
    """
    合并重叠区间,直到所有区间互不重叠。
    
    参数:
    intervals: 区间列表,例如 [[1,4], [2,7], [9,10]]
    
    返回:
    合并后的区间列表,例如 [[1,7], [9,10]]
    """
    if not intervals:
        return []
    
    # 按区间起点排序
    intervals.sort(key=lambda x: x[0])
    
    merged = []
    
    for interval in intervals:
        # 如果merged为空,或者当前区间与上一个区间不重叠
        if not merged or merged[-1][1] < interval[0]:
            merged.append(interval)
        else:
            # 有重叠,合并区间(取右端点的最大值)
            merged[-1][1] = max(merged[-1][1], interval[1])
    
    return merged

# 测试
intervals = [[1, 4], [2, 7], [9, 10]]
result = merge_intervals(intervals)
print(result)  # 输出: [[1, 7], [9, 10]]

这个算法简单,可以用递归CTE结合数组实现,以下是我翻译的代码,注意数组合并||要求左右值均是数组。

sql 复制代码
with recursive t as (select '3-5
10-14
16-20
12-18' t),
r as(select substr(b,1,instr(b,'-')-1)::bigint l,substr(b,instr(b,'-')+1)::bigint r from(select unnest(string_split(t,chr(10)))b from t)where instr(b,'-')>0)
,ar as(select list([l,r] order by l)ar from r)
,a as(select 2 lv ,[ar[1]] mg from ar 
union all
select lv+1, case when len(mg)=0 or mg[-1][2] < ar[lv][1] then mg||[ar[lv]] else mg[:len(mg)-1]::BIGINT[][]||[[mg[-1][1] , greatest(mg[-1][2], ar[lv][2]) ]]end  from a,ar where lv<=len(ar))
select sum(b[2]-b[1]+1) from(select unnest(mg) b from a where lv=(select max(lv) from a))
;
相关推荐
203号居民17 分钟前
LeetCode hot 100 —560. 和为 K 的子数组
java·算法·leetcode
MC皮蛋侠客31 分钟前
Redis 系列(六):过期、内存淘汰与内存优化——让 Redis 活得更久
数据库·redis·bootstrap
退休倒计时43 分钟前
【每日一题】LeetCode 20. 有效的括号 TypeScript
算法·leetcode·职场和发展·typescript
文心快码BaiduComate1 小时前
光本位联合文心快码打造面向光电芯片研发的全栈AI Agent Lightmate
算法
杜子不疼.1 小时前
模型效果不好时,我现在会先看数据集
数据库
夜雪一千1 小时前
MySQL的事务是什么?原理、ACID、隔离级别与实战详解
数据库·mysql
一次旅行1 小时前
ViT/CLIP/LLaVA/GPT-4V/VideoLLM多模态架构全解:原理仿真+工业落地选型+完整推理代码
人工智能·算法·架构
上下求索,莫负韶华1 小时前
笔记-数据库事务和java事务和切面
java·数据库·笔记
黄华SJ520it1 小时前
顶俏洗衣液模式制度开发介绍:S2B2C社交分销+多门店核销系统全解析
前端·数据库·小程序·零售·系统开发