刷题记录|目标101题--搜索
写在前面
开个贴记录一下 刷题的过程,目前参照 leetcode101,目标101题,由于太久没写java了,也会涉及到一些java的基础用法,希望能和大家共勉吧~
已刷链接:
- 贪心算法:https://www.nowcoder.com/discuss/1051877
- 双指针:https://www.nowcoder.com/discuss/1057237
- 二分查找:https://www.nowcoder.com/discuss/1059986
- 排序:链接
深度搜索dfs
No. 1 Max Area of Island
题目链接:链接
解题思路:
本题的重点在
- 创建一个direction数组,用两两相邻的位置表示方向上左右下
- dfs递归深搜
class Solution { int[] direction = {-1,0,1,0,-1}; public int maxAreaOfIsland(int[][] grid) { int m = grid.length, n = grid[0].length, result = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { result = Math.max(result,dfs(grid,i,j,m,n)); } } } return result; } private int dfs(int[][] grid,int x, int y, int m, int n) { if (x < 0 || x >= m || y < 0 || y >= n) return 0; if (grid[x][y] == 0) return 0; grid[x][y] = 0; int result = 1; for (int i = 0; i < 4; i++) { int x_direction = direction[i]; int y_direction = direction[i + 1]; result += dfs(grid,x + x_direction, y + y_direction,m,n); } return result; } }
No.2 Number of Provinces
题目链接:链接
解题思路:
//二刷本发现,本题的重点在于,要把一行理解为一个城市,这样的话就可以让n*n变成n的问题,最后降低时间复杂度
class Solution { public int findCircleNum(int[][] isConnected) { int n = isConnected.length; if (n == 0) return 1; int result = 0; int[] visited = new int[n]; for (int i = 0; i < n; i ++) { if (visited[i] == 0) { dfs(isConnected,i,visited); result++; } } return result; } private void dfs(int[][] isConnected, int i, int[] visited) { if (i > isConnected.length || i < 0) return; if (visited[i] == 1) return; visited[i] = 1; for (int j = 0; j < isConnected.length; j++) { if (isConnected[i][j] == 1 && i != j) { dfs(isConnected,j,visited); isConnected[i][j] = 0; } } } }
No.3 Pacific Atlantic Water Flow
题目:链接
解题思路:
本体的搜索思路有两点需要注意:
- 需要一个用来记录是否要返回不再搜索的数组,不然会死循环
- 如果单纯的搜索每个cell是否能reach则负责度太高了,最好是有办法能够在一次遍历中记录下每个cell能否reach。
综上我们这题采用反过来的思路,去从四个边开始,看水能否逆向回溯到cell。
所以我们设置两个canreach的数组,分别代表能reach太平洋和reach大西洋,从四边开始从四周向中心搜索,每个边代表可以reach一个洋,从四周开始最后整向里走,看每个cell能否reach某个洋,最后整体遍历看是否可以reach
class Solution { int[] direction = {-1,0,1,0,-1}; public List<List<Integer>> pacificAtlantic(int[][] heights) { List<List<Integer>> result = new ArrayList<List<Integer>>(); int 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 j = 0; j < n; j++) { dfs(heights,0,j,canReachP); dfs(heights,m - 1,j,canReachA); } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (canReachP[i][j] && canReachA[i][j]) { result.add(Arrays.asList(i,j)); } } } return result; } private void dfs(int[][] heights, int i, int j, boolean[][] canReach) { if (canReach[i][j]) return; canReach[i][j] = true; for (int k = 0; k < 4; k++) { int m = direction[k] + i; int n = direction[k + 1] + j; if (m >= 0 && m < heights.length && n >= 0 && n < heights[0].length && heights[m][n] >= heights[i][j]) { dfs(heights,m,n,canReach); } } } }
No.4 Surrounded Regions
题目:链接
解题思路:
和上面的海水题目一样,从外到内去填充。
我们可以理解为,只要边缘有一个O,这个island就无法被反转,反之可以被反转,所以我们可以先找到边缘的O,然后从外dfs,最后整体遍历一遍去反转O。
class Solution { int[] direction = {-1,0,1,0,-1}; public void solve(char[][] board) { int m = board.length; int n = board[0].length; boolean[][] keep = new boolean[m][n]; boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { if (board[i][0] == 'O') { keep[i][0] = true;dfs(board,i,0,keep,m,n,visited); } if (board[i][n - 1] == 'O') { keep[i][n - 1] = true;dfs(board,i,n - 1, keep,m,n,visited); } } for (int i = 0; i < n; i++) { if (board[0][i] == 'O') { keep[0][i] = true;dfs(board,0,i,keep,m,n,visited); } if (board[m - 1][i] == 'O') { keep[m - 1][i] = true; dfs(board,m - 1,i, keep,m,n,visited); } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j ++) { if (keep[i][j]) { continue; } board[i][j] = 'X'; } } } private void dfs(char[][] board, int i, int j, boolean[][] keep, int m, int n, boolean[][] visited) { if (!keep[i][j] || visited[i][j]) { return; } visited[i][j] = true; for (int k = 0; k < 4; k++) { int p = i + direction[k]; int q = j + direction[k + 1]; if (p >= 0 && p < m && q >= 0 && q < n) { if (board[p][q] == 'O') { keep[p][q] = true; dfs(board,p,q,keep,m,n,visited); } } } } }
回溯法
No.1 Permutations
题目:链接
解题思路:
回溯算法。此题需要注意的是:
- 所谓不重复数组的排列组合,只要当前位置与后面的每个位置交换,直到最后一位即可实现排列组合
- 为了防止每次都创建新的数组,要利用回溯法,直接修改,本次修改完再改回
这里需要注意的java知识:
int[] 转 List<Integer> 需要 List<Integer> tem = Arrays.stream(nums).boxed().collect(Collectors.toList());
- Arrays.stream(nums) 将基本类型数组转换为基本类型流。 int[ ] => IntStream
- .boxed() 将基本类型流转换为对象流。 => Stream< Integer >
- .collect(Collectors.toList()) 将对象流收集为集合。 => List< Integer >
class Solution { public List<List<Integer>> permute(int[] nums) { List<List<Integer>> result = new ArrayList<List<Integer>>(); dfs(nums,result,0); return result; } private void dfs(int[] nums, List<List<Integer>> result,int level) { if (level == nums.length - 1) { List<Integer> tem = Arrays.stream(nums).boxed().collect(Collectors.toList()); result.add(tem); return; } for (int i = level; i < nums.length; i ++) { swap(nums,i,level); dfs(nums,result,level+1); swap(nums,i,level); } } private void swap(int[] nums,int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } }
No.2 Combinations
题目:链接
解题思路:
class Solution { public List<List<Integer>> combine(int n, int k) { List<List<Integer>> result = new ArrayList<List<Integer>>(); dfs(n,k,1,result, new ArrayList<>()); return result; } private void dfs(int n, int k, int level, List<List<Integer>> result, List<Integer> temp) { if (temp.size() == k) { result.add(new ArrayList<>(temp)); return; } for (int i = level; i <= n; i++) { temp.add(i); dfs(n,k,i + 1,result,temp); temp.remove(temp.size() - 1); } } }
用到的java知识:
- java中如何初始化list:链接
- java如何深拷贝:java中的深拷贝方法要自己实现,刷题时简单起见可以用下面方法:
- 有一个list temp,可以用 new ArrayList<>(temp) 对其进行深拷贝
No.3 Word Search
题目:链接
解题思路:
class Solution { int[] direction = {-1,0,1,0,-1}; public boolean exist(char[][] board, String word) { int m = board.length, n = board[0].length; if (m * n < word.length()) return false; char[] wordArray = word.toCharArray(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (board[i][j] == wordArray[0]) { if (dfs(board,wordArray,0,i,j,new boolean[m][n])) { return true; } } } } return false; } private boolean dfs(char[][] board, char[] word, int k, int i, int j, boolean[][] reached) { if (k == word.length - 1) return true; reached[i][j] = true; for (int a = 0; a < 4; a++) { int q = direction[a] + i; int p = direction[a + 1] + j; if (q >= 0 && q < board.length && p >= 0 && p < board[0].length && board[q][p] == word[k + 1] && !reached[q][p]) { if (dfs(board,word,k + 1,q,p,reached)) return true; } } reached[i][j] = false; return false; } }
No.4 N-Queens
题目:链接
解题思路:
这题重点是
- 明白满足结果的每一行或者每一列仅有一个皇后,所以我们可以对行进行遍历,对列进行数组存储即可
- 45度和135度的数组要怎么创建?
这里设置45度和135的数组长度为2*n-1,45度数组从左上到右下,他的坐标为row+col,135度数组从左下到右上,他的数组坐标为n-row+col-1
涉及到的java知识点:
链接 string.valueof(char[])可以讲char数组的内容转为字符串
class Solution { public List<List<String>> solveNQueens(int n) { List<List<String>> result = new ArrayList<List<String>>(); char[][] board = new char[n][n]; boolean[] column = new boolean[n], dialog45 = new boolean[2 * n - 1], dialog135 = new boolean[2 * n - 1]; for (int i = 0; i < n; i ++) { Arrays.fill(board[i],'.'); } dfs(result,board,column,dialog45,dialog135,0,n); return result; } private void dfs(List<List<String>> result, char[][] board ,boolean[] column, boolean[] dialog45, boolean[] dialog135, int row, int n) { if (row == n) { List<String> temp = new ArrayList<>(); for (int i = 0; i < board.length; i++) { temp.add(String.valueOf(board[i])); } result.add(temp); return; } for (int i = 0; i < n; i ++) { if (column[i] || dialog45[row + i] || dialog135[n - 1 - row + i]) { continue; } column[i] = dialog45[row + i] = dialog135[n - 1 - row + i] = true; board[row][i] = 'Q'; dfs(result,board,column,dialog45,dialog135,row + 1,n); column[i] = dialog45[row + i] = dialog135[n - 1 - row + i] = false; board[row][i] = '.'; } } }
No.5 Binary Tree Paths
题目:链接
解题思路:
这题有个很重要的点就是java的string是可以使用+去链接string和int的,不知道这点的话要傻傻的转半天
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public List<String> binaryTreePaths(TreeNode root) { List<String> result = new ArrayList<String>(); dfs(root, result, ""); return result; } public void dfs(TreeNode cur,List<String> result, String s) { if (cur.left == null && cur.right == null) { result.add(s + cur.val); } if (cur.left != null) dfs(cur.left,result,s + cur.val + "->"); if (cur.right != null) dfs(cur.right,result,s + cur.val + "->"); } }
No.6 Sudoku Solver
题目:链接
解题思路:
这道题首先要进行回溯,也就是如果这个方法不行还要可以改回来继续走。
其次要知道这个数独要如何计算,不要害怕,其实可以理解为遍历每个cell,从1到9看能不能把数字放下,能放下就继续下面一个放,不能放下的话要回溯计算。
所以问题来到了如何可以在位置i,j放下一个数x
我们根据题目要求有三个条件:
- 同行不能有相同的数字
- 同列不能有相同的数字
- 同一个3*3的section不能有相同的数字
对应到代码中,1和2可以轻易的使用一个从1到9的遍历,判断board[i][col]和board[row][i]是否有数字x,但是第三个条件比较难以理解,特别出如下:
想要一个位置(i,j)的3*3方块遍历,我们先求得位置(i,j)所在的方块
- 对于行来说,第i行的元素位于i/3的方块行内
- 为了将方块行转为board的行数,我们先将(i/3)乘三,此时得到这个方块的起始行数
- 然后我们再取k为遍历1到9,将k/3即可得到每个方块内的行数,
- 3 * (i/3) + k / 3即为这个方块内的所有行
- 对于列来说,第j行的元素位置j/3的方块行内
- 为了将方块列转为board的列数,我们先将(j/3)乘三,此时得到这个方块的起始行数,
- 然后我们再取k为遍历1到9,配合上述的行数计算,我们将对k进行余3的操作,这样可以这个行数对应的列数,构成一个方块
- 3 * (j / ) + k %3即为这个方块内所有的列数
class Solution { public void solveSudoku(char[][] board) { dfs(board); } private boolean dfs(char[][] board) { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (board[i][j] == '.') { for (char k = '1'; k <= '9'; k++) { if (canPut(board,i,j,k)) { board[i][j] = k; if (dfs(board)) { return true; } board[i][j] = '.'; } } return false; } } } return true; } private boolean canPut(char[][] board, int row, int col, char c) { int section_r = 3 * (row / 3); int section_c = 3 * (col / 3); for (int i = 0; i < 9; i++) { if (board[i][col] == c) return false; if (board[row][i] == c) return false; if (board[section_r + i / 3][section_c + i % 3] == c) return false; } return true; } }
广度优先搜索BFS
No.1 Shortest Bridge
题目:链接
新的java知识:Queue:链接 可以当作一个LinkedList,但是增加为.offer(),删除第一个元素为poll(),顶峰元素为peek()
解题思路:
dfs 找到第一个island,将其赋值为2,然后用bfs找到链接到的最短距离。
bfs:
每次记录下来level,将所有的元素都pop出来依次处理,将每个pop出来元素的赋值为2,然后判断其上下左右的元素,是的0再入栈,是2的直接跳过,是1的则找到island可以返回了,因为我们是广搜,所以可以确保level最小
class Solution { int[] direction = {-1,0,1,0,-1}; public int shortestBridge(int[][] grid) { int n = grid.length; boolean flag = false; Queue<int[]> que = new LinkedList<>(); boolean[][] visited = new boolean[n][n]; for (int i = 0; i < n; i++) { if (flag) break; for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { dfs(que,grid,i,j,n,visited); flag = true; break; } } } int result = 0; while (que.size() != 0) { result++; int size = que.size(); while(size-- > 0) { int[] temp = que.poll(); grid[temp[0]][temp[1]] = 2; for (int i = 0; i < 4; i ++) { int q = temp[0] + direction[i]; int p = temp[1] + direction[i + 1]; if (q >=0 && q < n && p >= 0 && p < n) { if(grid[q][p] == 1) { return result; } else if (grid[q][p] == 2) { continue; } grid[q][p] = 2; que.offer(new int[]{q,p}); }
No.2 Word Ladder II
题目:链接
解题思路:
将字符串看为节点,先bfs寻得最短路径,再dfs一遍绘出
class Solution { public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) { List<List<String>> result = new ArrayList<List<String>>(); HashSet<String> dict = new HashSet<String>(); HashMap<String,List<String>> neighbors = new HashMap<String,List<String>>(); HashMap<String,Integer> distance = new HashMap<String,Integer>(); List<String> path = new ArrayList<String>(); for (String s : wordList) { dict.add(s); } if (!dict.contains(endWord)) return result; bfs(beginWord,endWord,dict,neighbors,distance); dfs(beginWord,endWord,dict,neighbors,result,path,distance); return result; } private void bfs(String beginWord, String endWord, HashSet<String> dict, HashMap<String,List<String>> neighbors, HashMap<String,Integer> distance) { for (String s : dict) { neighbors.put(s,new ArrayList<>()); } Queue<String> que = new LinkedList<String>(); que.offer(beginWord); distance.put(beginWord,0); neighbors.put(beginWord,new ArrayList<>()); while (que.size() != 0) { int count = que.size(); boolean found = false; for (int i = 0; i < count; i++) { String s = que.poll();
No.3 Minimum Height Trees
题目:链接
解题思路:
如果我们用bfs取遍历每个节点,求他是root的时候代表的depth,这样一个n*n的复杂度会超时
所以我们要变换思路使用拓扑排序的思路,先构建一个图,然后将从外围的子节点开始找起,将每个叶子节点从图中去除,这样会一步步逼近跟节点,最后跟节点可能存在1到2个,因为3个就会成环。
class Solution { public List<Integer> findMinHeightTrees(int n, int[][] edges) { if (n == 1) return new ArrayList<Integer>(Arrays.asList(0)); if (n == 2) return new ArrayList<Integer>(Arrays.asList(0,1)); List<Integer> result = new ArrayList<Integer>(); List<Set<Integer>> graph = new ArrayList<Set<Integer>>(); for (int i = 0; i < n; i++) { graph.add(new HashSet<Integer>()); } for (int i = 0;i < edges.length; i++) { graph.get(edges[i][0]).add(edges[i][1]); graph.get(edges[i][1]).add(edges[i][0]); } Queue<Integer> que = new LinkedList<Integer>(); for (int i = 0; i < n; i++) { if (graph.get(i).size() ==1) { que.offer(i); } } int remain = n; while (remain > 2) { int count = que.size(); while (count-- > 0) { Integer i = que.poll(); remain--; Integer j = graph.get(i).iterator().next(); graph.get(j).remove(i); graph.get(i).remove(j); if (graph.get(j).size() == 1) { que.offer(j); } } } int size = que.size(); for (int i = 0; i < size; i++) { result.add(que.poll()); } return result; } }#刷题#