337. 打家劫舍 III

思路:后序遍历 + 动态规划

推荐题解:树形 dp 入门问题(理解「无后效性」和「后序遍历」)

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public int rob(TreeNode root) {
  18. int[] temp = steal(root);
  19. return Math.max(temp[0], temp[1]);
  20. }
  21. //返回以当前结点为根节点的二叉树的根节点偷或不偷所能获得的最大值
  22. private int[] steal(TreeNode node) {
  23. if (node == null) {
  24. return new int[] {0, 0};
  25. }
  26. int[] left = steal(node.left);
  27. int[] right = steal(node.right);
  28. //dp[0]表示当前节点不被偷,dp[1]表示偷
  29. int[] dp = new int[2];
  30. //当前不被偷,那么子结点偷或不偷都可以,取最大的和即可
  31. dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
  32. //当前结点被偷,子结点都不能偷
  33. dp[1] = node.val + left[0] + right[0];
  34. return dp;
  35. }
  36. }

338. 比特位计数

思路: n & (n - 1)表示将 n 最后一位 1 置为 0:

  1. class Solution {
  2. public int[] countBits(int num) {
  3. int[] res = new int[num + 1];
  4. for (int i = 0; i <= num; i++) {
  5. res[i] = hamming(i);
  6. }
  7. return res;
  8. }
  9. private int hamming(int n) {
  10. int cnt = 0;
  11. //每次将最后一个1置0
  12. while (n != 0) {
  13. n = n & (n - 1);
  14. cnt++;
  15. }
  16. return cnt;
  17. }
  18. }

动态规划:

任何一个数的汉明重量等于将其最后一位 1 置 0 的数的汉明重量加1,可考虑用动态规划。

  1. class Solution {
  2. public int[] countBits(int num) {
  3. //每个数的汉明重量
  4. int[] dp = new int[num + 1];
  5. //base case
  6. dp[0] = 0;
  7. for (int i = 1; i <= num; i++) {
  8. dp[i] = dp[i & (i - 1)] + 1;
  9. }
  10. return dp;
  11. }
  12. }

347. 前 K 个高频元素

思路一:快速排序的应用,和第K大元素类似

  1. class Solution {
  2. public int[] topKFrequent(int[] nums, int k) {
  3. Map<Integer, Integer> map = new HashMap<>();
  4. for (int num : nums) {
  5. map.put(num, map.getOrDefault(num, 0) + 1);
  6. }
  7. int len = map.size();
  8. int[][] freq = new int[len][2];
  9. int i = 0;
  10. for (int key : map.keySet()) {
  11. freq[i][0] = key;
  12. freq[i][1] = map.get(key);
  13. i++;
  14. }
  15. int index = 0;
  16. int left = 0, right = len - 1;;
  17. int pos = len - k;
  18. while (true) {
  19. index = partition(freq, left, right);
  20. if (index == pos) {
  21. break;
  22. } else if (index < pos) {
  23. left= index + 1;
  24. } else if (index > pos) {
  25. right = index - 1;
  26. }
  27. }
  28. int[] res = new int[k];
  29. int cnt = 0;
  30. for (int j = pos; j < len; j++) {
  31. res[cnt++] = freq[j][0];
  32. }
  33. return res;
  34. }
  35. private Random random = new Random(47);
  36. private int partition(int[][] nums, int left, int right) {
  37. int rand = left + random.nextInt(right - left + 1);
  38. swap(nums, left, rand);
  39. int pivot = nums[left][1];
  40. int temp = nums[left][0];
  41. while (left < right) {
  42. while (left < right && pivot <= nums[right][1]) {
  43. right--;
  44. }
  45. if (left < right) {
  46. swap(nums, left, right);
  47. left++;
  48. }
  49. while (left < right && pivot > nums[left][1]) {
  50. left++;
  51. }
  52. if (left < right) {
  53. swap(nums, left, right);
  54. right--;
  55. }
  56. }
  57. nums[left][1] = pivot;
  58. nums[left][0] = temp;
  59. return left;
  60. }
  61. private void swap(int[][] nums, int i, int j) {
  62. int[] temp = nums[i];
  63. nums[i] = nums[j];
  64. nums[j] = temp;
  65. }
  66. }

