[leetcode刷题]——搜索

  此博客主要记录力扣中关于搜索的题解,包括 BFS、DFS、Backtracking

BFS 

一、计算在网格中从原点到特定点的最短路径长度

1091. 二进制矩阵中的最短路径 (medium) 2021-07-12

给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。

二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:

路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。

 

  经典BFS 题目。

 

class Solution {
    public int shortestPathBinaryMatrix(int[][] grid) {
        if(grid == null || grid.length == 0 || grid[0].length == 0){
            return -1;
        }
        if(grid[0][0] == 1) return -1;
        int[][] dir = {{1,1}, {1, 0}, {1, -1}, {0, 1}, {0, -1}, {-1, 1}, {-1, 0}, {-1, -1}};
        int m = grid.length;
        int n = grid[0].length;
        Queue<int[]> queue = new LinkedList<>();
        queue.add(new int[]{0, 0});
        grid[0][0] = 1;  //遍历一个节点就置 1 ,记为堵塞
        int path = 1;   //第一层
        while(!queue.isEmpty()){
            int size = queue.size();
            while(size > 0){
                int[] cur = queue.poll();
                int x = cur[0];
                int y = cur[1];
                //结束遍历,返回层数
                if(x == m - 1 && y == n - 1){
                    return path;
                }
                
                for(int[] d : dir){
                    int x1 = x + d[0];
                    int y1 = y + d[1];
                    if(x1 < 0 || x1 >= m || y1 >= m || y1 < 0 || grid[x1][y1] == 1){
                        continue;
                    }
                    queue.add(new int[]{x1, y1});
                    grid[x1][y1] = 1;
                }
                size--;
            }
            path++;
        }
        return -1;
    }
}

 

 

 

二、组成整数的最小平方数数量

279. 完全平方数  (medium) 2021-07-12

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

  这个题可以使用 BFS 求解,但不是最优解,此方法时间击败 9% 空间击败 4%

  

class Solution {
    public int numSquares(int n) {
        List<Integer> squares = generateSquare(n);
        Queue<Integer> queue = new LinkedList<>();
        queue.add(n);
        int level = 0;
        while(!queue.isEmpty()){
            int size = queue.size();
            level++;
            while(size-- > 0){
                int cur = queue.poll();
                for(int s : squares){
                    int next = cur - s;
                    if(next < 0) break;
                    if(next == 0) return level;
                    queue.add(next);
                }
            }
        }
        return n;
    }

    //这个函数返回小于 n 的所有完全平方数的和,n >= 1
    //实现方法并没有让两数直接相乘,而是使用数学规律
    public List<Integer> generateSquare(int n){
        List<Integer> list = new ArrayList<>();
        int increment = 1;
        int gap = 2;
        int i = 1;
        while(i <= n){
            list.add(i);
            i = i + increment + gap;
            increment += gap;
        }
        return list;
    }
}

 

三、 最短单词路径

127. 单词接龙 (hard) 2021-07-12

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:

序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

   官方解题方法:先给每个单词标号,给每一个单词分配一个id 。创建一个由单词word 到 id 之间的映射 wordId, 并将beginWord 与 wordList 中所有的单词都加入到这个映射中。检查endWord是否在该映射内,如果不存在则输入无解。

  建图的优化方法,创建虚拟节点。如 hit ,我们创建三个虚拟节点 *it , h*t, hi* ,让单词连接虚拟节点再连接单词。最后将路径长度除以 2 。

  官方答案如下

class Solution {
    Map<String, Integer> wordId = new HashMap<String, Integer>();
    List<List<Integer>> edge = new ArrayList<List<Integer>>();
    int nodeNum = 0;
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        for(String word : wordList){
            addEdge(word);
        }
        addEdge(beginWord);
        if(!wordId.containsKey(endWord)){
            return 0;
        }
        int[] dis = new int[nodeNum];
        Arrays.fill(dis, Integer.MAX_VALUE);
        int beginId = wordId.get(beginWord);
        int endId = wordId.get(endWord);
        dis[beginId] = 0;
        
