目录

题目描述

276. 栅栏涂色

题意分析

n 根排成一行的栅栏柱,k 种颜色可选,每根柱子必须涂一种颜色。唯一的限制是:不能出现连续三根颜色相同。问总共有多少种涂色方案。

先把限制读准。它允许相邻两根同色,只禁止三根连着同色。等价的局部表述是:若第 i-1 根和第 i-2 根同色,则第 i 根必须换色;除此之外第 i 根随便涂。这个表述很重要,因为它说明「当前能怎么涂」只取决于前两根的相对关系(同色还是异色),而不取决于它们具体是哪两种颜色。

「不取决于具体颜色」这一点是全题的技术核心。它意味着方案数在 k 种颜色之间是完全对称的,我们不必记录颜色本身,只需记录一个二值信息,状态空间因此从 $O(k)$ 压到 $O(1)$。

求的是方案总数而不是某个最优方案,且方案之间有明显的「按位置逐根决策、后一根的合法性只依赖前面局部」的递推结构,同时不存在贪心可利用的单调性——这些信号合起来指向按位置递推的计数。

边界要分清三处。n = 0 时没有柱子,题目按 0 种方案处理。n = 1 时限制根本无法触发,答案就是 kn = 2 时限制仍不可能触发(只有两根),答案是 $k^2$,需要确认递推能自然算出这个值而不是靠特判。

另有一个隐藏边界:k = 1 时只有一种颜色,n >= 3 必然出现三连同色,答案应为 0;n = 1 答案是 1,n = 2 答案是 1。好的递推应该让 k = 1 自然落进主逻辑,因为转移里会出现 k - 1 = 0 这个因子把非法分支归零。

解法:动态规划状态转移

核心思路

限制只关心末尾是否已有两根同色,不需要记录具体颜色。把前 i 根的合法方案分成两个互斥状态:

  • same:最后两根颜色相同;
  • different:最后两根颜色不同。

新柱与上一根同色时,旧状态必须是 different,且颜色没有选择,所以 newSame = different。新柱与上一根异色时,两种旧状态都可转移,并有 k - 1 种颜色可选,所以 newDifferent = (same + different) × (k - 1)

初始处理一根柱子:same = 0different = k。循环不变量是:进入第 i 次转移前,两变量准确统计前 i - 1 根的两类合法方案。新值必须先分别计算再统一覆盖旧值。

正确性说明:两状态互斥且覆盖所有合法涂法。上述转移枚举了新柱同色或异色的全部情况;同色只允许接在异色状态后,正好排除三连同色。由初值归纳到 n,same + different 即全部合法方案数。

解题步骤

  • n == 0 时返回 0。
  • 初始化一根柱子的状态 same = 0different = k
  • 从第 2 根开始计算 newSamenewDifferent
  • 两个新状态计算完成后再一起赋值。
  • 返回两种末尾状态之和。

n = 3, k = 2 时状态从 (0,2) 变为 (2,2),再变为 (2,4),答案为 6。k = 1 时 n 为 1 或 2 的答案是 1,n 至少为 3 时答案自然变成 0。

代码实现

class Solution {
    public int numWays(int n, int k) {
        if (n == 0) {
            return 0;
        }

        int same = 0;
        int different = k;
        for (int posts = 2; posts <= n; posts++) {
            int newSame = different;
            int newDifferent = (same + different) * (k - 1);
            same = newSame;
            different = newDifferent;
        }
        return same + different;
    }
}
func numWays(n int, k int) int {
	if n == 0 {
		return 0
	}

	same, different := 0, k
	for posts := 2; posts <= n; posts++ {
		newSame := different
		newDifferent := (same + different) * (k - 1)
		same, different = newSame, newDifferent
	}
	return same + different
}

复杂度分析

  • 时间复杂度:$O(n)$。每根柱子只进行常数次运算。
  • 空间复杂度:$O(1)$。仅保留上一层的两个状态。

关键点总结

  • 状态记录末两根的关系,而非具体颜色,利用了颜色之间的对称性。
  • 同色状态只能从上一层异色状态转移,异色状态可来自全部合法方案。
  • 两个新状态都依赖旧值,必须先计算完再覆盖。
  • n = 0k = 1 无需额外复杂分支,初值和转移可统一处理。

易错点总结

  • newSame = same + differentn = 3, k = 2 会把 AAA、BBB 计入,错误返回 8。
  • newDifferent 乘 k 而非 k - 1:把与上一根同色的选择也算入异色状态。
  • 先覆盖 same 再计算 different:后者读到本轮新值,状态层次混乱。
  • 只返回一个状态:同色和异色两类都可能合法,答案必须相加。
  • 漏掉 n = 0:会把初始化的 k 种一柱方案错误当作空栅栏答案。
  • k = 1 一律返回 0:一根或两根柱子仍各有一种合法涂法。

相似题目

题目 难度 考察点
70. 爬楼梯 简单 单状态线性递推,用来对照理解本题为何必须拆成两个状态
198. 打家劫舍 中等 同样按位置递推且相邻受限,但求的是最大值而非计数,转移取 max 而非求和
213. 打家劫舍 II 中等 首尾相接的环形约束,需要拆成两次线性 dp,考察边界耦合的处理
256. 粉刷房子 中等 颜色不再对称(每种颜色代价不同),状态必须按具体颜色展开成三维
265. 粉刷房子 II 困难 k 种颜色下用「最小值 + 次小值」把每层转移从 $O(k^2)$ 优化到 $O(k)$
91. 解码方法 中等 同为计数 dp,但转移的合法性依赖字符内容,需要处理 0 带来的不可达状态
746. 使用最小花费爬楼梯 简单 两来源转移的最小化版本,重点在起点与终点的边界约定
1137. 第 N 个泰波那契数 简单 依赖前三层的滚动递推,练习多变量滚动时的赋值顺序