337. 打家劫舍 III

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

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

/**
* 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[] temp = steal(root);
return Math.max(temp[0], temp[1]);
} //返回以当前结点为根节点的二叉树的根节点偷或不偷所能获得的最大值
private int[] steal(TreeNode node) {
if (node == null) {
return new int[] {0, 0};
} int[] left = steal(node.left);
int[] right = steal(node.right);
//dp[0]表示当前节点不被偷,dp[1]表示偷
int[] dp = new int[2]; //当前不被偷,那么子结点偷或不偷都可以,取最大的和即可
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
//当前结点被偷,子结点都不能偷
dp[1] = node.val + left[0] + right[0]; return dp;
}
}

338. 比特位计数

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

class Solution {
public int[] countBits(int num) {
int[] res = new int[num + 1];
for (int i = 0; i <= num; i++) {
res[i] = hamming(i);
} return res;
} private int hamming(int n) {
int cnt = 0; //每次将最后一个1置0
while (n != 0) {
n = n & (n - 1);
cnt++;
} return cnt;
}
}

动态规划:

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

class Solution {
public int[] countBits(int num) {
//每个数的汉明重量
int[] dp = new int[num + 1]; //base case
dp[0] = 0; for (int i = 1; i <= num; i++) {
dp[i] = dp[i & (i - 1)] + 1;
} return dp;
}
}

347. 前 K 个高频元素

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

class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>(); for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
} int len = map.size();
int[][] freq = new int[len][2]; int i = 0;
for (int key : map.keySet()) {
freq[i][0] = key;
freq[i][1] = map.get(key);
i++;
} int index = 0;
int left = 0, right = len - 1;;
int pos = len - k;
while (true) {
index = partition(freq, left, right); if (index == pos) {
break;
} else if (index < pos) {
left= index + 1;
} else if (index > pos) {
right = index - 1;
}
} int[] res = new int[k];
int cnt = 0;
for (int j = pos; j < len; j++) {
res[cnt++] = freq[j][0];
} return res;
} private Random random = new Random(47); private int partition(int[][] nums, int left, int right) {
int rand = left + random.nextInt(right - left + 1);
swap(nums, left, rand);
int pivot = nums[left][1];
int temp = nums[left][0]; while (left < right) {
while (left < right && pivot <= nums[right][1]) {
right--;
} if (left < right) {
swap(nums, left, right);
left++;
} while (left < right && pivot > nums[left][1]) {
left++;
} if (left < right) {
swap(nums, left, right);
right--;
}
} nums[left][1] = pivot;
nums[left][0] = temp;
return left;
} private void swap(int[][] nums, int i, int j) {
int[] temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

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

class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>(); for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
} PriorityQueue<Integer> pqueue = new PriorityQueue<>((e1, e2) -> map.get(e2) - map.get(e1)); for (int key : map.keySet()) {
pqueue.offer(key);
} int[] res = new int[k];
for (int i = 0; i < k; i++) {
res[i] = pqueue.poll();
} return res;
}
}

394. 字符串解码

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

class Solution {
public String decodeString(String s) {
Deque<String> stack = new LinkedList<>(); for (int i = 0; i < s.length(); i++) {
String str = s.substring(i, i + 1); if (str.equals("]")) {
//拼接 [] 之间的字符,这里得到的是逆序,不用反转
StringBuilder strSB = new StringBuilder();
while (!stack.peek().equals("[")) {
strSB.append(stack.pop());
} //弹出 [
stack.pop(); //拼接 [ 之前的重复次数
StringBuilder reTimesSB = new StringBuilder();
while (!stack.isEmpty() && isDigit(stack.peek())) {
reTimesSB.append(stack.pop());
} //根据重复次数拼接字符串,反转后转为整型
int reTimes = Integer.parseInt(reTimesSB.reverse().toString()); StringBuilder sb = new StringBuilder();
while (reTimes > 0) {
sb.append(strSB);
reTimes--;
} //新字符串入栈
stack.push(sb.toString());
} else {
stack.push(str);
}
} StringBuilder res = new StringBuilder();
while (!stack.isEmpty()) {
res.append(stack.pop());
} //由于之前的字符拼接都是逆序的,反转后再返回
return res.reverse().toString();
} //首字符是否为数字
private boolean isDigit(String str) {
char ch = str.charAt(0); return ch >= '0' && ch <= '9';
}
}

399. 除法求值

406. 根据身高重建队列

416. 分割等和子集

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

思路一:dfs

class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
} if (sum % 2 == 1) {
return false;
} int target = sum / 2; return dfs(nums, 0, 0, target);
} private boolean dfs(int[] nums, int index, int sum, int target) {
//base case
if (nums.length == index) {
if (sum == target) {
return true;
} else {
return false;
}
} //对于任意一个数,可与选或者不选
return dfs(nums, index + 1, sum + nums[index], target) ||
dfs(nums, index + 1, sum, target);
}
}

用例超时:

[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 + 备忘录

class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
} if (sum % 2 == 1) {
return false;
} int target = sum / 2; return dfs(nums, 0, 0, target);
} // 备忘录:也可用一个二维数组,一维表示元素和sum,一维表示当前索引index
private Map<String, Boolean> map = new HashMap<>();
private boolean dfs(int[] nums, int index, int sum, int target) {
if (nums.length == index) {
if (sum == target) {
return true;
} else {
return false;
}
} //描述一个子问题的两个变量是 sum 和 index,组成 key 字符串
String key = sum + "&" + index;
if (map.containsKey(key)) {
return map.get(key);
} boolean ret = dfs(nums, index + 1, sum + nums[index], target) ||
dfs(nums, index + 1, sum, target); map.put(key, ret); return ret;
}
}

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

class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
} if (sum % 2 == 1) {
return false;
} //背包容量
int target = sum / 2;
//物品个数
int len = nums.length; //状态:dp[m][n]表示背包容量为 m,有 前 n 个物品( [0, n-1]共n个 )时能否装满背包
boolean[][] dp = new boolean[target + 1][len + 1]; // base case
// dp[0][0] = true, 不选即满
// dp[0][i] = true, i >= 1; 表示容量为0,有1个及以上个物品时可认为不选即满,固可以填满
// dp[i][0] = false, i >= 1; 表示容量 大于等于 1时,没有物品可选则无法填满
for (int i = 0; i < len + 1; i++) {
dp[0][i] = true;
} for (int i = 1; i < target + 1; i++) {
for (int j = 1; j < len + 1 ; j++) {
// 如果不能装下前j个物品中的最后一个
if (i < nums[j - 1]) {
dp[i][j] = dp[i][j - 1];
// 能装下,可以选择装或者不装
} else if (i >= nums[j - 1]) {
dp[i][j] = dp[i][j - 1] || dp[i - nums[j - 1]][j - 1];
}
}
} return dp[target][len];
}
}

437. 路径总和 III

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

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

/**
* 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 pathSum(TreeNode root, int targetSum) {
pathSum(root, 0, targetSum);
return cnt;
} private int cnt = 0;
private Map<Integer, Integer> map = new HashMap<>() {
{
//前缀树为0的结点个数是一个
put(0, 1);
}
}; // 前序遍历 + 回溯
private void pathSum(TreeNode node, int prefixSum, int targetSum) {
if (node == null) {
return;
} // 当前结点的前缀和
prefixSum += node.val;
// 和当前结点前缀和 之差 为targetSum 的(父、祖先)结点即可满足条件
cnt += map.getOrDefault(prefixSum - targetSum, 0); // 选择,使得子结点可以使用当前结点的前缀和
map.put(prefixSum, map.getOrDefault(prefixSum, 0) + 1); pathSum(node.left, prefixSum, targetSum);
pathSum(node.right, prefixSum, targetSum); // 撤销选择,使得兄弟结点无法使用当前结点的前缀和
map.put(prefixSum, map.get(prefixSum) - 1);
}
}

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

思路:滑动窗口

class Solution {
public List<Integer> findAnagrams(String s, String p) {
//记录窗口中目标字符的出现次数
Map<Character, Integer> window = new HashMap<>();
//记录目标子串中每个字符出现的次数
Map<Character, Integer> need = new HashMap<>();
for (int i = 0; i < p.length(); i++) {
char ch = p.charAt(i);
need.put(ch, need.getOrDefault(ch, 0) + 1);
} //记录已经匹配的目标字符数
int match = 0;
//窗口区间,左闭右开
int left = 0, right = 0; List<Integer> res = new LinkedList<>(); while (right < s.length()) {
//窗口扩张(右边界右移)
char rightChar = s.charAt(right);
right++;
//如果当前字符是目标字符
if (need.containsKey(rightChar)) {
window.put(rightChar, window.getOrDefault(rightChar, 0) + 1);
//当 window 中 rightChar 的个数 小于等于 need 中 rightChar 的数量时
if (window.get(rightChar).compareTo(need.get(rightChar)) <= 0) {
match++;
}
} //当窗口中已经包含所有目标字符
while(match == p.length()) {
//如果此时窗口大小刚好等于目标字符串长度,说明窗口内容刚好为目标字符串的异位词
if (right - left == p.length()) {
res.add(left);
} //收缩窗口(左边界右移),直到window不再包含所有目标字符
char leftChar = s.charAt(left);
left++; if (need.containsKey(leftChar)) {
if (window.get(leftChar).compareTo(need.get(leftChar)) <= 0) {
match--;
} window.put(leftChar, window.get(leftChar) - 1);
}
}
} return res;
}
}

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

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

class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
List<Integer> res = new LinkedList<>(); for (int i : nums) {
map.put(i, map.getOrDefault(i, 0) + 1);
} for (int i = 1; i <= nums.length; i++) {
if (!map.containsKey(i)) {
res.add(i);
}
} return res;
}
}

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

class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
int len = nums.length;
for (int i = 0; i < len; i++) {
// 如果元素的 值 和 下标 不匹配,则将其交换至对的位置
while (nums[i] - 1 != i) {
// 如果发现待交换的两个元素相同则跳过
if (nums[i] == nums[nums[i] - 1]) {
break;
} swap(nums, i, nums[i] - 1);
}
} List<Integer> res = new LinkedList<>();
for (int i = 0; i < len; i++) {
// 值 与 下标 不对应,下标加1 即为未出现的元素
if (nums[i] - 1 != i) {
res.add(i + 1);
}
} return res;
} private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}

🔥 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. 安装redHat6.5详细图文教程

    ​ ​ 进入VM虚拟机,双击进入已经创建好的红帽6虚拟机 双击进入CD/DVD,准备添加红帽6.5的iso镜像文件 [红帽6.5的iso镜像文件需要先下载,redhat_6.5下载地址:https:/ ...

  2. Gogs+Jenkins+Docker 自动化部署.NetCore

    环境说明 腾讯云轻量服务器, 配置 1c 2g 6mb ,系统是 ubuntu 20.14,Docker 和 Jenkins 都在这台服务器上面, 群晖218+一台,Gogs 在这台服务器上. Doc ...

  3. Android开发万能Utils(工具大全)

    AndroidUtils Android开发不得不收藏的Utils About AndroidUtilCode  是一个强大易用的安卓工具类库,它合理地封装了安卓开发中常用的函数,具有完善的 Demo ...

  4. error C3646: 未知重写说明符

    记录一个报错 error C3646: 未知重写说明符 ①循环引用 在项目文件变多时,由于组织不当,很容易出现头文件的循环引用 有时候虽然没有出现循环引用,但是头文件多处被include :适当的使用 ...

  5. ESCMScript(2)Module语法

    严格模式 ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";. 严格模式的限制如下 变量必须声明后再使用 函数的参数不能有同名属性,否则报错 不能 ...

  6. Machine Schedule 赤裸裸的二分匹配 二部图中的点覆盖书==匹配数

    Machine Schedule 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 ...

  7. 怎么用git将自己的源代码提交到git服务器上

    在git服务器上新建仓库 在本地初始化本地仓库 初始化 git init 添加远程仓库地址 git remote add origin XXX.git 同步 git pull origin maste ...

  8. hadoop安装前的准备

    1.操作系统安装 2.hostname设定 3.hosts文件设定 4.ssh免密码登录 5.NTP时间同步设定 6.iptables永久关闭 7.selinux永久关闭

  9. Java实验项目三——简单工厂模式

    Program: 请采用采用简单工厂设计模式,为某个汽车销售店设计汽车销售系统,接口car至少有方法print(), 三个汽车类:宝马.奥迪.大众 (属性:品牌,价格),在测试类中根据客户要求购买的汽 ...

  10. java基础---类和对象(2)

    一.继承(extends) 面向对象的三大特性之一,当多个类中存在相同属性和行为时,将这些内容抽取到一个公共类中,让多个类(子类)吸收公共类(父类.超类)中已有特征和行为,而在多个类型只需要编写自己独 ...