        Queue<Integer> que = new LinkedList<Integer>();
        que.add(beginId);
        while(!que.isEmpty()){
            int x = que.poll();
            if(x == endId){
                return dis[endId] / 2 + 1;
            }
            for(int it : edge.get(x)){
                if(dis[it] == Integer.MAX_VALUE){
                    dis[it] = dis[x] + 1;
                    que.add(it);
                }
            }
        }
        return 0;

    }
    public void addEdge(String word){
        addWord(word);
        int id1 = wordId.get(word);
        char[] array = word.toCharArray();
        int length = array.length;
        for(int i = 0; i < length; i++){
            char tmp = array[i];
            array[i] = '*';
            String newWord = new String(array);
            addWord(newWord);
            int id2 = wordId.get(newWord);
            edge.get(id1).add(id2);
            edge.get(id2).add(id1);
            array[i] = tmp;
        }
    }
    
    public void addWord(String word){
        if(!wordId.containsKey(word)){
            wordId.put(word, nodeNum++);
            edge.add(new ArrayList<Integer>());
        }
    }
   
}

 

 DFS

一、 查找最大的连通面积

695. 岛屿的最大面积 (medium ) 2021-07-13

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

class Solution {
    private int m, n;
    private int[][] direction = {{0, 1}, {0, - 1}, {1, 0}, {-1, 0}};
    public int maxAreaOfIsland(int[][] grid) {
        if(grid == null || grid.length == 0){
            return 0;
        } 
        m = grid.length;
        n = grid[0].length;
        int maxArea = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                maxArea = Math.max(maxArea, dfs(grid, i, j));
            }
        }
        return maxArea;
    }
    
    private int dfs(int[][] grid, int r, int c){
        if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0){
            return 0;
        }
        grid[r][c] = 0;
        int area = 1;
        for(int[] d : direction){
            area = area + dfs(grid, r + d[0], c + d[1]);
        }
        return area;
    }
}

 

二、矩阵中的连通分量数目

200. 岛屿数量 (medium) 2021-07-13

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

 

class Solution {
    private int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    private int m;
    private int n;
    public int numIslands(char[][] grid) {
        m = grid.length;
        n = grid[0].length;
        int count =  0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(dfs(grid, i, j)){
                    count++;
                }
            }
        }
        return count;
    }
    private boolean dfs(char[][] grid, int r, int c){
        if(r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == '0'){
            return false;
        }
        grid[r][c] = '0'; //将遍历到的这个节点置为0   
        for(int[] dir : direction){
            dfs(grid, r + dir[0] , c + dir[1]);
        }
        return true;  
    }
}

 

三、好友关系的连通分量数目

547. 省份数量 (medium) 2021-07-14

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。

返回矩阵中 省份 的数量。

  这个题和上面的岛屿相通有点相似但是不尽相同,这个矩阵表示的不是物理连通关系,而是逻辑上的连通。

class Solution {
    private boolean[] isVisted;
    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        isVisted = new boolean[n];
        int num = 0;
        for(int j = 0; j < n; j++){
            if(dfs(isConnected, j, isVisted)){
                num++;
            }
        }
        return num;
    }
    public boolean dfs(int[][] isConnected, int i, boolean[] isVisted){
        int n = isConnected.length;
        if(i < 0 || i >= n || isVisted[i] == true){
            return false;
        }
        isVisted[i] = true;
        for(int k = 0; k < n; k++){
            if(isConnected[i][k] == 1){
                dfs(isConnected, k, isVisted);
            }
        }
        return true;
    }
}

 

四、 填充封闭区域

130. 被围绕的区域 (medium)2021-07-14

给你一个 m x n 的矩阵 board ,由若干字符 'X'  和 'O' ,找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。

class Solution {
    private boolean[][] isVisted;
    private int m;
    private int n;
    private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    public void solve(char[][] board) {
        m = board.length;
        n = board[0].length;
        isVisted = new boolean[m][n];
        for(int i = 0; i < m; i++){
            dfs(board, i, 0);
            dfs(board, i, n - 1);
        }
        for(int j = 0; j < n; j++){
            dfs(board, 0, j);
            dfs(board, m - 1, j);  
        }
 
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(board[i][j] == 'O' && !isVisted[i][j]){
                    board[i][j] = 'X';
                }
            }
        }
        return;
    }
    public void dfs(char[][] board, int r, int c){
        if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] == 'X' || isVisted[r][c] == true){
            return;
        }
        isVisted[r][c] = true;
        for(int[] dir : direction){
            dfs(board, r + dir[0], c + dir[1]);
        }
        return;
    }
}

 

