LeetCode 100题(10题)
1:438. 找到字符串中所有字母异位词
题意:给定两个字符串 s
和 p
,找到 s
中所有 p
的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
提示:字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次。
思路:将 s 和 p 的字母分别映射成两个数组的下标即可,如果出现一个字母,就让对应的下标加1,然后一直比较这两个数组是否相同即可
class Solution {public List<Integer> findAnagrams(String s, String p) {int s_len = s.length();int p_len = p.length();if(s_len < p_len){return new ArrayList<>();}int[] count_s = new int[26];int[] count_p = new int[26];List<Integer> ans = new ArrayList<>();for(int i = 0; i < p_len; i ++){count_s[s.charAt(i) - 'a'] ++;count_p[p.charAt(i) - 'a'] ++; }if(Arrays.equals(count_p, count_s)){ans.add(0);}for(int i = 0; i < s_len - p_len; i ++){count_s[s.charAt(i) - 'a'] --;count_s[s.charAt(i + p_len) - 'a'] ++;if(Arrays.equals(count_p, count_s)){ans.add(i + 1);}}return ans;}
}
2:437. 路径总和 III
题意:给定一个二叉树的根节点 root
,和一个整数 targetSum
,求该二叉树里节点值之和等于 targetSum
的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
思路:以这个二叉树的每个节点都为起点来遍历一遍,时间复杂度是O(n ^ 2)级别的,可以用前缀和来优化,这里懒得想了
/*** 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 int ans = 0;public int pathSum(TreeNode root, int targetSum) {if(root== null) return 0;dfs2(root, targetSum);return ans;}public void dfs2(TreeNode node, int tar){dfs1(node, tar, 0L); // 以当前点为起点开始遍历if(node.left != null) dfs2(node.left, tar);if(node.right != null) dfs2(node.right, tar);}public void dfs1(TreeNode node, int tar, Long sum){sum += node.val;if(sum == tar) ans ++;if(node.left != null) dfs1(node.left, tar, sum);if(node.right != null) dfs1(node.right, tar, sum);}
}
3:416. 分割等和子集
题意:给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路:很容易发现这个集合里面所有元素的和必须是偶数才能分成两个等和子集,其实这题也就是问我们,在保证集合里面的元素的和为偶数的前提下,能不能找出任意数量的元素,让他们的和是所有元素的和的一半,类似于背包问题把
class Solution {public boolean canPartition(int[] nums) {int sum = 0;int len = nums.length;for(int i = 0; i < len; i ++){sum += nums[i];}if(sum % 2 > 0) return false;int tar = sum / 2;boolean[] dp = new boolean[tar + 1];dp[0] = true;for(int i = 0; i < nums.length; i ++){for(int j = tar; j >= nums[i]; j --){dp[j] = dp[j] || dp[j - nums[i]];}}return dp[tar];}
}
4:406. 根据身高重建队列
题意:
假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)
思路:先把这个数组按照people[1]这一维进行排序,然后慢慢模拟即可,详细解释见代码
class Solution {public int[][] reconstructQueue(int[][] people) {int len = people.length;int[][] ans = new int[len][2];Arrays.sort(people, (a, b) -> {if(a[1] != b[1]){return Integer.compare(a[1], b[1]);}else{return Integer.compare(a[0], b[0]);}});int ans_len = 0;for(int i = 0; i < len; i ++){int cnt = 0; // 记录前面有多少个人的身高大于等于当前人int h = people[i][0];int k = people[i][1];for(int j = 0; j < ans_len; j ++){if(ans[j][0] >= h) cnt ++; }if(cnt == k){ // 说明目前符合要求ans[ans_len] = people[i];ans_len ++;}else{ // 说明当前不符合要求int cha = cnt - k; // cnt 一定比 k 大,不然无法重建队列ans_len ++;int pos = ans_len - cha - 1; //找到需要插入的位置 for(int j = ans_len - 1; j > pos; j --){ans[j] = ans[j - 1];}ans[pos] = people[i];}}return ans;}
}
5:399. 除法求值
题意:
给你一个变量对数组 equations
和一个实数值数组 values
作为已知条件,其中 equations[i] = [Ai, Bi]
和 values[i]
共同表示等式 Ai / Bi = values[i]
。每个 Ai
或 Bi
是一个表示单个变量的字符串。
另有一些以数组 queries
表示的问题,其中 queries[j] = [Cj, Dj]
表示第 j
个问题,请你根据已知条件找出 Cj / Dj = ?
的结果作为答案。
返回 所有问题的答案 。如果存在某个无法确定的答案,则用 -1.0
替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串,也需要用 -1.0
替代这个答案。
注意:输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。
注意:未在等式列表中出现的变量是未定义的,因此无法确定它们的答案。
思路:抽象出来实际上是一个并查集的问题,而且是带权并查集,这个 “权” 在代码里我们用 w[x] 来表示,代表 x 这个节点是根节点的几倍; 然后没什么特别的了,无非就是并查集的一些连通性和路径压缩的板子写法,这题主要难点还是要想到并查集这个数据结构,最后讲一下那个 merge 方法里的式子是怎么推出来的,见下图:
class Solution {public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {int len = equations.size();Ds ds = new Ds(len * 2);Map<String, Integer> mp = new HashMap<>();int id = 0;for(int i = 0; i < len; i ++){List<String> equation = equations.get(i);String var1 = equation.get(0);String var2 = equation.get(1);if(!mp.containsKey(var1)){mp.put(var1, id);id ++;}if(!mp.containsKey(var2)){mp.put(var2, id);id ++;}ds.merge(mp.get(var1), mp.get(var2), values[i]); // 合并}len = queries.size();double[] res = new double[len]; for(int i = 0; i < len; i ++){List<String> querie = queries.get(i);String var1 = querie.get(0);String var2 = querie.get(1);if(!mp.containsKey(var1) || !mp.containsKey(var2)){res[i] = -1.0;}else{res[i] = ds.isConnect(mp.get(var1), mp.get(var2));}}return res;}
}class Ds{public int[] p;public double[] w;public Ds(int n){p = new int[n];w = new double[n];for(int i = 0; i < n; i ++){p[i] = i;w[i] = 1.0;}}public void merge(int X, int Y, double var){int rootX = find(X);int rootY = find(Y);p[rootX] = rootY;w[rootX] = var * w[Y] / w[X];}public int find(int X){if(X != p[X]){int orign = p[X];p[X] = find(p[X]);w[X] *= w[orign];}return p[X];}public double isConnect(int X, int Y){int rootX = find(X);int rootY = find(Y);if(rootX != rootY) return -1.0;else return w[X] / w[Y];}
}
6:394. 字符串解码
题意:
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string]
,表示其中方括号内部的 encoded_string
正好重复 k
次。注意 k
保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k
,例如不会出现像 3a
或 2[4]
的输入。
测试用例保证输出的长度不会超过 105
。
思路:一眼考的栈,当我们遍历到 "]" 时,就将栈里的元素取出来,一直取到数字为止,然后就是将这些字母重复拼凑再放进栈里,注意字母取出来的时候是反着的,所以我们要 reverse 一下
class Solution {public String decodeString(String s) {Stack<Character> stack = new Stack<>();int len = s.length();for(int i = 0; i < len; i ++){stack.push(s.charAt(i));if(s.charAt(i) == ']'){stack.pop();String ss = "";while(!(stack.peek() >= '0' && stack.peek() <= '9')){if(stack.peek() == '['){stack.pop();continue;}ss += stack.peek();stack.pop();}// System.out.println(ss);String reversed = new StringBuilder(ss).reverse().toString();// System.out.println(reversed);int cnt = 0;String cnt1 = "";while(!stack.isEmpty() && stack.peek() >= '0' && stack.peek() <= '9'){cnt1 += stack.peek();stack.pop();}String cnt2 = new StringBuilder(cnt1).reverse().toString();for(int j = 0; j < cnt2.length(); j ++){cnt = cnt * 10 + cnt2.charAt(j) - '0';}String tmp = "";for(int j = 0; j < cnt; j ++){tmp += reversed;}// System.out.println(tmp);for(int j = 0; j < tmp.length(); j ++){stack.push(tmp.charAt(j));}}}String ans = "";while(!stack.isEmpty()){ans += stack.peek();stack.pop();}String res = new StringBuilder(ans).reverse().toString();return res;}
}
7:347. 前 K 个高频元素
题意:给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
思路:用map记录,然后放进堆里,再用堆来取就行
class Solution {public int[] topKFrequent(int[] nums, int k) {int len = nums.length;Map<Integer, Integer> map = new HashMap<>();for(int i = 0; i < len; i ++){if(map.containsKey(nums[i])){int cnt = map.get(nums[i]);map.put(nums[i], cnt + 1);}else{map.put(nums[i], 1);}}PriorityQueue<Integer> minHeap = new PriorityQueue<>((a, b) -> map.get(a) - map.get(b));for(int num: map.keySet()){minHeap.offer(num);if(minHeap.size() > k){minHeap.poll();}}int[] result = new int[k];for(int i = k - 1; i >= 0; i--) {result[i] = minHeap.poll();}return result;}
}
8:338. 比特位计数
题意:给你一个整数 n
,对于 0 <= i <= n
中的每个 i
,计算其二进制表示中 1
的个数 ,返回一个长度为 n + 1
的数组 ans
作为答案
思路:主要考的是对位运算的理解
class Solution {public int[] countBits(int n) {int[] ans = new int[n + 1];for(int i = 0; i <= n; i ++){int cnt = 0;int tmp = i;while(tmp > 0){if(tmp % 2 == 1){cnt ++;}tmp >>= 1;}ans[i] = cnt;}return ans;}
}
9:337. 打家劫舍 III
题意:小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root
。
除了 root
之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root
。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
思路:树形动态规划的板子题,可以参考:285. 没有上司的舞会 - AcWing题库
left[0] 代表不考虑左子节点,left[1]代表考虑左子节点,right[0] 和 right[1]同理,注意一个比较容易错的地方,就是如果我不选当前节点,我也可以不选它的左右子节点,一开始这里忘了,导致错了
/*** 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 int rob(TreeNode root) {int[] res = dfs(root);return Math.max(res[0], res[1]);}public int[] dfs(TreeNode u){if(u == null){return new int[]{0, 0};}int[] left = dfs(u.left);int[] right = dfs(u.right);int select = u.val + left[1] + right[1];int unselect = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);return new int[]{select, unselect};}
}
10:121. 买卖股票的最佳时机
题意:给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
思路:遍历过程中记录最小值即可
class Solution {public int maxProfit(int[] prices) {int len = prices.length;int ans = 0;int minn = 10005;for(int i = 0; i < len; i ++){minn = Math.min(minn, prices[i]);ans = Math.max(ans, prices[i] - minn);}return ans;}
}