Arrays 访谈问题,递归+;回溯

Arrays 访谈问题,递归+;回溯,arrays,algorithm,recursion,backtracking,Arrays,Algorithm,Recursion,Backtracking,这个问题是在采访中提出的,涉及递归/回溯。假设我们有两个布尔数组:bool*source和bool*target,它们都具有相同的长度n(source/target/n作为参数给出)。问题的目标是使用操作开关将源转换为目标 如果有多个新生儿:提供其中任何一个 如果没有解决方案:断言没有解决方案 定义:操作开关(int i,bool*arr)反转arr[i]和arr[i-1]以及arr[i+1]处的值(如果这些指数在0…n-1范围内) 换言之,开关操作通常会翻转三位(i和它的相邻位),但在末端

这个问题是在采访中提出的,涉及递归/回溯。假设我们有两个布尔数组:
bool*source
bool*target
,它们都具有相同的长度
n
(source/target/n作为参数给出)。问题的目标是使用操作开关
转换为
目标

  • 如果有多个新生儿:提供其中任何一个
  • 如果没有解决方案:断言没有解决方案
定义:操作
开关(int i,bool*arr)
反转arr[i]和arr[i-1]以及arr[i+1]处的值(如果这些指数在0…n-1范围内)

换言之,
开关
操作通常会翻转三位(i和它的相邻位),但在末端只有两位

例如:

  • 开关(0,arr)将仅切换arr[0]和arr[1]的值
  • 开关(n-1,arr)将仅切换arr[n-1]和arr[n-2]的值

提前感谢您对算法的建议。

据我所知,主要观察结果是,我们在任何位置上都不需要执行多个开关。这是因为切换两次与根本不切换相同,因此所有偶数开关都相当于0开关,所有奇数开关都相当于1开关

另一件事是开关的顺序无关紧要。切换第i个和第i+1个元素与切换第i+1个和第i个元素相同。最后得到的图案是相同的

使用这两个观察结果,我们可以简单地尝试将开关应用于n长度数组的所有可能方法。这可以递归地完成(即在索引i处执行切换/不在索引i处执行切换,然后尝试i+1),或者通过枚举所有2**n位掩码并使用它们应用切换,直到其中一个创建目标值

下面是我对解决方案的破解。我将数组打包成整数,并将它们用作位掩码以简化操作。这将打印出需要切换以获得目标阵列的索引,如果无法获得目标,则打印“不可能”

#include <cstdio>

int flip(int mask, int bit){
    return mask^(1<<bit);
}

int switch_(int mask, int index, int n){
    for(int i=-1;i<=+1;i++){
        if ((index+i)>=0 && (index+i)<n) mask=flip(mask,index+i);
    }
    return mask;
}

int apply(int source, int flips, int n){
    int result=source;
    for(int i=0;i<n;i++){
        if (flips&(1<<i)) result=switch_(result,i,n);
    }
    return result;
}

void solve(int source, int target, int n){
    bool found=false;
    int current=0;
    int flips=0;
    for(flips=0;flips<(1<<n) && !found;flips++){
        current=apply(source,flips,n);
        found=(current==target);
    }
    if (found){
        flips--;
        for(int i=0;i<n;i++){
            if (flips&(1<<i)) printf("%d ",n-i-1); //prints the indices in descending order
        }
        printf("\n");
    }
    else{
        printf("Impossible\n");
    }
}