五、 能到达的太平洋和大西洋的区域

417. 太平洋大西洋水的问题 (medium) 2021-07-14

给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。

规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。

请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

 

这个题,使用dfs 的思路没错。但是算法思想的出发点,我初始的想法是遍历所有节点,使其向高度小的地方遍历,直至边缘,这种方法过于复杂.

 

class Solution {
    private int m, n;
    private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        List<List<Integer>> ret = new ArrayList<>();
        if(heights == null || heights.length == 0){
            return ret;
        }
        m = heights.length;
        n = heights[0].length;
        boolean[][] canReachP = new boolean[m][n];
        boolean[][] canReachA = new boolean[m][n];
        for(int i = 0; i < m; i++){
            dfs(heights, i, 0, canReachP);
            dfs(heights, i, n - 1, canReachA);
        }
        for(int i = 0; i < n; i++){
            dfs(heights, 0, i, canReachP);
            dfs(heights, m - 1, i, canReachA);
        }
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(canReachA[i][j] && canReachP[i][j]){
                    ret.add(Arrays.asList(i, j));
                }
            }
        }
        return ret;
    }
    private void dfs(int[][] heights, int r, int c, boolean[][] canReach){
        if(canReach[r][c]) return;
        canReach[r][c] = true;
        for(int[] d : direction){
            int nextR = d[0] + r;
            int nextC = d[1] + c;
            if(nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || heights[r][c] > heights[nextR][nextC]){
                continue;
            }
            dfs(heights, nextR, nextC, canReach);
        }
    }
}

 

Backtracking

一、 数字键盘组合

17. 电话号码的字母组合 (medium) 2021-07-14

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

  回溯的经典题目,需要多加练习。

 

private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

public List<String> letterCombinations(String digits) {
    List<String> combinations = new ArrayList<>();
    if (digits == null || digits.length() == 0) {
        return combinations;
    }
    doCombination(new StringBuilder(), combinations, digits);
    return combinations;
}

private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) {
    if (prefix.length() == digits.length()) {
        combinations.add(prefix.toString());
        return;
    }
    int curDigits = digits.charAt(prefix.length()) - '0';
    String letters = KEYS[curDigits];
    for (char c : letters.toCharArray()) {
        prefix.append(c);                         // 添加
        doCombination(prefix, combinations, digits);
        prefix.deleteCharAt(prefix.length() - 1); // 删除
    }
}

 

二、Ip地址划分

93.复原IP地址 (medium)2021-07-15

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。

   回溯题,使用常规套路解题

 

class Solution {
    public List<String> restoreIpAddresses(String s) {
        List<String> retList = new ArrayList<>();
        if(s.length() < 4 || s.length() > 12) return retList;
        restoreIp(s, retList, new ArrayList<Integer>(), 0);
        return retList;
    }
    public void restoreIp(String s, List<String> list, 
                          List<Integer> prefix, int idx){
        if(prefix.size() == 4 && idx == s.length()){
            addIp(list, prefix); //将整理好的ip地址放进列表
        }
        for(int i = 1; i <=3; i++){
            if(idx + i > s.length()) break;
            String substr = s.substring(idx, idx + i); //左闭右开的区间
            if(substr.length() > 1 && substr.charAt(0) == '0') break;
            int num = Integer.valueOf(substr);
            if(num > 255) break;
            prefix.add(num);
            restoreIp(s, list, prefix, idx + i);
            prefix.remove(prefix.size() - 1);
        }   
    }
    public void addIp(List<String> list, List<Integer> prefix){
        StringBuffer strb = new StringBuffer();
        for(int pre : prefix){
            strb.append(pre + ".");
        }
        strb.deleteCharAt(strb.length() - 1);
        list.add(strb.toString());
    } 
}

 

 

 三、在矩阵中寻找字符串

79. 单词搜索  (medium) 2021-07-19

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

   剑指offer上的原题,写了第二遍,居然又写了bug。

 

