LeetCode刷题笔记,记录一些非常值得记录的算法题…..
寻找输入流的中位数
LeetCode 295. Find Median from Data Stream 剑指Offer
使用两个堆:一个大顶堆,一个小顶堆。其中,大顶堆用于存放输入流中较小的一半数,小顶堆存放输入流中较大的一半数。约定,大顶堆中的数最多比小顶堆中的数多一个。那么,输入流中的中位数要么是大顶堆的头部元素(输入流中数字个数为奇),要么是两个堆顶元素和的平均值(输入流中数字个数为偶)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class MedianFinder { private PriorityQueue<Integer> prioritySmall; private PriorityQueue<Integer> priorityBig; public MedianFinder () { prioritySmall = new PriorityQueue<>((a, b) -> {return b - a;}); priorityBig = new PriorityQueue<>((a, b) -> {return a - b;}); } public void addNum (int num) { if (prioritySmall.isEmpty() || prioritySmall.peek() >= num){ prioritySmall.offer(num); } else { priorityBig.offer(num); } if (prioritySmall.size() > priorityBig.size() + 1 ){ priorityBig.offer(prioritySmall.poll()); } if (priorityBig.size() > prioritySmall.size()){ prioritySmall.offer(priorityBig.poll()); } } public double findMedian () { if (prioritySmall.size() > priorityBig.size()){ return prioritySmall.peek(); } return (prioritySmall.peek() + priorityBig.peek()) / 2.0 ; } }
乱序整数数组中缺失的第一个正整数
LeetCode 41. First Missing Positive
将数组中的正整数放到数组中的指定位置上:m放到数组中的第m个位置。之后,遍历数组,找出不符合该规律的位置。需要注意一点,由于数组中的数字可能存在重复,在swap时应该避免相同元素进行交换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Solution { public int firstMissingPositive (int [] nums) { if (nums == null ){ return -1 ; } int n = nums.length; int index = 0 ; while (index < n){ if (nums[index] > 0 && nums[index] != index+1 ){ if (nums[index] <= n && nums[nums[index]-1 ] != nums[index]){ swap(nums, index, nums[index]-1 ); continue ; } } index++; } int res = 1 ; for (int m : nums){ if (res != m){ return res; } res++; } return res; } public void swap (int [] nums, int i, int j) { int t = nums[i]; nums[i] = nums[j]; nums[j] = t; } }
统计数组中每个元素后面比自己小的元素数
LeetCode 315. Count of Smaller Numbers After Self
普通方法的时间复杂度一般是O(n^2),使用二叉查找树,可以将时间复杂度降低到O(nlogn)。
该二叉查找树的每个节点除了value值之外,还需要维护一个变量ct,代表该节点的左子树的节点个数。这样,如果按数组逆序来构建这棵二查找树:
插入时如果元素比该节点的value大,则说明该节点的左子树上的节点以及该节点都比插入元素小,元素插入右子树
如果比该节点的value小的话,元素要插入该节点的左子树,该节点的ct需要加1
插入元素与节点value相等时,可以将其插入到右子树
这样,一个元素后面有多少个比其小的元素,就是在插入该元素过程中所有小于该元素的的节点ct域的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 class Solution { public List<Integer> countSmaller (int [] nums) { if (nums == null || nums.length == 0 ){ return new ArrayList<>(); } List<Integer> list = new ArrayList<>(); int i = nums.length - 1 ; TreeNode root = new TreeNode(nums[i]); list.add(0 ); for (i = i - 1 ; i >= 0 ; i--){ int m = insert(root, nums[i]); list.add(m); } Collections.reverse(list); return list; } public int insert (TreeNode root, int num) { if (num < root.val){ root.ct++; if (root.left == null ){ root.left = new TreeNode(num); return 0 ; } else { return insert(root.left, num); } } else { int res = root.ct; if (num > root.val){ res++; } if (root.right == null ){ root.right = new TreeNode(num); return res; } else { return res + insert(root.right, num); } } } } class TreeNode { int val; int ct; TreeNode left, right; TreeNode(int val){ this .val = val; } }
分式的小数形式表示
LeetCode 166. Fraction to Recurring Decimal
这道题的难点在于如何找出小数部分的循环,思路是用一个Map记录和判断,具体看代码实现。这道题还需注意一下几点:
分式的分子分母可能为正也可能为负
将负数转为正数时,当心越界(非常容易出现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public String fractionToDecimal (long numerator, long denominator) { boolean flag = (numerator < 0 && denominator > 0 ) || (numerator > 0 && denominator < 0 ); numerator = Math.abs(numerator); denominator = Math.abs(denominator); Map<Long, Integer> map = new HashMap<>(); String posStr = "" , negStr = "" ; posStr = String.valueOf(numerator / denominator); numerator = (numerator % denominator) * 10 ; for (int i = 0 ; i < 100000 ; i++){ if (numerator == 0 ){ break ; } if (map.containsKey(numerator)){ int startIndex = map.get(numerator); negStr = negStr.substring(0 , startIndex) + "(" + negStr.substring(startIndex, i) + ")" ; break ; } map.put(numerator, i); negStr += String.valueOf(numerator / denominator); numerator = (numerator % denominator) * 10 ; } if (flag){ posStr = "-" + posStr; } if (negStr.equals("" )){ return posStr; } return posStr + "." + negStr; } }
去掉最小子数组,使剩余数组之和能整除P
Leetcode 1590. Make Sum Divisible by P date: 2020.09.22 算法指数:☆☆☆☆ 是否做出:是
思路类似求数组中和为target的最小子数组,只不过在这道题中求的是和%sum= m的最小子数组,其中sum是整个数组的和,m是sum%p。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int minSubarray (int [] nums, int p) { Map<Integer, Integer> map = new HashMap<>(); int target = 0 ; for (int m : nums){ target += m; target %= p; } if (target == 0 ){ return 0 ; } int res = nums.length, cur = 0 ; map.put(0 , -1 ); for (int i = 0 ; i < nums.length; i++){ cur += nums[i]; cur = cur % p; int need = (cur - target + p) % p; res = Math.min(res, i - map.getOrDefault(need, -nums.length)); map.put(cur, i); } return res == nums.length ? -1 : res; } }
判断数组中长度为3的递增子序列
Leetcode 334. Increasing Triplet Subsequence
date: 2020.10.12
算法指数:☆☆☆
是否做出:否
此类问题一般可以通过用简单方法解决。 长度为2的递增此序列—>长度为3/的递增子序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public boolean increasingTriplet (int [] nums) { int a = Integer.MAX_VALUE; int b = Integer.MAX_VALUE; for (int m : nums){ if (m <= a){ a = m; } else if (m <= b){ b = m; } else { return true ; } } return false ; } }
获取随机值Set
LeetCode380. Insert Delete GetRandom O(1)
date: 2020.10.17
算法指数:☆☆☆
是否做出:是
通过数组+Map实现随机访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class RandomizedSet { private Map<Integer, Integer> map; private int [] array; private int nextInsert; public RandomizedSet () { map = new HashMap<>(); array = new int [100005 ]; nextInsert = 0 ; } public boolean insert (int val) { if (map.containsKey(val)){ return false ; } array[nextInsert] = val; map.put(val, nextInsert++); return true ; } public boolean remove (int val) { if (map.containsKey(val)){ int index = map.get(val); swap(array, index, --nextInsert); map.put(array[index], index); map.remove(val); return true ; } return false ; } public int getRandom () { int index = ((int )(Math.random() * 10000000 )) % nextInsert; return array[index]; } public void swap (int [] array, int i, int j) { int m = array[i]; array[i] = array[j]; array[j] = m; } }
最大的数字
LeetCode 179. Largest Number
date: 2020.10.17
算法指数:☆☆☆☆
是否做出:是
这道题的关键在于字符串比较部分:34和3哪个应该放前面、30和3哪个应该放前面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Solution { public String largestNumber (int [] nums) { if (nums == null || nums.length == 0 ){ return "" ; } String[] strs = new String[nums.length]; for (int i = 0 ; i < nums.length; i++){ strs[i] = String.valueOf(nums[i]); } Arrays.sort(strs, (s1, s2) -> { return compareString(s1, s2); }); String res = "" ; for (String s : strs){ res += s; } int startIndex; for (startIndex = 0 ; startIndex < res.length()-1 ; startIndex++){ if (res.charAt(startIndex) != '0' ){ break ; } } return res.substring(startIndex); } public int compareString (String s1, String s2) { int i = 0 ; for (; i < s1.length() && i < s2.length(); i++){ if (s1.charAt(i) > s2.charAt(i)){ return -1 ; } if (s1.charAt(i) < s2.charAt(i)){ return 1 ; } } if (i < s1.length()){ return compareString(s1.substring(i), s2); } if (i < s2.length()){ return compareString(s1, s2.substring(i)); } return 0 ; } }
直方图中的最大面积
LeetCode 84. Largest Rectangle in Histogram
date: 2020.10.17
算法指数:☆☆☆☆☆
是否做出:否
单调递增栈可以实现记录当前bar在最大区间[i, j]中是最低的。用图来说明递增栈是如何解决这个问题的:
首先,高度为2的bar<0, 2>进栈(0代表该bar从位置0到当前位置是最低的,2代表该bar的高度 ):
然后,高度为1的bar想要进栈,但是小于栈顶高度为2的bar,所以栈中的元素要出栈。<0, 2>出栈,该bar最低的最大区间为[0, 0],area = 2*1。此时栈为空,高度为1的bar继续进栈,此时注意进栈的是<0, 1>,因为前面出栈的bar的高度都比1高。高度为5,6的bar<2, 5>,<3, 6>依次进栈,
现在该高度为2的bar进栈了,重复前面的操作。首先<3, 6>出栈,该bar对应的区间为[3, 3],area = 1 * 6;然后<2, 5>出栈,对应的区间为[2, 3],area = 2 * 5。然后<2, 2>进栈,<5, 3>进栈。
最终依次出栈:<5, 3>对应区间为[5, 5],area = 1 * 3; <2, 2>对应区间为[2, 5],area = 4 * 2; <0, 1>对应区间为[0, 5],area = 6 * 1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution { public int largestRectangleArea (int [] heights) { if (heights == null || heights.length == 0 ){ return 0 ; } int maxArea = 0 ; Stack<int []> stack = new Stack<>(); for (int i = 0 ; i < heights.length; i++){ int start = i; while (!stack.isEmpty() && stack.peek()[1 ] > heights[i]){ int [] arr = stack.pop(); int height = arr[1 ]; int width = i - arr[0 ]; start = arr[0 ]; maxArea = Math.max(maxArea, height * width); } stack.push(new int []{start, heights[i]}); } while (!stack.isEmpty()){ int [] arr = stack.pop(); int height = arr[1 ]; int width = heights.length - arr[0 ]; maxArea = Math.max(maxArea, height * width); } return maxArea; } }
快速幂
LeetCode 50. Pow(x, n)
date: 2020.10.19
算法指数:☆☆☆☆
是否做出:否
利用二分思想,xn = xn/2 * xn/2 * xn%2==1 ? 1 : 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { public double myPow (double x, long n) { boolean isNegative = n < 0 ; n = Math.abs(n); double res = calculate(x, n); return isNegative ? 1 /res : res; } public double calculate (double x, long n) { if (n == 0 ){ return 1 ; } if (n == 1 ){ return x; } double res = calculate(x, n/2 ); res *= res; res *= n % 2 == 1 ? x : 1 ; return res; } }
另一种方法: 211 = 28 * 22 * 21 = 2(1000) * 2(10) *2(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public double myPow (double x, long n) { if (n == 0 ){ return 1 ; } boolean flag = n < 0 ? true : false ; n = Math.abs(n); double res = 1 ; double base = x; while (n != 0 ){ if ((n&1 ) == 1 ){ res = res * base; } base = base * base; n = n>>1 ; } if (flag){ return 1 /res; } return res; } }
无序数组中最长的有序序列
LeetCode 128.Longest Consecutive Sequence
date: 2020.10.19
难度:hard
算法指数:☆☆☆☆
是否做出:否
这道题目的是找最长的有序序列,那么就从所有有序序列的开始元素找起。将元素放到Set中可以快速找到每个有序序类的第一个元素。在找下一个元素时,要小心加1导致的位溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int longestConsecutive (int [] nums) { if (nums == null || nums.length == 0 ){ return 0 ; } Set<Integer> set = new HashSet<>(); for (int m : nums){ set.add(m); } int maxLen = 0 ; for (int m : nums){ if (!set.contains(m-1 )){ int curCount = 0 ; int curNum = m; while (set.contains(curNum)){ curCount++; if (curNum == Integer.MAX_VALUE){ break ; } curNum++; } maxLen = Math.max(maxLen, curCount); } } return maxLen; } }
矩阵中最长的增长路径
LeetCode 329. Longest Increasing Path in a Matrix
date: 2020.10.23
难度:hard
算法指数:☆☆☆☆
是否做出:是
暴力遍历方法可以轻易解决这个问题,但是会超时,在搜索过程中可以有两个优化的地方:
使用Map记录已经搜索过的路径
从最小的数开始搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 class Solution { int [][] directions = new int [][]{{0 , -1 }, {0 , 1 }, {1 , 0 }, {-1 , 0 }}; Map<String, Integer> map; int m, n; public int longestIncreasingPath (int [][] matrix) { if (matrix == null || matrix.length == 0 ){ return 0 ; } m = matrix.length; n = matrix[0 ].length; int maxLen = 0 ; boolean [][] visited = new boolean [m][n]; map = new HashMap<>(); for (int i = 0 ; i < m; i++){ for (int j = 0 ; j < n; j++){ if (isSmallest(matrix, i, j)){ maxLen = Math.max(maxLen, dfs(matrix, i, j, visited)); } } } return maxLen; } public boolean isSmallest (int [][] matrix, int i, int j) { int m = matrix.length; int n = matrix[0 ].length; if (i > 0 && matrix[i-1 ][j] < matrix[i][j]){ return false ; } if (i < m-1 && matrix[i+1 ][j] < matrix[i][j]){ return false ; } if (j > 0 && matrix[i][j-1 ] < matrix[i][j]){ return false ; } if (j < n-1 && matrix[i][j+1 ] < matrix[i][j]){ return false ; } return true ; } public int dfs (int [][] matrix, int x, int y, boolean [][] visited) { String key = "(" + x + "," + y + ")" ; if (map.containsKey(key)){ return map.get(key); } visited[x][y] = true ; int maxLen = 0 ; for (int [] dir : directions){ int i = x + dir[0 ]; int j = y + dir[1 ]; if (i >= 0 && i < m && j >= 0 && j < n && !visited[i][j] && matrix[i][j] > matrix[x][y]){ maxLen = Math.max(maxLen, dfs(matrix, i, j, visited)); } } visited[x][y] = false ; map.put(key, 1 +maxLen); return 1 + maxLen; } }
可以使用数组代替Map,效率会有不错的提升,visited数组也可以不用使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 class Solution { int [][] directions = new int [][]{{0 , -1 }, {0 , 1 }, {1 , 0 }, {-1 , 0 }}; int [][] cache; int m, n; public int longestIncreasingPath (int [][] matrix) { if (matrix == null || matrix.length == 0 ){ return 0 ; } m = matrix.length; n = matrix[0 ].length; cache = new int [m][n]; int maxLen = 0 ; for (int i = 0 ; i < m; i++){ for (int j = 0 ; j < n; j++){ if (isSmallest(matrix, i, j)){ maxLen = Math.max(maxLen, dfs(matrix, i, j)); } } } return maxLen; } public boolean isSmallest (int [][] matrix, int i, int j) { int m = matrix.length; int n = matrix[0 ].length; if (i > 0 && matrix[i-1 ][j] < matrix[i][j]){ return false ; } if (i < m-1 && matrix[i+1 ][j] < matrix[i][j]){ return false ; } if (j > 0 && matrix[i][j-1 ] < matrix[i][j]){ return false ; } if (j < n-1 && matrix[i][j+1 ] < matrix[i][j]){ return false ; } return true ; } public int dfs (int [][] matrix, int x, int y) { if (cache[x][y] != 0 ){ return cache[x][y]; } int maxLen = 0 ; for (int [] dir : directions){ int i = x + dir[0 ]; int j = y + dir[1 ]; if (i >= 0 && i < m && j >= 0 && j < n && matrix[i][j] > matrix[x][y]){ maxLen = Math.max(maxLen, dfs(matrix, i, j)); } } cache[x][y] = maxLen + 1 ; return 1 + maxLen; } }
摆动序列II
LeetCode 324. Wiggle Sort II
date: 2020.10.31
难度:Medium
算法指数:☆☆☆☆
是否做出:否
先对数组nums排序,然后分成两部分:[1, … , (n+1)/2],[(n+1)/2 +1, …, n],然后将两个序列分别插入到数组中。这里插入时需要倒序插入 ,因为正序插入的话会出现特殊情况:
如数组:[1, 2, 4, 4, 4, 6 ],加粗代表较大的部分。
正序插入:[1, 4 , 2, 4 , 4, 6 ]
倒序插入:[4, 6 , 2, 4 , 1, 4 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public void wiggleSort (int [] nums) { int n = nums.length; int [] copyArray = Arrays.copyOf(nums, n); Arrays.sort(copyArray); int mid = (n + 1 ) / 2 ; int s = mid-1 , l = n-1 ; for (int i = 0 ; i < n; i+=2 ){ nums[i] = copyArray[s--]; } for (int i = 1 ; i < n; i+=2 ){ nums[i] = copyArray[l--]; } } }