把字符串转换成整数
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内容也会被删除
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