【Lintcode】704. Bulb Switcher II

题目地址:

https://www.lintcode.com/problem/bulb-switcher-ii/description

一共有 n n n盏灯是开着的,编号是 1 ∼ n 1\sim n 1n,有 4 4 4个开关,每个开关功能如下:
1、将所有灯从开变成关,从关变成开;
2、将标号为偶数的灯从开变成关,从关变成开;
3、将标号为奇数的灯从开变成关,从关变成开;
4、将标号为 3 k + 1 3k + 1 3k+1的灯从开变成关,从关变成开, k = 0 , 1 , . . . k = 0, 1,... k=0,1,...
问进行了 m m m次未知操作后,灯的所有可能的状态一共有多少种。

思路是位运算。考虑域 F 2 \mathbb{F}_2 F2上的无穷维向量组成的向量空间,将每个操作映射成一个向量:
1、操作 1 1 1映射为 ( 1 , 1 , 1 , 1 , 1 , 1 , . . . ) (1,1,1,1,1,1,...) (1,1,1,1,1,1,...)
2、操作 2 2 2映射为 ( 0 , 1 , 0 , 1 , 0 , 1 , . . . ) (0,1,0,1,0,1,...) (0,1,0,1,0,1,...)
3、操作 3 3 3映射为 ( 1 , 0 , 1 , 0 , 1 , 0 , . . . ) (1,0,1,0,1,0,...) (1,0,1,0,1,0,...)
2、操作 4 4 4映射为 ( 1 , 0 , 0 , 1 , 0 , 0 , . . . ) (1,0,0,1,0,0,...) (1,0,0,1,0,0,...)
那么相当于在问,若干个上述向量做加法,能得到哪些向量。一开始灯是开着还是关闭是无所谓的,因为答案和一开始灯是什么状态没有关系。不妨设一开始灯的关闭的。我们看到, 4 4 4个操作的循环节长度有 1 , 2 , 3 1,2,3 1,2,3这些可能,取最小公倍数 6 6 6,则发现经过无论多少次操作,操作的结果的循环节都是 6 6 6,也就是说我们只需要考虑前 6 6 6盏灯是什么状态即可,所以问题就转变为 6 6 6维向量做加法。接着考虑 m m m次操作,显然如果同一个操作做了 2 2 2次,相当于没做,即,设第 k k k种操作做了 x k x_k xk次,那么有 x 1 + x 2 + x 3 + x 4 = m x_1+x_2+x_3+x_4=m x1+x2+x3+x4=m,但就效果而言,我们只需要考虑 0 ≤ x i ≤ 1 0\le x_i\le 1 0xi1的情况,要枚举满足这两个条件的解,我们只需枚举 x i x_i xi 0 0 0 1 1 1,并且 x 1 + x 2 + x 3 + x 4 ≤ m x_1+x_2+x_3+x_4\le m x1+x2+x3+x4m x 1 + x 2 + x 3 + x 4 ≡ m ( m o d    2 ) x_1+x_2+x_3+x_4\equiv m(\mod 2) x1+x2+x3+x4m(mod2)的情况即可。这可以用一个 0 ∼ 15 0\sim 15 015的二进制位来表示每个数是取 0 0 0还是 1 1 1。考虑完所有满足的情况之后用哈希表存一下最后可能的状态,然后返回哈希表的size即可。代码如下:

import java.util.HashSet;
import java.util.Set;

public class Solution {
    /**
     * @param n: number of lights
     * @param m: number of operations
     * @return: the number of status
     */
    public int flipLights(int n, int m) {
        // write your code here
        // 如果n大于6,那么等价于6盏灯
        n = Math.min(n, 6);
        Set<Integer> set = new HashSet<>();
        // 开一个向量,每个位置存一个操作,需要从左向右看
        int[] change = {0b111111, 0b101010, 0b010101, 0b100100};
        
        // 枚举1 ~ 15,表示所有可能的操作方法
        for (int i = 0; i <= (1 << 4) - 1; i++) {
        	// 初始化为灯全关
            int state = 0;
            // 数一下i对应的是操作多少次
            int bitCount = Integer.bitCount(i);
            // 如果次数和m的奇偶性相同,并且小于m,那么这个操作是合法的
            if (bitCount % 2 == m % 2 && bitCount <= m) {
            	// 枚举change里的各个操作,看一下各个操作是否做了,如果做了,就在state上操作
                for (int j = 0; j < 4; j++) {
                    if (((i >> j) & 1) == 1) {
                        state ^= change[j];
                    }
                }
                
                // 只用看前n位,把后面的位都右移掉
                state >>= 6 - n;
                set.add(state);
            }
        }
        
        return set.size();
    }
}

