算法學習(八)遞歸和回溯法

這樣的算法思想通常都應用在樹形問題上

leetcode17. 電話號碼的字母組合

給定一個僅包含數字 2-9 的字符串,返回所有它能表示的字母組合。
給出數字到字母的映射如下(與電話按鍵相同)。注意 1 不對應任何字母。
輸入:"23"
輸出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

轉換爲樹

 

 

 

回溯算法,複雜度O(2^n)

class Solution {
    private String letterMap[] = {
        " ",    //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz" //9
    };
    private ArrayList<String> res = new ArrayList<>();

    public List<String> letterCombinations(String digits) {
        res.clear();
        if (digits.isEmpty()) {
            return res;
        }
        findCombination(digits, 0, "");
        return res;
    }

    //s中保存了此時從digits[0...index-1]翻譯得到的一個字母字符串
    //尋找gidits[index]匹配的字母,獲得digits[0...index]翻譯得到的解
    private void findCombination(String digits, int index, String s) {
        
        if(index == digits.length()){
            res.add(s);
            return;
        }

        Character c = digits.charAt(index);
        String letters = letterMap[c - '0'];
        for(int i = 0 ; i < letters.length() ; i ++){
            findCombination(digits, index+1, s + letters.charAt(i));
        }
        return;
    }
}

相關問題,93,131

2、回溯算法的應用

排列問題

leetcode46. 全排列

給定一個沒有重複數字的序列,返回其所有可能的全排列。
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]

 

image.png

 

Perms(nums[0...n-1])={取出一個數字}+Perms(nums{0...n-1}-這個數字]}

class Solution {
    private List<List<Integer>> res = new ArrayList<>();
    private boolean[] used;
    public List<List<Integer>> permute(int[] nums) {
        res.clear();
        if (nums.length == 0) {
            return res;
        }
        used = new boolean[nums.length];
        LinkedList<Integer> p = new LinkedList<>();
       
        generatePermutation(0, nums, p);
        return res;
    }

    //p中保存了一個有index個元素的排列
    //向這個排列的末尾添加低index+1,獲得一個有index+1個元素的排列
    private void generatePermutation(int index, int[] nums, LinkedList<Integer> p) {
        if (index == nums.length) {
            res.add((List<Integer>)p.clone());
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!used[i]) {
                p.addLast(nums[i]);
                used[i] = true;
                generatePermutation(index + 1, nums, p);
                p.removeLast();
                used[i] = false;
            }
        }
        return;
    }
}

相關問題,47
組合問題

leetcode77. 組合

給定兩個整數 n 和 k,返回 1 ... n 中所有可能的 k 個數的組合。
輸入: n = 4, k = 2
輸出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]

 

 

class Solution {
    LinkedList<List<Integer>> res = new LinkedList();

    public List<List<Integer>> combine(int n, int k) {
        res.clear();
        if (n <= 0 || k <= 0 || k > n) {
            return res;
        }
        LinkedList<Integer> c = new LinkedList<>();
        findCombination(n, k, 1, c);
        return res;
    }
    
    //求解C(n,k),當前已經找到的組合存儲在c中,需要從start開始搜索新元素
    private void findCombination(int n, int k, int start, LinkedList<Integer> c) {
        if (c.size() == k) {
            res.addLast((List<Integer>) c.clone());
            return;
        }
        //還有k-c.size()個空位,所以,[i...n]中至少要有k-c.size()個元素
        //i最多爲n-(k-c.size())+1
        for (int i = start; i <= n-(k-c.size())+1; i++) {
            c.addLast(i);
            findCombination(n, k, i + 1, c);
            c.removeLast();
        }
        return;
    }
}

相關問題39,40,216,78,90,401

3、二維平面上使用回溯法

leetcode79. 單詞搜索

給定一個二維網格和一個單詞,找出該單詞是否存在於網格中。
單詞必須按照字母順序,通過相鄰的單元格內的字母構成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個單元格內的字母不允許被重複使用。
board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
給定 word = "ABCCED", 返回 true.
給定 word = "SEE", 返回 true.
給定 word = "ABCB", 返回 false.

 

 

class Solution {
    //        x-1,y
    // x,y-1  x,y    x,y+1
    //        x+1,y
    private int[][] d = { { -1, 0 }, { 0, -1 }, { 0, 1 }, { 1, 0 } };
    // 盤面上有多少行
    private int m;
    // 盤面上有多少列
    private int n;
    private boolean[][] visited;//被訪問過