class Solution {
    private int m, n;
    private boolean[][] isVisted;
    private int[][] directions = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    public boolean exist(char[][] board, String word) {
        if(board.length == 0) return false;
        m = board.length;
        n = board[0].length;
        isVisted = new boolean[m][n];
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(search(board, word, 0, i, j)){
                    return true;
                }
            }
        }
        return false;  
    }
    private boolean search(char[][] board, String word,int index,
                           int r, int c){
        if(r < 0 || r>=m||c<0||c>=n||
           index>=word.length()||isVisted[r][c]||
           board[r][c] != word.charAt(index)) {
               return false;
           }
        
        if(board[r][c] == word.charAt(index)){
            index++;
            isVisted[r][c] = true;
        }
        if(index == word.length()){
            return true;
        }
        for(int[] dir : directions){
            if(search(board, word, index, r + dir[0], c + dir[1])){
                return true;
            }
        }
        isVisted[r][c] = false;
        return false;           
    }
}

 

四、输出二叉树中所有根到叶子的路径

257.  二叉树的所有路径  (easy) 2021-07-19

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

   

   这个题是我看完答案后得到的启发,一直在 Sting 和StringBuffer 之间进行切换操作。

  我之前的代码是直接使用StringBuffer 进行传递,但是它只会不停的增加长度。

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> ret = new ArrayList<>();
        if(root == null) return ret;
        search(root, ret, "");
        return ret;
    }
    private void search(TreeNode node, List<String> list, 
                       String prefix){
        
        if(node == null) return;
        StringBuffer prefixSB = new StringBuffer(prefix);
        if(node.left == null && node.right == null){
            prefixSB.append(node.val+"");
            list.add(prefixSB.toString());
            return;
        }
        prefixSB.append(node.val +"->");
        search(node.left, list, prefixSB.toString());
        search(node.right, list, prefixSB.toString());
    }
}

 

   又把代码改了改, 下面这个代码比较符合我的代码习惯

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> list = new ArrayList<>();
        if(root == null) return list;
        search(root, list, new ArrayList<Integer>());
        return list;
    }
    public void search(TreeNode node, List<String> list, List<Integer> prefix){
        if(node == null) return;
        if(node.left == null && node.right == null) {
            prefix.add(node.val);
            doRoad(prefix, list);
        }else{
            prefix.add(node.val);
            search(node.left, list, prefix);
            search(node.right, list, prefix);
        }
        prefix.remove(prefix.size() - 1);
        return;
    }
    
    public void doRoad(List<Integer> prefix, List<String> list){
        StringBuffer strb = new StringBuffer();
        for(Integer pre : prefix){
            strb.append(pre + "->");
        }
        strb.deleteCharAt(strb.length() - 1);
        strb.deleteCharAt(strb.length() - 1);
        list.add(strb.toString());
    }
}

 

五、排列

46. 全排列  (medium) 2021-07-20

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

  在循环那个地方,逻辑上有点难理解。

  需要注意的是  permutes.add(new ArrayList(permuteList)), 需要新建一个新 ArrayList 。

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> permutes = new ArrayList<>();
        List<Integer> permuteList = new ArrayList<>();
        boolean[] isVisted = new boolean[nums.length];
        searchPermute(permutes, permuteList, isVisted, nums);
        return permutes;
    }
    public void searchPermute(List<List<Integer>> permutes,
            List<Integer> permuteList,boolean[] isVisted,int[] nums){
        if(permuteList.size() == nums.length){
            permutes.add(new ArrayList(permuteList));
        }
        for(int i = 0; i < nums.length; i++){
            if(isVisted[i]) continue;
            permuteList.add(nums[i]);
            isVisted[i] = true;
            searchPermute(permutes, permuteList, isVisted, nums);
            isVisted[i] = false;
            permuteList.remove(permuteList.size() - 1);
        }
        return;
    }
}

 

  

六、含有相同元素求排列

