672. 灯泡开关 Ⅱ [DFS][找规律]

题解

假设四个开关分别为s1,s2,s3,s4

  • s1反转所有灯泡的状态
  • s2反转偶数编号灯泡的状态
  • s3反转奇数编号灯泡的状态
  • s4反转3k+1,k=0,1,...编号灯泡的状态

有以下规律:

  1. 一个开关按偶数次与不按是等价的,按奇数次与只按一次是等价的,因此我们只需要考虑每个开关按下次数的奇偶性,再对灯泡的状况做判断即可。

做后续考虑时:

  1. 每个灯泡只有两个状态:1-亮,0-暗
  2. 每个开关只有两个状态:1-按下,0-没有按

DFS

根据上面的规律,笔者第一时间想到通过DFS进行模拟,每个灯泡亮代表1,不亮代表0,用n个字符构成的字符串来表示所有灯泡的亮暗情况,通过一个集合存储来存储所有不同的状态。如果不出意外的话,一定是超时的

代码如下:

class Solution {
private Set<String> set = new HashSet<>();
    private int n;

    public int flipLights(int n, int presses) {
        this.n = n;
        dfs(0,0,0,0, presses);
        return set.size();
    }

    private void dfs(int s1, int s2, int s3, int s4, int restPresses){
        if(restPresses == 0){
            //处理
            process(s1, s2, s3, s4);
            return;
        }
        dfs(s1+1, s2, s3, s4, restPresses-1);
        dfs(s1, s2+1, s3, s4, restPresses-1);
        dfs(s1, s2, s3+1, s4, restPresses-1);
        dfs(s1, s2, s3, s4+1, restPresses-1);
    }

    private void process(int s1, int s2, int s3, int s4) {
        s1 %= 2;
        s2 %= 2;
        s3 %= 2;
        s4 %= 2;

        char[] chars = new char[n];
        if(s1 == 0){
            Arrays.fill(chars, '1');
        } else {
            Arrays.fill(chars, '0');
        }

        if (s2 == 1){
            for (int i = 1; i < n; i+=2) {
                chars[i] = chars[i] == '0' ? '1' : '0';
            }
        }
        if (s3 == 1){
            for (int i = 0; i < n; i+=2) {
                chars[i] = chars[i] == '0' ? '1' : '0';
            }
        }
        if (s4 == 1){
            for (int i = 0; i < n; i+=3) {
                chars[i] = chars[i] == '0' ? '1' : '0';
            }
        }
        String s = String.valueOf(chars);
        set.add(s);
    }
}

找规律

实际上,该题目不止有一个规律,还有:

  1. 灯泡的状态以6为周期(但在后续方法中起始没有体现出这个周期性的作用)
  2. 同时按下开关s2,s3与只按s1的情况是等价的,因此需要考虑是否会出现同一个presses情况下,出现s1=1,s2=0,s3=0以及s1=0,s2=1,s3=1的情况,这样灯泡状态是重复的,需要剔除。

实际上这种情况是不会出现的,因为当presses为奇数时,s1+s2+s3+s4必须为奇数,为偶数时也是同样的道理。s1s2s3s4=100xs1s2s3s4=011x不会出现在同一种开关选择情况中。

  1. 无论灯泡数量是多少,presses=0时只有一种情况

接下来,我们需要考虑按下不同的开关是否会导致最后灯泡状态是相同的情况(考虑presses>0的情况):

  1. 只有一个灯泡,按下s1,s3,s4其中一个导致这唯一一个灯泡状态变化是相同的,均作为s1考虑。
    • presses=1:按s1,灯暗;按s2,灯亮。
    • presses=2:同一个开关按两次等于不按,灯亮;s1,s2各按一次,灯暗。
    • presses为奇数的情况等价于presses=1的情况;presses为偶数的情况等于presses=2的情况。综上,n=1时,灯泡的状态只有两种。
  2. 有两个灯泡a,b,按下s3,s4导致编号1的灯泡状态变化是相同的,作为s3考虑。
    • presses=1s1,s2,s3选一个按,灯泡有ab=00, 10, 01三种情况
    • presses=2:同一开关按两次,ab=11s1,s2,s3任选2个按:ab=01, 10, 00三种情况。共4种状态
    • 实际上两个灯泡也不可能再出现第五种状态了。
  3. 若有三个灯泡a,b,c
    s1控制灯泡a,b,c
    s2控制灯泡b
    s3控制灯泡a,c
    s4控制灯泡a.
    可以看出,不同开关控制的灯泡已经全部不同了,因此四个开关均需要分开考虑。
    • presses=1:四个开关选择有 C 4 1 C_4^1 C41种情况,s1s2s3s4=1000,0100,0010,0001对应的灯泡状态为:abc=000,101,010,011,共4种状态。
    • presses=2:四个开关选择有 C 4 0 + C 4 2 C_4^0+C_4^2 C40+C42种情况(即同一个开关按两次或者任选两个开关按下),s1s2s3s4=0000,1100,1010,1001,0110,0101,0011,对应的灯泡状态为:abc=111,010,101,100,000,001,110,共7种状态。
    • presses=3:四个开关选择共有 C 4 1 + C 4 3 C_4^1+C_4^3 C41+C43种情况,s1s2s3s4=1000,0111,0100,1011,0010,1101,0001,1110,对应的灯泡状态为:abc=000,100,101,001,010,110,011,111,共8种状态。
    • presses=4:四个开关选择共有 C 4 0 + C 4 2 + C 4 4 C_4^0+C_4^2+C_4^4 C40+C42+C44种情况,s1s2s3s4=0000,1100,1010,1001,0110,0101,0011,1111,对应的灯泡状态为:abc=111,010,101,100,000,001,110,011,共8种状态。
    • 实际上对于presses > 3的奇数,其开关选择情况与presses=3的情况是相同的;对于presses > 4的偶数,其开关选择情况与presses=4的情况是相同的,均不会出现其他按开关的情况。并且可以看出不同的开关按下情况对应的是不同的灯泡明暗情况,没有重复。
  4. 对于三个以上的灯泡,其开关选择的情况与只有三个灯泡时开关选择的情况是相同的。而由于不同的开关选择情况在前三个灯泡中对应的灯泡明暗状况也不同,所以其灯泡状态数与只有3个灯泡时的状态数是相同的。

因此,最后的代码为:

class Solution {
    public int flipLights(int n, int presses) {
        //不按开关,只有灯全亮的一种情况
        if (presses == 0){
            return 1;
        }

        if (n==1){
            //只有一个灯泡,无论怎么按都只有两种情况,亮或者暗
            return 2;  
        }
        if (n==2){
            //两个灯泡
            if(presses == 1){
                return 3;
            } else {
                return 4;
            }
        }
        
        //3个即3个以上的灯泡情况
        if(presses == 1){
            return 4;
        } else if (presses == 2){
            return 7;
        } else {
            return 8;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值