AdventOfCode 2025 Day 9

Part One

with matrix as (
    SELECT split_part(line, ',', 1)::integer as _row, split_part(line, ',', 2)::integer as _col
    FROM lance_input
)
SELECT (abs(a._row - b._row)+1)::bigint * (abs(a._col - b._col)+1)
FROM matrix a
JOIN matrix b
ON a._row != b._row
OR a._col != b._col
order by 1 desc limit 1;

Part Two

在这些坐标做成的一个大的多边形中,找到最大的图形内部的矩形。

内部点判断问题

又是一个判断点是否处于多边形内部的问题,参见之前的方式

先判断每个顶点的拐角类型,是F7JL中的哪种

with matrix as (
    SELECT row_number() over() as rn, 
           split_part(line, ',', 1)::integer as _row, 
           split_part(line, ',', 2)::integer as _col
    FROM lance_input
), pre_next as (
    SELECT x._row, x._col,
            coalesce(pre_row, z._row) as pre_row,
            coalesce(pre_col, z._col) as pre_col,
            coalesce(next_row, y._row) as next_row,
            coalesce(next_col, y._col) as next_col
    FROM (
        SELECT _row, _col,
            lag(_row) over (order by rn) as pre_row,
            lag(_col) over (order by rn) as pre_col,
            lead(_row) over (order by rn) as next_row,
            lead(_col) over (order by rn) as next_col
        FROM matrix a
    ) x, (
        SELECT _row, _col FROM matrix where rn = 1
    ) y, (
        SELECT _row, _col FROM matrix where rn = (select max(rn) from matrix)
    ) z
), vertex as (
    SELECT _row, _col,
           case when pre_row < _row and next_col < _col then 'J'
                when pre_col < _col and next_row < _row then 'J'
                when pre_col > _col and next_row < _row then 'L'
                when pre_row < _row and next_col > _col then 'L'
                when pre_col < _col and next_row > _row then '7'
                when pre_row > _row and next_col < _col then '7'
                when pre_col > _col and next_row > _row then 'F'
                when pre_row > _row and next_col > _col then 'F'
            end as type
    FROM pre_next
)

再根据某条边的两个顶角的类型,从而判断这条边是U型的。在判断*是否为内部点的时候,可以直接忽略。

#####
    #     #######
    #     #
*   #######

这条边还有可能是Z型的,判断*是否内部点的时候,等价于经过了一条边。

#####
    #
    #
*   #####
        #
        #
edge as (
    SELECT least(a.pre_row, a._row) as start_row,
           least(a.pre_col, a._col) as start_col,
           greatest(a.pre_row, a._row) as end_row,
           greatest(a.pre_col, a._col) as end_col,
           case when a.pre_row = a._row then 'H'
                when a.pre_col = a._col then 'V'
            end as direction,
           case when b.type = 'F' and c.type = '7' THEN 'U'
                when b.type = '7' and c.type = 'F' THEN 'U'
                when b.type = 'L' and c.type = 'J' THEN 'U'
                when b.type = 'J' and c.type = 'L' THEN 'U'
                when b.type = 'F' and c.type = 'J' THEN 'Z'
                when b.type = 'J' and c.type = 'F' THEN 'Z'
                when b.type = 'L' and c.type = '7' THEN 'Z'
                when b.type = '7' and c.type = 'L' THEN 'Z'
            end as type
    FROM pre_next a
    JOIN vertex b
    ON a._row = b._row
    AND a._col = b._col
    JOIN vertex c
    ON a.pre_row = c._row
    AND a.pre_col = c._col
)

判断矩形是否越过边界

即使四个顶点都在多边形内部,但是整个矩形仍然有可能越过边界,比如

##########
#        #           #######
#   *    #           #  *  #
#        #############     #
#   *                   *  #
############################

因此需要额外判断矩形的四条边是否与边界线有交叉

最终的判断条件如下