47. 全排列Ⅱ (medium)2021-07-20

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

  第一个想法当然是魔改上一题, 然后时间击败 5% ,哈哈。

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> permutes = new ArrayList<>();
        List<Integer> permuteList = new ArrayList<>();
        boolean[] isVisted = new boolean[nums.length];
        searchPermute(permutes, permuteList, isVisted, nums);
        return permutes;
    }
    public void searchPermute(List<List<Integer>> permutes,
            List<Integer> permuteList,boolean[] isVisted,int[] nums){
        if(permuteList.size() == nums.length){
            if(!permutes.contains(permuteList)){
                permutes.add(new ArrayList(permuteList));
            }
            
        }
        for(int i = 0; i < nums.length; i++){
            if(isVisted[i]) continue;
            permuteList.add(nums[i]);
            isVisted[i] = true;
            searchPermute(permutes, permuteList, isVisted, nums);
            isVisted[i] = false;
            permuteList.remove(permuteList.size() - 1);
        }
        return;
    }
}

  list.contains()进行判断的时间复杂度太高了。

  大佬的做法,先将nums 数组进行排序,然后在添加排列的时候进行重复的判断。

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> permutes = new ArrayList<>();
        List<Integer> permuteList = new ArrayList<>();
        boolean[] isVisted = new boolean[nums.length];
        Arrays.sort(nums);
        searchPermute(permutes, permuteList, isVisted, nums);
        return permutes;
    }
    public void searchPermute(List<List<Integer>> permutes,
            List<Integer> permuteList,boolean[] isVisted,int[] nums){
        if(permuteList.size() == nums.length){
            permutes.add(new ArrayList(permuteList));
        }
        for(int i = 0; i < nums.length; i++){
            if (i != 0 && nums[i] == nums[i - 1] && !isVisted[i - 1]) {
            continue;  // 防止重复
        }
            if(isVisted[i]) continue;
            permuteList.add(nums[i]);
            isVisted[i] = true;
            searchPermute(permutes, permuteList, isVisted, nums);
            isVisted[i] = false;
            permuteList.remove(permuteList.size() - 1);
        }
        return;
    }
}

 

七、 组合

77. 组合 (medium) 2021-07-20

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

  上述题目都是排列问题,这个题是组合问题。

  解题方法大致相同,需要多增加一个参数,避免重复遍历

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> combines = new ArrayList<>();
        List<Integer> combineList = new ArrayList<>();
        searchCombine(combines, combineList, n, k,1);
        return combines;
    }
    public void searchCombine(List<List<Integer>> combines,List<Integer> combineList,int n, int k, int start){
        if(combineList.size() == k){
            combines.add(new ArrayList(combineList));
        }
        for(int i = start; i <= n; i++){
            combineList.add(i);
            searchCombine(combines, combineList, n, k, start + 1);
            combineList.remove(combineList.size() - 1);
            start ++;
        }
        return;
    }
}

 

 八、组合求和

39. 组合求和 (medium) 2021-07-21

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

 

 组合问题,我的常见思路,时间超过 9%的用户, 哈哈。

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        search(candidates, target, ret, list, 0);
        return ret;
    }
    
    public void search(int[] candidates, int target, 
                       List<List<Integer>> ret, List<Integer> list,
                      int start){
        if(getSum(list) > target){
            return;
        }
        if(getSum(list) == target){
            ret.add(new ArrayList(list));
        }
        for(int i = start; i < candidates.length; i++){
            list.add(candidates[i]);
            search(candidates, target, ret, list, i);
            list.remove(list.size() - 1);
        }
    }
    public int getSum(List<Integer> list){
        int sum = 0;
        for(int l : list){
            sum += l;
        }
        return sum;
    }
}

  使用getSum (list) 太消耗时间了, 每一次遍历将target 减去 数组中的值, 优化程序。 超过 77% 的用户,yes

 

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        search(candidates, target, ret, list, 0);
        return ret;
    }
    public void search(int[] candidates, int target, 
                       List<List<Integer>> ret, List<Integer> list,
                      int start){
        if(target < 0){
            return;
        }
        if(target == 0){
            ret.add(new ArrayList(list));
        }
        for(int i = start; i < candidates.length; i++){
            list.add(candidates[i]);
            search(candidates, target - candidates[i], ret, list, i);
            list.remove(list.size() - 1);
        }
    }
}

 

 

九、含有相同元素的组合求和

40. 组合求和 Ⅱ (medium) 2021-07-21

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

  这个题是上个题的变形,数组中有重复,但是组合中每个数只能用一次。

  最重要的一段代码是这个。

for(int i = start; i < candidates.length; i++){
            if (i != 0 && candidates[i] == candidates[i - 1] && !isVisited[i - 1]) {
            continue;
        }
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        Arrays.sort(candidates);
        boolean[] isVisited = new boolean[candidates.length];
        search(ret, list, candidates, target, 0, isVisited);
        return ret;
    }
    
    public void search(List<List<Integer>> ret, List<Integer> list,
                      int[] candidates, int target, int start, boolean[] isVisited){
        if(target < 0) return;
        if(target == 0) {
            ret.add(new ArrayList(list));
            return;
        }
        for(int i = start; i < candidates.length; i++){
            if (i != 0 && candidates[i] == candidates[i - 1] && !isVisited[i - 1]) {
            continue;
        }
            //if(isVisited[i]) return;
            isVisited[i] = true;
            list.add(candidates[i]);
            search(ret, list, candidates, target - candidates[i],
                   i+1, isVisited);
            isVisited[i] = false;
            list.remove(list.size() - 1);
        }
    }
}

 

