【LeetCode】第71场双周赛参赛实录

时间:2022-02-05 22:30-24:00

地址:竞赛 - 力扣 (LeetCode)

结局: 

【LeetCode】第71场双周赛参赛实录

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 的元素之间的 相对顺序 不发生改变。
    • 更正式的,考虑每一对 pipj ,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:

【LeetCode】第71场双周赛参赛实录

输入: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:

【LeetCode】第71场双周赛参赛实录

输入: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,继续努力。

上一篇:Spring系列-3.1 registerBeanPostProcessors


下一篇:生成Excel文件