DFS leetcode
把字符串转换成整数
class Solution { public: int StrToInt(string str) { int n = str.size(), s = 1; long long res = 0; if(!n) return 0; if(str[0] == '-') s = -1; for(int i = (str[0] == '-' || str[0] == '+') ? 1 : 0; i < n; ++i){ if(!('0' <= str[i] && str[i] <= '9')) return 0; res = (res << 1) + (res << 3) + (str[i] & 0xf);//res=res*10+str[i]-'0'; } return res * s; } };
res = (res << 1) + (res << 3) + (str[i] & 0xf);
和res=res*10+str[i]-'0'是一样的。左移是乘以2的次方。(res << 1) + (res << 3) = res * 2 + res * 8 = res * 10 。
str[i] & 0xf:针对字符0-9的,0-9的ascii码值为0x30,0x31,0x32 0x33 ...0x39,因此与0x0f按位与后只保留个位上的书即0x0,0x1,。。。0x9
113. Path Sum II
只要在root为null返回就行了,不该return那么多地方。
public List<List<Integer>> pathSum(TreeNode root, int sum) { List<List<Integer>> res = new ArrayList<>(); List<Integer> list = new ArrayList<>(); helper(res, list, root, sum); return res; } private void helper(List<List<Integer>> res, List<Integer> list, TreeNode root, int sum) { if (root == null) return; list.add(root.val); if (root.left == null && root.right == null && root.val == sum) { res.add(new ArrayList<>(list)); } helper(res, list, root.left, sum - root.val); helper(res, list, root.right, sum - root.val); list.remove(list.size() - 1); }
res.add(new ArrayList<>(list));
必须new,不然随着list的remove操作res的list内容也会被删除
最后逐级remove掉list最后一个节点,其他路径不会加上当前路径的节点
java List复制:浅拷贝与深拷贝
list.remove(list.size() - 1);
删除列表最后一个元素
114. Flatten Binary Tree to Linked List
把树压缩成链表,用root.right连接后面的结点
private TreeNode prev = null; public void flatten(TreeNode root) { if (root == null) return; flatten(root.right); flatten(root.left); root.right = prev; root.left = null; prev = root; }
原始树:链表形式:
算法访问顺序是右,左,中,prev存前一个访问的结点,按访问顺序后一个的right=前一个。如图,顺序是6->5->4->3->2->1,所以5的right是6,以此类推 从后往前连接。
129. Sum Root to Leaf Numbers
根节点到每个叶节点都可以从根到叶排成一个数,求这些数的和
public int sumNumbers(TreeNode root) { return sum(root, 0); } public int sum(TreeNode n, int s){ if (n == null) return 0; if (n.right == null && n.left == null) return s*10 + n.val; return sum(n.left, s*10 + n.val) + sum(n.right, s*10 + n.val); }
我想的是递归时修改s的值再还原,其实完全没必要修改,传递时用s*10 + n.val
199. Binary Tree Right Side View
列出从树的右侧能看到的节点
树从右至左的遍历,中->右->左
public class Solution { public List<Integer> rightSideView(TreeNode root) { List<Integer> result = new ArrayList<Integer>(); rightView(root, result, 0); return result; } public void rightView(TreeNode curr, List<Integer> result, int currDepth){ if(curr == null){ return; } if(currDepth == result.size()){ result.add(curr.val); } rightView(curr.right, result, currDepth + 1); rightView(curr.left, result, currDepth + 1); } }
332. Reconstruct Itinerary
public List<String> findItinerary(String[][] tickets) { Map<String, PriorityQueue<String>> targets = new HashMap<>(); for (String[] ticket : tickets) targets.computeIfAbsent(ticket[0], k -> new PriorityQueue()).add(ticket[1]); List<String> route = new LinkedList(); Stack<String> stack = new Stack<>(); stack.push("JFK"); while (!stack.empty()) { while (targets.containsKey(stack.peek()) && !targets.get(stack.peek()).isEmpty()) stack.push(targets.get(stack.peek()).poll()); //peek() 方法用于查找在此堆栈顶部的对象,无需从堆栈中取出。 //poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。 route.add(0, stack.pop());//插入到链表头 } return route; }
computeIfAbsent: 如果map里没有这个key,那么就按照后面的这个function添加对应的key和value
如果要这个key,那么就不添加
看了别人的思路,基本是bottom up的DFS,就是路径是反向从最低层往上得出的.
将所有的机票用hash表保存起来,最后我们就是要找出一条路径将机票用完,并且如果有多条路径,就找字典序最小的.
我们可以构造一个hash表,key为一个地点字符串,value为一个PriorityQueue保存这个地点可以飞到的其他地方,之所以用PriorityQueue是因为从一个地方可以去别的地方几次.并且这个数据结构是可以将数据排序的,方便我们有序的取数据.
然后我们利用DFS思想从"JFK"机场出发,按照字典序从小到达取与其连接的地点,从下一个地点再递归的搜索,直到没有和某地点相连的机票的了.我们就到达了最底层,然后往上返回路径即可.
6ms:
class Solution { HashMap<String,PriorityQueue<String>> ticketsMap=new HashMap<>(); LinkedList<String> result=new LinkedList<>(); public List<String> findItinerary(String[][] tickets) { if(tickets==null) return result; //构造hashmap for(String[] ticket:tickets){ if(!ticketsMap.containsKey(ticket[0])) ticketsMap.put(ticket[0],new PriorityQueue<>()); ticketsMap.get(ticket[0]).offer(ticket[1]); } DFS("JFK"); return result; } public void DFS(String ticket){ PriorityQueue<String> temp=ticketsMap.get(ticket); while (temp!=null && !temp.isEmpty()){ DFS(temp.poll()); } result.addFirst(ticket); } }
101. Symmetric Tree
判断一颗树是不是沿中线左右对称
对称 不对称
非递归:也可以用两个队列分别存两边的。
public boolean isSymmetric(TreeNode root) { Queue<TreeNode> q = new LinkedList<TreeNode>(); if(root == null) return true; q.add(root.left); q.add(root.right); while(q.size() > 1){ TreeNode left = q.poll(), right = q.poll(); //如果都是空就继续 if(left== null&& right == null) continue; //不全为空就返回false if(left == null ^ right == null) return false; if(left.val != right.val) return false; q.add(left.left);//可能有空 q.add(right.right);//从树的外围向里面成对push q.add(left.right); q.add(right.left); } return true; }
递归:短路法
public boolean isSymmetric(TreeNode root) { if(root==null) return true; return isMirror(root.left,root.right); } public boolean isMirror(TreeNode p, TreeNode q) { if(p==null && q==null) return true; if(p==null || q==null) return false; return (p.val==q.val) && isMirror(p.left,q.right) && isMirror(p.right,q.left); }
1、在方法体内对参数进行运算,不会影响原有变量的值(基本类型不会改变值,引用类型不会改变引用地址)。
public class ParamTest { public static void integerParam(int a,int b){ a += 1; b += 1; } public static void quoteParam(Random x){ x = new Random(); } public static void main(String[] args) { int a = 1; int b = 2; integerParam(1,2); System.out.println("a:"+a); System.out.println("b:"+b); System.out.println("=======我是分割线======"); Random r = new Random(); System.out.println(r); quoteParam(r); System.out.println(r); } }
输出结果:a:1
b:2
=======我是分割线======
java.util.Random@5910e440
java.util.Random@5910e440
说明整数类型在方法体内没有改变值,引用类型的地址也没发生变化。
2、在方法体内对参数的属性进行操作,将改变原有变量的属性值(如集合、数组中的元素)
public class ParamTest { public static void integerParam(int a,int b){ a += 1; b += 1; } public static void quoteParam(Random x){ x = new Random(); } public static void arrayParam(String[] strArray){ strArray[0] = "a"; strArray[1] = "b"; } public static void main(String[] args) { int a = 1; int b = 2; integerParam(1,2); System.out.println("a:"+a); System.out.println("b:"+b); System.out.println("=======我是分割线======"); Random r = new Random(); System.out.println(r); quoteParam(r); System.out.println(r); System.out.println("========我是分割线========="); String[] strArray = new String[2]; strArray[0] = "x"; System.out.println(strArray); for (int i = 0; i < strArray.length; i++) { System.out.println(strArray[i]); } arrayParam(strArray); System.out.println(strArray); for (int i = 0; i < strArray.length; i++) { System.out.println(strArray[i]); } }
输出结果:
a:1
b:2
=======我是分割线======
java.util.Random@5910e440
java.util.Random@5910e440
========我是分割线=========
[Ljava.lang.String;@6267c3bb
x
null
[Ljava.lang.String;@6267c3bb
a
b
我们可以看到在最下边数组参数的测试结果中,参数的引用地址没有发生变化,而参数内部的元素发生了变化。
417. Pacific Atlantic Water Flow
这道题给了我们一个二维数组,说是数组的左边和上边是太平洋,右边和下边是大西洋,假设水能从高处向低处流,问我们所有能流向两大洋的点的集合。刚开始我们没有理解题意,以为加括号的点是一条路径,连通两大洋的,但是看来看去感觉也不太对,后来终于明白了,是每一个点单独都路径来通向两大洋。那么就是典型的搜索问题,那么我最开始想的是对于每个点来搜索是否能到达边缘,只不过搜索的目标点不在是一个单点,而是所有的边缘点,找这种思路写出的代码无法通过OJ大数据集,那么我们就要想办法来优化我们的代码,优化的方法跟之前那道Surrounded Regions很类似,都是换一个方向考虑问题,既然从每个点像中间扩散会TLE,那么我们从边缘当作起点开始遍历搜索,然后标记能到达的点位true,分别标记出pacific和atlantic能到达的点,那么最终能返回的点就是二者均为true的点。我们可以先用DFS来遍历二维数组,参见代码如下:
public class Solution { public List<int[]> pacificAtlantic(int[][] matrix) { List<int[]> res = new LinkedList<>(); if(matrix == null || matrix.length == 0 || matrix[0].length == 0){ return res; } int n = matrix.length, m = matrix[0].length; boolean[][]pacific = new boolean[n][m]; boolean[][]atlantic = new boolean[n][m]; for(int i=0; i<n; i++){ dfs(matrix, pacific, Integer.MIN_VALUE, i, 0); dfs(matrix, atlantic, Integer.MIN_VALUE, i, m-1); } for(int i=0; i<m; i++){ dfs(matrix, pacific, Integer.MIN_VALUE, 0, i); dfs(matrix, atlantic, Integer.MIN_VALUE, n-1, i); } for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) if (pacific[i][j] && atlantic[i][j]) res.add(new int[] {i, j}); return res; } int[][]dir = new int[][]{{0,1},{0,-1},{1,0},{-1,0}}; public void dfs(int[][]matrix, boolean[][]visited, int height, int x, int y){ int n = matrix.length, m = matrix[0].length; if(x<0 || x>=n || y<0 || y>=m || visited[x][y] || matrix[x][y] < height) return; visited[x][y] = true; for(int[]d:dir){ dfs(matrix, visited, matrix[x][y], x+d[0], y+d[1]); } } }
472. Concatenated Words
题解: 从list中找出所有字符串,该字符串至少由list中的两个单词构成。
我们首先按字符串长度由小到大排列words. 然后构造一个set, 依次加入set中。对于具体的字符串word,如果word可以由至少set中的两个word构成,则该word加入结果集中。这种字符串的prefix问题,很明显要用dynamic programming来解。
public class Solution { public static List<String> findAllConcatenatedWordsInADict(String[] words) { List<String> result = new ArrayList<>(); Set<String> preWords = new HashSet<>(); Arrays.sort(words, new Comparator<String>() { public int compare (String s1, String s2) { return s1.length() - s2.length(); } }); for (int i = 0; i < words.length; i++) { if (canForm(words[i], preWords)) { result.add(words[i]); } preWords.add(words[i]); } return result; } private static boolean canForm(String word, Set<String> dict) { if (dict.isEmpty()) return false; boolean[] dp = new boolean[word.length() + 1]; dp[0] = true; for (int i = 1; i <= word.length(); i++) { for (int j = 0; j < i; j++) { if (!dp[j]) continue; if (dict.contains(word.substring(j, i))) { dp[i] = true; break; } } } return dp[word.length()]; } }
也可以用trie树保存list结合dfs
491. Increasing Subsequences
找到全部连续增长的序列,数字可能会重复,输入数组并不是一直增长的,可能会比前面小,所以不能排序
Input: [4, 6, 7, 7]
Output: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
public List<List<Integer>> findSubsequences(int[] nums) { List<List<Integer>> list = new ArrayList<>(); SubList(nums, list, new ArrayList<>(), 0); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } return list; } public void SubList(int[] nums, List<List<Integer>> list, List<Integer> temp, int pos) { if (pos >= nums.length) { return; } //保存访问过的数字,如果已经访问过一次就跳过,避免同一个位置访问多次同一个数字,这样结果就不会出现重复地序列 Set<Integer> used = new HashSet<>(); for (int i = pos; i < nums.length; i++) { if (used.contains(nums[i]) || (temp.size() > 0 && temp.get(temp.size() - 1) > nums[i])) { continue; } used.add(nums[i]); temp.add(nums[i]); if (temp.size() >= 2) { list.add(new ArrayList<>(temp)); } SubList(nums, list, temp, i + 1); temp.remove(temp.size() - 1); } }
542. 01 Matrix
LeetCode Weekly Contest 24 之 542.01 Matrix
给定一个只含0和1的矩阵,找到每个1到0的最短距离。
两个相邻单元格的距离是1
普通dfs会超时,最短距离用bfs
本题目中其实质就是求出每个单元格到其最近0之间的最短路径,可以使用广度优先(BFS)搜索进行求解。广度优先搜索一般使用队列实现。
首先将矩阵中matrix中元素值为零的单元格的坐标入队q中,其最短距离矩阵ans中相应值设置为0,将元素值为1的单元格的最短距离ans设置为-1;
从队首q中取出元素front,将其相邻且未被访问过的单元格的最短距离ans设为ans[front]+1,并入队。
public class Solution { public int[][] updateMatrix(int[][] matrix) { int m = matrix.length; int n = matrix[0].length; Queue<int[]> queue = new LinkedList<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (matrix[i][j] == 0) { queue.offer(new int[] {i, j});//把0元素加入队列中,以备波及影响周围元素 } else { matrix[i][j] = Integer.MAX_VALUE;//设为最大值,方便求0元素影响值 } } } //代表传播的四个方向 int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; while (!queue.isEmpty()) { int[] cell = queue.poll(); for (int[] d : dirs) { //需要比较的下一位置元素 int r = cell[0] + d[0]; int c = cell[1] + d[1]; //不符合条件的均跳过,找来的比如:0 的周围 还是0的哪些元素,1的周围是1,但不符合影响值更新规则的同样忽略。 if (r < 0 || r >= m || c < 0 || c >= n || matrix[r][c] <= matrix[cell[0]][cell[1]] + 1) continue; //把受0波及的1元素也放入队列中,涟漪是会持续的。 queue.add(new int[] {r, c}); matrix[r][c] = matrix[cell[0]][cell[1]] + 1; } } return matrix; } }
java.util.ArrayList.set(int index, E element) 替换与指定元素在此列表中指定位置的元素。
list.set(0,1)
java Queue中 remove/poll, add/offer, element/peek区别:是否抛出异常
offer,add区别:
一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。
这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
poll,remove区别:
remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似,
但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
peek,element区别:
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null
79. Word Search
这道题是典型的深度优先遍历 DFS 的应用,原二维数组就像是一个迷宫,可以上下左右四个方向行走,我们以二维数组中每一个数都作为起点和给定字符串做匹配,因为题目要求一个 cell 只能被访问一次,所以访问后的位置就异或256,这样就不会与ascii的任何字母重合,之后在还原。如果二维数组 board 的当前字符和目标字符串 word 对应的字符相等,则对其上下左右四个邻字符分别调用 DFS 的递归函数,只要有一个返回 true,那么就表示可以找到对应的字符串,否则就不能找到
public boolean exist(char[][] board, String word) { char[] w = word.toCharArray(); for (int y=0; y<board.length; y++) { for (int x=0; x<board[y].length; x++) { if (exist(board, y, x, w, 0)) return true; } } return false; } private boolean exist(char[][] board, int y, int x, char[] word, int i) { if (i == word.length) return true; if (y<0 || x<0 || y == board.length || x == board[y].length) return false; if (board[y][x] != word[i]) return false; board[y][x] ^= 256; boolean exist = exist(board, y, x+1, word, i+1) || exist(board, y, x-1, word, i+1) || exist(board, y+1, x, word, i+1) || exist(board, y-1, x, word, i+1); board[y][x] ^= 256; return exist; }
The binary value for 256 is 100000000. Now we have ascii chars upto 255 numbers so their binary value is between - [0000可以0000 - 11111111] (0 - 255).
Now if you do XOR(^) operation between these ascii chars and 256 it will convert all the range above 256 number. Java has 16 bits for char type. So this is possible. And now none of out string chars will match any of these masked chars because one is under 0-255 other is above 256 range.
可以用二维数组表示移动方向
public class Solution { public boolean exist(char[][] board, String word) { char[] wordArray = word.toCharArray(); int[][] dirs = new int[][]{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; for(int i = 0; i < board.length; i++) { for(int j = 0; j < board[0].length; j++) { if(dfs(board, dirs, i, j, wordArray, 0)) return true; } } return false; } public boolean dfs(char[][] board, int[][] dirs, int i, int j, char[] word, int start) { if(start == word.length) return true; if(i < 0 || j < 0 || i == board.length || j == board[0].length) return false; if(board[i][j] == '#' || board[i][j] != word[start]) return false; boolean res = false; char c = board[i][j]; board[i][j] = '#'; // use '#' to represent this cell is visited for(int[] dir: dirs) { int newRow = i + dir[0], newCol = j + dir[1]; res |= dfs(board, dirs, newRow, newCol, word, start + 1); if(res) return true; // if successfully find the word, return immediately } board[i][j] = c; // backtracking return false; } }