連連看遊戲消除演算法
阿新 • • 發佈:2019-01-25
今天在收到一道的面試題,覺得比較有意思,決定記錄下來,整個題目與解答過程大概如下。
連連看是一種很受大家歡迎的小遊戲。下面四張圖給出了最基本的消除規則:
圖 A 中出現在同一直線上無障礙的圈圈可以消除;圖 B 中兩個圈圈可以通過一次轉彎消除;圖 C 和圖 D 中,兩個圈圈可以通過兩次轉彎消除。
已知以下介面,表示位置(x, y)上有無障礙物:
int isBlocked(int x, int y);
return 0; // 無障礙物(位置(x,y)為空)
return 1; // 有障礙物(位置(x,y)上有方塊或圈圈)
請寫一個函式來判斷給定的任意兩個圈圈是否可消除(x1, y1與x2, y2為兩個圈圈的位置):
int remove(int x1, int y1, int x2, int y2);
水平檢測
水平檢測用來判斷兩個點的縱座標是否相等,同時判斷兩點間有沒有障礙物。
因此直接檢測兩點間是否有障礙物就可以了,程式碼如下:
static bool horizon(int x1, int y1, int x2, int y2) { if (x1 == x2 && y1 == y2) { return false; } if (x1 != x2) { return false; } int start_y = std::min(y1, y2) int end_y = std::max(y1, y2); for (int j = start_y; j < end_y; j++) { if (isBlocked(x1, j)) { return false; } } return true; }
垂直檢測
垂直檢測用來判斷兩個點的橫座標是否相等,同時判斷兩點間有沒有障礙物。
同樣地,直接檢測兩點間是否有障礙物,程式碼如下:
static int vertical(int x1, int y1, int x2, int y2) { if (x1 == x2 && y1 == y2) { return false; } if (y1 != y2) { return false; } int start_x = std::min(x1, x2); int end_x = std::max(x1, x2); for (int i = start_x; i < end_x; i++) { if (isBlocked(i, y1)) { return false; } } return true; }
一個拐角檢測
一個拐角檢測可分解為水平檢測和垂直檢測,當兩個同時滿足時,便兩點可通過一個拐角相連。即:
一個拐角檢測 = 水平檢測 && 垂直檢測
A 點至 B 點能否連線可轉化為滿足任意一點:
- A 點至 C 點的垂直檢測,以及 C 點至 B 點的水平檢測;
- A 點至 D 點的水平檢測,以及 D 點至 B 點的垂直檢測。
程式碼如下:
static int turn_once(int x1, int y1, int x2, int y2)
{
if (x1 == x2 && y1 == y2)
{
return false;
}
int c_x = x1, c_y = y2;
int d_x = x2, d_y = y1;
int ret = false;
if (!isBlocked(c_x, c_y))
{
ret |= horizon(x1, y1, c_x, c_y) && vertical(c_x, c_y, x2, y2);
}
if (!isBlocked(d_x, d_y))
{
ret |= horizon(x1, y1, d_x, d_y) && vertical(d_x, d_y, x2, y2);
}
if (ret)
{
return true;
}
return false;
}
兩個拐角檢測
兩個拐角檢測可分解為一個拐角檢測和水平檢測或垂直檢測。即:
兩個拐角檢測 = 一個拐角檢測 && (水平檢測 || 垂直檢測)
如圖,水平、垂直分別穿過 A B 共有四條直線,掃描直線上所有不包含 A B 的點,看是否存在一點 C ,滿足以下任意一項:
- A 點至 C 點通過水平或垂直檢測,C 點至 B 點可通過一個拐角連線。(圖中用 C 表示)
- A 點至 C 點可通過一個拐角連線,C 點至 B 點通過水平或垂直連線。(圖中用 C 下劃線表示)
程式碼如下:
static int turn_twice(int x1, int y1, int x2, int y2)
{
if (x1 == x2 && y1 == y2)
{
return false;
}
for (int i = 0; i <= MAX_X; i++)
{
for (int j = 0; j <= MAX_Y; j++)
{
if (i != x1 && i != x2 && j != y1 && j != y2)
{
continue;
}
if ((i == x1 && j == y1) || (i == x2 && j == y2))
{
continue;
}
if (isBlocked(i, j))
{
continue;
}
if (turn_once(x1, y1, i, j) && (horizon(i, j, x2, y2) || vertical(i, j, x2, y2)))
{
return true;
}
if (turn_once(i, j, x2, y2) && (horizon(x1, y1, i, j) || vertical(x1, y1, i, j)))
{
return true;
}
}
}
return false;
}
整合
最後,整合以上四種情況,判斷兩點是否能消除的程式碼可以寫成:
int remove(int x1, int y1, int x2, int y2)
{
int ret = false;
ret = horizon(x1, y1, x2, y2);
if (ret)
{
return 1;
}
ret = vertical(x1, y1, x2, y2);
if (ret)
{
return 1;
}
ret = turn_once(x1, y1, x2, y2);
if (ret)
{
return 1;
}
ret = turn_twice(x1, y1, x2, y2);
if (ret)
{
return 1;
}
return 0;
}
個人公眾號:ACM演算法日常
專注於基礎演算法的研究工作,深入解析ACM演算法題,五分鐘閱讀,輕鬆理解每一行原始碼。內容涉及演算法、C/C++、機器學習等。