    public boolean exist(char[][] board, String word) {
        m = board.length;
        if (m == 0) {
            return false;
        }
        n = board[0].length;
        visited=new boolean[m][n];
        char[] wordChar=word.toCharArray();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[i].length; j++) {
                if (searchWord(board, wordChar, 0, i, j)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    //從board的[startx][starty]開始,尋找word[index...word.length]
    private boolean searchWord(char[][] board, char[] word, int index, int startx, int starty) {
        if (index == word.length - 1) {
            return board[startx][starty] == word[index];
        }
        if (board[startx][starty] == word[index]) {
            visited[startx][starty] = true;
            //從startx,starty出發,向四個方向尋找
            for (int i = 0; i < 4; i++) {
                int newx = startx + d[i][0];
                int newy = starty + d[i][1];
                if (inArea(newx, newy) && !visited[newx][newy]) {
                    if (searchWord(board, word, index + 1, newx, newy)) {
                        return true;
                    }
                }
            }
            visited[startx][starty] = false;//回溯
        }

        return false;
    }

    private boolean inArea(int x, int y) {
        return x >= 0 && x < m && y >= 0 && y < n;
    }

}

floodfill算法,一類經典問題
這個算法的本質是深度優先遍歷

leetcode200. 島嶼數量

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,並且它是通過水平方向或垂直方向上相鄰的陸地連接而成的。你可以假設網格的四個邊均被水包圍。
輸入:
11110
11010
11000
00000
輸出: 1

class Solution {
    private int[][] d = { { -1, 0 }, { 0, -1 }, { 0, 1 }, { 1, 0 } };
    int m, n;
    private boolean[][] visited;//被訪問過

    public int numIslands(char[][] grid) {
        m = grid.length;
        if (m == 0) {
            return 0;
        }
        n = grid[0].length;
        visited = new boolean[m][n];
        int res = 0;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                //沒有被標記過的陸地
                if (grid[i][j] == '1' && !visited[i][j]) {
                    res++;
                    visited[i][j] = true;//將陸地標記
                    dfs(grid, i, j);//從這個陸地開始找,和他相連的陸地都標記上
                }
            }
        }
        return res;
    }

    //從gird[x][y]的位置開始,進行floodfill
    private void dfs(char[][] grid, int x, int y) {
        visited[x][y]=true;//將陸地標記
        for (int i = 0; i < 4; i++) {
            int newx = x + d[i][0];
            int newy = y + d[i][1];
            //保證(x,y)合法,且grid[x][y]是沒有被訪問過的陸地,這個也是遞歸終止條件
            if (inArea(newx, newy) && !visited[newx][newy] && grid[newx][newy] == '1') {
                dfs(grid, newx, newy);
            }
        }
        return;
    }
    private boolean inArea(int x, int y) {
        return x >= 0 && x < m && y >= 0 && y < n;
    }
}

相關問題,130,417

4、回溯法是經典人工智能的基礎

leetcode51. N皇后

n 皇后問題研究的是如何將 n 個皇后放置在 n×n 的棋盤上,並且使皇后彼此之間不能相互攻擊。(即任意兩個皇后都不能處於同一行、同一列或同一斜線上)

 

 

給定一個整數 n,返回所有不同的 n 皇后問題的解決方案。
每一種解法包含一個明確的 n 皇后問題的棋子放置方案,該方案中 'Q' 和 '.' 分別代表了皇后和空位
輸入: 4
輸出: [
[".Q..", // 解法 1
"...Q",
"Q...",
"..Q."],

["..Q.", // 解法 2
"Q...",
"...Q",
".Q.."]
]
解釋: 4 皇后問題存在兩個不同的解法。

 

 

快速判斷不合法的情況
豎向:col[i]表示第i列被佔用
對角線1:dia1[i]表示在對角線1中,第i個元素被佔用
對角線2:dia2[i]表示在對角線2中,第i個元素被佔用
可以用橫縱座標相加的值表示對角線1

 

 


可以用橫縱座標相減的值表示對角線2,爲了方便用數組表示還要+n-1

 

class Solution {
    private List<List<String>> res = new ArrayList();
    private boolean[] colUsed;// 縱方向
    private boolean[] dia1, dia2;// 2個斜對角線

    public List<List<String>> solveNQueens(int n) {
        res.clear();
        colUsed = new boolean[n];
        dia1 = new boolean[2 * n - 1];
        dia2 = new boolean[2 * n - 1];
        List<Integer> row = new ArrayList();
        putQueen(n, 0, row);
        return res;
    }

    // 嘗試在一個n皇后問題中,擺放第index行的皇后位置,結果存在row
    private void putQueen(int n, int index, List<Integer> row) {
        if (index == n) {
            res.add(generateBoard(n, row));
            return;
        }

        for (int i = 0; i < n; i++) {
            // 嘗試將第index行的皇后擺放在第i列
            if (!colUsed[i] && !dia1[index + i] && !dia2[index - i + n - 1]) {
                row.add(i);
                colUsed[i] = true;
                dia1[index + i] = true;
                dia2[index - i + n - 1] = true;
                putQueen(n, index + 1, row);
                colUsed[i] = false;
                dia1[index + i] = false;
                dia2[index - i + n - 1] = false;
                row.remove(row.size() - 1);
            }
        }
        return;
    }

    private List<String> generateBoard(int n, List<Integer> row) {
        List<String> list = new ArrayList<>();
        
        for (int i = 0; i < n; i++) {
            String s = "";
            for (int j = 0; j < n; j++) {
                if (j == row.get(i)) {
                    s += "Q";
                } else {
                    s += "."; 
                }
            }
            list.add(s);
        }
       
        return list;
    }

}

相關問題,52,37

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章