自动前几天笔试阿里之后,我痛定思痛!为啥总共两个题目,你就整出来一个,而且Case通过率0.00%? 这不是完败吗!
所以我开始我的刷题 目标是好多好多题目,LeetCode还有剑指Offer,加油。
现在开始整理 LeetCode杯 2020 春季个人赛
第1题来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/na-ying-bi/
第2题来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/chuan-di-xin-xi
第3题来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ju-qing-hong-fa-shi-jian/
第4题来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zui-xiao-tiao-yue-ci-shu/
第5题来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/er-cha-shu-ren-wu-diao-du/
第1题:拿硬币
桌上有 n 堆力扣币,每堆的数量保存在数组 coins 中。我们每次可以选择任意一堆,拿走其中的一枚或者两枚,求拿完所有力扣币的最少次数。
示例 1:
输入:[4,2,1]
输出:4
解释:第一堆力扣币最少需要拿 2 次,第二堆最少需要拿 1 次,第三堆最少需要拿 1 次,总共 4 次即可拿完。
示例 2:
输入:[2,3,10]
输出:8
限制:
- 1 <= n <= 4
- 1 <= coins[i] <= 10
题目分析
1.保存在数组之中,可以拿任意一堆,然后每次1-2个硬币。那就是先按照多的拿?剩下的1就一次,如果只有只就拿一次
2.伪代码 如下
- 在数组外定义总共的次数 count = 0 循环遍历数组,取出每个位置的值
1.1 把取出的值 取模 2 if(coins[i]%2==0) 就是偶数,需要拿 count += coins[i] / 2
1.2 如果是 if(coins[i]%2==1) 就是奇数,需要拿 count += coins[i] / 2 +1 因为 Java中去整数会向下取整,所以需要多加一个1- 返回结果 count
3.代码实现 minCount2 是装x写法,本质一样
public class _2020_04_18_01_TakeCoins {
public static void main(String[] args) {
int[] num = {4, 2, 1};
int result = minCount2(num);
System.out.println(result);
}
public static int minCount2(int[] coins) {
int resultCounts = 0;
for (int i = 0; i < coins.length; i++) {
resultCounts += (coins[i] % 2 == 0) ? (coins[i] / 2) : (coins[i] / 2 + 1);
}
return resultCounts;
}
public static int minCount(int[] coins) {
if (coins == null || coins.length < 1 || coins.length > 4) {
throw new RuntimeException("参数不合法");
}
int resultCounts = 0;
for (int i = 0; i < coins.length; i++) {
if (coins[i] < 1 || coins[i] > 10) {
throw new RuntimeException("参数不合法");
}
if (coins[i] % 2 == 0) {
//如果等于0 就是偶数 就需要拿偶数次
resultCounts += coins[i] / 2;
} else {
//是奇数
resultCounts += coins[i] / 2 + 1;
}
}
return resultCounts;
}
}
第2题:传递信息
小朋友 A 在和 ta 的小伙伴们玩传信息游戏,游戏规则如下:
- 有 n 名玩家,所有玩家编号分别为 0 ~ n-1,其中小朋友 A 的编号为 0
- 每个玩家都有固定的若干个可传信息的其他玩家(也可能没有)。传信息的关系是单向的(比如 A 可以向 B 传信息,但 B 不能向 A 传信息)。
- 每轮信息必须需要传递给另一个人,且信息可重复经过同一个人
给定总玩家数 n,以及按 [玩家编号,对应可传递玩家编号] 关系组成的二维数组 relation。返回信息从小 A (编号 0 ) 经过 k 轮传递到编号为 n-1 的小伙伴处的方案数;若不能到达,返回 0。
示例 1:
输入:n = 5, relation = [[0,2],[2,1],[3,4],[2,3],[1,4],[2,0],[0,4]], k = 3
输出:3
解释:信息从小 A 编号 0 处开始,经 3 轮传递,到达编号 4。共有 3 种方案,分别是 0->2->0->4, 0->2->1->4, 0->2->3->4。
示例 2:
输入:n = 3, relation = [[0,2],[2,1]], k = 2
输出:0
解释:信息不能从小 A 处经过 2 轮传递到编号 2
限制:
- 2 <= n <= 10
- 1 <= k <= 5
- 1 <= relation.length <= 90, 且 relation[i].length == 2
- 0 <= relation[i][0],relation[i][1] < n 且 relation[i][0] != relation[i][1]
题目分析
我当时在做这个题目的时候,真的是毫无头绪,感觉像是图,然后又在纸上画树,图的遍历,不会了,阿里的笔试也是死于图的遍历。 这题就是DFS算法,话不多说,
直接上代码了,我也解释不清楚。
代码实现
public class _2020_04_18_02_TransmitInformation {
public static void main(String[] args) {
int n = 5, k = 3;
int[][] relation = {{0, 2}, {2, 1}, {3, 4}, {2, 3}, {1, 4}, {2, 0}, {0, 4}};
int result = numWays(n, relation, k);
System.out.println(result);
}
public static int numWays(int n, int[][] relation, int k) {
return dfs(n, relation, k, n - 1);
}
public static int dfs(int n, int[][] relation, int k, int tail) {
if (k == 1) {
int count = 0;
for (int i = 0; i < relation.length; i++) {
if (relation[i][1] == tail && relation[i][0] == 0) {
count++;
}
}
return count;
}
int count = 0;
for (int i = 0; i < relation.length; i++) {
int time = 0;
if (relation[i][1] == tail) {
time += dfs(n, relation, k - 1, relation[i][0]);
}
count += time;
}
return count;
}
}
第3题:剧情触发时间
在战略游戏中,玩家往往需要发展自己的势力来触发各种新的剧情。一个势力的主要属性有三种,分别是文明等级(C),资源储备(R)以及人口数量(H)。在游戏开始时(第 0 天),三种属性的值均为 0。
随着游戏进程的进行,每一天玩家的三种属性都会对应增加,我们用一个二维数组 increase 来表示每天的增加情况。这个二维数组的每个元素是一个长度为 3 的一维数组,例如 [[1,2,1],[3,4,2]] 表示第一天三种属性分别增加 1,2,1 而第二天分别增加 3,4,2。
所有剧情的触发条件也用一个二维数组 requirements 表示。这个二维数组的每个元素是一个长度为 3 的一维数组,对于某个剧情的触发条件 c[i], r[i], h[i],如果当前 C >= c[i] 且 R >= r[i] 且 H >= h[i] ,则剧情会被触发。
根据所给信息,请计算每个剧情的触发时间,并以一个数组返回。如果某个剧情不会被触发,则该剧情对应的触发时间为 -1 。
示例 1:
输入: increase = [[2,8,4],[2,5,0],[10,9,8]] requirements = [[2,11,3],[15,10,7],[9,17,12],[8,1,14]]
输出: [2,-1,3,-1]
解释:
初始时,C = 0,R = 0,H = 0
第 1 天,C = 2,R = 8,H = 4
第 2 天,C = 4,R = 13,H = 4,此时触发剧情 0
第 3 天,C = 14,R = 22,H = 12,此时触发剧情 2
剧情 1 和 3 无法触发。
示例 2:
输入: increase = [[0,4,5],[4,8,8],[8,6,1],[10,10,0]] requirements = [[12,11,16],[20,2,6],[9,2,6],[10,18,3],[8,14,9]]
输出: [-1,4,3,3,3]
示例 3:
输入: increase = [[1,1,1]] requirements = [[0,0,0]]
输出: [0]
限制:
- 1 <= increase.length <= 10000
- 1 <= requirements.length <= 100000
- 0 <= increase[i] <= 10
- 0 <= requirements[i] <= 100000
题目分析
这个题目我当时看的第一眼就是想到,把要求计算出来,然后一个一个去减法,求出值,但是有一个地方一直做不做来,就是控制指定天数是指定的方案。然后评论区的大哥们有的是使用二分法,我糊涂了,没明白。
代码演示
public class _2020_04_18_03_PlotTriggerTime {
public static void main(String[] args) {
int[][] increase = {{2, 8, 4}, {2, 5, 0}, {10, 9, 8}};
int[][] requirements = {{2, 11, 3}, {15, 10, 7}, {9, 17, 12}, {8, 1, 14}};
int[] triggerTime = getTriggerTime2(increase, requirements);
for (int trig : triggerTime) {
System.out.print(trig + " ");
}
}
public static int[] getTriggerTime(int[][] increase, int[][] requirements) {
int[] result = new int[requirements.length];
Arrays.fill(result, -1);
for (int i = 1; i < increase.length; i++) {
increase[i][0] += increase[i - 1][0];
increase[i][1] += increase[i - 1][1];
increase[i][2] += increase[i - 1][2];
}
for (int i = 0; i < requirements.length; i++) {
if (isEqual(requirements[i], new int[]{0, 0, 0})) {
result[i] = 0;
}
}
for (int i = 0; i < requirements.length; i++) {
if (result[i] != -1) {
continue;
}
int left = 0;
int right = increase.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (isDay(increase[mid], requirements[i])) {
right = mid - 1;
} else {
left = mid + 1;
}
if (left >= 0 && left < increase.length && isDay(increase[left], requirements[i])) {
result[i] = left + 1;
}
}
}
return result;
}
public static boolean isDay(int[] incr, int req[]) {
return incr[0] >= req[0] && incr[1] >= req[1] && incr[2] >= req[2];
}
public static boolean isEqual(int[] incr, int[] req) {
return incr[0] == req[0] && incr[1] == req[1] && incr[2] == req[2];
}
public static int[] getTriggerTime2(int[][] increase, int[][] requirements) {
Integer[] ans = new Integer[requirements.length];
for (int day = 0; day <= increase.length; day++) {
boolean hasRemain = false;
for (int i = 0; i < requirements.length; i++) {
if (day == 0) {
if (requirements[i][0] == 0 && requirements[i][1] == 0 && requirements[i][2] == 0) {
ans[i] = 0;
} else {
continue;
}
}
if (ans[i] != null) {
continue;
}
hasRemain = true;
requirements[i][0] -= increase[day - 1][0];
requirements[i][1] -= increase[day - 1][1];
requirements[i][2] -= increase[day - 1][2];
if (requirements[i][0] <= 0 && requirements[i][1] <= 0 && requirements[i][2] <= 0) {
ans[i] = day;
}
}
if (day != 0 && !hasRemain) {
break;
}
}
int[] realAns = new int[requirements.length];
for (int i = 0; i < ans.length; i++) {
if (ans[i] == null) {
realAns[i] = -1;
} else {
realAns[i] = ans[i];
}
}
return realAns;
}
}
PS:关于二分查找法 给个 不错的文章 https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/er-fen-cha-zhao-xiang-jie
第4题:最小跳跃次数
为了给刷题的同学一些奖励,力扣团队引入了一个弹簧游戏机。游戏机由 N 个特殊弹簧排成一排,编号为 0 到 N-1。初始有一个小球在编号 0 的弹簧处。若小球在编号为 i 的弹簧处,通过按动弹簧,可以选择把小球向右弹射 jump[i] 的距离,或者向左弹射到任意左侧弹簧的位置。也就是说,在编号为 i 弹簧处按动弹簧,小球可以弹向 0 到 i-1 中任意弹簧或者 i+jump[i] 的弹簧(若 i+jump[i]>=N ,则表示小球弹出了机器)。小球位于编号 0 处的弹簧时不能再向左弹。
为了获得奖励,你需要将小球弹出机器。请求出最少需要按动多少次弹簧,可以将小球从编号 0 弹簧弹出整个机器,即向右越过编号 N-1 的弹簧。
示例 1:
输入:jump = [2, 5, 1, 1, 1, 1
输出:3
解释:小 Z 最少需要按动 3 次弹簧,小球依次到达的顺序为 0 -> 2 -> 1 -> 6,最终小球弹出了机器。
限制:
- 1 <= jump.length <= 10^6
- 1 <= jump[i] <= 10000
题目分析
这个是个动态规划题目,我又不会了,来看大佬的解答
从右向左计算dp值(从后向前),当前位置如果为i 则它如果直接跳到右边(前面)去就是dp[jump[i]+i]+1(这个值已经计算过了),计算出当前位置dp[i]之后,当前位置i可以影响 i+1到dp[j] >= dp[i]+1位置上的值(因为某个位置可以跳到左边任意位置)注意遍历到dp[j]>=dp[i]+1即可。
作者:dongyangliu
链接:https://leetcode-cn.com/problems/zui-xiao-tiao-yue-ci-shu/solution/jian-dan-yi-dong-de-dong-tai-gui-hua-wan-fa-by-don/
来源:力扣(LeetCode)
代码演示
public class _2020_04_18_04_MinimumNumberOfJumps {
public static void main(String[] args) {
int[] jump = {2, 5, 1, 1, 1, 1};
System.out.println(minJump(jump));
}
public static int minJump(int[] jump) {
//dp[]数组 每一位 代表该位到跳出的最少次数
int[] dp = new int[jump.length];
dp[jump.length - 1] = 1;
for (int i = jump.length - 2; i >= 0; i--) {
//遍历当前位置更新后影响到的后面的位置,只需要更新到dp[j] >= dp[i] + 1 即可
//如果遍历到某dp[j]<dp[i]+1就不需要向右遍历了,因为j到dp.length的值会被当前遍历到的dp[j]更新而不是dp[i]+1
dp[i] = jump[i] + i >= jump.length ? 1 : dp[jump[i] + i] + 1;
for (int j = i + 1; j < dp.length && dp[j] >= dp[i] + 1; j++) {
dp[j] = dp[i] + 1;
}
}
System.out.println(Arrays.toString(dp));
return dp[0];
}
}
第5题:二叉树任务调度
任务调度优化是计算机性能优化的关键任务之一。在任务众多时,不同的调度策略可能会得到不同的总体执行时间,因此寻求一个最优的调度方案是非常有必要的。
通常任务之间是存在依赖关系的,即对于某个任务,你需要先完成他的前导任务(如果非空),才能开始执行该任务。我们保证任务的依赖关系是一棵二叉树,其中 root 为根任务,root.left 和 root.right 为他的两个前导任务(可能为空),root.val 为其自身的执行时间。
在一个 CPU 核执行某个任务时,我们可以在任何时刻暂停当前任务的执行,并保留当前执行进度。在下次继续执行该任务时,会从之前停留的进度开始继续执行。暂停的时间可以不是整数。
现在,系统有两个 CPU 核,即我们可以同时执行两个任务,但是同一个任务不能同时在两个核上执行。给定这颗任务树,请求出所有任务执行完毕的最小时间。
示例 1:
输入:root = [47, 74, 31]
输出:121
解释:根节点的左右节点可以并行执行31分钟,剩下的43+47分钟只能串行执行,因此总体执行时间是121分钟。
示例 2:
输入:root = [15, 21, null, 24, null, 27, 26]
输出:87
示例3:
输入:root = [1,3,2,null,null,4,4]
输出:7.5
限制:
- 1 <= 节点数量 <= 1000
- 1 <= 单节点执行时间 <= 1000
思路分析
我不会我就CV了一份来emmm 大佬真的强
解题思路
任务调度存在依赖关系,任何一个node节点任务,都依赖于node.left和node.right前置节点任务的完成。所以,这是一个很明显的后序遍历思路。
这里很多人应该都会想到递归左右子树,获取左右子树的最小执行时间的较大值+当前节点的时间,得到解。
于是乎,卡在了测试用例3的7.5中。
上面思想类似贪心选择,局部最优,统一起来,不一定是整体最优。因为任务是可以暂时中断的,在这个情况下,我们可以把所有的前置任务看成一个整体来思考,摒弃细节,这样思路会清晰一些。
其他题解也说了大体的思路,就是如果left>right,就要把left多出来的串行时间往right的并行时间里摊,反之亦然。
这里我谈一下自己的理解,也许存在问题,也希望各位大佬可以指正。
首先,假设不存在多个cpu的情况(即只有一个cpu),要执行完所有的前置任务,那前置任务的总时间肯定是preTime = sum(node.left) + sum(node.right)preTime=sum(node.left)+sum(node.right)。这时,我们将preTime看成一个整体。
那么现在变成双核CPU了。一个整体的preTime开始双核并行了,那么一个整体下的preTime是多少呢?
很显然,是preTime/2
是不是豁然开朗了?在前置任务最为理想的情况下,所有任务都能一直并行,不存在串行时间。这个类似于动态规划的前置条件。至于前置任务再往前的任务,和任务它到底是怎么分配时间的,怎么去分摊时间,我们并不关心,总之,你能在preTime/2的时间内完成,就OK了。如果CPU核心更多,就是preTime/n。
当然,前置任务最优的解preTime/2,是一个理想情况,具体的任务执行,是非常有可能达不到这种情况。比如题目的测试用例1。
所以,每个节点的任务执行时间的最小值,应该是Max(time(node.left),time(node.right),preTime/2) + node.valMax(time(node.left),time(node.right),preTime/2)+node.val。
即左子树执行完成的最小时间、右子树执行完成的最小时间、左右子树全部节点并行执行的时间,三者的最大值,再加上当前节点的任务时间。
最后将最优时间往根节点递推。抵达根节点后的最优解,就是全局的最优解。
作者:burning-summer
链接:https://leetcode-cn.com/problems/er-cha-shu-ren-wu-diao-du/solution/hou-xu-bian-li-di-gui-fan-hui-zuo-you-zi-shu-zui-y/
来源:力扣(LeetCode)
代码实现
public class _2020_04_18_05_BinaryTreeTaskScheduling {
/**
* LCP 10. 二叉树任务调度
*
* @param root
* @return
*/
public double minimalExecTime(TreeNode root) {
double[] res = execTime(root, 2);
return res[0];
}
/**
* 获取node最小执行时间
*
* @param node node
* @param n 并行数
* @return [0]执行完当前节点最小耗时,[1]当前node为根的时间串行之和
*/
private double[] execTime(TreeNode node, int n) {
if (node == null) {
// [0]执行完当前节点最小耗时,[1]当前node为根的时间串行之和
return new double[]{0.0D, 0.0D};
}
// 获取左右子树的值
double[] leftTime = execTime(node.left, n);
double[] rightTime = execTime(node.right, n);
// 左右子树节点之和
double sum = leftTime[1] + rightTime[1];
// 当前节点执行完的最小消耗时间
double minTime = Math.max(Math.max(leftTime[0], rightTime[0]), sum / n) + node.val;
return new double[]{minTime, sum + node.val};
}
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}