思路二:优先队列(大顶堆)

  1. class Solution {
  2. public int[] topKFrequent(int[] nums, int k) {
  3. Map<Integer, Integer> map = new HashMap<>();
  4. for (int num : nums) {
  5. map.put(num, map.getOrDefault(num, 0) + 1);
  6. }
  7. PriorityQueue<Integer> pqueue = new PriorityQueue<>((e1, e2) -> map.get(e2) - map.get(e1));
  8. for (int key : map.keySet()) {
  9. pqueue.offer(key);
  10. }
  11. int[] res = new int[k];
  12. for (int i = 0; i < k; i++) {
  13. res[i] = pqueue.poll();
  14. }
  15. return res;
  16. }
  17. }

394. 字符串解码

思路:栈,思路简单,关键在于字符串拼接顺序的细节问题。

  1. class Solution {
  2. public String decodeString(String s) {
  3. Deque<String> stack = new LinkedList<>();
  4. for (int i = 0; i < s.length(); i++) {
  5. String str = s.substring(i, i + 1);
  6. if (str.equals("]")) {
  7. //拼接 [] 之间的字符,这里得到的是逆序,不用反转
  8. StringBuilder strSB = new StringBuilder();
  9. while (!stack.peek().equals("[")) {
  10. strSB.append(stack.pop());
  11. }
  12. //弹出 [
  13. stack.pop();
  14. //拼接 [ 之前的重复次数
  15. StringBuilder reTimesSB = new StringBuilder();
  16. while (!stack.isEmpty() && isDigit(stack.peek())) {
  17. reTimesSB.append(stack.pop());
  18. }
  19. //根据重复次数拼接字符串,反转后转为整型
  20. int reTimes = Integer.parseInt(reTimesSB.reverse().toString());
  21. StringBuilder sb = new StringBuilder();
  22. while (reTimes > 0) {
  23. sb.append(strSB);
  24. reTimes--;
  25. }
  26. //新字符串入栈
  27. stack.push(sb.toString());
  28. } else {
  29. stack.push(str);
  30. }
  31. }
  32. StringBuilder res = new StringBuilder();
  33. while (!stack.isEmpty()) {
  34. res.append(stack.pop());
  35. }
  36. //由于之前的字符拼接都是逆序的,反转后再返回
  37. return res.reverse().toString();
  38. }
  39. //首字符是否为数字
  40. private boolean isDigit(String str) {
  41. char ch = str.charAt(0);
  42. return ch >= '0' && ch <= '9';
  43. }
  44. }

399. 除法求值

406. 根据身高重建队列

416. 分割等和子集

推荐题解:「手画图解」416.分割等和子集 | 记忆化递归 思路详解

思路一:dfs

  1. class Solution {
  2. public boolean canPartition(int[] nums) {
  3. int sum = 0;
  4. for (int num : nums) {
  5. sum += num;
  6. }
  7. if (sum % 2 == 1) {
  8. return false;
  9. }
  10. int target = sum / 2;
  11. return dfs(nums, 0, 0, target);
  12. }
  13. private boolean dfs(int[] nums, int index, int sum, int target) {
  14. //base case
  15. if (nums.length == index) {
  16. if (sum == target) {
  17. return true;
  18. } else {
  19. return false;
  20. }
  21. }
  22. //对于任意一个数,可与选或者不选
  23. return dfs(nums, index + 1, sum + nums[index], target) ||
  24. dfs(nums, index + 1, sum, target);
  25. }
  26. }

用例超时:

  1. [100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,100,99,97]

思路二:dfs + 备忘录

  1. class Solution {
  2. public boolean canPartition(int[] nums) {
  3. int sum = 0;
  4. for (int num : nums) {
  5. sum += num;
  6. }
  7. if (sum % 2 == 1) {
  8. return false;
  9. }
  10. int target = sum / 2;
  11. return dfs(nums, 0, 0, target);
  12. }
  13. // 备忘录:也可用一个二维数组,一维表示元素和sum,一维表示当前索引index
  14. private Map<String, Boolean> map = new HashMap<>();
  15. private boolean dfs(int[] nums, int index, int sum, int target) {
  16. if (nums.length == index) {
  17. if (sum == target) {
  18. return true;
  19. } else {
  20. return false;
  21. }
  22. }
  23. //描述一个子问题的两个变量是 sum 和 index,组成 key 字符串
  24. String key = sum + "&" + index;
  25. if (map.containsKey(key)) {
  26. return map.get(key);
  27. }
  28. boolean ret = dfs(nums, index + 1, sum + nums[index], target) ||
  29. dfs(nums, index + 1, sum, target);
  30. map.put(key, ret);
  31. return ret;
  32. }
  33. }