candidates as (
    SELECT least(a._row, b._row) as start_row, 
           least(a._col, b._col) as start_col,
           greatest(a._row, b._row) as end_row,
           greatest(a._col, b._col) as end_col
    FROM matrix a
    JOIN matrix b
    ON a._row < b._row
    AND a._col != b._col
), candidate_stats as (
    SELECT a.start_row, a.start_col, a.end_row, a.end_col,
           max(case when (b.direction = 'V' and a.start_col = b.start_col and a.start_row between b.start_row and b.end_row)
           OR (b.direction = 'H' and a.start_row = b.start_row and a.start_col between b.start_col and b.end_col) then 1 end) as NW_edge,
           max(case when (b.direction = 'V' and a.end_col = b.start_col and a.start_row between b.start_row and b.end_row)
           OR (b.direction = 'H' and a.start_row = b.start_row and a.end_col between b.start_col and b.end_col) then 1 end) as NE_edge,
           max(case when (b.direction = 'V' and a.start_col = b.start_col and a.end_row between b.start_row and b.end_row)
           OR (b.direction = 'H' and a.end_row = b.start_row and a.start_col between b.start_col and b.end_col) then 1 end) as SW_edge,
           max(case when (b.direction = 'V' and a.end_col = b.start_col and a.end_row between b.start_row and b.end_row)
           OR (b.direction = 'H' and a.end_row = b.start_row and a.end_col between b.start_col and b.end_col) then 1 end) as SE_edge,

           count(case when b.direction = 'V' and b.start_col > a.start_col and a.start_row > b.start_row and a.start_row < b.end_row then 1 end)
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col > a.start_col and a.start_row = b.start_row then 1 end) as NW_E_CNT,
           count(case when b.direction = 'V' and b.start_col < a.start_col and a.start_row > b.start_row and a.start_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col < a.start_col and a.start_row = b.start_row then 1 end) as NW_W_CNT,
           
           count(case when b.direction = 'V' and b.start_col > a.end_col and a.start_row > b.start_row and a.start_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col > a.end_col and a.start_row = b.start_row then 1 end) as NE_E_CNT,
           count(case when b.direction = 'V' and b.start_col < a.end_col and a.start_row > b.start_row and a.start_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col < a.end_col and a.start_row = b.start_row then 1 end) as NE_W_CNT,

           count(case when b.direction = 'V' and b.start_col > a.start_col and a.end_row > b.start_row and a.end_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col > a.start_col and a.end_row = b.end_row then 1 end) as SW_E_CNT,
           count(case when b.direction = 'V' and b.start_col < a.start_col and a.end_row > b.start_row and a.end_row < b.end_row then 1 end)
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col < a.start_col and a.end_row = b.end_row then 1 end) as SW_W_CNT,

           count(case when b.direction = 'V' and b.start_col > a.end_col and a.end_row > b.start_row and a.end_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col > a.end_col and a.end_row = b.end_row then 1 end) as SE_E_CNT,
           count(case when b.direction = 'V' and b.start_col < a.end_col and a.end_row > b.start_row and a.end_row < b.end_row then 1 end) 
           + count(case when b.direction = 'H' and b.type = 'Z' and b.start_col < a.end_col and a.end_row = b.end_row then 1 end) as SE_W_CNT,
           
           count(case when b.direction = 'V' and b.start_col > a.start_col and b.start_col < a.end_col and a.start_row > b.start_row and a.start_row < b.end_row then 1 end) as n_cnt,
           count(case when b.direction = 'V' and b.start_col > a.start_col and b.start_col < a.end_col and a.end_row > b.start_row and a.end_row < b.end_row then 1 end) as s_cnt,
           count(case when b.direction = 'H' and b.start_row > a.start_row and b.start_row < a.end_row and a.start_col > b.start_col and a.start_col < b.end_col then 1 end) as w_cnt,
           count(case when b.direction = 'H' and b.start_row > a.start_row and b.start_row < a.end_row and a.end_col > b.start_col and a.end_col < b.end_col then 1 end) as e_cnt

    FROM candidates a
    JOIN edge b
    ON 1 = 1
    GROUP BY a.start_row, a.start_col, a.end_row, a.end_col
), candidate_coordinates as (
    SELECT a.start_row, a.start_col, a.end_row, a.end_col,
            case when NW_edge = 1 then TRUE
                when NW_E_CNT % 2 = 1 AND NW_W_CNT % 2 = 1 THEN TRUE
                else false
            end as nw_inside,
            case when NE_edge = 1 then TRUE
                when NE_E_CNT % 2 = 1 AND NE_W_CNT % 2 = 1 THEN TRUE
                else false
            end as ne_inside,
            case when SW_edge = 1 then TRUE
                when SW_E_CNT % 2 = 1 AND SW_W_CNT % 2 = 1 THEN TRUE
                else false
            end as sw_inside,
            case when SE_edge = 1 then TRUE
                when SE_E_CNT % 2 = 1 AND SE_W_CNT % 2 = 1 THEN TRUE
                else false
            end as se_inside,
            n_cnt + s_cnt + w_cnt + e_cnt as intersect_cnt
    FROM candidate_stats a
)
select (end_row - start_row + 1)::bigint * (end_col - start_col + 1)
from candidate_coordinates 
where nw_inside and ne_inside and sw_inside and se_inside and intersect_cnt = 0
order by 1 desc limit 1;

发表评论