int array2int(int* arr, int n){
    int ret=0;
    for(int i=0;i<n;i++){
        ret<<=1;
        if (arr[i]==1) ret++;
    }
    return ret;
}
int main(){
    int source[]={0,0,0,0};
    int target[]={1,1,1,1};
    int n=4;
    solve(array2int(source,n),array2int(target,n),n);
    return 0;
}
#包括
整数翻转(整数掩码,整数位){

返回掩码^(1使用回溯可以得到O(n)解。为什么

  • 在单个索引中,最坏情况下只有一个开关
  • 索引处的开关只能更改自身和两个相邻通道
  • 从左边开始,向右移动,回溯是最好的方法

    在任何时候,你最多只能后退两步。 例如,如果您位于索引n,则只能更改索引n-1,而不能更改索引n-2,并且在达到索引n+2时,只需检查索引n即可

    您最多可以有两种解决方案

    解决方案(python)

    输出

    [1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52, 55, 58, 61, 64, 67, 70, 73, 76, 79, 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127, 130, 133, 136, 139, 142, 145, 148, 151, 154, 157, 160, 163, 166, 169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 229, 232, 235, 238, 241, 244, 247, 250, 253, 256, 259, 262, 265, 268, 271, 274, 277, 280, 283, 286, 289, 292, 295, 298, 301, 304, 307, 310, 313, 316, 319, 322, 325, 328, 331, 334, 337, 340, 343, 346, 349, 352, 355, 358, 361, 364, 367, 370, 373, 376, 379, 382, 385, 388, 391, 394, 397, 400, 403, 406, 409, 412, 415, 418, 421, 424, 427, 430, 433, 436, 439, 442, 445, 448, 451, 454, 457, 460, 463, 466, 469, 472, 475, 478, 481, 484, 487, 490, 493, 496, 499]
    [0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 129, 132, 135, 138, 141, 144, 147, 150, 153, 156, 159, 162, 165, 168, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 207, 210, 213, 216, 219, 222, 225, 228, 231, 234, 237, 240, 243, 246, 249, 252, 255, 258, 261, 264, 267, 270, 273, 276, 279, 282, 285, 288, 291, 294, 297, 300, 303, 306, 309, 312, 315, 318, 321, 324, 327, 330, 333, 336, 339, 342, 345, 348, 351, 354, 357, 360, 363, 366, 369, 372, 375, 378, 381, 384, 387, 390, 393, 396, 399, 402, 405, 408, 411, 414, 417, 420, 423, 426, 429, 432, 435, 438, 441, 444, 447, 450, 453, 456, 459, 462, 465, 468, 471, 474, 477, 480, 483, 486, 489, 492, 495, 498]
    
    您可以看到,最简单的解决方案是只运行两个for循环。第一个解决方案设置第一个灯/开关,第二个不设置。然后决定所有剩余的开关

    #do not switch first light
    for i in range(1,len(goal)+1):
        if goal[n-1] != arr[n-1]:
            light(arr,n)
    check_solution()
    
    #switch first light
    switch(arr,n)
    for i in range(1,len(goal)+1):
        if goal[n-1] != arr[n-1]:
            light(arr,n)
    check_solution()
    

    回溯是不必要的

    很容易看出,对于N=3k和N=3k+1(k>0),可以翻转每个单独的位,因此对于这些尺寸,始终存在一个解决方案。很容易找到一个解决方案,将需要翻转的每个位的解决方案相加


    对于3k+2元素,只能单独翻转一些位,而另一些位可以成对翻转。也就是说,我们可以单独翻转位2、5、8…或同时翻转位0、1、3、4、6…中的任意两个。因此,只有当且仅当必须翻转的位置0、1、3、4、6、8…处的位数为偶数时,才存在解决方案。同样,它是很容易为每个位或位对想出一个算法。将它们相加以得到解决方案。

    仍然不太理解开关操作的定义听起来像是说
    开关(n)
    将翻转
    n
    处的位和
    n
    两侧的位,但前提是这一侧有数据;换句话说,它通常会翻转三位,但末端只有两位。目标是将
    中的位模式转换为
    目标
    中的位模式。除此之外,所有这些都类似于经典游戏这是一维的,而不是二维的。我理解吗?这个问题听起来很熟悉,我想我以前在什么地方见过它。@Tom Zych-你理解得对!它是哪个游戏?你能给我它的参考吗?@Tom Zych-当然,当source={1,0}或source={0,1}和target={1,1}无法解决这是“熄灯”的一维版本是的,但这需要指数时间。希望我们能做得更好。我现在正在编写我的想法,看看它是如何工作的。+1:虽然这是低效的,而且我自己也对多项式解感兴趣,但这正是OP要求的。我没有时间去摸索这段代码,但它似乎是错误的。
    source[]={0,0,0,0,0,0}
    target[]={1,0,0,0,0,1,1};
    它打印了
    013
    ,这显然是错误的,因为它不会在最后翻转位。(这些数字是我的程序找不到解决方案的-正在尝试检查。)@汤姆·齐奇:你改变了n的值吗?D'oh!忽略了这一点。这是有效的。但是,它在这方面还是失败的:
    source[]={1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1};
    ,它有解决方案
    14
    0367
    。它正在打印
    356
    ,这不是
    O(2^n)
    在最坏的情况下?在每一步中,你都有可能在两个方向上分支。实际上,你的第四个
    if
    会剪掉很多分支,但是你能证明它总是
    O(n)吗
    ?不可以。因为edge最多可以生成两个解决方案,所以您只能从一开始就在两个方向上分支,但在索引2之后,只有一个分支。如果指示灯不正确,则只需在bac之前进行两步
    #do not switch first light
    for i in range(1,len(goal)+1):
        if goal[n-1] != arr[n-1]:
            light(arr,n)
    check_solution()
    
    #switch first light
    switch(arr,n)
    for i in range(1,len(goal)+1):
        if goal[n-1] != arr[n-1]:
            light(arr,n)
    check_solution()