🔥 LeetCode 热题 HOT 100(71-80)
253. 会议室 II(NO)
279. 完全平方数
class Solution {
public int numSquares(int n) {
// dp[i] : 组成和为 i 的最少完全平方数个数
// base case: dp[0] = 0;
int[] dp = new int[n + 1];
for (int i = 1; i < n + 1; i++) {
// 最大值即为i
dp[i] = i;
// 转移方程
for (int j = 1; i - j*j >= 0; j++) {
dp[i] = Math.min(dp[i], dp[i - j*j] + 1);
}
}
return dp[n];
}
}
推荐题解:画解算法:279. 完全平方数
283. 移动零
思路一:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就插入 i 指向的位置并一起向后移动,最后将 i 及其后面全部补0即可。
class Solution {
public void moveZeroes(int[] nums) {
int len = nums.length;
int i = 0; // i 指向 非0 元素的下一个待插入位置
// j 一直向后遍历,每次遇到 非0 元素就插入i指向的位置并一起向后移动
for (int j = 0; j < len; j++) {
if (nums[j] != 0) {
nums[i++] = nums[j];
}
}
// i 及其后面全部补0即可
for ( ; i < len; i++) {
nums[i] = 0;
}
}
}
思路二:双指针,i 指向 非0 元素的下一个待插入位置,j 遍历数组,每次遇到 非0 元素就与 i 指向元素交换并一起向后移动,最后 [0, i)
之前全部为非0元素,(i, j)
之间全部为 0。
class Solution {
public void moveZeroes(int[] nums) {
int len = nums.length;
int i = 0; // i 指向 非0 元素的下一个待插入位置
// 每次遇到 非0 元素就与 i 指向元素交换并一起向后移动
for (int j = 0; j < len; j++) {
if (nums[j] != 0) {
swap(nums, i, j);
i++;
}
}
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
287. 寻找重复数
该题最简单的思路便是使用set
,但题目要求空间复杂度为O(1)
。另外一种简单思路是使用排序,然后检查重复数即可,但题目又要求时间复杂度为O(nlogn)
,且不能修改原数组。面试时我们应该询问面试官 时间 和 空间复杂度 要求之后再确定具体使用的方法。
思路一:快慢指针,建议先刷:141. 环形链表、142. 环形链表 II。
推荐题解:287.寻找重复数
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0;
int fast = nums[slow];
while (slow != fast) {
fast = nums[nums[fast]];
slow = nums[slow];
}
fast = nums[slow];
slow = 0;
while (slow != fast) {
fast = nums[fast];
slow = nums[slow];
}
return slow;
}
}
思路二:将所有元素放置在其值减1的下标位置。如:1 应该放置在 0 位置,3 应该放置在 2 位置,这样,重复的元素至少有一个无法放置在对应位置。思路来自:剑指 Offer 03. 数组中重复的数字
class Solution {
public int findDuplicate(int[] nums) {
int len = nums.length;
for (int i = 0; i < len - 1; i++) {
// 如果元素的 值 和 下标 不匹配,则将其交换至对的位置
while ((nums[i] - 1) != i) {
// 如果发现待交换的两个元素相同则直接返回 如:[3,1,3,4,2]
if (nums[i] == nums[nums[i] - 1]) {
return nums[i];
}
swap(nums, i, nums[i] - 1);
}
}
// 通常,最后一个元素为重复元素
return nums[len - 1];
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
备注:该方法修改了原来的数组。时间复杂度为O(n)
,因为每个元素至多交换一次,空间复杂度为O(1)
。
297. 二叉树的序列化与反序列化
思路一:使用先序遍历进行序列化(dfs)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) {
return "#";
}
String leftSerial = serialize(root.left);
String rightSerial = serialize(root.right);
return root.val + "," + leftSerial + "," + rightSerial;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] strs = data.split(",");
Deque<String> queue = new LinkedList<>();
for (String str : strs) {
queue.offer(str);
}
return deserialize(queue);
}
private TreeNode deserialize(Deque<String> queue) {
String rootVal = queue.poll();
if (rootVal.equals("#")) {
return null;
}
TreeNode root = new TreeNode(Integer.parseInt(rootVal));
root.left = deserialize(queue);
root.right = deserialize(queue);
return root;
}
}
// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));
思路二:使用层次遍历进行序列化(bfs)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
StringBuilder sb = new StringBuilder();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode temp = queue.poll();
if (temp != null) {
sb.append(temp.val).append(",");
queue.offer(temp.left);
queue.offer(temp.right);
} else {
sb.append("#").append(",");
}
}
System.out.println(sb.toString());
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] strs = data.split(",");
Deque<TreeNode> queue = new LinkedList<>();
int i = 0;
if (strs[i].equals("#")) return null;
TreeNode root = new TreeNode(Integer.parseInt(strs[i++]));
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode temp = queue.poll();
String leftVal = strs[i++];
String rightVal = strs[i++];
if (!leftVal.equals("#")) {
TreeNode leftChild = new TreeNode(Integer.parseInt(leftVal));
temp.left = leftChild;
queue.offer(leftChild);
}
if (!rightVal.equals("#")) {
TreeNode rightChild = new TreeNode(Integer.parseInt(rightVal));
temp.right = rightChild;
queue.offer(rightChild);
}
}
return root;
}
}
// Your Codec object will be instantiated and called as such:
// Codec ser = new Codec();
// Codec deser = new Codec();
// TreeNode ans = deser.deserialize(ser.serialize(root));
推荐题解:『手画图解』剖析DFS、BFS解法 | 二叉树的序列化与反序列化
300. 最长递增子序列
思路:动态规划
状态:以当前字符结尾的字符串中最长递增子序列的长度
转移方程:dp[i] = max(dp[j] + 1, dp[i])
,其中 j < i
且 nums[j] < nums[i]
base case:dp[i] = 1
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
// dp[i] 表示以当前字符结尾的字符串中最长递增子序列的长度
int[] dp = new int[len];
int maxLen = 0;
for (int i = 0; i < len; i++) {
//base case
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
}
301. 删除无效的括号
思路:回溯法
class Solution {
public List<String> removeInvalidParentheses(String s) {
// 左括号和右括号最少需要移除的个数
int leftRemove = 0, rightRemove = 0;
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) == '(') {
leftRemove++;
} else if (s.charAt(i) == ')') {
//存在左括号则消去一个
if (leftRemove > 0) {
leftRemove--;
} else if (leftRemove == 0) {
rightRemove++;
}
}
}
StringBuilder track = new StringBuilder();
dfs(s, 0, track, 0, 0, leftRemove, rightRemove);
return new ArrayList<>(res);
}
private Set<String> res = new HashSet<>();
private void dfs(String s, int index, StringBuilder track, int leftCnt, int rightCnt, int leftRemove, int rightRemove) {
if (s.length() == index) {
if (leftRemove == 0 && rightRemove == 0) {
res.add(track.toString());
}
return;
}
char ch = s.charAt(index);
// 选择1:删除当前字符
if (ch == '(' && leftRemove > 0) {
dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove - 1, rightRemove);
} else if (ch == ')' && rightRemove > 0) {
dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove - 1);
}
// 选择2:保留当前字符
track.append(ch);
if (ch == '(' ) {
dfs(s, index + 1, track, leftCnt + 1, rightCnt, leftRemove, rightRemove);
} else if (ch == ')' && leftCnt > rightCnt) {
dfs(s, index + 1, track, leftCnt, rightCnt + 1, leftRemove, rightRemove);
} else if (ch != '(' && ch != ')') {
dfs(s, index + 1, track, leftCnt, rightCnt, leftRemove, rightRemove);
}
track.deleteCharAt(track.length() - 1);
}
}
推荐题解:删除无效的括号
309. 最佳买卖股票时机含冷冻期
思路:动态规划
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], (i >= 2 ? dp[i - 2][0] : 0) - prices[i]);
}
return dp[len - 1][0];
}
}
312. 戳气球
思路:动态规划。推荐题解:[这个菜谱, 自己在家也能做] 关键思路解释
class Solution {
public int maxCoins(int[] nums) {
int len = nums.length;
// 前后两位各插入1
int[] newNums = new int[len + 2];
newNums[0] = 1;
newNums[len + 1] = 1;
for (int i = 0; i < len; i++) {
newNums[i + 1] = nums[i];
}
//dp[i][j] 代表 戳爆开区间 (i, j) 中的气球所能取得的最大值
//默认值都为0,base case: dp[i][j] = 0, j - i < 3
int[][] dp = new int[len + 2][len + 2];
for (int size = 3; size <= len + 2; size++) {
//开区间起始位置
for (int start = 0; start <= len + 2 - size; start++) {
int max = 0;
//列举最后一个被戳爆的气球为 newNums[k]的所有情况, K属于[start + 1, start + size - 1),找到最大值
for (int k = start + 1; k < start + size - 1; k++) {
max = Math.max(max, dp[start][k] + dp[k][start + size - 1] + newNums[start] * newNums[k] * newNums[start + size - 1]);
}
dp[start][start + size - 1] = max;
}
}
return dp[0][len + 1];
}
}
322. 零钱兑换
思路:动态规划。
class Solution {
public int coinChange(int[] coins, int amount) {
//状态:dp[i] 表示凑够i需要的最少硬币数
int[] dp = new int[amount + 1];
//求最小值,先初始为足够大。(若能凑成,最多需要amount枚硬币)
Arrays.fill(dp, amount + 1);
//base case
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
//当前背包(总金额)若能装下物品(硬币面额)
if (i >= coins[j]) {
dp[i] = Math.min(dp[i - coins[j]] + 1, dp[i]);
}
}
}
return dp[amount] >= amount + 1 ? -1 : dp[amount];
}
}
🔥 LeetCode 热题 HOT 100(71-80)的更多相关文章
- LeetCode 热题 HOT 100(05,正则表达式匹配)
LeetCode 热题 HOT 100(05,正则表达式匹配) 不够优秀,发量尚多,千锤百炼,方可成佛. 算法的重要性不言而喻,无论你是研究者,还是最近比较火热的IT 打工人,都理应需要一定的算法能力 ...
- 🔥 LeetCode 热题 HOT 100(81-90)
337. 打家劫舍 III 思路:后序遍历 + 动态规划 推荐题解:树形 dp 入门问题(理解「无后效性」和「后序遍历」) /** * Definition for a binary tree nod ...
- 🔥 LeetCode 热题 HOT 100(51-60)
142. 环形链表 II 思路:快慢指针,快慢指针相遇后,慢指针回到头,快慢指针步伐一致一起移动,相遇点即为入环点 /** * Definition for singly-linked list. * ...
- 🔥 LeetCode 热题 HOT 100(31-40)
75. 颜色分类 思路:将 2 往后放,0 往前放,剩余的1自然就放好了. 使用双指针:left.right 分别指向待插入的 0 和 2 的位置,初始 left 指向数组头,right 指向数组尾部 ...
- 🔥 LeetCode 热题 HOT 100(21-30)
46. 全排列 思路:典型回溯法 class Solution { public List<List<Integer>> permute(int[] nums) { Linke ...
- 🔥 LeetCode 热题 HOT 100(61-70)
207. 课程表 思路:根据题意可知:当课程之间不存在 环状 循环依赖时,便能完成所有课程的学习,反之则不能.因此可以将问题转换成: 判断有向图中是否存在环.使用 拓扑排序法 : 构建 入度表:记录每 ...
- 🔥 LeetCode 热题 HOT 100(41-50)
102. 二叉树的层序遍历 思路:使用队列. /** * Definition for a binary tree node. * public class TreeNode { * int val; ...
- 🔥 LeetCode 热题 HOT 100(11-20)
20. 有效的括号 class Solution { public boolean isValid(String s) { Map<Character, Character> map = ...
- 🔥 LeetCode 热题 HOT 100(1-10)
1. 两数之和 思路一:暴力遍历所有组合 class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; ...
随机推荐
- 一文读懂高速PCB设计跟高频放大电路应用当中的阻抗匹配原理
这一期课程当中,我们会重点介绍高频信号传输当中的阻抗匹配原理以及共基极放大电路在高频应用当中需要注意的问题,你将会初步了解频率与波长的基础知识.信号反射的基本原理.特性阻抗的基本概念以及怎么样为放大电 ...
- Java安全之挖掘回显链
Java安全之挖掘回显链 0x00 前言 前文中叙述反序列化回显只是为了拿到Request和Response对象.在这里说的的回显链其实就是通过一连串反射代码获取到该Request对象. 在此之前想吹 ...
- 在js中使用moment将秒转换为多少天多少小时多少分多少秒
let x = 2703750;//单位是秒 var d = moment.duration(x, 'seconds'); console.log(Math.floor(d.asDays()) + ' ...
- 29、vi和vim用法详解
vi类似于windows中的文本文件,用于普通的文本文件 vim:专家版的文件编辑器,用于shell程序型文件,带颜色,自检查语法 一般模式快捷键 O:光标到一行的首 $:光标到一行的尾 H:光标到整 ...
- JS刷新窗口的几种方式
浮层内嵌iframe及frame集合窗口,刷新父页面的多种方法 <script language=JavaScript> parent.location.reload(); ...
- hdu 1540 Tunnel Warfare 线段树 区间合并
题意: 三个操作符 D x:摧毁第x个隧道 R x:修复上一个被摧毁的隧道,将摧毁的隧道入栈,修复就出栈 Q x:查询x所在的最长未摧毁隧道的区间长度. 1.如果当前区间全是未摧毁隧道,返回长度 2. ...
- 适合企业的CRM系统选型法则?
在市场竞争激烈的今天,企业需要找到一款好用的企业CRM系统来帮助维护客户关系,同时也能够帮助企业进行销售管理.营销管理,CRM可以说是当代企业管理的最强工具之一.那么适合企业的CRM客户管理系统要如何 ...
- MyBatis框架的使用解析!数据库相关API的基本介绍
动态SQL if 根据条件包含where子句的一部分 <select id="findActiveBlogLike" resultType="Blog"& ...
- linux学习之路第三天(vim和vi使用)
vi和vim编辑器 vi和vim的三种常见模式 1.正常模式 在正常模式下,我们可以使用快捷键 以vim打开一个档案就直接进入一般模式了(这是默认的模式).在这个模式中,你可以使用 上下左右按键来移动 ...
- Java程序设计(2021春)——第二章课后题(选择题+编程题)答案与详解
Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 目录 Java程序设计(2021春)--第二章课后题(选择题+编程题)答案与详解 第二章选择题 2.1 面向对象方法的特性 ...