1. 程式人生 > 資訊 >ofo“拉好友退押金”上熱搜,央視網評:這是拉好友“共享”入坑

ofo“拉好友退押金”上熱搜,央視網評:這是拉好友“共享”入坑

用遞迴實現回溯切忌遞得進去、歸不出來。

N皇后問題是比較典型的回溯法問題,不過在回溯法中,解決N皇后問題算是有一些難度的了。

網上有很多該問題的解法,我這個雖然不是抄來的,效能也不算彪悍,但相比之下,我對“機長出品”的可讀性還是很有信心的。所以撈點兒乾貨貼在這裡,以備將來查閱之需。

回溯法的本質是對樹形結構的遍歷,一層一層遞進,保留符合條件的節點。到達終點後,隨著遞迴的一層層退出,抹掉上一次遍歷時留下的痕跡,從而開始下一輪遍歷。

回溯法一般都會結合遞迴來實現,遞迴可以簡化多層巢狀for迴圈的場景。但寫遞迴函式時一定要把子問題邏輯捋清楚,並確保退出條件生效。也就是說,每經過一次遞迴操作,退出條件中的變數一定要有所變化,否則就是遞得進去,歸不出來了。遞迴雖然能夠簡化多層巢狀迴圈,但並沒有改變其本質,因此並不是說遞迴一定比for

迴圈效率高,避免無效遍歷是提升迴圈效率的一個主要套路。

在N皇后問題中,樹的每個節點都代表一個N×N大小的棋盤,棋盤上記錄了該節點所有直系父級節點裡皇后所在的位置,而每個皇后所在的位置必須保證以該位置為中心點畫出的“米”字每一筆延伸到棋盤邊緣都不會經過另一個皇后。其實,琢磨透了這點兒區別,N皇后問題和一般的用回溯法在一維數組裡找組合的問題也就沒多大差別了。

除了保證邏輯上正確,剩下主要就是優化。比如:皇后的位置是從上到下一行一行確定的,所以在判斷對角線上是否存在其他皇后時,不需要檢查“米”字下面那一撇一捺,因為此時下面的資料還沒有生成,不會有皇后。

另外,別忘了每層遞迴完成時移除上次記錄的資料。在這個問題中,還要記著抹掉上一行皇后佔位的狀態,否則下一輪遞迴到這一層時,殘留的狀態會讓程式碼輸出的結果“莫名其妙”。

class NQueensProlbem {
    private boolean[][] squareStates;

    public List<List<String>> solve(int sideLength) {
        List<List<String>> resultList = new ArrayList<>();

        squareStates = new boolean[sideLength][sideLength];
        backtrack(resultList, sideLength, new ArrayList<>(), 0, 0);

        return resultList;
    }

    private void backtrack(List<List<String>> resultList, int sideLength, List<String> rowList, int rowIndex,
            int columnIndex) {
        if (rowIndex == sideLength) {
            if (rowList.size() == sideLength) {
                resultList.add(new ArrayList<>(rowList));
            }

            return;
        }

        for (int index = columnIndex; index < sideLength + columnIndex; index++) {
            int offset = (index + 1) % sideLength;

            if (!isSquareAvailable(rowIndex, offset)) {
                continue;
            }

            StringBuilder rowBuilder = newRow(sideLength);

            rowBuilder.setCharAt(offset, 'Q');
            squareStates[rowIndex][offset] = true;
            rowList.add(rowBuilder.toString());
            backtrack(resultList, sideLength, rowList, rowIndex + 1, offset);

            if (!rowList.isEmpty()) {
                rowList.remove(rowList.size() - 1);
                Arrays.fill(squareStates[rowIndex], false);
            }
        }
    }

    private boolean isSquareAvailable(int rowIndex, int columnIndex) {
        if (rowIndex < 0 || rowIndex >= squareStates.length || columnIndex < 0 || columnIndex >= squareStates.length) {
            return false;
        }

        int sideLength = squareStates.length;

        // Description: Both entire row and column must be available.
        for (int index = 0; index < sideLength; index++) {
            if (squareStates[index][columnIndex] || squareStates[rowIndex][index]) {
                return false;
            }
        }

        // Description: From the current row to the top one, check if every top left and
        // top right one is available.
        for (int index = 0; index < rowIndex; index++) {
            int offset = rowIndex - index;
            int leftSquareIndex = columnIndex - offset;
            int rightSquareIndex = columnIndex + offset;

            if ((leftSquareIndex >= 0 && squareStates[index][leftSquareIndex])
                    || (rightSquareIndex < sideLength && squareStates[index][rightSquareIndex])) {
                return false;
            }
        }

        return true;
    }

    private StringBuilder newRow(int sideLength) {
        StringBuilder builder = new StringBuilder(sideLength);

        for (int squareIndex = 0; squareIndex < sideLength; squareIndex++) {
            builder.append(".");
        }

        return builder;
    }
}
轉載請註明出處。版權所有©1983-2021 麥機長,保留所有權利。