本文最后更新于 452 天前,其中的信息可能已经有所发展或是发生改变。
题目分析
- 本题最优解必然满足所有按键最多只按下过一次,因为多次按压会抵消之前按压的效果;
- 本题的最优解与按键顺序无关,因为所有灯的亮灭与按键是叠加关系,与先后顺序无关。
思路分享
本题是一道典型的枚举题,但如果我们对所有位置都进行遍历,我们最多需要考虑216*16种情况,这显然不满足题目要求,因此我们需要对算法进行优化。
经过观察,我们不难发现:当上一行以及之前所有行的状态都不改变时,上一行灯的亮灭状态只与本行的按键情况有关。因此,我们可以只遍历第一行灯的按键情况,后续每行只按压上一行还亮着的灯对应下方的按键,最后判断所有灯是否全部熄灭即可。
如何进行遍历
二进制方法
注意到本题中只有【按键】和【不按键】两种情况,故我们可以使用二进制数进行枚举,16位,每一位分别表示一个灯的亮灭情况,每次情况计算完成后加一即可。
DFS 深度优先搜索 方法
但是不是所有题目都能有这种简单的情形。考虑让题解的枚举方法具有普适性,此处我想讲解一种新的枚举方法:使用 DFS(深度优先搜索)进行枚举。
我们想象所有的按键情况由一棵树组成,每一层对应一个灯的按键情况:
我们可以沿着某一条路径走到最底层【中止条件】(id = m),进行一次运算,统计出总的按键次数。接着,我们回溯到上一层,进行另一个抉择【回溯】,直到遍历完所有情况。算法的过程可以参考下图示意。
DFS有一套固定的流程如下:
void dfs(状态参数)
{
if (达到中止条件 / 条件不合法)
{
添加相应内容
return;
}
for (下一步所有的可行方法)
{
标记;
dfs(参数进行相应改变);
还原标记;
}
}
对于本道题目,dfs部分的伪代码可以参考如下:
void dfs(灯的id, 按键次数)
{
if (灯的id == m)
{
根据第一行状态推演所有按键次数;
判断所有灯是否全部熄灭;
更新最小按键次数;
return;
}
// 第一种:按下的情况
按下(第一行,第id个灯);
dfs(id + 1, 按键数 + 1);
按下(第一行,第id个灯); // 还原灯的状态
// 第二种:不按下的情况
dfs(id + 1, 按键数);
}