思路三:所有 target 绝对值大于 元素总和 或 元素总和 不为 偶数 显然不能拆分;问题等价于能否找到一组元素的和为所有元素总和的一半。(01背包问题:可选物品有限)

  1. class Solution {
  2. public boolean canPartition(int[] nums) {
  3. int sum = 0;
  4. for (int num : nums) {
  5. sum += num;
  6. }
  7. if (sum % 2 == 1) {
  8. return false;
  9. }
  10. //背包容量
  11. int target = sum / 2;
  12. //物品个数
  13. int len = nums.length;
  14. //状态:dp[m][n]表示背包容量为 m,有 前 n 个物品( [0, n-1]共n个 )时能否装满背包
  15. boolean[][] dp = new boolean[target + 1][len + 1];
  16. // base case
  17. // dp[0][0] = true, 不选即满
  18. // dp[0][i] = true, i >= 1; 表示容量为0,有1个及以上个物品时可认为不选即满,固可以填满
  19. // dp[i][0] = false, i >= 1; 表示容量 大于等于 1时,没有物品可选则无法填满
  20. for (int i = 0; i < len + 1; i++) {
  21. dp[0][i] = true;
  22. }
  23. for (int i = 1; i < target + 1; i++) {
  24. for (int j = 1; j < len + 1 ; j++) {
  25. // 如果不能装下前j个物品中的最后一个
  26. if (i < nums[j - 1]) {
  27. dp[i][j] = dp[i][j - 1];
  28. // 能装下,可以选择装或者不装
  29. } else if (i >= nums[j - 1]) {
  30. dp[i][j] = dp[i][j - 1] || dp[i - nums[j - 1]][j - 1];
  31. }
  32. }
  33. }
  34. return dp[target][len];
  35. }
  36. }

437. 路径总和 III

提示:路径并非是一条向下的直线,可以是折线。如root = [10,5,-3,3,2,null,11,3,0,null,1], targetSum = 8[5, 3][5, 3, 0]都符合条件。

推荐题解:对前缀和解法的一点解释

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public int pathSum(TreeNode root, int targetSum) {
  18. pathSum(root, 0, targetSum);
  19. return cnt;
  20. }
  21. private int cnt = 0;
  22. private Map<Integer, Integer> map = new HashMap<>() {
  23. {
  24. //前缀树为0的结点个数是一个
  25. put(0, 1);
  26. }
  27. };
  28. // 前序遍历 + 回溯
  29. private void pathSum(TreeNode node, int prefixSum, int targetSum) {
  30. if (node == null) {
  31. return;
  32. }
  33. // 当前结点的前缀和
  34. prefixSum += node.val;
  35. // 和当前结点前缀和 之差 为targetSum 的(父、祖先)结点即可满足条件
  36. cnt += map.getOrDefault(prefixSum - targetSum, 0);
  37. // 选择,使得子结点可以使用当前结点的前缀和
  38. map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1);
  39. pathSum(node.left, prefixSum, targetSum);
  40. pathSum(node.right, prefixSum, targetSum);
  41. // 撤销选择,使得兄弟结点无法使用当前结点的前缀和
  42. map.put(prefixSum, map.get(prefixSum) - 1);
  43. }
  44. }

438. 找到字符串中所有字母异位词

