├── .DS_Store ├── 124.二叉树中的最大路径和.md ├── 125.验证回文串.md ├── 128.最长连续序列.md ├── 129.求根到叶子节点数字之和.md ├── 130.被围绕的区域.md ├── 131.分割回文串.md ├── 133.克隆图.md ├── 134.加油站.md ├── 135.分发糖果.md ├── 136. 只出现一次的数字.md ├── 137.只出现一次的数字 II.md ├── 200.岛屿数量.md ├── README.md ├── img ├── 125.png ├── 131.png └── QQ.png └── join_us.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acpipe/LeetcodeTraining/fede69b4d0fe0b047ad54b0756cbee856bae0e40/.DS_Store -------------------------------------------------------------------------------- /124.二叉树中的最大路径和.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 给定一个非空二叉树,返回其最大路径和。 3 | 4 | 本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。 5 | 6 | 示例 1: 7 | 8 | 输入: [1,2,3] 9 | ``` 10 | 1 11 | / \ 12 | 2 3 13 | ``` 14 | 输出: 6 15 | 示例 2: 16 | 17 | 输入: [-10,9,20,null,null,15,7] 18 | ``` 19 | -10 20 | / \ 21 | 9 20 22 | / \ 23 | 15 7 24 | ``` 25 | 输出: 42 26 | 27 | # 题解 28 | 这道题是一个post-order-traversal的变形题目。我们很容易想到left的最大路径和right的最大路径求完之后更新最终结果的状态。这时候我们就会去思考递归子问题的代码怎么去构造。会发现面临两个关键问题: 29 | 30 | 1. 递归需要记录下来左右子树**经过**根节点的最大值,以便计算后面的父节点,对应代码即: 31 | 32 | `return Math.max(leftSum, rightSum) + root.val;` 33 | 34 | 2. 递归还要记录下**不经过**根节点的最大值。 35 | 36 | 但是我们的递归只能返回一个参数啊。怎么办呢? 37 | 38 | 回头再看看我们的第1点,return 回去是为了父节点的下一次计算;而第2点,不经过根节点的最大值,我们只需要记录下来即可,因为它并不涉及后面的计算。 39 | 40 | ```java 41 | class Solution { 42 | private int maxSum; 43 | public int maxPathSum(TreeNode root) { 44 | maxSum = Integer.MIN_VALUE; 45 | helper(root); 46 | return maxSum; 47 | } 48 | 49 | private int helper(TreeNode root) { 50 | // [-1, -2, -3] 51 | if (root == null) return 0; 52 | int leftSum = Math.max(helper(root.left), 0); 53 | // 和0比较要么要这个分支,要么不要这个分支 54 | int rightSum = Math.max(helper(root.right), 0); 55 | // 当前节点路径下最大值,对应解析中的第2点 56 | maxSum = Math.max(leftSum + rightSum + root.val, maxSum); 57 | // 这个分支最大的值是多少 58 | return Math.max(leftSum, rightSum) + root.val; 59 | } 60 | } 61 | ``` 62 | 63 | # 热门阅读 64 | * [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 65 | * [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 66 | * [社招面试总结——算法题篇](https://mp.weixin.qq.com/s/g9RyWpDWfGCnvPvw4res4Q) 67 | * [Redis中的集合类型是怎么实现的?](https://mp.weixin.qq.com/s/JG-el06-1ehDntF_E7h1hQ) 68 | 69 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) -------------------------------------------------------------------------------- /125.验证回文串.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 4 | 5 | **说明:**本题中,我们将空字符串定义为有效的回文串。 6 | 7 | **示例 1:** 8 | 9 | ```java 10 | 输入: "A man, a plan, a canal: Panama" 11 | 输出: true 12 | ``` 13 | 14 | **示例 2:** 15 | 16 | ```java 17 | 输入: "race a car" 18 | 输出: false 19 | ``` 20 | 21 | # 题解 22 | 23 | 这道题目比较简单,用两个指针,分别指向字符串头和字符串尾,一个向前走,一个向后面走。 24 | 25 | ![遍历规则](img/125.png) 26 | 27 | java中`isLetterOrDigit`方法用来判定是不是字母和数字。 28 | 29 | ```java 30 | class Solution { 31 | public boolean isPalindrome(String s) { 32 | if (s == null) return false; 33 | if (s.length() == 0) return true; 34 | int i = 0; 35 | int j = s.length() - 1; 36 | while (i < j) { 37 | while (i < j && !Character.isLetterOrDigit(s.charAt(i))) i++; 38 | while (i < j && !Character.isLetterOrDigit(s.charAt(j))) j--; 39 | if (Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(j))) return false; 40 | i++; 41 | j--; 42 | } 43 | return true; 44 | } 45 | } 46 | ``` 47 | 48 | # 热门阅读 49 | 50 | * [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 51 | * [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 52 | * [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 53 | * [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 54 | * [社招面试总结——算法题篇](https://mp.weixin.qq.com/s/g9RyWpDWfGCnvPvw4res4Q) 55 | 56 | -------------------------------------------------------------------------------- /128.最长连续序列.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个未排序的整数数组,找出最长连续序列的长度。 4 | 5 | 要求算法的时间复杂度为 *O(n)*。 6 | 7 | **示例:** 8 | 9 | ```java 10 | 输入: [100, 4, 200, 1, 3, 2] 11 | 输出: 4 12 | 解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。 13 | ``` 14 | 15 | # 题解 16 | 17 | 这道题目最开始大家想的肯定是sort,然后计数计算最长序列。但是要求时间复杂度为:o(n),就不能用sort了。一般在leetcode中,**对时间复杂度有要求,就用空间来换,对空间复杂度有要求,就用时间来换。** 18 | 19 | 基于这种思路我们就想要求最长的,就是要记录下有没有相邻的元素,比如遍历到100这个元素,我们需要查看[99, 101]这两个元素在不在序列中,这样去更新最大长度。而记录元素有没有这个事我们太熟悉了,用set这种数据结构,而set这种数据结构是需要o(n)的空间来换取的,这就是我们刚才说的用空间来换时间。 20 | 21 | ```java 22 | class Solution { 23 | public int longestConsecutive(int[] nums) { 24 | Set numsSet = new HashSet<>(); 25 | for (Integer num : nums) { 26 | numsSet.add(num); 27 | } 28 | int longest = 0; 29 | for (Integer num : nums) { 30 | if (numsSet.remove(num)) { 31 | // 向当前元素的左边搜索,eg: 当前为100, 搜索:99,98,97,... 32 | int currentLongest = 1; 33 | int current = num; 34 | while (numsSet.remove(current - 1)) current--; 35 | currentLongest += (num - current); 36 | // 向当前元素的右边搜索,eg: 当前为100, 搜索:101,102,103,... 37 | current = num; 38 | while(numsSet.remove(current + 1)) current++; 39 | currentLongest += (current - num); 40 | // 搜索完后更新longest. 41 | longest = Math.max(longest, currentLongest); 42 | } 43 | } 44 | return longest; 45 | } 46 | } 47 | ``` 48 | 49 | # 热门阅读 50 | 51 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 52 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 53 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 54 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 55 | 56 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 57 | 58 | -------------------------------------------------------------------------------- /129.求根到叶子节点数字之和.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个二叉树,它的每个结点都存放一个 `0-9` 的数字,每条从根到叶子节点的路径都代表一个数字。 4 | 5 | 例如,从根到叶子节点路径 `1->2->3` 代表数字 `123`。 6 | 7 | 计算从根到叶子节点生成的所有数字之和。 8 | 9 | **说明:** 叶子节点是指没有子节点的节点。 10 | 11 | **示例 1:** 12 | 13 | ```java 14 | 输入: [1,2,3] 15 | 1 16 | / \ 17 | 2 3 18 | 输出: 25 19 | 解释: 20 | 从根到叶子节点路径 1->2 代表数字 12. 21 | 从根到叶子节点路径 1->3 代表数字 13. 22 | 因此,数字总和 = 12 + 13 = 25. 23 | ``` 24 | 25 | **示例 2:** 26 | 27 | ```java 28 | 输入: [4,9,0,5,1] 29 | 4 30 | / \ 31 | 9 0 32 | / \ 33 | 5 1 34 | 输出: 1026 35 | 解释: 36 | 从根到叶子节点路径 4->9->5 代表数字 495. 37 | 从根到叶子节点路径 4->9->1 代表数字 491. 38 | 从根到叶子节点路径 4->0 代表数字 40. 39 | 因此,数字总和 = 495 + 491 + 40 = 1026. 40 | ``` 41 | 42 | # 题解 43 | 44 | 二叉树的题目我们首先想到的就是递归求解。递归的方式很简单,用先序遍历的变形。 45 | 46 | 1. 先遍历根节点; 47 | 2. 遍历左子树,遍历左子树的时候,把走当前路径的数字带到左子树的求解中; 48 | 3. 遍历右子树,遍历右子树的时候,把走当前路径的数字带到右子树的求解中; 49 | 4. 更新总的和。 50 | 51 | ```java 52 | class Solution { 53 | private int sum = 0; 54 | private void helper(TreeNode node, int father) { 55 | if (node == null) return ; 56 | int current = father * 10 + node.val; 57 | if (node.left == null && node.right == null) { 58 | sum += current; 59 | return; 60 | } 61 | helper(node.left, current); 62 | helper(node.right, current); 63 | } 64 | 65 | public int sumNumbers(TreeNode root) { 66 | if (root == null) return sum; 67 | helper(root, 0); 68 | return sum; 69 | } 70 | } 71 | ``` 72 | 73 | 通常还可以用stack的思路来解递归的题目。先序非递归的代码我们知道是用stack来保存遍历过的元素。而因为本题要记录到叶节点的数字,所以需要一个额外的stack来记录数字。每次出stack之后,如果是叶子节点,那么加和;如果不是,那么就看左右子树,入stack。 74 | 75 | ```java 76 | class Solution { 77 | public int sumNumbers(TreeNode root) { 78 | int sum = 0; 79 | if (root == null) return sum; 80 | Stack nodeStack = new Stack<>(); 81 | Stack numStack = new Stack<>(); 82 | nodeStack.add(root); 83 | numStack.add(0); 84 | while (!nodeStack.isEmpty()) { 85 | TreeNode current = nodeStack.pop(); 86 | Integer currentNum = numStack.pop() * 10 + current.val; 87 | if (current.left == null && current.right == null) { 88 | sum += currentNum; 89 | } 90 | if (current.left != null) { 91 | nodeStack.add(current.left); 92 | numStack.add(currentNum); 93 | } 94 | if (current.right != null) { 95 | nodeStack.add(current.right); 96 | numStack.add(currentNum); 97 | } 98 | } 99 | return sum; 100 | } 101 | } 102 | ``` 103 | 104 | 其实,我们可以看到,最关键的是找到叶子节点,然后加和这个操作。叶子节点我们同样可以用层序遍历的方式来解这道题目。层序遍历用队列来解。 105 | 106 | ```java 107 | public class Solution { 108 | public int sumNumbers(TreeNode root) { 109 | Queue queue = new LinkedList(); 110 | Queue numQueue = new LinkedList(); 111 | if(root == null) return 0; 112 | int res = 0; 113 | queue.add(root); 114 | numQueue.add(0); 115 | while(!queue.isEmpty()) { 116 | int size = queue.size(); 117 | // 把该层的都入队,同时如果遇到叶节点,计算更新 118 | while(size-- > 0) { 119 | root = queue.poll(); 120 | int val = numQueue.poll() * 10 + root.val; 121 | if(root.left == null && root.right == null) 122 | res += val; 123 | if(root.left != null) { 124 | queue.add(root.left); 125 | numQueue.add(val); 126 | } 127 | if (root.right != null) { 128 | queue.add(root.right); 129 | numQueue.add(val); 130 | } 131 | } 132 | } 133 | return res; 134 | } 135 | } 136 | ``` 137 | 138 | 总结,二叉树的题目,大多数都是遍历的变形,面试时候看用bfs,还是dfs,一般来说很快就能得出答案。写非递归代码的时候,注意判断一下非空,不要把null节点入队或者入栈。 139 | 140 | # 热门阅读 141 | 142 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 143 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 144 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 145 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 146 | 147 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 148 | 149 | -------------------------------------------------------------------------------- /130.被围绕的区域.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个二维的矩阵,包含 `'X'` 和 `'O'`(**字母 O**)。 4 | 5 | 找到所有被 `'X'` 围绕的区域,并将这些区域里所有的 `'O'` 用 `'X'` 填充。 6 | 7 | **示例:** 8 | 9 | ``` 10 | X X X X 11 | X O O X 12 | X X O X 13 | X O X X 14 | ``` 15 | 16 | 运行你的函数后,矩阵变为: 17 | 18 | ``` 19 | X X X X 20 | X X X X 21 | X X X X 22 | X O X X 23 | ``` 24 | 25 | **解释:** 26 | 27 | 被围绕的区间不会存在于边界上,换句话说,任何边界上的 `'O'` 都不会被填充为 `'X'`。 任何不在边界上,或不与边界上的 `'O'` 相连的 `'O'` 最终都会被填充为 `'X'`。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。 28 | 29 | # 题解 30 | 31 | 这道题我们拿到基本就可以确定是图的dfs、bfs遍历的题目了。题目中解释说被包围的区间不会存在于边界上,所以我们会想到边界上的o要特殊处理,只要把边界上的o特殊处理了,那么剩下的o替换成x就可以了。问题转化为,如何**寻找和边界联通的o**,我们需要考虑如下情况。 32 | 33 | ``` 34 | X X X X 35 | X O O X 36 | X X O X 37 | X O O X 38 | ``` 39 | 40 | 这时候的o是不做替换的。因为和边界是连通的。为了记录这种状态,我们把这种情况下的o换成#作为占位符,待搜索结束之后,遇到o替换为x(**和边界不连通的o**);遇到#,替换回o(**和边界连通的o**)。 41 | 42 | **如何寻找和边界联通的o?** 从边界出发,对图进行dfs和bfs即可。这里简单总结下dfs和dfs。 43 | 44 | * bfs递归。可以想想二叉树中如何递归的进行层序遍历。 45 | * bfs非递归。一般用队列存储。 46 | * dfs递归。最常用,如二叉树的先序遍历。 47 | * dfs非递归。一般用stack。 48 | 49 | 那么基于上面这种想法,我们有四种方式实现。 50 | 51 | ## dfs递归 52 | 53 | ```java 54 | class Solution { 55 | public void solve(char[][] board) { 56 | if (board == null || board.length == 0) return; 57 | int m = board.length; 58 | int n = board[0].length; 59 | for (int i = 0; i < m; i++) { 60 | for (int j = 0; j < n; j++) { 61 | // 从边缘o开始搜索 62 | boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; 63 | if (isEdge && board[i][j] == 'O') { 64 | dfs(board, i, j); 65 | } 66 | } 67 | } 68 | 69 | for (int i = 0; i < m; i++) { 70 | for (int j = 0; j < n; j++) { 71 | if (board[i][j] == 'O') { 72 | board[i][j] = 'X'; 73 | } 74 | if (board[i][j] == '#') { 75 | board[i][j] = 'O'; 76 | } 77 | } 78 | } 79 | } 80 | 81 | public void dfs(char[][] board, int i, int j) { 82 | if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] == 'X' || board[i][j] == '#') { 83 | // board[i][j] == '#' 说明已经搜索过了. 84 | return; 85 | } 86 | board[i][j] = '#'; 87 | dfs(board, i - 1, j); // 上 88 | dfs(board, i + 1, j); // 下 89 | dfs(board, i, j - 1); // 左 90 | dfs(board, i, j + 1); // 右 91 | } 92 | } 93 | ``` 94 | 95 | ## dsf 非递归 96 | 97 | 非递归的方式,我们需要记录每一次遍历过的位置,我们用stack来记录,因为它先进后出的特点。而位置我们定义一个内部类Pos来标记横坐标和纵坐标。注意的是,在写非递归的时候,我们每次查看stack顶,但是并不出stack,直到这个位置上下左右都搜索不到的时候出Stack。 98 | 99 | ```java 100 | class Solution { 101 | public class Pos{ 102 | int i; 103 | int j; 104 | Pos(int i, int j) { 105 | this.i = i; 106 | this.j = j; 107 | } 108 | } 109 | public void solve(char[][] board) { 110 | if (board == null || board.length == 0) return; 111 | int m = board.length; 112 | int n = board[0].length; 113 | for (int i = 0; i < m; i++) { 114 | for (int j = 0; j < n; j++) { 115 | // 从边缘第一个是o的开始搜索 116 | boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; 117 | if (isEdge && board[i][j] == 'O') { 118 | dfs(board, i, j); 119 | } 120 | } 121 | } 122 | 123 | for (int i = 0; i < m; i++) { 124 | for (int j = 0; j < n; j++) { 125 | if (board[i][j] == 'O') { 126 | board[i][j] = 'X'; 127 | } 128 | if (board[i][j] == '#') { 129 | board[i][j] = 'O'; 130 | } 131 | } 132 | } 133 | } 134 | 135 | public void dfs(char[][] board, int i, int j) { 136 | Stack stack = new Stack<>(); 137 | stack.push(new Pos(i, j)); 138 | board[i][j] = '#'; 139 | while (!stack.isEmpty()) { 140 | // 取出当前stack 顶, 不弹出. 141 | Pos current = stack.peek(); 142 | // 上 143 | if (current.i - 1 >= 0 144 | && board[current.i - 1][current.j] == 'O') { 145 | stack.push(new Pos(current.i - 1, current.j)); 146 | board[current.i - 1][current.j] = '#'; 147 | continue; 148 | } 149 | // 下 150 | if (current.i + 1 <= board.length - 1 151 | && board[current.i + 1][current.j] == 'O') { 152 | stack.push(new Pos(current.i + 1, current.j)); 153 | board[current.i + 1][current.j] = '#'; 154 | continue; 155 | } 156 | // 左 157 | if (current.j - 1 >= 0 158 | && board[current.i][current.j - 1] == 'O') { 159 | stack.push(new Pos(current.i, current.j - 1)); 160 | board[current.i][current.j - 1] = '#'; 161 | continue; 162 | } 163 | // 右 164 | if (current.j + 1 <= board[0].length - 1 165 | && board[current.i][current.j + 1] == 'O') { 166 | stack.push(new Pos(current.i, current.j + 1)); 167 | board[current.i][current.j + 1] = '#'; 168 | continue; 169 | } 170 | // 如果上下左右都搜索不到,本次搜索结束,弹出stack 171 | stack.pop(); 172 | } 173 | } 174 | } 175 | 176 | ``` 177 | 178 | ## bfs 非递归 179 | 180 | dfs非递归的时候我们用stack来记录状态,而bfs非递归,我们则用队列来记录状态。和dfs不同的是,dfs中搜索上下左右,只要搜索到一个满足条件,我们就顺着该方向继续搜索,所以你可以看到dfs代码中,只要满足条件,就入Stack,然后continue本次搜索,进行下一次搜索,直到搜索到没有满足条件的时候出stack。而bfs中,我们要把上下左右满足条件的都入队,所以搜索的时候就不能continue。大家可以对比下两者的代码,体会bfs和dfs的差异。 181 | 182 | ```java 183 | class Solution { 184 | public class Pos{ 185 | int i; 186 | int j; 187 | Pos(int i, int j) { 188 | this.i = i; 189 | this.j = j; 190 | } 191 | } 192 | public void solve(char[][] board) { 193 | if (board == null || board.length == 0) return; 194 | int m = board.length; 195 | int n = board[0].length; 196 | for (int i = 0; i < m; i++) { 197 | for (int j = 0; j < n; j++) { 198 | // 从边缘第一个是o的开始搜索 199 | boolean isEdge = i == 0 || j == 0 || i == m - 1 || j == n - 1; 200 | if (isEdge && board[i][j] == 'O') { 201 | bfs(board, i, j); 202 | } 203 | } 204 | } 205 | 206 | for (int i = 0; i < m; i++) { 207 | for (int j = 0; j < n; j++) { 208 | if (board[i][j] == 'O') { 209 | board[i][j] = 'X'; 210 | } 211 | if (board[i][j] == '#') { 212 | board[i][j] = 'O'; 213 | } 214 | } 215 | } 216 | } 217 | 218 | public void bfs(char[][] board, int i, int j) { 219 | Queue queue = new LinkedList<>(); 220 | queue.add(new Pos(i, j)); 221 | board[i][j] = '#'; 222 | while (!queue.isEmpty()) { 223 | Pos current = queue.poll(); 224 | // 上 225 | if (current.i - 1 >= 0 226 | && board[current.i - 1][current.j] == 'O') { 227 | queue.add(new Pos(current.i - 1, current.j)); 228 | board[current.i - 1][current.j] = '#'; 229 | // 没有continue. 230 | } 231 | // 下 232 | if (current.i + 1 <= board.length - 1 233 | && board[current.i + 1][current.j] == 'O') { 234 | queue.add(new Pos(current.i + 1, current.j)); 235 | board[current.i + 1][current.j] = '#'; 236 | } 237 | // 左 238 | if (current.j - 1 >= 0 239 | && board[current.i][current.j - 1] == 'O') { 240 | queue.add(new Pos(current.i, current.j - 1)); 241 | board[current.i][current.j - 1] = '#'; 242 | } 243 | // 右 244 | if (current.j + 1 <= board[0].length - 1 245 | && board[current.i][current.j + 1] == 'O') { 246 | queue.add(new Pos(current.i, current.j + 1)); 247 | board[current.i][current.j + 1] = '#'; 248 | } 249 | } 250 | } 251 | } 252 | ``` 253 | 254 | # bfs 递归 255 | 256 | bfs 一般我们不会去涉及,而且比较绕,之前我们唯一A过的用bfs递归的方式是层序遍历二叉树的时候可以用递归的方式。 257 | 258 | ## 并查集 259 | 260 | 并查集这种数据结构好像大家不太常用,实际上很有用,我在实际的production code中用过并查集。并查集常用来解决连通性的问题,即将一个图中连通的部分划分出来。当我们判断图中两个点之间是否存在路径时,就可以根据判断他们是否在一个连通区域。 而这道题我们其实求解的就是和边界的O在一个连通区域的的问题。 261 | 262 | 并查集的思想就是,同一个连通区域内的所有点的根节点是同一个。将每个点映射成一个数字。先假设每个点的根节点就是他们自己,然后我们以此输入连通的点对,然后将其中一个点的根节点赋成另一个节点的根节点,这样这两个点所在连通区域又相互连通了。 263 | 并查集的主要操作有: 264 | 265 | * find(int m):这是并查集的基本操作,查找m的根节点。 266 | 267 | * isConnected(int m,int n):判断m,n两个点是否在一个连通区域。 268 | 269 | * union(int m,int n):合并m,n两个点所在的连通区域。 270 | 271 | ``` 272 | class UnionFind { 273 | int[] parents; 274 | 275 | public UnionFind(int totalNodes) { 276 | parents = new int[totalNodes]; 277 | for (int i = 0; i < totalNodes; i++) { 278 | parents[i] = i; 279 | } 280 | } 281 | // 合并连通区域是通过find来操作的, 即看这两个节点是不是在一个连通区域内. 282 | void union(int node1, int node2) { 283 | int root1 = find(node1); 284 | int root2 = find(node2); 285 | if (root1 != root2) { 286 | parents[root2] = root1; 287 | } 288 | } 289 | 290 | int find(int node) { 291 | while (parents[node] != node) { 292 | // 当前节点的父节点 指向父节点的父节点. 293 | // 保证一个连通区域最终的parents只有一个. 294 | parents[node] = parents[parents[node]]; 295 | node = parents[node]; 296 | } 297 | 298 | return node; 299 | } 300 | 301 | boolean isConnected(int node1, int node2) { 302 | return find(node1) == find(node2); 303 | } 304 | } 305 | ``` 306 | 307 | 我们的思路是把所有边界上的O看做一个连通区域。遇到O就执行并查集合并操作,这样所有的O就会被分成两类 308 | 309 | * 和边界上的O在一个连通区域内的。这些O我们保留。 310 | * 不和边界上的O在一个连通区域内的。这些O就是被包围的,替换。 311 | 312 | 由于并查集我们一般用一维数组来记录,方便查找parants,所以我们将二维坐标用node函数转化为一维坐标。 313 | 314 | ``` 315 | public void solve(char[][] board) { 316 | if (board == null || board.length == 0) 317 | return; 318 | 319 | int rows = board.length; 320 | int cols = board[0].length; 321 | 322 | // 用一个虚拟节点, 边界上的O 的父节点都是这个虚拟节点 323 | UnionFind uf = new UnionFind(rows * cols + 1); 324 | int dummyNode = rows * cols; 325 | 326 | for (int i = 0; i < rows; i++) { 327 | for (int j = 0; j < cols; j++) { 328 | if (board[i][j] == 'O') { 329 | // 遇到O进行并查集操作合并 330 | if (i == 0 || i == rows - 1 || j == 0 || j == cols - 1) { 331 | // 边界上的O,把它和dummyNode 合并成一个连通区域. 332 | uf.union(node(i, j), dummyNode); 333 | } else { 334 | // 和上下左右合并成一个连通区域. 335 | if (i > 0 && board[i - 1][j] == 'O') 336 | uf.union(node(i, j), node(i - 1, j)); 337 | if (i < rows - 1 && board[i + 1][j] == 'O') 338 | uf.union(node(i, j), node(i + 1, j)); 339 | if (j > 0 && board[i][j - 1] == 'O') 340 | uf.union(node(i, j), node(i, j - 1)); 341 | if (j < cols - 1 && board[i][j + 1] == 'O') 342 | uf.union(node(i, j), node(i, j + 1)); 343 | } 344 | } 345 | } 346 | } 347 | 348 | for (int i = 0; i < rows; i++) { 349 | for (int j = 0; j < cols; j++) { 350 | if (uf.isConnected(node(i, j), dummyNode)) { 351 | // 和dummyNode 在一个连通区域的,那么就是O; 352 | board[i][j] = 'O'; 353 | } else { 354 | board[i][j] = 'X'; 355 | } 356 | } 357 | } 358 | } 359 | 360 | int node(int i, int j) { 361 | return i * cols + j; 362 | } 363 | } 364 | ``` 365 | 366 | # 热门阅读 367 | 368 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 369 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 370 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 371 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 372 | 373 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 374 | 375 | -------------------------------------------------------------------------------- /131.分割回文串.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个字符串 *s*,将 *s *分割成一些子串,使每个子串都是回文串。 4 | 5 | 返回 *s* 所有可能的分割方案。 6 | 7 | **示例:** 8 | 9 | ``` 10 | 输入: "aab" 11 | 输出: 12 | [ 13 | ["aa","b"], 14 | ["a","a","b"] 15 | ] 16 | ``` 17 | 18 | # 题解 19 | 20 | 这算是backtrack的经典题目了。首先我们需要一个`isPalindrome` 函数判断是不是回文字符串,思路很简单,就是从字符串的两端检查到中间。接下来就是backtrack的过程。如下图,从左到右是循环的过程,对于你代码中的for循环,每次循环,字符加一个:a -> aa -> aab。而每次递归就是进入子问题进行深度搜索的过程。 21 | 22 | ![image-20190526211207653](img/131.png) 23 | 24 | ``` 25 | class Solution { 26 | public List> partition(String s) { 27 | List> res = new LinkedList<>(); 28 | if (s == null || s.length() == 0) { return res;} 29 | List current = new LinkedList<>(); 30 | helper(res, current, s, 0); 31 | return res; 32 | } 33 | 34 | public void helper(List> res, List current, String s, int startIndex) { 35 | if (s.length() == startIndex) { 36 | res.add(new LinkedList<>(current)); 37 | return; 38 | } 39 | 40 | for (int i = startIndex; i < s.length(); i++) { 41 | String currentPartition = s.substring(startIndex, i + 1); 42 | if (currentPartition.isEmpty() || !isPalindrome(currentPartition)) { 43 | continue; 44 | } 45 | current.add(currentPartition); 46 | helper(res, current, s, i+1); 47 | current.remove(current.size() - 1); 48 | } 49 | } 50 | 51 | public boolean isPalindrome(String s) { 52 | char[] res = s.toCharArray(); 53 | int left = 0; 54 | int right = s.length() - 1; 55 | while (left <= right) { 56 | if (res[left] !=res[right]) { 57 | return false; 58 | } 59 | left++; 60 | right--; 61 | } 62 | return true; 63 | } 64 | } 65 | ``` 66 | 67 | # 热门阅读 68 | 69 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 70 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 71 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 72 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 73 | 74 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 75 | 76 | -------------------------------------------------------------------------------- /133.克隆图.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定无向[**连通**](https://baike.baidu.com/item/连通图/6460995?fr=aladdin)图中一个节点的引用,返回该图的[**深拷贝**](https://baike.baidu.com/item/深拷贝/22785317?fr=aladdin)(克隆)。图中的每个节点都包含它的值 `val`(`Int`) 和其邻居的列表(`list[Node]`)。 4 | 5 | **示例:** 6 | 7 | ![img](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/02/23/113_sample.png) 8 | 9 | ``` 10 | 输入: 11 | {"$id":"1","neighbors":[{"$id":"2","neighbors":[{"$ref":"1"},{"$id":"3","neighbors":[{"$ref":"2"},{"$id":"4","neighbors":[{"$ref":"3"},{"$ref":"1"}],"val":4}],"val":3}],"val":2},{"$ref":"4"}],"val":1} 12 | 13 | 解释: 14 | 节点 1 的值是 1,它有两个邻居:节点 2 和 4 。 15 | 节点 2 的值是 2,它有两个邻居:节点 1 和 3 。 16 | 节点 3 的值是 3,它有两个邻居:节点 2 和 4 。 17 | 节点 4 的值是 4,它有两个邻居:节点 1 和 3 。 18 | ``` 19 | 20 | **提示:** 21 | 22 | 1. 节点数介于 1 到 100 之间。 23 | 2. 无向图是一个[简单图](https://baike.baidu.com/item/简单图/1680528?fr=aladdin),这意味着图中没有重复的边,也没有自环。 24 | 3. 由于图是无向的,如果节点 *p* 是节点 *q* 的邻居,那么节点 *q* 也必须是节点 *p* 的邻居。 25 | 4. 必须将**给定节点的拷贝**作为对克隆图的引用返回。 26 | 27 | # 题解 28 | 29 | 这道题目是图的遍历,节点遍历我们很熟悉,要么dfs,要么dfs。关键点是:**图的边怎么复制**。为了复制两个节点之间边的关系,我们用一个map去记录原节点和新节点之间的对应关系,这样遍历节点的的时候我们就可以通过map 查到复制节点的,把他们的边复制上。我们先采用bfs的方式来复制图。 30 | 31 | ```java 32 | class Solution { 33 | public Node cloneGraph(Node node) { 34 | if (node == null) return null; 35 | Map map = new HashMap<>(); 36 | Queue queue = new LinkedList<>(); 37 | queue.add(node); 38 | Node copyNode = new Node(); 39 | copyNode.val = node.val; 40 | map.put(node, copyNode); 41 | 42 | while (!queue.isEmpty()) { 43 | Node current = queue.poll(); 44 | if (current.neighbors == null) { 45 | continue; 46 | } 47 | 48 | if (map.get(current).neighbors == null) { 49 | map.get(current).neighbors = new LinkedList<>(); 50 | } 51 | 52 | List neighbors = current.neighbors; 53 | List copiedNeighbors = new LinkedList<>(); 54 | for (Node neighbor : neighbors) { 55 | if (!map.containsKey(neighbor)) { 56 | queue.add(neighbor); 57 | Node tmp = new Node(); 58 | tmp.val = neighbor.val; 59 | map.put(neighbor, tmp); 60 | } 61 | map.get(current).neighbors.add(map.get(neighbor)); 62 | } 63 | } 64 | return map.get(node); 65 | } 66 | } 67 | ``` 68 | 69 | 采用dfs的递归方式来复制图。 70 | 71 | ```java 72 | class Solution { 73 | public Node cloneGraph(Node node) { 74 | Map resNode2CopyNode = new HashMap<>(); 75 | return helper(node, resNode2CopyNode); 76 | } 77 | 78 | public Node helper(Node node, Map resNode2CopyNode) { 79 | if (node == null) return null; 80 | if (!resNode2CopyNode.containsKey(node)) { 81 | Node tmp = new Node(); 82 | tmp.val = node.val; 83 | resNode2CopyNode.put(node, tmp); 84 | } 85 | Node copy = resNode2CopyNode.get(node); 86 | if (node.neighbors == null) { 87 | return copy; 88 | } 89 | 90 | copy.neighbors = new LinkedList<>(); 91 | List neighbors = node.neighbors; 92 | for (Node neighbor : neighbors) { 93 | Node copyNeighbor = null; 94 | if (resNode2CopyNode.containsKey(neighbor)) { 95 | copyNeighbor = resNode2CopyNode.get(neighbor); 96 | } else { 97 | copyNeighbor = helper(neighbor, resNode2CopyNode); 98 | } 99 | 100 | copy.neighbors.add(copyNeighbor); 101 | } 102 | return copy; 103 | } 104 | } 105 | ``` 106 | 107 | 采用dfs迭代的方式来解。 108 | 109 | ```java 110 | class Solution { 111 | public Node cloneGraph(Node node) { 112 | if (node == null) return null; 113 | Map resNode2CopyNode = new HashMap<>(); 114 | Stack stack = new Stack<>(); 115 | stack.push(node); 116 | Node copy = new Node(); 117 | copy.val = node.val; 118 | resNode2CopyNode.put(node, copy); 119 | while (!stack.isEmpty()) { 120 | Node current = stack.pop(); 121 | List neighbors = current.neighbors; 122 | if (neighbors == null) { 123 | continue; 124 | } 125 | 126 | Node copyNode = resNode2CopyNode.get(current); 127 | if (copyNode.neighbors == null) { 128 | copyNode.neighbors = new LinkedList<>(); 129 | } 130 | 131 | for (Node neighbor : neighbors) { 132 | Node copyNeighbor = null; 133 | if (resNode2CopyNode.containsKey(neighbor)) { 134 | copyNeighbor = resNode2CopyNode.get(neighbor); 135 | } else { 136 | copyNeighbor = new Node(); 137 | copyNeighbor.val = neighbor.val; 138 | resNode2CopyNode.put(neighbor, copyNeighbor); 139 | stack.push(neighbor); 140 | } 141 | copyNode.neighbors.add(copyNeighbor); 142 | } 143 | } 144 | return copy; 145 | } 146 | } 147 | ``` 148 | 149 | # 热门阅读 150 | 151 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 152 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 153 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 154 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 155 | 156 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 157 | 158 | -------------------------------------------------------------------------------- /134.加油站.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 4 | 5 | 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。 6 | 7 | 如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。 8 | 9 | 说明: 10 | 11 | 如果题目有解,该答案即为唯一答案。 12 | 输入数组均为非空数组,且长度相同。 13 | 输入数组中的元素均为非负数。 14 | 示例 1: 15 | 16 | 输入: 17 | 18 | ``` 19 | gas = [1,2,3,4,5] 20 | cost = [3,4,5,1,2] 21 | 输出: 3 22 | ``` 23 | 24 | 解释: 25 | 26 | ``` 27 | 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油 28 | 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油 29 | 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油 30 | 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油 31 | 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油 32 | 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。 33 | 因此,3 可为起始索引。 34 | ``` 35 | 36 | 示例 2: 37 | 38 | ``` 39 | 输入: 40 | gas = [2,3,4] 41 | cost = [3,4,3] 42 | 43 | 输出: -1 44 | ``` 45 | 46 | 47 | 48 | 解释: 49 | 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 50 | 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 51 | 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 52 | 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 53 | 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 54 | 因此,无论怎样,你都不可能绕环路行驶一周。 55 | 56 | # 题解 57 | 58 | -------------------------------------------------------------------------------- /135.分发糖果.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 4 | 5 | 你需要按照以下要求,帮助老师给这些孩子分发糖果: 6 | 7 | 每个孩子至少分配到 1 个糖果。 8 | 相邻的孩子中,评分高的孩子必须获得更多的糖果。 9 | 那么这样下来,老师至少需要准备多少颗糖果呢? 10 | 11 | 示例 1: 12 | 13 | ``` 14 | 输入: [1,0,2] 15 | 输出: 5 16 | 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。 17 | ``` 18 | 19 | 示例 2: 20 | 21 | ``` 22 | 输入: [1,2,2] 23 | 输出: 4 24 | 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。 25 | 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。 26 | ``` 27 | 28 | # 题解 29 | 30 | 1. 我们首先给每一个小朋友都发糖果,保证每位喜至少分配到一个糖果; 31 | 2. 从左到右遍历,考虑右边的小朋友比左边小朋友排名高的情况,此时更新为 candy[i] = candy[i - 1] + 1,暂时不考虑左边比右边高的情况; 32 | 3. 从右到左遍历,考虑左边比右边高的情况,希望左边比右边高,但是又需要考虑不破坏第二点中的已经满足的规则,所以更新条件为candy[i] = max(candy[i], candy[i + 1] + 1); 33 | 4. 把所有的分配加起来; 34 | 35 | ```java 36 | class Solution { 37 | public int candy(int[] ratings) { 38 | if (ratings == null || ratings.length == 0) return 0; 39 | int[] resCandy = new int[ratings.length]; 40 | for (int i = 0; i < resCandy.length; i++) { 41 | resCandy[i] = 1; 42 | } 43 | 44 | for (int i = 1; i < ratings.length; i++) { 45 | // 比左边的小朋友排名更高. 46 | if (ratings[i] > ratings[i - 1]) { 47 | resCandy[i] = resCandy[i - 1] + 1; 48 | } 49 | } 50 | 51 | for (int i = ratings.length - 2; i >= 0; i--) { 52 | // 当前比右边的小朋友排名更高.resCandy[i] = resCandy[i + 1] + 1 53 | // 同时需要保证不破坏当前比左边小朋友高的规则 54 | if (ratings[i] > ratings[i + 1]) { 55 | resCandy[i] = Math.max(resCandy[i + 1] + 1, resCandy[i]); 56 | } 57 | } 58 | int res = 0; 59 | for (int i = 0; i < resCandy.length; i++) { 60 | res += resCandy[i]; 61 | } 62 | return res; 63 | } 64 | } 65 | ``` 66 | 67 | # 热门阅读 68 | 69 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 70 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 71 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 72 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 73 | 74 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 75 | 76 | -------------------------------------------------------------------------------- /136. 只出现一次的数字.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 4 | 5 | 说明: 6 | 7 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 8 | 9 | 示例 1: 10 | 11 | ``` 12 | 输入: [2,2,1] 13 | 输出: 1 14 | ``` 15 | 16 | 示例 2: 17 | 18 | ``` 19 | 输入: [4,1,2,1,2] 20 | 输出: 4 21 | ``` 22 | 23 | # 题解 24 | 25 | 这道题要求时间复杂度是o(n),空间复杂度是o(1)。难点在于空间复杂度,所以最开始我们很容易想到的hash的方式肯定是不行的。那就要找数组本身有没有什么性质了,本题考查的是异或运算。 26 | 27 | 先复习一下异或运算,**同为0,异为1**: 28 | 29 | 0 ^ 0=0,1 ^ 0=1,0 ^ 1=1,1 ^ 1=0。 30 | 31 | 同时异或具有如下性质: 32 | 33 | * A ^ B = B ^ A。异或满足交换律。 34 | * A ^ 0= A。任何数与0的异或不改变本身的值。 35 | 36 | 假设题目中描述的数组为 [A1,A1,A2,A2,…,An,An,Ax]。其中,Ax 只出现一次,其余的元素都出现两次。对该数组中的所有元素进行异或运算可得: 37 | 38 | [A1,A1,A2,A2,…,An,An,Ax] 的乱序序列异或 39 | 40 | = A1 ^ A1 ^ A2 ^ A2… ^ An ^ An ^ Ax 41 | 42 | = (A1 ^ A1) ^ (A2 ^ A2)… ^ (An ^ An) ^ Ax 43 | 44 | = 0 ^ 0… ^ Ax 45 | 46 | = Ax 47 | 48 | 因此,只需要遍历数组中的所有元素,依次进行两两异或操作就可以找出只出现一次的元素。 49 | 50 | ```java 51 | class Solution { 52 | public int singleNumber(int[] nums) { 53 | int ret = nums[0]; 54 | for (int i = 1; i < nums.length; ++i) { 55 | ret ^= nums[i]; 56 | } 57 | return ret; 58 | } 59 | } 60 | ``` 61 | 62 | ```python 63 | class Solution: 64 | def singleNumber(self, nums: List[int]) -> int: 65 | if nums is None or len(nums) == 0: 66 | return 0 67 | res = 0 68 | for x in nums: 69 | res ^= x 70 | return res 71 | ``` 72 | 73 | 74 | 75 | # 热门阅读 76 | 77 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 78 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 79 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 80 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 81 | 82 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) -------------------------------------------------------------------------------- /137.只出现一次的数字 II.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 3 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 4 | 5 | 说明: 6 | 7 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 8 | 9 | 示例 1: 10 | 11 | ``` 12 | 输入: [2,2,3,2] 13 | 输出: 3 14 | ``` 15 | 16 | 示例 2: 17 | 18 | ``` 19 | 输入: [0,1,0,1,0,1,99] 20 | 输出: 99 21 | ``` 22 | 23 | # 题解 24 | 25 | 根据上一道题目的经验,我们很明确的知道不能用数数字的办法去解。考虑位运算的办法,找相关的性质。 26 | 27 | 这个题其实就是求,在其他数都出现k次的数组中有一个数只出现一次,求出这个数。 28 | 29 | 而上面那个k次的是有通用解法的。 30 | 31 | 使用一个32维的数组,用这个32维的数组存储: 32 | 33 | [第31位1的个数, 第30位1的个数,…, 第1位1的个数] 34 | 35 | * 假如第0位1的个数是k的倍数,那么要求的这个数在该位一定是0; 36 | 37 | * 若不是k的倍数,那么要求的这个数在该位一定是1。 38 | 39 | 因为假如说数组中的某些数在该位置是1,那么因为这个数要么出现k次,那么出现1次。 40 | 41 | 这样我们就可以找出只出现一次那个数的二进制表示形式。二进制如何转化为10进制呢? 42 | 43 | 假如,按照上面的规则,最终找到的二进制为: 44 | 45 | A = [0, 0, 0, 0, 0, …, 1] 46 | 47 | 因为异或操作是:相同为0,不同为1。 48 | 49 | 那么久可以依次用 1, 2, 4, 8… 和依次每一位异或,即可得到最终答案。 50 | 51 | 第二部分可能不好理解,可以手动模拟下。 52 | 53 | ```java 54 | /* 55 | * @lc app=leetcode id=137 lang=java 56 | * 57 | * [137] Single Number II 58 | */ 59 | class Solution { 60 | public int singleNumber(int[] nums) { 61 | // [高位1的个数,...,低位1的个数] 62 | int[] bitNum = new int[32]; 63 | 64 | for (int i = 0; i < nums.length; i++) { 65 | int compare = 1; 66 | int num = nums[i]; 67 | for (int j = bitNum.length - 1; j >= 0; j--) { 68 | if ((compare&num) != 0) { 69 | bitNum[j]++; 70 | } 71 | compare = compare << 1; 72 | } 73 | } 74 | 75 | int compare = 1; 76 | int res = 0; 77 | for(int i = bitNum.length - 1; i >= 0; i--) { 78 | if(bitNum[i] % 3 != 0) { 79 | res = res ^ compare; 80 | } 81 | compare = compare << 1; 82 | } 83 | return res; 84 | } 85 | } 86 | ``` 87 | 88 | python 负数 没有过的版本 89 | 90 | ``` 91 | class Solution: 92 | def singleNumber(self, nums: List[int]) -> int: 93 | bit_nums = [0 for i in range(32)] 94 | 95 | for num in nums: 96 | compare = 1 97 | for i in range(len(bit_nums) - 1, -1, -1): 98 | if compare & num != 0 : 99 | bit_nums[i] = bit_nums[i] + 1 100 | compare <<= 1 101 | print(bit_nums) 102 | compare = 1 103 | res = 0 104 | N = 3 105 | for bit_num in reversed(bit_nums): 106 | if bit_num % N != 0: 107 | res ^= compare 108 | compare <<= 1 109 | return res 110 | ``` 111 | 112 | 113 | 114 | 115 | 116 | # 热门阅读 117 | 118 | - [看了很多技术书,为啥仍然写不出项目?](https://mp.weixin.qq.com/s/9r1nZihRrW2FVZVvKg8P3A) 119 | - [【Spring】IOC是啥有什么好处](https://mp.weixin.qq.com/s/VB5MSionhHEGFbdlRIjWDg) 120 | - [百度社招面试题——Redis实现分布式锁](https://mp.weixin.qq.com/s/6_uJ03bMyY8HeUDeb4HxYQ) 121 | - [【Leetcode】114. 二叉树展开为链表](https://mp.weixin.qq.com/s/4IxEj0B_CUW6B46HrZQmdA) 122 | 123 | ![Leetcode名企之路](https://user-gold-cdn.xitu.io/2019/4/8/169fd1c8a047aff0?w=679&h=318&f=jpeg&s=31588) 124 | 125 | -------------------------------------------------------------------------------- /200.岛屿数量.md: -------------------------------------------------------------------------------- 1 | # 题目 2 | 给定一个由'1'和'0'组成的二维数组 1代表陆地 2代表水,被水包围的陆地为岛屿,要求计算岛屿的数量 3 | **示例**: 4 | 5 | **输入:** 6 | 7 | ``` 8 | 11110 9 | 11010 10 | 11000 11 | 00000 12 | ``` 13 | 14 | **输出:** 1 15 | 16 | **输入** 17 | 18 | ``` 19 | 11000 20 | 11000 21 | 00100 22 | 00011 23 | ``` 24 | 25 | **输出:** 3 26 | 27 | 28 | # 题解: 29 | 我们首先想到的可能是DFS但是这道题可以用并查集来合并每一个岛屿最后统计一下根的数量就可以了,具体看代码。 30 | ``` 31 | class Solution { 32 | public: 33 | int Find(vector& fathers,int a) { 34 | if(a==fathers[a]) { 35 | return a; 36 | } 37 | return Find(fathers,fathers[a]); 38 | } 39 | void Union(vector& fathers,int a,int b) { 40 | int fa=Find(fathers,a); 41 | int fb=Find(fathers,b); 42 | if(fa!=fb) { 43 | fathers[fa] = fb; 44 | } 45 | } 46 | int numIslands(vector>& grid) { 47 | if(!(grid.size())||!(grid[0].size())) return 0; 48 | int m=grid.size(),n=grid[0].size(); 49 | int k=m*n; 50 | vector fathers(k,-1); 51 | int res=0; 52 | for(int i=0;i