14

This question was asked in interview and deals with recursion/backtracking. Suppose we have two arrays of booleans:bool* source and bool* target,each of them the same length n(source/target/n are given as arguments). The goal of the question is to transform source to target using operation switch.

  • If there are several transforns: present any one of them
  • If there is no solution: assert that there is no solution

Definition: operation switch(int i, bool* arr) inverts the value at arr[i] and arr[i-1] and arr[i+1] (if these indices are in the range 0...n-1).

In other words, switch operation will usually flip three bits (i and its neighbors), but only two at the ends.

For example:

  • switch(0,arr) will switch the values of arr[0] and arr[1] only
  • switch(n-1,arr) will switch the values of arr[n-1] and arr[n-2] only

Thank you in advance for the suggestions about the algorithm.

YAKOVM
  • 9,805
  • 31
  • 116
  • 217
  • still don't quite understand the definition of your switch operation – Mu Qiao Sep 03 '11 at 09:17
  • It sounds like you're saying that `switch(n)` will flip the bit at `n` and the bits on each side of `n`, but only if there's data on that side; in other words, it will usually flip three bits, but only two at the ends. And the goal is to transform the bit pattern in `source` to that in `target`. All of which resembles a classic game, except this is in one dimension instead of two. Am I understanding it? This problem sounds familiar, I think I've seen it before somewhere. – Tom Zych Sep 03 '11 at 09:29
  • @Tom Zych - you understand correctly!which game is it?can you give me the references to it? – YAKOVM Sep 03 '11 at 09:44
  • @Mu Qiao - I have updated the question.See the explanation of Tom Zych - it is well defined – YAKOVM Sep 03 '11 at 09:54
  • @Yakov now I see the problem. It's easy to come up with a backtracking solution. I'm not sure if that's the optimal one but seems you just need a backtracking solution? – Mu Qiao Sep 03 '11 at 10:11
  • http://en.wikipedia.org/wiki/Lights_Out_(game) - I seem to recall playing a free version either in Linux or online, but I can't find it. – Tom Zych Sep 03 '11 at 10:17
  • Occurs to me that some cases where n=2 can't be solved. Not sure about all cases n>2. – Tom Zych Sep 03 '11 at 11:05
  • 1
    @Tom Zych - sure ,when source = {1,0} or source = {0,1} and target = {1,1} it is impossible to solve – YAKOVM Sep 03 '11 at 11:38
  • 3
    This is a one-dimensional version of "lights out" https://secure.wikimedia.org/wikipedia/en/wiki/Lights_Out_(game). – Svante Sep 03 '11 at 12:44

3 Answers3

5

Using backtracking you can have O(n) solution. Why?

  1. At single index you have at worst one switch
  2. Swicth at index can change only self and two neigboughrs.

Starting at left an moving right and backtracking is best approach.

At any time time you have to go back at most by two steps. For instance if you are at index n, you can change only index n-1, but not index n-2 and once Or more precisely when you reach index n+2 you only have to check index n.

You can have at worst 2 solutions.

Solution (in python)

def light(arr,n):
    for i in range(max([0,n-1]),min([len(arr),n+2])):
        arr[i] = not arr[i]
    return arr

goal = [True]*500
def trackback(arr,index,moves):
    if index == len(arr):
        if arr == goal:
            print(moves)
        return

    if index > 1:
        if arr[index-2] != goal[index-2]:
            return
    #print(arr,index,moves)
    #do not make switch
    trackback(arr,index+1,moves)
    #make switch
    moves=moves+[index]
    arr=light(arr,index)
    trackback(arr,index+1,moves)
    arr=light(arr,index) #undo move


arr=[False]*500
trackback(arr,0,[])

output

[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]

You can see that simplest solution would be just running two for loops. First solution set first light/switch and second does not. All remaining switching are then decided

#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()
Luka Rahne
  • 10,336
  • 3
  • 34
  • 56
  • 1
    Isn't this `O(2^n)` in the worst case? At each step you can potentially branch in two directions. In practice, your fourth `if` will prune a lot of these branches, but can you actually prove that it's always going to be `O(n)`? – IVlad Sep 03 '11 at 11:56
  • Nope. You can branch in 2 directions only from beginning since edge can generate up to two solution, but once you go after index 2 there is only one branch. And if it is wrong light you go only 2 step deep before you backtrack. That make it O(n) solution. – Luka Rahne Sep 03 '11 at 12:04
  • Why? You can see that there is only one light next to it, that can change its value. This brings that value next to it is fixed. You can rewrite this program to run just whit two for loops (not nested for loops). First solution would switch first value and then light next to it will decide its value. – Luka Rahne Sep 03 '11 at 12:51
4

As far as I understand the problem, the main observation is that we never need to perform more than one switch on any position. This is because switching twice is the same as not switching at all, so all even switches are equivalent to 0 switches and all odd switches are as good as 1 switch.

Another thing is that the order of the switches don't matter. switching the i-th and i+1-th elements is the same as switching i+1-th and then the i-th. The pattern obtained at the end is the same.

Using these two observations, we can simply try out all possible ways of applying switch to the n length array. This can be done recursively (i.e. doing a switch at index i/not doing a switch at index i and then trying out i+1) or by enumerating all 2**n n bit bitmask and using them to apply switches until one of them creates the target value.

Below is my hack at the solution. I have packed the arrays into ints and used them as bitmasks to simplify the operations. This prints out the indices that need to be switched to get the target array and prints "Impossible" if the target is not obtainable.

#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;
}
MAK
  • 26,140
  • 11
  • 55
  • 86
  • Yes, but that takes exponential time. Hopefully we can do better. I'm coding up my idea now to see how it works. – Tom Zych Sep 03 '11 at 11:02
  • 2
    +1: while this is inefficient and I'm interested in a polynomial solution myself, this is exactly what the OP asked for. – IVlad Sep 03 '11 at 11:27
  • Don't have time to grok this code, but it seems to be wrong. With `source[]={0,0,0,0,0,0,0,0};` and `target[]={1,0,0,0,0,0,1,1};` it printed `0 1 3`, which is clearly wrong since it wouldn't flip the bits at the end. (These numbers are ones for which my program didn't find a solution - was trying to check that.) – Tom Zych Sep 03 '11 at 12:22
  • @Tom Zych: Did you change the value of n? – MAK Sep 03 '11 at 12:25
  • D'oh! Overlooked that. That works. However, it's still failing on this: `source[]={1,1,1,0,0,0,1,1};` and `target[]={0,1,1,0,1,1,1,1};`, which has solutions `1 2 4` and `0 2 3 6 7`. It's printing `3 5 6`. – Tom Zych Sep 03 '11 at 12:59
  • @Tom Zych: Good catch! That's a bug in the way I am printing the indices. I am updating my code. Thanks. – MAK Sep 03 '11 at 16:51
1

Backtracking is not necessary.

It is easy to see that for N=3k and N=3k+1 (k>0) it is possible to flip each individual bit, so for these sizes a solution always exists. It is easy to come up with one, adding up solutions for each bit we need to flip.

For 3k+2 elements, it is only possible to to flip some of the bits individually, and others in pairs. Namely, we can flip either bits 2, 5, 8... individually, or any two of 0, 1, 3, 4, 6, ... simultaneously. So the solution only exists if and only if there's even number of bits at positions 0, 1, 3, 4, 6, 8 ... that must be flipped. Again it is easy to come up with an algorithm for each bit or bit pair. Add them up to get the solution.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243