时空复杂度 O ( 1 ) O(1) O(1)

还可以做优化。回顾上面,从数学的角度考虑,将每个操作看为一个 6 6 6维向量操作 1 1 1 u 1 = ( 1 , 1 , 1 , 1 , 1 , 1 ) u_1=(1,1,1,1,1,1) u1=(1,1,1,1,1,1),操作 2 2 2 u 2 = ( 0 , 1 , 0 , 1 , 0 , 1 ) u_2=(0,1,0,1,0,1) u2=(0,1,0,1,0,1),操作 3 3 3 u 3 = ( 1 , 0 , 1 , 0 , 1 , 0 ) u_3=(1,0,1,0,1,0) u3=(1,0,1,0,1,0),操作 4 4 4 u 4 = ( 1 , 0 , 0 , 1 , 0 , 0 ) u_4=(1,0,0,1,0,0) u4=(1,0,0,1,0,0),排成矩阵就是:
[ u 1 u 2 u 3 u 4 ] = [ 1 1 1 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 1 0 0 1 0 0 ] \left[ \begin{matrix} u_1 \\ u_2 \\ u_3 \\ u_4 \end{matrix} \right]= \left[ \begin{matrix} 1 & 1 & 1 & 1 & 1 & 1 \\ 0 & 1 & 0 & 1 & 0 & 1 \\ 1 & 0 & 1 & 0 & 1 & 0 \\ 1 & 0 & 0 & 1 & 0 & 0 \end{matrix} \right] u1u2u3u4=101111001010110110101100我们发现,无论进行多少次操作,第 2 2 2盏灯和第 6 6 6盏灯永远是同一个状态,第 3 3 3盏灯和第 5 5 5盏灯永远是同一个状态,而第 4 4 4盏灯的状态就是前三盏灯的异或得到的状态。所以本质上后面三盏灯的状态可以由前面三盏灯的状态推出来(数学上的说法是,前 3 3 3个列向量构成了列向量空间的一组基。容易看出前 3 3 3个列向量是线性无关的),所以本质上前 3 3 3盏灯的状态就已经决定了全部 6 6 6盏灯的状态了。这样的话就暴力枚举前 3 3 3盏灯就好了。不妨设一开始所有灯都是关的。如果 m = 0 m=0 m=0,那只有 ( 0 , 0 , 0 ) (0,0,0) (0,0,0)这个状态,答案是 1 1 1;如果 m = 1 m=1 m=1,那只有 ( 1 , 1 , 1 ) , ( 0 , 1 , 0 ) , ( 1 , 0 , 1 ) , ( 1 , 0 , 0 ) (1,1,1),(0,1,0),(1,0,1),(1,0,0) (1,1,1),(0,1,0),(1,0,1),(1,0,0)四种可能,那么当 n = 1 , 2 , 3 n=1,2,3 n=1,2,3时答案是 2 , 3 , 4 2,3,4 2,3,4 n > 3 n>3 n>3时按照 n = 3 n=3 n=3来算,下同);如果 m = 2 m=2 m=2,那只有 ( 1 , 0 , 0 ) (1,0,0) (1,0,0)得不到,别的都可以(这个就直接暴力枚举可以知道),所以当 n = 1 , 2 , 3 n=1,2,3 n=1,2,3时答案是 2 , 4 , 7 2,4,7 2,4,7;如果 m = 3 m=3 m=3,两步可以到 ( 0 , 0 , 0 ) (0,0,0) (0,0,0),那么 ( 1 , 0 , 0 ) (1,0,0) (1,0,0)也是可以到的,别的也都可以到(只需要在 m = 2 m=2 m=2的结果里再加个 ( 1 , 0 , 0 ) (1,0,0) (1,0,0)即可),答案就是 2 , 4 , 8 2, 4, 8 2,4,8。而 m > 3 m>3 m>3的时候,再做操作相当于是对一个 2 n ( n = 1 , 2 , 3 ) 2^n(n=1,2,3) 2n(n=1,2,3)阶阿贝尔群做群作用,群作用是个双射,所以答案仍然是 2 , 4 , 8 2, 4, 8 2,4,8。代码如下:

  public class Solution {
    /**
     * @param n: number of lights
     * @param m: number of operations
     * @return: the number of status
     */
    public int flipLights(int n, int m) {
        // write your code here
        n = Math.min(n, 3);
        if (m == 0) {
            return 1;
        }
        if (m == 1) {
            return n + 1;
        }
        if (m == 2) {
            if (n == 3) {
                return 7;
            } else {
                return 1 << n;
            }
        }
        
        return 1 << n;
    }
}

时空复杂度 O ( 1 ) O(1) O(1)

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页