十、1-9 数字的组合求和

216. 组合求和Ⅲ (medium) 2021-07-21

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

所有数字都是正整数。
解集不能包含重复的组合。

  超过 100% 用户,开心

class Solution {
    public static int[] nums = {1,2,3,4,5,6,7,8,9};
    public List<List<Integer>> combinationSum3(int k, int n) {
        
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        search(ret, list, k, n, 0);
        return ret;   

    }
    public void search(List<List<Integer>> ret, List<Integer> list,
                      int k, int n, int start){
        if(n < 0 || k < 0) return;
        if(n == 0 && k == 0) ret.add(new ArrayList(list));
        for(int i = start; i < nums.length; i++){
            list.add(nums[i]);
            k--;
            search(ret, list, k, n - nums[i], i + 1);
            list.remove(list.size() - 1);
            k++;
        }
    }
}

 

十一、子集

78. 子集 (medium) 2021-07-21

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

  又一个超 100%用户的答案

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        ret.add(new ArrayList());
        for(int i = 1; i <= nums.length; i++){
            search(nums, ret, new ArrayList(), i,0);
        }
        return ret;

    }
    public void search(int[] nums, List<List<Integer>> ret, 
                      List<Integer> list, int k, int start){
        if(list.size() > k) return;
        if(list.size() == k) ret.add(new ArrayList(list));
        for(int i = start; i < nums.length; i++){
            list.add(nums[i]);
            search(nums, ret, list, k, i + 1);
            list.remove(list.size() - 1);
        }
    }
}

 

十二、含有相同元素求子集

90. 子集Ⅱ  (medium) 2021-07-21

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

 

   超过 99.9% 的用户

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        boolean[] hasVisited = new boolean[nums.length];
        Arrays.sort(nums);
        for(int i =  0; i <= nums.length; i++){
            search(nums, ret, list, i, 0, hasVisited);
        }
        return ret;
    }
    public void search(int[] nums,List<List<Integer>> ret, 
                       List<Integer> list, int k, int start, 
                       boolean[] hasVisited){
        if(list.size() == k){
            ret.add(new ArrayList(list));
            return;
        }
        for(int i = start; i < nums.length; i++){
            if(i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]){
                continue;
            }
            list.add(nums[i]);
            hasVisited[i] = true;
            search(nums, ret, list, k, i + 1, hasVisited);
            list.remove(list.size() - 1);
            hasVisited[i] = false;
        }
    }
}

 

 十三、分割字符串使得每个字符串都是回文数

131. 分割回文数  (medium) 2021-07-23

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

回文串 是正着读和反着读都一样的字符串。

 

class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> ret = new ArrayList<>();
        List<String> list = new ArrayList<>();
        search(s, ret, list, 0);
        return ret;
    }
    public void search(String s, List<List<String>> ret, List<String> list,int start){
        int len = s.length();
        if(start > len) return;
        if(start == len){
            ret.add(new ArrayList(list));
        }
        for(int i = start; i <= len; i++){
            if(!isPalindrome(s.substring(start, i))) continue;  //左闭右开
            list.add(s.substring(start, i));
            search(s, ret, list, i);
            list.remove(list.size() - 1);
        }
        return;
    }
    public boolean isPalindrome(String s){
        char front;
        char back;
        int len = s.length();
        if(len == 0) return false;
        if(len == 1) return true;
        for(int i = 0; i < len / 2; i++){
            front = s.charAt(i);
            back = s.charAt(len - 1 - i);
            if(front != back) return false;
        }
        return true;
    }
}

 

十四、数独

37. 解数独 (hard) 2021-07-23

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)
数独部分空格内已填入了数字,空白格用 '.' 表示。

             

 

 

   这个题是个 hard 题, 但是思路并不但理解。难顶的是我写了两天的bug 没改出来,贴个答案,思路相同。

  我的回溯函数定义为 void ,他的是Boolean 类型,我的就始终报错。

     后面需要多练习几次

