【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 二叉树的序列化与反序列化

同剑指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
//DP   hard题,有点难理解
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<n; i++){
            temp[i+1] = nums[i];
        }
        int[][] dp = new int[n+2][n+2];
        // len表示开区间长度
        for(int len=3; len<=n+2; len++){
            // i表示开区间左端点
            for(int i=0; i<=n+2-len; i++){
                int res = 0; 
                // k为开区间内的索引
                for(int k = i+1; k<i+len-1; k++){
                    int left = dp[i][k];
                    int right = dp[k][i+len-1];
                    res = Math.max(res, left + temp[i]*temp[k]*temp[i+len-1] + right);
                }
                dp[i][i+len-1] = res;
            }
        }
        return dp[0][n+1];
    }
}

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题_第1张图片

//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<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
            public int compare(int[] m, int[] n) {
                return m[1] - n[1];
            }
        });
        for (Map.Entry<Integer, Integer> 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.后面中困难度的题还是需要好好理解,至少思路要完全搞明白。

你可能感兴趣的:(春招,leetcode,android,java)