思路:滑动窗口

  1. class Solution {
  2. public List<Integer> findAnagrams(String s, String p) {
  3. //记录窗口中目标字符的出现次数
  4. Map<Character, Integer> window = new HashMap<>();
  5. //记录目标子串中每个字符出现的次数
  6. Map<Character, Integer> need = new HashMap<>();
  7. for (int i = 0; i < p.length(); i++) {
  8. char ch = p.charAt(i);
  9. need.put(ch, need.getOrDefault(ch, 0) + 1);
  10. }
  11. //记录已经匹配的目标字符数
  12. int match = 0;
  13. //窗口区间,左闭右开
  14. int left = 0, right = 0;
  15. List<Integer> res = new LinkedList<>();
  16. while (right < s.length()) {
  17. //窗口扩张(右边界右移)
  18. char rightChar = s.charAt(right);
  19. right++;
  20. //如果当前字符是目标字符
  21. if (need.containsKey(rightChar)) {
  22. window.put(rightChar, window.getOrDefault(rightChar, 0) + 1);
  23. //当 window 中 rightChar 的个数 小于等于 need 中 rightChar 的数量时
  24. if (window.get(rightChar).compareTo(need.get(rightChar)) <= 0) {
  25. match++;
  26. }
  27. }
  28. //当窗口中已经包含所有目标字符
  29. while(match == p.length()) {
  30. //如果此时窗口大小刚好等于目标字符串长度,说明窗口内容刚好为目标字符串的异位词
  31. if (right - left == p.length()) {
  32. res.add(left);
  33. }
  34. //收缩窗口(左边界右移),直到window不再包含所有目标字符
  35. char leftChar = s.charAt(left);
  36. left++;
  37. if (need.containsKey(leftChar)) {
  38. if (window.get(leftChar).compareTo(need.get(leftChar)) <= 0) {
  39. match--;
  40. }
  41. window.put(leftChar, window.get(leftChar) - 1);
  42. }
  43. }
  44. }
  45. return res;
  46. }
  47. }

448. 找到所有数组中消失的数字

思路一:使用map记录各个元素出现的频次,然后依次获取 [1, n] 出现的频次,频次为0则说明没有出现。此时空间复杂度为O(n)面试时我们应该询问面试官 时间 和 空间复杂度 要求之后再确定具体使用的方法。

  1. class Solution {
  2. public List<Integer> findDisappearedNumbers(int[] nums) {
  3. Map<Integer, Integer> map = new HashMap<>();
  4. List<Integer> res = new LinkedList<>();
  5. for (int i : nums) {
  6. map.put(i, map.getOrDefault(i, 0) + 1);
  7. }
  8. for (int i = 1; i <= nums.length; i++) {
  9. if (!map.containsKey(i)) {
  10. res.add(i);
  11. }
  12. }
  13. return res;
  14. }
  15. }

思路二:将所有元素放置在其值减1的下标位置。如:1 应该放置在 0 位置,3 应该放置在 2 位置,这样一轮处理后所有 值 与 下标 差值不为1 的 下标加1 即为未出现的元素。思路来自:剑指 Offer 03. 数组中重复的数字

  1. class Solution {
  2. public List<Integer> findDisappearedNumbers(int[] nums) {
  3. int len = nums.length;
  4. for (int i = 0; i < len; i++) {
  5. // 如果元素的 值 和 下标 不匹配,则将其交换至对的位置
  6. while (nums[i] - 1 != i) {
  7. // 如果发现待交换的两个元素相同则跳过
  8. if (nums[i] == nums[nums[i] - 1]) {
  9. break;
  10. }
  11. swap(nums, i, nums[i] - 1);
  12. }
  13. }
  14. List<Integer> res = new LinkedList<>();
  15. for (int i = 0; i < len; i++) {
  16. // 值 与 下标 不对应,下标加1 即为未出现的元素
  17. if (nums[i] - 1 != i) {
  18. res.add(i + 1);
  19. }
  20. }
  21. return res;
  22. }
  23. private void swap(int[] nums, int i, int j) {
  24. int temp = nums[i];
  25. nums[i] = nums[j];
  26. nums[j] = temp;
  27. }
  28. }