class Solution {
    public void solveSudoku(char[][] board) {
        if(board==null||board.length<=0||board[0].length<=0) return;
        char[] nums = {'1','2','3','4','5','6','7','8','9'};
        dfs(board, 0, 0, nums);
        return;
    }
    boolean dfs(char[][] board,int x,int y,char[] nums){
        if(y>=board[0].length) return dfs(board,x+1,0,nums);
        if(x>=board.length) return true;
        if(board[x][y]!='.') return dfs(board,x,y+1,nums);
        for(char num:nums){
            if(check(board,x,y,num)){
                board[x][y]=num;//填入
                if(dfs(board,x,y+1,nums)){
                    return true;
                }else{//回溯
                    board[x][y]='.';
                }
            }
        }
        return false;
    }
    boolean check(char[][] board,int x,int y,char c){
        for(int i=0;i<board.length;i++){//横竖
            if(board[x][i]==c||board[i][y]==c) return false;
        }
        for(int i=x/3*3;i<x/3*3+3;i++){//找到九宫格左上元素,依次遍历
            for(int j=y/3*3;j<y/3*3+3;j++){
                if(board[i][j]==c) return false;
            }
        }
        return true;
    }
}

 

   下面是我独立写出来的,用的是我的惯性解题思路,需要注意的一点是。这个题和之前排列组合的题区别在于他只有一个结果。我错误判断找到正确答案后会返回,事实上使用回溯算法是会找出所有解,也就是会遍历所有方法。所以,这个题需要像排列组合题一样,设置一个空数组,当找到正确答案后放置进去。

十五、 N 皇后

51. N 皇后 (hard) 2021-07-24

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。 

   虽然时间击败 5% ,但是没参考任何答案按照自己的解题套路独自完成的。

 

class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> ret= new ArrayList<>();
        char[][] chessBoard = new char[n][n];
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                chessBoard[i][j] = '.';
            }
        }
        boolean[][] road = new boolean[n][n]; //false代表可通行
        search(ret, chessBoard, road, n, 0, 0, 0);
        return ret;
    }
    public void search(List<List<String>> ret,
           char[][] chessBoard,boolean[][] road, int n, int count,
                      int start_x, int start_y){
        if(count == n){
            List<String> list = new ArrayList<>();
            for(int i = 0; i < n; i++){
                String s = String.valueOf(chessBoard[i]);
                list.add(s);
            }
            ret.add(list);
            return;
        }
        if(start_x >= n) return ;
        while(road[start_x][start_y] == true){
            if(start_y < n - 1){
                start_y++;
            }else{
                start_x++;
                start_y = 0;
            }
            if(start_x >= n) return;
        }
        for(int i = start_x; i < n; i++){
            for(int j = start_y; j < n; j++){
                if(road[i][j] == true) continue;
                if(road[i][j] == false){
                    chessBoard[i][j] = 'Q';
                }
                count++;
                refresh(n, chessBoard, road);
                search(ret, chessBoard, road, n, count, i + 1, 0);
                count--;
                chessBoard[i][j] = '.';
                refresh(n, chessBoard, road);
            }
        }
        return;
        
    }
    
    public void refresh(int n, char[][] chessBoard, boolean[][] road){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                road[i][j] = false;
            }
        } //重置,不能直接new一个新的
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                if(chessBoard[i][j] == 'Q'){
                    for(int k = 0; k < n; k++){
                        road[k][j] = true;
                        road[i][k] = true; //行和列
                    }
                    int x1 = i, x2 = i, x3 = i, x4 = i;
                    int y1 = j, y2 = j, y3 = j, y4 = j;
                    while(0 <= x1 && x1 < n && 0 <= y1 && y1 < n){
                        road[x1--][y1--] = true;
                    }
                    while(0 <= x2 && x2 < n && 0 <= y2 && y2 < n){
                        road[x2--][y2++] = true;
                    }
                    while(0 <= x3 && x3 < n && 0 <= y3 && y3 < n){
                        road[x3++][y3--] = true;
                    }
                    while(0 <= x4 && x4 < n && 0 <= y4 && y4 < n){
                        road[x4++][y4++] = true;
                    } 
                }
            }
        }
    }
}

 

posted @ 2021-07-13 16:51  -野比大雄-  阅读(136)  评论(0)    收藏  举报