春招|【Android春招每日一练】(三十二) LeetCode Hot 10题


文章目录

    • 概览
    • 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 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
春招|【Android春招每日一练】(三十二) LeetCode Hot 10题
文章图片

//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.后面中困难度的题还是需要好好理解,至少思路要完全搞明白。

    推荐阅读