算法:一圈灯,最少按几次开关才能点亮所有的灯

题目描述

  • 给定一个数组arr,长度为N
  • arr中的值不是0就是1
  • arr[i]表示第i盏灯的状态,0代表灭灯,1代表亮灯
  • 每一栈灯都有开关,但是按下i号灯的开关,会同时改变i-1、i、i+1栈灯的状态

问题二: 如果N盏灯排成一个圈,请问最少按下多少次开关,能让灯都亮起来 排成一个圈

说明:

  • i为中间位置时,i号灯的开关能影响i-1、i和i+1 0号灯的开关能影响N-1、0和1位置的灯 N-1号灯的开关能影响N-2、N-1和0位置的灯

题目解析

本题为从左到右尝试模型。

对于所有尝试结果,它有两种可能:

  • 本次尝试全亮了
  • 本次尝试灯没有全亮

对于这两种可能:

  • 对于灯全亮的尝试,我们需要知道本次尝试按了几次按钮。然后从所有全亮的按钮中选出最少的操作次数然后返回
  • 如果灯没有全亮,那么说明这个方法不对。因为要求最少的操作次数,我们用MAX来表示这是错误的结果

当然,不管是哪种尝试,我们一定要看遍所有的灯,所有的灯都要去尝试一下,有两种可能:

  • 按下开关
  • 不按下开关

特别暴力的写法就是从0~N-1的所有灯都去尝试去点灯或者不点灯,然后全部灯都看完了,我们再去遍历一次数组,看灯有没有全亮。

然后我们应该根据某些条件剪枝,否则就是一个全排列了

int loopMinStep(std::vector<int> arr){
    if(arr.empty()){
        return 0;
    }
    
    if(arr.size() == 1){
        return arr[0] == 1 ? 0 : 1;
    }
    
    if(arr.size() == 2){
        return arr[0] != arr[1] ? INT32_MAX : 1;
    }

    if(arr.size() == 3){
        return (arr[0] != arr[1] || arr[0] != arr[2]) ? INT32_MAX : (arr[0] ^ 1);
    }

    // 0不变,1不变
    int p1 = process(arr, 3, arr[1], arr[2], arr[arr.size() - 1], arr[0]);
    // 0改变,1不变
    int p2 = process(arr, 3, arr[1] ^ 1, arr[2], arr[arr.size() - 1] ^ 1, arr[0] ^ 1);
    // 0不变,1改变
    int p3 = process(arr, 3, arr[1] ^ 1, arr[2] ^ 1, arr[arr.size() - 1], arr[0] ^ 1);
    // 0改变,1改变
    int p4 = process(arr, 3, arr[1], arr[2] ^ 1, arr[arr.size() - 1] ^ 1, arr[0]);
    p2 = p2 != INT32_MAX ? (p2 + 1) : p2;
    p3 = p3 != INT32_MAX ? (p3 + 1) : p3;
    p4 = p4 != INT32_MAX? (p4 + 2) : p4;
    return std::min( std::min(p1, p2),  std::min(p3, p4));
}


// 上一个位置preIndex=  nextIndex - 2 ,   preStatus
// 当前位置curIndex = nextIndex - 1 , curStatus
// 下一个位置,nextIndex
// firstStatus, 0位置的状态
// endStatus, N-1位置的状态
// 返回,让所有灯都亮,至少按下几次按钮

// 当前来到的位置curIndex,一定不能是1!至少从2开始
// nextIndex >= 3
int process(std::vector<int> &arr, int nextIndex, int preStatus, int curStatus, int endStatus, int firstStatus ){
    if(nextIndex == arr.size()){// 最后一按钮!
        return (endStatus != firstStatus || endStatus != preStatus) ? INT32_MAX : (endStatus ^ 1);
    }
    // 当前位置,nextIndex - 1
    // 当前的状态,叫curStatus
    // 如果不按下按钮,下一步的preStatus, curStatus
    // 如果按下按钮,下一步的preStatus, curStatus ^ 1
    // 如果不按下按钮,下一步的curStatus, arr[nextIndex]
    // 如果按下按钮,下一步的curStatus, arr[nextIndex] ^ 1
    int noNextPreStatus = 0;
    int yesNextPreStatus = 0;
    int noNextCurStatus =0;
    int yesNextCurStatus = 0;
    int noEndStatus = 0;
    int yesEndStatus = 0;
    if(nextIndex < arr.size() - 1){  // 当前没来到N-2位置
        noNextPreStatus = curStatus;
        yesNextPreStatus = curStatus ^ 1;
        noNextCurStatus = arr[nextIndex];
        yesNextCurStatus = arr[nextIndex] ^ 1;
    }else if(nextIndex == arr.size() - 1){
        noNextPreStatus = curStatus;
        yesNextPreStatus = curStatus ^ 1;
        noNextCurStatus = endStatus;
        yesNextCurStatus = endStatus ^ 1;
        noEndStatus = endStatus;
        yesEndStatus = endStatus ^ 1;
    }
    if(preStatus == 0) {
        int next = process(arr, nextIndex + 1, yesNextPreStatus, yesNextCurStatus,
                            nextIndex == arr.size() - 1 ? yesEndStatus : endStatus, firstStatus);
        return next == INT32_MAX ? next : (next + 1);
    }else {
        return process(arr, nextIndex + 1, noNextPreStatus, noNextCurStatus,
                        nextIndex == arr.size() - 1 ? noEndStatus : endStatus, firstStatus);
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值