文章目录
-
- 概览
- LeetCode Hot
-
-
- 2.71 移动零
- 2.72 寻找重复数
- 2.73 二叉树的序列化与反序列化
- 2.74 最长递增子序列
- 2.75 最佳买卖股票时机含冷冻期
- 2.76 戳气球
- 2.77 零钱兑换
- 2.78 打家劫舍 III
- 2.79 比特位计数
- 2.80 前 K 个高频元素
-
- 总结
概览 LeetCode Hot:移动零、寻找重复数、二叉树的序列化与反序列化、最长递增子序列、最佳买卖股票时机含冷冻期、戳气球、零钱兑换、打家劫舍 III、比特位计数、前 K 个高频元素
LeetCode Hot 2.71 移动零 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
//快排思想
//遍历数组,遇到非零数字放到数组前面(交换),交换完后末尾自动全是0
class Solution {
public void moveZeroes(int[] nums) {
if(nums == null) return;
int j = 0;
//指针从下标0依次递增,依次存放非零数字
for(int i = 0;
i < nums.length;
i++){
if(nums[i] != 0){ //非零数字交换
int tmp = nums[i];
//先保存i(后面一个指针)位置数字
nums[i] = nums[j];
nums[j] = tmp;
j++;
}
}
}
}
2.72 寻找重复数 给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:输入:nums = [1,3,4,2,2]
输出:2
//转换为环形链表快慢指针解法
/*
如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,我们将数组下标 n 和数 nums[n] 建立一个映射关系f(n),
其映射关系 n->f(n) 为:
0->1
1->3
2->4
3->2
4->2
同样的,我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再用这个函数计算,以此类推产生一个类似链表一样的序列。从理论上讲,数组中如果有重复的数,那么就会产生多对一的映射,这样,形成的链表就一定会有环路了
1.数组中有一个重复的整数 <==> 链表中存在环
2.找到数组中的重复整数 <==> 找到链表的环入口
慢指针走一步 slow = slow.next ==> 本题 slow = nums[slow]
快指针走两步 fast = fast.next.next ==> 本题 fast = nums[nums[fast]]
*/
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0;
int fast = 0;
slow = nums[slow];
fast = nums[nums[fast]];
while(slow != fast){
slow = nums[slow];
fast = nums[nums[fast]];
}
int pre1 = 0;
int pre2 = slow;
while(pre1 != pre2){
pre1 = nums[pre1];
pre2 = nums[pre2];
}
return pre1;
}
}
2.73 二叉树的序列化与反序列化 【春招|【Android春招每日一练】(三十二) LeetCode Hot 10题】同剑指offer36题
2.74 最长递增子序列 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
//DP
/*dp[i] 的值代表nums以nums[i]结尾的最长子序列长度
设j∈[0,i),考虑每轮计算新dp[i] 时,遍历[0,i) 列表区间,做以下判断:
当 nums[i]>nums[j] 时:nums[i] 可以接在nums[j] 之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j]+1 ;
当 nums[i]<=nums[j] 时:nums[i] 无法接在nums[j] 之后,此情况上升子序列不成立,跳过。
*/
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0;
i < nums.length;
i++) {
for(int j = 0;
j < i;
j++) {
if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
2.75 最佳买卖股票时机含冷冻期 给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:输入: prices = [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
//DP
class Solution {
public int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
// f0: 手上持有股票的最大收益
// f1: 手上不持有股票,并且处于冷冻期中的累计最大收益
// f2: 手上不持有股票,并且不在冷冻期中的累计最大收益
int n = prices.length;
int f0 = -prices[0];
int f1 = 0;
int f2 = 0;
for (int i = 1;
i < n;
++i) {
//我们目前持有的这一支股票可以是在第 i?1 天就已经持有的,对应的状态为 f0;或者是第 i 天买入的,那么第 i?1 天就不能持有股票并且不处于冷冻期中,对应的状态为 f2 加上买入股票的负收益 prices[i]
int newf0 = Math.max(f0, f2 - prices[i]);
//我们在第 i 天结束之后处于冷冻期的原因是在当天卖出了股票,那么说明在第 i?1 天时我们必须持有一支股票,对应的状态为 f0 加上卖出股票的正收益prices[i]
int newf1 = f0 + prices[i];
//我们在第 i 天结束之后不持有任何股票并且不处于冷冻期,说明当天没有进行任何操作,即第 i?1 天时不持有任何股票:如果处于冷冻期,对应的状态为 f1;如果不处于冷冻期,对应的状态为 f2。
int newf2 = Math.max(f1, f2);
f0 = newf0;
f1 = newf1;
f2 = newf2;
}return Math.max(f1, f2);
}
}
2.76 戳气球 有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
示例 1:
输入:nums = [3,1,5,8]
输出:167
解释:
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins =3*1*5+3*5*8+1*3*8+ 1*8*1 = 167
//DPhard题,有点难理解
class Solution {
public int maxCoins(int[] nums) {
int n = nums.length;
// 创建一个辅助数组,并在首尾各添加1,方便处理边界情况
int[] temp = new int[n+2];
temp[0] = 1;
temp[n+1] = 1;
for(int i=0;
i
2.77 零钱兑换 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
//DP
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1;
i <= amount;
i++) {
for (int j = 0;
j < coins.length;
j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
//从枚举的这枚硬币面值转移,再加上本身这一枚的贡献
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
2.78 打家劫舍 III 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
文章图片
//DP
/*使用一个大小为 2 的数组来表示 int[] res = new int[2];
0 代表不偷,1 代表偷
任何一个节点能偷到的最大钱的状态可以定义为:
当前节点选择不偷:当前节点能偷到的最大钱数 = 左孩子能偷到的钱 + 右孩子能偷到的钱
当前节点选择偷:当前节点能偷到的最大钱数 = 左孩子选择自己不偷时能得到的钱 + 右孩子选择不偷时能得到的钱 + 当前节点的钱数
*/
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left);
int[] right = robInternal(root.right);
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
result[1] = left[0] + right[0] + root.val;
return result;
}
2.79 比特位计数 给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
示例 1:输入:n = 2
输出:[0,1,1]
解释:
0 --> 0
1 --> 1
2 --> 10
class Solution {
public int[] countBits(int n) {
int[] res = new int[n+1];
for(int i = 0;
i <= n;
i++){
if(i % 2 == 1){//奇数的1的个数=前一个数(偶数)+1
res[i] = res[i-1] + 1;
}else{
res[i] = res[i/2];
//偶数的1的个数=当前数/2的数的1的个数(因为偶数二进制第一位为0,除以2相当于右移一位)
}
}
return res;
}
}
2.80 前 K 个高频元素 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例 1:输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
//HashMap + 堆
/*
首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」。找出原数组的前 k个高频元素,就相当于找出「出现次数数组」的前 k 大的值。建立一个小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。
如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。
*/
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map occurrences = new HashMap();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue queue = new PriorityQueue(new Comparator() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0;
i < k;
++i) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
总结 1.后面中困难度的题还是需要好好理解,至少思路要完全搞明白。
推荐阅读
- 春招|【Android春招每日一练】(三十四) LeetCode Hot 5题+总结(完)
- Android|关于 Gradle 你应该知道的知识点
- Android|实例说明 Android 多线程、多进程与全局变量之间的关系
- sqlite|Jetpack(七)—— Room
- 算法|【路径规划】基于蚁群算法求解栅格地图路径规划问题matlab源码含GUI
- 算法|【TSP问题】基于蚁群算法求解TSP问题matlab源码