时间:2022-02-05 22:30-24:00
结局:
5984. 拆分数位后四位数字的最小和
难度:简单
给你一个四位 正 整数 num
。请你使用 num
中的 数位 ,将 num
拆成两个新的整数 new1
和 new2
。new1
和 new2
中可以有 前导 0 ,且 num
中 所有 数位都必须使用。
- 比方说,给你
num = 2932
,你拥有的数位包括:两个2
,一个9
和一个3
。一些可能的[new1, new2]
数对为[22, 93]
,[23, 92]
,[223, 9]
和[2, 329]
。
请你返回可以得到的 new1
和 new2
的 最小 和。
示例 1:
输入:num = 2932 输出:52 解释:可行的 [new1, new2] 数对为 [29, 23] ,[223, 9] 等等。 最小和为数对 [29, 23] 的和:29 + 23 = 52 。
示例 2:
输入:num = 4009 输出:13 解释:可行的 [new1, new2] 数对为 [0, 49] ,[490, 0] 等等。 最小和为数对 [4, 9] 的和:4 + 9 = 13 。
提示:
1000 <= num <= 9999
思路:从题目中拿到的思路是很直观的,考虑到题中的各种限制,以及num的取值范围,也没有任何特殊情况需要处理。简单的排序后,将最小的两个数字放在十位,余下放在个位,拿到和即可。
比赛解答:
class Solution {
public int minimumSum(int num) {
List<Integer> list = new ArrayList<>();
while (num > 0) {
list.add(num % 10);
num = num / 10;
}
Collections.sort(list);
return 10 * list.get(0) + 10 * list.get(1) + list.get(2) + list.get(3);
}
}
后续整理:事实上,部分高手对于num各数位的取值,是通过String来做的,可以避免在循环上犯错,但整个思路是不变的。
class Solution {
public int minimumSum(int num) {
String s = String.valueOf(num);
List<Integer> list = new ArrayList<>();
for (int i = 0; i < s.length(); i++) {
list.add(s.charAt(i) - '0');
}
Collections.sort(list);
return 10 * list.get(0) + 10 * list.get(1) + list.get(2) + list.get(3);
}
}
5985. 根据给定数字划分数组
难度:中等
给你一个下标从 0 开始的整数数组 nums
和一个整数 pivot
。请你将 nums
重新排列,使得以下条件均成立:
- 所有小于
pivot
的元素都出现在所有大于pivot
的元素 之前 。 - 所有等于
pivot
的元素都出现在小于和大于pivot
的元素 中间 。 - 小于
pivot
的元素之间和大于pivot
的元素之间的 相对顺序 不发生改变。- 更正式的,考虑每一对
pi
,pj
,pi
是初始时位置i
元素的新位置,pj
是初始时位置j
元素的新位置。对于小于pivot
的元素,如果i < j
且nums[i] < pivot
和nums[j] < pivot
都成立,那么pi < pj
也成立。类似的,对于大于pivot
的元素,如果i < j
且nums[i] > pivot
和nums[j] > pivot
都成立,那么pi < pj
。
- 更正式的,考虑每一对
请你返回重新排列 nums
数组后的结果数组。
示例 1:
输入:nums = [9,12,5,10,14,3,10], pivot = 10 输出:[9,5,3,10,10,12,14] 解释: 元素 9 ,5 和 3 小于 pivot ,所以它们在数组的最左边。 元素 12 和 14 大于 pivot ,所以它们在数组的最右边。 小于 pivot 的元素的相对位置和大于 pivot 的元素的相对位置分别为 [9, 5, 3] 和 [12, 14] ,它们在结果数组中的相对顺序需要保留。
示例 2:
输入:nums = [-3,4,3,2], pivot = 2 输出:[-3,2,4,3] 解释: 元素 -3 小于 pivot ,所以在数组的最左边。 元素 4 和 3 大于 pivot ,所以它们在数组的最右边。 小于 pivot 的元素的相对位置和大于 pivot 的元素的相对位置分别为 [-3] 和 [4, 3] ,它们在结果数组中的相对顺序需要保留。
提示:
1 <= nums.length <= 105
-106 <= nums[i] <= 106
-
pivot
等于nums
中的一个元素。
思路:刚开始以为是快排的一个index,再将各个值分列两边即可,但细思后并没有那么简单,因为这样完全无法保证数字间的相对顺序。如果使用双指针,也很难合理的把各个值的位置放好。考虑到尽量在O(n)的时间复杂度内完成,便急急忙忙的想了一个先遍历计数,提前统计好等于和小于pivot的数字个数,再循环一次,分别放置大于等于小于pivot三种情况的数字的位置。
比赛解答:
class Solution {
public int[] pivotArray(int[] nums, int pivot) {
int count1 = 0;
int count2 = 0;
for (int i : nums) {
if (i < pivot) {
count1++;
}
if (i == pivot) {
count2++;
}
}
int[] res = new int[nums.length];
int t1 = 0;
int t2 = count1;
int t3 = count1 + count2;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < pivot) {
res[t1++] = nums[i];
} else if (nums[i] == pivot) {
res[t2++] = nums[i];
} else if (nums[i] > pivot) {
res[t3++] = nums[i];
}
}
return res;
}
}
后续整理:事实上,大部分思路还是一致的,不过代码可以简化很多,一个思路方向是,一次循环,通过三个list分别存储大于等于小于pivot三种情况的数字,然后转化为结果。当然还有arignote大佬的实力解答,学习中。
class Solution {
public int[] pivotArray(int[] nums, int pivot) {
return Arrays.stream(nums).boxed()
.sorted((o, p) -> Integer.compare(Integer.compare(o, pivot),
Integer.compare(p, pivot))).mapToInt(i -> i).toArray();
}
}
5986. 设置时间的最少代价
难度:中等
常见的微波炉可以设置加热时间,且加热时间满足以下条件:
- 至少为
1
秒钟。 - 至多为
99
分99
秒。
你可以 最多 输入 4 个数字 来设置加热时间。如果你输入的位数不足 4 位,微波炉会自动加 前缀 0 来补足 4 位。微波炉会将设置好的四位数中,前 两位当作分钟数,后 两位当作秒数。它们所表示的总时间就是加热时间。比方说:
- 你输入
9
5
4
(三个数字),被自动补足为0954
,并表示9
分54
秒。 - 你输入
0
0
0
8
(四个数字),表示0
分8
秒。 - 你输入
8
0
9
0
,表示80
分90
秒。 - 你输入
8
1
3
0
,表示81
分30
秒。
给你整数 startAt
,moveCost
,pushCost
和 targetSeconds
。一开始,你的手指在数字 startAt
处。将手指移到 任何其他数字 ,需要花费 moveCost
的单位代价。每 输入你手指所在位置的数字一次,需要花费 pushCost
的单位代价。
要设置 targetSeconds
秒的加热时间,可能会有多种设置方法。你想要知道这些方法中,总代价最小为多少。
请你能返回设置 targetSeconds
秒钟加热时间需要花费的最少代价。
请记住,虽然微波炉的秒数最多可以设置到 99
秒,但一分钟等于 60
秒。
示例 1:
输入:startAt = 1, moveCost = 2, pushCost = 1, targetSeconds = 600 输出:6 解释:以下为设置加热时间的所有方法。 - 1 0 0 0 ,表示 10 分 0 秒。 手指一开始就在数字 1 处,输入 1 (代价为 1),移到 0 处(代价为 2),输入 0(代价为 1),输入 0(代价为 1),输入 0(代价为 1)。 总代价为:1 + 2 + 1 + 1 + 1 = 6 。这是所有方案中的最小代价。 - 0 9 6 0,表示 9 分 60 秒。它也表示 600 秒。 手指移到 0 处(代价为 2),输入 0 (代价为 1),移到 9 处(代价为 2),输入 9(代价为 1),移到 6 处(代价为 2),输入 6(代价为 1),移到 0 处(代价为 2),输入 0(代价为 1)。 总代价为:2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 = 12 。 - 9 6 0,微波炉自动补全为 0960 ,表示 9 分 60 秒。 手指移到 9 处(代价为 2),输入 9 (代价为 1),移到 6 处(代价为 2),输入 6(代价为 1),移到 0 处(代价为 2),输入 0(代价为 1)。 总代价为:2 + 1 + 2 + 1 + 2 + 1 = 9 。
示例 2:
输入:startAt = 0, moveCost = 1, pushCost = 2, targetSeconds = 76 输出:6 解释:最优方案为输入两个数字 7 6,表示 76 秒。 手指移到 7 处(代价为 1),输入 7 (代价为 2),移到 6 处(代价为 1),输入 6(代价为 2)。总代价为:1 + 2 + 1 + 2 = 6 其他可行方案为 0076 ,076 ,0116 和 116 ,但是它们的代价都比 6 大。
提示:
0 <= startAt <= 9
1 <= moveCost, pushCost <= 105
1 <= targetSeconds <= 6039
思路:这题着实有点搞心态,单读题就花了两三分钟。读明白后,很容易就知道,这不是一道算法,而是一个解决实际问题的方案。仔细分析时间,意识到,对于输入的任何秒数,实际上最多只有两种表示方式(易得示例1中的0960一定不会比960优秀)。且只有秒数在40以内的才有两种,例如9分30秒可以表示为8分90秒,但9分40秒,无法表示成8分100秒,特殊的,即使是9分0秒,也无法表示成7分120秒,这都是很快就get到了的。为了避免被一些特殊情况干扰,所以单独把60秒内和99分--5940秒以上的情况都做特殊处理。
比赛解答:(事实上,大于5940这个条件是有问题的,不过案例通过了,应该是大于5979)
class Solution {
public int minCostSetTime(int startAt, int moveCost, int pushCost, int targetSeconds) {
if (targetSeconds < 60) {
return getCost(0, targetSeconds, startAt, moveCost, pushCost);
} else if (targetSeconds > 5940) {
return getCost(99, targetSeconds - 99 * 60, startAt, moveCost, pushCost);
}
int seconds = targetSeconds % 60;
int minutes = targetSeconds / 60;
if (seconds >= 40) {
return getCost(minutes, seconds, startAt, moveCost, pushCost);
} else {
return Math.min(getCost(minutes, seconds, startAt, moveCost, pushCost), getCost(minutes - 1, seconds + 60, startAt, moveCost, pushCost));
}
}
private int getCost(int minutes, int targetSeconds, int startAt, int moveCost, int pushCost) {
int num = minutes * 100 + targetSeconds;
List<Integer> list = new ArrayList<>();
while (num > 0) {
list.add(num % 10);
num = num / 10;
}
Collections.reverse(list);
int res = 0;
if (list.get(0) == startAt) {
res += pushCost;
} else {
res += (moveCost + pushCost);
}
for (int i = 1; i < list.size(); i++) {
if (list.get(i) == list.get(i - 1)) {
res += pushCost;
} else {
res += (moveCost + pushCost);
}
}
return res;
}
}
后续整理:不涉及算法,大家的思路基本一致,写法上可以简化一些,就不做记录了。
5987. 删除元素后和的最小差值
难度:困难
给你一个下标从 0 开始的整数数组 nums
,它包含 3 * n
个元素。
你可以从 nums
中删除 恰好 n
个元素,剩下的 2 * n
个元素将会被分成两个 相同大小 的部分。
- 前面
n
个元素属于第一部分,它们的和记为sumfirst
。 - 后面
n
个元素属于第二部分,它们的和记为sumsecond
。
两部分和的 差值 记为 sumfirst - sumsecond
。
- 比方说,
sumfirst = 3
且sumsecond = 2
,它们的差值为1
。 - 再比方,
sumfirst = 2
且sumsecond = 3
,它们的差值为-1
。
请你返回删除 n
个元素之后,剩下两部分和的 差值的最小值 是多少。
示例 1:
输入:nums = [3,1,2] 输出:-1 解释:nums 有 3 个元素,所以 n = 1 。 所以我们需要从 nums 中删除 1 个元素,并将剩下的元素分成两部分。 - 如果我们删除 nums[0] = 3 ,数组变为 [1,2] 。两部分和的差值为 1 - 2 = -1 。 - 如果我们删除 nums[1] = 1 ,数组变为 [3,2] 。两部分和的差值为 3 - 2 = 1 。 - 如果我们删除 nums[2] = 2 ,数组变为 [3,1] 。两部分和的差值为 3 - 1 = 2 。 两部分和的最小差值为 min(-1,1,2) = -1 。
示例 2:
输入:nums = [7,9,5,8,1,3] 输出:1 解释:n = 2 。所以我们需要删除 2 个元素,并将剩下元素分为 2 部分。 如果我们删除元素 nums[2] = 5 和 nums[3] = 8 ,剩下元素为 [7,9,1,3] 。和的差值为 (7+9) - (1+3) = 12 。 为了得到最小差值,我们应该删除 nums[1] = 9 和 nums[4] = 1 ,剩下的元素为 [7,5,8,3] 。和的差值为 (7+5) - (8+3) = 1 。 观察可知,最优答案为 1 。
提示:
nums.length == 3 * n
1 <= n <= 105
1 <= nums[i] <= 105
思路:本菜鸡对于困难,着实有畏难情绪,这题花了十分钟才有了第一版不成熟的思路。当时的想法是,假设删除后剩余2n数字的中点,在原3n长度的数组中,一定是在n-2n范围内。因此,将原数组划分为[n,2n],[n+1,2n-1],......,[2n,n]。再分别排序,对于前半截,留下最小的n个数,对于后半截,留下最大的n个数。最后,再筛选出一个最值。其实当时写出来,就知道不可行了,复杂度来到了惊人的O(n*n*logn),对于10^5的示例不可能有戏,但确实没有思路,也只能硬着头皮上了。最后果然超时。此时时间还有20分钟左右,后面再无建树,最后五分钟放弃,坐等答案。
class Solution {
public long minimumDifference(int[] nums) {
int n = nums.length / 3;
long res = 0;
int[] arr1 = Arrays.copyOfRange(nums, 0, n);
int[] arr2 = Arrays.copyOfRange(nums, n, nums.length);
int sum1 = 0;
int sum2 = 0;
Arrays.sort(arr1);
Arrays.sort(arr2);
for (int j = 0; j < n; j++) {
sum1 += arr1[j];
}
for (int j = n; j < 2 * n; j++) {
sum2 += arr2[j];
}
res = sum1 - sum2;
for (int i = n + 1; i <= 2 * n; i++) {
arr1 = Arrays.copyOfRange(nums, 0, i);
arr2 = Arrays.copyOfRange(nums, i, nums.length);
Arrays.sort(arr1);
Arrays.sort(arr2);
sum1 = 0;
sum2 = 0;
for (int j = 0; j < n; j++) {
sum1 += arr1[j];
}
for (int j = arr2.length - n; j < arr2.length; j++) {
sum2 += arr2[j];
}
res = Math.min(res, sum1 - sum2);
}
return res;
}
}
后续整理: 依然参考arignote大佬的解答,可以使用两个优先队列,分别记录两边数组保留下来的数字,上述思路分析得到,分割点在原数组的位置必在[n,2n]范围内,因此在这个范围内每次进行计算,拿到最小值即可。更多具体说明可以参考题解。
class Solution {
public long minimumDifference(int[] nums) {
long left[] = new long[nums.length + 1], right[] = new long[nums.length + 1], min = Long.MAX_VALUE;
PriorityQueue<Integer> leftQueue = new PriorityQueue<>(), rightQueue = new PriorityQueue<>();
for (int i = 0; i < nums.length; i++) {
leftQueue.offer(-nums[i]);
left[i + 1] = left[i] + nums[i] + (leftQueue.size() > nums.length / 3 ? leftQueue.poll() : 0);
}
for (int i = nums.length - 1; i >= 0; i--) {
rightQueue.offer(nums[i]);
right[i] = right[i + 1] + nums[i] - (rightQueue.size() > nums.length / 3 ? rightQueue.poll() : 0);
}
for (int i = nums.length / 3; i <= 2 * nums.length / 3; i++) {
min = Math.min(min, left[i] - right[i]);
}
return min;
}
}
总结
这是本菜鸡第二次参赛,当然依然是重在参与,不过前三题还是算比较顺风顺水的,第4题下来仔细参详了一下,思路应该说没问题,但对于常用的数据结构不够敏感,如果能很快的想到堆或者优先队列,可能能够拿下此题。总的来说比起第一次在第三题就有若干案例卡住,还是有所进步。Anyway,继续努力。