🔥 LeetCode 热题 HOT 100(81-90)的更多相关文章

  1. LeetCode 热题 HOT 100(05,正则表达式匹配)

    LeetCode 热题 HOT 100(05,正则表达式匹配) 不够优秀,发量尚多,千锤百炼,方可成佛. 算法的重要性不言而喻,无论你是研究者,还是最近比较火热的IT 打工人,都理应需要一定的算法能力 ...

  2. 🔥 LeetCode 热题 HOT 100(71-80)

    253. 会议室 II(NO) 279. 完全平方数 class Solution { public int numSquares(int n) { // dp[i] : 组成和为 i 的最少完全平方 ...

  3. 🔥 LeetCode 热题 HOT 100(51-60)

    142. 环形链表 II 思路:快慢指针,快慢指针相遇后,慢指针回到头,快慢指针步伐一致一起移动,相遇点即为入环点 /** * Definition for singly-linked list. * ...

  4. 🔥 LeetCode 热题 HOT 100(31-40)

    75. 颜色分类 思路:将 2 往后放,0 往前放,剩余的1自然就放好了. 使用双指针:left.right 分别指向待插入的 0 和 2 的位置,初始 left 指向数组头,right 指向数组尾部 ...

  5. 🔥 LeetCode 热题 HOT 100(21-30)

    46. 全排列 思路:典型回溯法 class Solution { public List<List<Integer>> permute(int[] nums) { Linke ...

  6. 🔥 LeetCode 热题 HOT 100(61-70)

    207. 课程表 思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能.因此可以将问题转换成: 判断有向图中是否存在环.使用 拓扑排序法 : 构建 入度表:记录每 ...

  7. 🔥 LeetCode 热题 HOT 100(41-50)

    102. 二叉树的层序遍历 思路:使用队列. /** * Definition for a binary tree node. * public class TreeNode { * int val; ...

  8. 🔥 LeetCode 热题 HOT 100(11-20)

    20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...

  9. 🔥 LeetCode 热题 HOT 100(1-10)

    1. 两数之和 思路一:暴力遍历所有组合 class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; ...

随机推荐

  1. bzoj2427 软件安装! 树dp

    软件安装 内存限制:128 MiB 时间限制:1000 ms 标准输入输出     题目描述 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一 些软 ...

  2. matplotlib 并列条形图

    1 绘制并列条形图,并在条形图上加标注 1.1 代码 from matplotlib import pyplot from matplotlib import font_manager import ...

  3. 散列数据结构以及在HashMap中的应用

    1. 为什么需要散列表? 对于线性表和链表而言,访问表中的元素,时间复杂度均为O(n).即便是通过树结构存储数据,时间复杂度也为O(logn).那么有没有一种方式可以将这个时间复杂度降为O(1)呢?当 ...

  4. 一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位

    一次鞭辟入里的 Log4j2 日志输出阻塞问题的定位 问题现象 线上某个应用的某个实例突然出现某些次请求服务响应极慢的情况,有几次请求超过 60s 才返回,并且通过日志发现,服务线程并没有做什么很重的 ...

  5. 22.17、heartbeat和drbd整合

    1.要确保master-db和slave-db的drbd服务和heartbeat服务都已经停止了: 2.heartbeate设置: 修改master-db和slave-db的'/etc/ha.d/ha ...

  6. 13、mysql主从复制原理解析

    13.1.mysql主从复制介绍: 1.普通文件,磁盘上的文件的同步方法: (1)nfs网络文件共享可以同步数据存储: (2)samba共享数据: (3)ftp数据同步: (4)定时任务:cronta ...

  7. JDK1.8 ArrayList 源码解析

    源码的解读逻辑按照程序运行的轨迹展开 Arraylist的继承&实现关系 打开ArrayList源码,会看到有如下的属性定义, ArrayList中定义的属性 /** * Default in ...

  8. nexus AD 集成配置

    nexus AD 集成配置 管理用户登录 点击设置图标-->LDAP-->Create connection 进入AD 集成配置页面 Connection配置 User and group ...

  9. 如何Spring Cloud Zuul作为网关的分布式系统中整合Swagger文档在同一个页面上

    本文不涉及技术,只是单纯的一个小技巧. 阅读本文前,你需要对spring-cloud-zuul.spring-cloud-eureka.以及swagger的配置和使用有所了解. 如果你的系统也是用zu ...

  10. Spring中的<context:annotation-config/>配置

    当我们需要使用BeanPostProcessor时,直接在Spring配置文件中定义这些Bean显得比较笨拙,例如: 使用@Autowired注解,必须事先在Spring容器中声明AutowiredA ...