题目描述
8 间牢房排成一排,每间牢房不是有人住就是空着。
每天,无论牢房是被占用或空置,都会根据以下规则进行更改:
- 如果一间牢房的两个相邻的房间都被占用或都是空的,那么该牢房就会被占用。
- 否则,它就会被空置。
(请注意,由于监狱中的牢房排成一行,所以行中的第一个和最后一个房间无法有两个相邻的房间。)
我们用以下方式描述监狱的当前状态:如果第 i
间牢房被占用,则 cell[i] == 1
,否则 cell[i] == 0
。
根据监狱的初始状态,在 N
天后返回监狱的状况(和上述 N 种变化)。
样例
输入:cells = [0,1,0,1,1,0,0,1], N = 7
输出:[0,0,1,1,0,0,0,0]
解释:
下表概述了监狱每天的状况:
Day 0: [0, 1, 0, 1, 1, 0, 0, 1]
Day 1: [0, 1, 1, 0, 0, 0, 0, 0]
Day 2: [0, 0, 0, 0, 1, 1, 1, 0]
Day 3: [0, 1, 1, 0, 0, 1, 0, 0]
Day 4: [0, 0, 0, 0, 0, 1, 0, 0]
Day 5: [0, 1, 1, 1, 0, 1, 0, 0]
Day 6: [0, 0, 1, 0, 1, 1, 0, 0]
Day 7: [0, 0, 1, 1, 0, 0, 0, 0]
输入:cells = [1,0,0,1,0,0,1,0], N = 1000000000
输出:[0,0,1,1,1,1,1,0]
注意
cells.length == 8
cells[i]
is in{0, 1}
1 <= N <= 10^9
算法1
(矩阵乘法,快速幂) O(m3logN)
假设格子的数目为 m 个,下标从 0 开始。
- 我们用一个 (m+1)×(m+1) 的矩阵 A 来描述每一次变化。假设 cells 是一个 (m+1) 的列向量,其中 cells[m] 始终为 1,则 A×cells 在模 2 意义下的矩阵乘法相当于做了一次变换。
- 下面构造 A 矩阵:由于在每次变化中,首尾两个格子始终保持 0,则 A[0][i] 和 A[m - 1][i] 两行全为 0;从第 1 行到第 m - 2 行,每行中 A[i][i - 1],A[i][i + 1] 和 A[i][m] 为 1,其余为 0。这是因为我们每次的变化是,每个格子与相邻两个格子有关,但简单的做模 2 的加法状态是相反的,因此需要一个常数 1 来修正。最后,第 m 行中,A[m][m] 为 1。
- 做 N 次变换相当于 AN×cells,利用结合律,我们可以用快速幂计算 tot=AN,最后 tot×cells 就是答案。
时间复杂度
- 每次矩阵乘法的时间复杂度为 O(m3),快速幂为 O(logN) 次乘法,故总时间复杂度为 O(m3logN)。
C++ 代码
vector<vector<int>> operator * (const vector<vector<int>> &x, const vector<vector<int>> &y) {
int n = x.size();
vector<vector<int>> z(n, vector<int>(n, 0));
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
z[i][j] = (z[i][j] + x[i][k] * y[k][j]) & 1;
return z;
}
class Solution {
public:
vector<vector<int>> power(const vector<vector<int>> &x, int y) {
int n = x.size();
vector<vector<int>> tot(n, vector<int>(n, 0)), p = x;
for (int i = 0; i < n; i++)
tot[i][i] = 1;
for (; y; y >>= 1) {
if (y & 1) {
tot = tot * p;
}
p = p * p;
}
return tot;
}
vector<int> prisonAfterNDays(vector<int>& cells, int N) {
int m = cells.size();
vector<vector<int>> A(m + 1, vector<int>(m + 1, 0)), tot;
vector<int> ans(m, 0);
for (int i = 1; i < m - 1; i++)
A[i][i - 1] = A[i][i + 1] = A[i][m] = 1;
A[m][m] = 1;
tot = power(A, N);
ans[0] = ans[m - 1] = 0;
for (int i = 1; i < m - 1; i++) {
int tmp = tot[i][m];
for (int j = 0; j < m; j++)
tmp += tot[i][j] * cells[j];
ans[i] = tmp & 1;
}
return ans;
}
};
算法2
(哈希表) O(m⋅2m)
- 题目中说格子的个数始终为 8 个,则在 N 天的过程中,必然会出现循环节,循环节的长度不超过 256。
- 我们开一个长度为 256 的哈希数组来记录某个情况最早出现在哪天。
- 假设变化过程中,第 i 次的变化和第 h (< i) 天的变化后是一致的,则循环节的长度为 l=i−h,我们可以跳过若干个循环节,然后再处理结尾部分即可。
时间复杂度
- 循环节的长度最大为 O(2m),每次需要 O(m) 的时间更新,故总时间复杂度为 O(m⋅2m)。
C++ 代码
class Solution {
public:
vector<int> nextDay(const vector<int>& cells) {
int m = cells.size();
vector<int> ans(m, 0);
for (int i = 1; i < m - 1; i++)
ans[i] = 1 - (cells[i - 1] ^ cells[i + 1]);
return ans;
}
int getHash(const vector<int>& cells) {
int m = cells.size(), tot = 0;
for (int i = 0; i < m; i++)
if (cells[i])
tot += 1 << i;
return tot;
}
vector<int> prisonAfterNDays(vector<int>& cells, int N) {
int m = cells.size();
vector<int> vis(1 << m, -1);
vis[getHash(cells)] = 0;
for (int i = 1; i <= N; i++) {
cells = nextDay(cells);
int h = getHash(cells);
if (vis[h] != -1) {
int l = i - vis[h];
i = N - (N - vis[h]) % l;
}
vis[h] = i;
}
return cells;
}
};
矩阵运算的方法好硬核啊- -
棒