给定一个具有15个图块的4×4板(每个图块都有一个1到15的数字)和一个空白空间。目的是使用空白空间按顺序将数字放置在图块上。我们可以将四个相邻的(左,右,上方和下方)磁贴滑动到空白区域。例如,
这里的X标记了元素可以移动到的位置,并且最终配置始终保持不变,可以解决难题。
通常,对于给定的宽度N的网格,我们可以通过遵循以下简单规则来确定N * N – 1个拼图是否可解决:
- 如果N为奇数,则在输入状态下,如果反转次数为偶数,则拼图实例是可解的。
- 如果N是偶数,则拼图实例是可解的
- 空白从底部(倒数第二,第四等)开始在偶数行上,反转次数为奇数。
- 空格位于从底部开始计数的奇数行(倒数,倒数第三,倒数第五等),并且倒数为偶数。
- 对于所有其他情况,拼图实例无法解决。
什么是反转?
如果我们假设将图块写成单行(1D数组)而不是散布在N行(2D数组)中,则如果a出现在b之前但a> b,则一对图块(a,b)构成一个反转。
对于上面的示例,请考虑连续写出的图块,如下所示:
2 1 3 4 5 6 7 8 9 10 11 12 13 14 15 X
上面的网格仅形成1个反演,即(2,1)。
插图:
下面是一个简单的C++程序,用于检查给定的15个拼图实例是否可解决。该程序是通用程序,可以扩展到任何网格宽度。
C++
// C++ program to check if a given instance of N*N-1
// puzzle is solvable or not
#include
#define N 4
using namespace std;
// A utility function to count inversions in given
// array 'arr[]'. Note that this function can be
// optimized to work in O(n Log n) time. The idea
// here is to keep code small and simple.
int getInvCount(int arr[])
{
int inv_count = 0;
for (int i = 0; i < N * N - 1; i++)
{
for (int j = i + 1; j < N * N; j++)
{
// count pairs(i, j) such that i appears
// before j, but i > j.
if (arr[j] && arr[i] && arr[i] > arr[j])
inv_count++;
}
}
return inv_count;
}
// find Position of blank from bottom
int findXPosition(int puzzle[N][N])
{
// start from bottom-right corner of matrix
for (int i = N - 1; i >= 0; i--)
for (int j = N - 1; j >= 0; j--)
if (puzzle[i][j] == 0)
return N - i;
}
// This function returns true if given
// instance of N*N - 1 puzzle is solvable
bool isSolvable(int puzzle[N][N])
{
// Count inversions in given puzzle
int invCount = getInvCount((int*)puzzle);
// If grid is odd, return true if inversion
// count is even.
if (N & 1)
return !(invCount & 1);
else // grid is even
{
int pos = findXPosition(puzzle);
if (pos & 1)
return !(invCount & 1);
else
return invCount & 1;
}
}
/* Driver program to test above functions */
int main()
{
int puzzle[N][N] =
{
{12, 1, 10, 2},
{7, 11, 4, 14},
{5, 0, 9, 15}, // Value 0 is used for empty space
{8, 13, 6, 3},
};
/*
int puzzle[N][N] = {{1, 8, 2},
{0, 4, 3},
{7, 6, 5}};
int puzzle[N][N] = {
{13, 2, 10, 3},
{1, 12, 8, 4},
{5, 0, 9, 6},
{15, 14, 11, 7},
};
int puzzle[N][N] = {
{6, 13, 7, 10},
{8, 9, 11, 0},
{15, 2, 12, 5},
{14, 3, 1, 4},
};
int puzzle[N][N] = {
{3, 9, 1, 15},
{14, 11, 4, 6},
{13, 0, 10, 12},
{2, 7, 8, 5},
};
*/
isSolvable(puzzle)? cout << "Solvable":
cout << "Not Solvable";
return 0;
}
PHP
j.
$inv_count++;
}
}
return $inv_count;
}
// find Position of blank from bottom
function findXPosition($puzzle)
{
global $N;
// start from bottom-right corner of matrix
for ($i = $N - 1; $i >= 0; $i--)
for ($j = $N - 1; $j >= 0; $j--)
if ($puzzle[$i][$j] == 0)
return $N - $i;
}
// This function returns true if given
// instance of N*N - 1 puzzle is solvable
function isSolvable( $puzzle)
{
global $N;
// Count inversions in given puzzle
$invCount = getInvCount($puzzle);
// If grid is odd, return true if inversion
// count is even.
if ($N & 1)
return !($invCount & 1);
else // grid is even
{
$pos = findXPosition($puzzle);
if ($pos & 1)
return !($invCount & 1);
else
return $invCount & 1;
}
}
/* Driver program to test above functions */
$puzzle =
array(
array(12, 1, 10, 2),
array(7, 11, 4, 14),
array(5, 0, 9, 15), // Value 0 is used for empty space
array(8, 13, 6, 3),
);
if(isSolvable($puzzle)==0)
echo "Solvable";
else
echo "Not Solvable";
#This code is contributed by aj_36
?>
Solvable
时间复杂度:O(n 2 )
空间复杂度:O(n)
这是如何运作的?
事实1:对于宽度为奇数的网格,所有合法移动都会保留反转数量的极性(偶数或奇数)。
事实证明1
- 沿着行(左或右)移动磁贴不会改变反转的次数,因此也不会改变其极性。
- 沿着列(向上或向下)移动磁贴可以更改反转次数。该图块会移动经过偶数个其他图块(N – 1)。因此,将反相计数增加或减少2,或将反相计数保持不变。
事实2:对于偶数宽度的网格,以下内容是不变的:(#inversions偶数)==(从底部开始的奇数行上为空白)。
示例:考虑上述举动。左侧的反转次数为49,空白处在从底部开始的偶数行中。因此,不变量的值为“ false == false”,这是正确的。右侧的反转数为48,因为11个反转了2个反转,而14个反转了1个。空白位于底部的奇数行上。因此,不变式的值为“ true == true”,这仍然是正确的。
事实证明2
- 沿着行(向左或向右)移动图块不会更改反转次数,也不会更改空白行。
- 沿着列(向上或向下)移动磁贴确实会改变反转次数。磁贴移动经过其他奇数个磁贴(N – 1)。因此,反转次数会发生奇数次变化。空白行也从奇数更改为偶数,或从偶数更改为奇数。因此,不变性的两个部分都发生了变化。因此,它的价值得以保留。
结合事实1 +事实2 =事实3:
- 如果宽度是奇数,则每个可解状态都有偶数个反转。
如果宽度是偶数,那么每个可解状态都具有- 如果空白在从底部开始计数的奇数行中,则为偶数个反转;
- 如果空白处在从底部开始计数的偶数行中,则反转的数目为奇数;
事实证明3:
- 初始(已解决)状态具有这些属性。
- 那些法律行为都会保留这些财产。
- 通过一系列合法的举动,可以从初始状态达到任何可解决的状态。