位运算题目:N 天后的牢房
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:N 天后的牢房
出处:957. N 天后的牢房
难度
5 级
题目描述
要求
8 \texttt{8} 8 间牢房排成一排,每间牢房的状态是被占用或空置。
每天,无论牢房是被占用或空置,都会根据以下规则进行更改:
- 如果一间牢房的两个相邻的房间都被占用或都被空置,那么该牢房就会被占用。
- 否则,它就会被空置。
由于监狱中的牢房排成一行,所以行中的第一个和最后一个房间无法有两个相邻的房间。
给定一个整数数组 cells \texttt{cells} cells,其中 cells[i] = 1 \texttt{cells[i] = 1} cells[i] = 1 表示第 i \texttt{i} i 间牢房被占用, cells[i] = 0 \texttt{cells[i] = 0} cells[i] = 0 表示第 i \texttt{i} i 间牢房被空置。另外给定一个整数 n \texttt{n} n。
返回 n \texttt{n} n 天后的监狱的状态(即 n \texttt{n} n 次上述描述的更改之后)。
示例
示例 1:
输入:
cells
=
[0,1,0,1,1,0,0,1],
n
=
7
\texttt{cells = [0,1,0,1,1,0,0,1], n = 7}
cells = [0,1,0,1,1,0,0,1], n = 7
输出:
[0,0,1,1,0,0,0,0]
\texttt{[0,0,1,1,0,0,0,0]}
[0,0,1,1,0,0,0,0]
解释:以下是监狱每天的状态:
第
0
\texttt{0}
0 天:
[0,1,0,1,1,0,0,1]
\texttt{[0,1,0,1,1,0,0,1]}
[0,1,0,1,1,0,0,1]
第
1
\texttt{1}
1 天:
[0,1,1,0,0,0,0,0]
\texttt{[0,1,1,0,0,0,0,0]}
[0,1,1,0,0,0,0,0]
第
2
\texttt{2}
2 天:
[0,0,0,0,1,1,1,0]
\texttt{[0,0,0,0,1,1,1,0]}
[0,0,0,0,1,1,1,0]
第
3
\texttt{3}
3 天:
[0,1,1,0,0,1,0,0]
\texttt{[0,1,1,0,0,1,0,0]}
[0,1,1,0,0,1,0,0]
第
4
\texttt{4}
4 天:
[0,0,0,0,0,1,0,0]
\texttt{[0,0,0,0,0,1,0,0]}
[0,0,0,0,0,1,0,0]
第
5
\texttt{5}
5 天:
[0,1,1,1,0,1,0,0]
\texttt{[0,1,1,1,0,1,0,0]}
[0,1,1,1,0,1,0,0]
第
6
\texttt{6}
6 天:
[0,0,1,0,1,1,0,0]
\texttt{[0,0,1,0,1,1,0,0]}
[0,0,1,0,1,1,0,0]
第
7
\texttt{7}
7 天:
[0,0,1,1,0,0,0,0]
\texttt{[0,0,1,1,0,0,0,0]}
[0,0,1,1,0,0,0,0]
示例 2:
输入:
cells
=
[1,0,0,1,0,0,1,0],
n
=
1000000000
\texttt{cells = [1,0,0,1,0,0,1,0], n = 1000000000}
cells = [1,0,0,1,0,0,1,0], n = 1000000000
输出:
[0,0,1,1,1,1,1,0]
\texttt{[0,0,1,1,1,1,1,0]}
[0,0,1,1,1,1,1,0]
数据范围
- cells.length = 8 \texttt{cells.length} = \texttt{8} cells.length=8
- cells[i] \texttt{cells[i]} cells[i] 的值为 0 \texttt{0} 0 或 1 \texttt{1} 1
- 1 ≤ n ≤ 10 9 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{9} 1≤n≤109
解法
思路和算法
为了得到 n n n 天后的每间牢房的状态,需要根据规则模拟牢房状态的变化。由于 n n n 的最大值是 1 0 9 10^9 109,直接模拟的时间复杂度过高,因此需要考虑时间复杂度更低的做法。
将牢房的初始状态记为第 0 0 0 天的状态,变化 k k k 次之后为第 k k k 天。由于第一个和最后一个房间无法有两个相邻的房间,因此根据规则,从第 1 1 1 天开始,第一个和最后一个房间的状态一定是空置,其余 6 6 6 个房间的状态可能是被占用或空置,不同的状态总数是 2 6 = 64 2^6 = 64 26=64 种,加上初始状态最多有 65 65 65 种状态。由于不同的状态数非常有限,因此在牢房状态的变化过程中一定会出现与之前重复的状态,即出现循环。假设第 x x x 天和第 y y y 天的牢房状态相同, x < y x < y x<y,则对于任意非负整数 k k k 都有第 x + k x + k x+k 天和第 y + k y + k y+k 天的牢房状态相同,即牢房状态的循环周期是 y − x y - x y−x 天,循环周期一定不超过 65 65 65 天。对于牢房的任意初始状态,实际循环周期都不超过 14 14 14 天。
为了根据循环周期得到第 n n n 天的牢房状态,需要使用两个哈希表分别记录每个牢房状态首次出现的天数以及每天对应的牢房状态。由于题目给定的牢房状态是数组的形式,数组不适合存入哈希表,因此需要使用位运算将数组表示的牢房状态转换成整数表示的牢房状态,整数为 8 8 8 位二进制数,从低到高的第 0 0 0 位到第 7 7 7 位分别表示第 0 0 0 个到第 7 7 7 个房间的状态。
从牢房的初始状态开始模拟,计算每天的牢房状态并将状态和天数分别存入两个哈希表,当遇到结束条件时,结束模拟并计算第 n n n 天的牢房状态。
-
如果到达第 n n n 天,则当前的牢房状态是第 n n n 天的牢房状态,将当前的牢房状态转成数组的形式返回。
-
如果遇到一个状态是之前已经出现过的,则当前状态的首次出现即为循环的开始时间。将当前天数记为 day \textit{day} day,将当前状态的首次出现(即哈希表中记录的当前状态对应的天数)记为 start \textit{start} start,则循环周期 cycle = day − start \textit{cycle} = \textit{day} - \textit{start} cycle=day−start。计算第 n n n 天的牢房状态的方法如下。
-
第 n n n 天与循环的开始时间的天数之差为 n − start n - \textit{start} n−start,该天数之差除以循环周期的余数是 remainder = ( n − start ) m o d cycle \textit{remainder} = (n - \textit{start}) \bmod \textit{cycle} remainder=(n−start)modcycle。
-
第 n n n 天与第 start + remainder \textit{start} + \textit{remainder} start+remainder 天的天数之差为 cycle \textit{cycle} cycle 的整数倍,即从第 start + remainder \textit{start} + \textit{remainder} start+remainder 天到第 n n n 天经过若干个完整的周期且没有余数,因此第 n n n 天的牢房状态与第 start + remainder \textit{start} + \textit{remainder} start+remainder 天的牢房状态相同。
-
由于 remainder < cycle \textit{remainder} < \textit{cycle} remainder<cycle,因此哈希表中一定记录了第 start + remainder \textit{start} + \textit{remainder} start+remainder 天的牢房状态,从哈希表中获得第 start + remainder \textit{start} + \textit{remainder} start+remainder 天的牢房状态,将其转换成数组的形式返回。
-
代码
class Solution {
static final int LENGTH = 8;
public int[] prisonAfterNDays(int[] cells, int n) {
Map<Integer, Integer> stateDayMap = new HashMap<Integer, Integer>();
Map<Integer, Integer> dayStateMap = new HashMap<Integer, Integer>();
int state = 0;
for (int i = 0; i < LENGTH; i++) {
state += cells[i] << i;
}
int day = 0;
stateDayMap.put(state, day);
dayStateMap.put(day, state);
while (day < n) {
state = getNextState(state);
day++;
if (!stateDayMap.containsKey(state)) {
stateDayMap.put(state, day);
dayStateMap.put(day, state);
} else {
break;
}
}
if (day == n) {
return stateToArr(state);
}
int start = stateDayMap.get(state);
int cycle = day - start;
int remainder = (n - start) % cycle;
int lastState = dayStateMap.get(start + remainder);
return stateToArr(lastState);
}
public int getNextState(int state) {
int nextState = 0;
for (int i = 1; i < LENGTH - 1; i++) {
int prev = (state >> (i - 1)) & 1;
int next = (state >> (i + 1)) & 1;
if (prev == next) {
nextState += 1 << i;
}
}
return nextState;
}
public int[] stateToArr(int state) {
int[] arr = new int[LENGTH];
for (int i = 0; i < LENGTH; i++) {
arr[i] = (state >> i) & 1;
}
return arr;
}
}
复杂度分析
-
时间复杂度: O ( 2 m × m ) O(2^m \times m) O(2m×m),其中 m m m 是数组 cells \textit{cells} cells 的长度, m = 8 m = 8 m=8。不同的状态总数不超过 2 m 2^m 2m,每种状态只会遍历一次,每次得到下一个状态需要 O ( m ) O(m) O(m) 的时间,遍历所有的状态需要 O ( 2 m × m ) O(2^m \times m) O(2m×m) 的时间,生成答案数组需要 O ( m ) O(m) O(m) 的时间,因此时间复杂度是 O ( 2 m × m ) O(2^m \times m) O(2m×m)。
-
空间复杂度: O ( 2 m ) O(2^m) O(2m),其中 m m m 是数组 cells \textit{cells} cells 的长度, m = 8 m = 8 m=8。空间复杂度主要取决于两个哈希表,每个哈希表中的记录个数不超过 2 m 2^m 2m 个。