周赛349(模拟、贪心、枚举)

文章目录

  • 周赛349
    • [2733. 既不是最小值也不是最大值](https://leetcode.cn/problems/neither-minimum-nor-maximum/)
      • 模拟
      • O(1)做法
    • [2734. 执行子串操作后的字典序最小字符串](https://leetcode.cn/problems/lexicographically-smallest-string-after-substring-operation/)
      • 贪心
    • [2735. 收集巧克力](https://leetcode.cn/problems/collecting-chocolates/)
      • 枚举
    • [2736. 最大和查询](https://leetcode.cn/problems/maximum-sum-queries/)

周赛349

2733. 既不是最小值也不是最大值

难度简单1

给你一个整数数组 nums ,数组由 不同正整数 组成,请你找出并返回数组中 任一 既不是 最小值 也不是 最大值 的数字,如果不存在这样的数字,返回 -1

返回所选整数。

示例 1:

输入:nums = [3,2,1,4]
输出:2
解释:在这个示例中,最小值是 1 ,最大值是 4 。因此,2 或 3 都是有效答案。

示例 2:

输入:nums = [1,2]
输出:-1
解释:由于不存在既不是最大值也不是最小值的数字,我们无法选出满足题目给定条件的数字。因此,不存在答案,返回 -1 。

示例 3:

输入:nums = [2,1,3]
输出:2
解释:2 既不是最小值,也不是最大值,这个示例只有这一个有效答案。 

提示:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100
  • nums 中的所有数字互不相同

模拟

class Solution {
    public int findNonMinOrMax(int[] nums) {
        int mx = Integer.MIN_VALUE, mn = Integer.MAX_VALUE;
        for(int num : nums){
            mx = Math.max(mx, num);
            mn = Math.min(mn, num);
        }
        for(int num : nums){
            if(num != mx && num != mn)
                return num;
        }
        return -1;
    }
}

O(1)做法

数组由 不同正整数 组成,只需要看前三个就行了

class Solution {
    public int findNonMinOrMax(int[] nums) {
        if (nums.length < 3) return -1;
        Arrays.sort(nums, 0, 3); // 只对前三个数排序
        return nums[1];
    }
}

2734. 执行子串操作后的字典序最小字符串

难度中等3

给你一个仅由小写英文字母组成的字符串 s 。在一步操作中,你可以完成以下行为:

  • 选则 s 的任一非空子字符串,可能是整个字符串,接着将字符串中的每一个字符替换为英文字母表中的前一个字符。例如,‘b’ 用 ‘a’ 替换,‘a’ 用 ‘z’ 替换。

返回执行上述操作 恰好一次 后可以获得的 字典序最小 的字符串。

子字符串 是字符串中的一个连续字符序列。

现有长度相同的两个字符串 x 和 字符串 y ,在满足 x[i] != y[i] 的第一个位置 i 上,如果 x[i] 在字母表中先于 y[i] 出现,则认为字符串 x 比字符串 y 字典序更小

示例 1:

输入:s = "cbabc"
输出:"baabc"
解释:我们选择从下标 0 开始、到下标 1 结束的子字符串执行操作。 
可以证明最终得到的字符串是字典序最小的。

示例 2:

输入:s = "acbbc"
输出:"abaab"
解释:我们选择从下标 1 开始、到下标 4 结束的子字符串执行操作。
可以证明最终得到的字符串是字典序最小的。

示例 3:

输入:s = "leetcode"
输出:"kddsbncd"
解释:我们选择整个字符串执行操作。
可以证明最终得到的字符串是字典序最小的。

提示:

  • 1 <= s.length <= 3 * 105
  • s 仅由小写英文字母组成

贪心

根据题意,把 a 替换成 z 会让字典序变大,所以子串里面是不能包含 a 的。

替换其它字符可以让字典序变小。

那么从左到右找到第一个不等于 a 的字符 s[i],并向后不断减一,直到 s 末尾或者遇到了 a。

class Solution {
    public String smallestString(String s) {
        char[] arr = s.toCharArray();
        int n = arr.length;
        int left = 0;
        while(left < n && arr[left] == 'a') left += 1;
        int right = left;
        while(right < n && arr[right] != 'a') right += 1;
        if(left == n){
            arr[n-1] = 'z';
        }else{
            for(int i = left; i < right; i++){
                int c = arr[i] - 'a';
                c -= 1;
                arr[i] = (char)('a' + c);
            }
        }
        return new String(arr);
    }
}

2735. 收集巧克力

难度中等8

给你一个长度为 n 、下标从 0 开始的整数数组 nums ,表示收集不同巧克力的成本。每个巧克力都对应一个不同的类型,最初,位于下标 i 的巧克力就对应第 i 个类型。

在一步操作中,你可以用成本 x 执行下述行为:

  • 同时对于所有下标 0 <= i < n - 1 进行以下操作, 将下标 i 处的巧克力的类型更改为下标 (i + 1) 处的巧克力对应的类型。如果 i == n - 1 ,则该巧克力的类型将会变更为下标 0 处巧克力对应的类型。

假设你可以执行任意次操作,请返回收集所有类型巧克力所需的最小成本。

示例 1:

输入:nums = [20,1,15], x = 5
输出:13
解释:最开始,巧克力的类型分别是 [0,1,2] 。我们可以用成本 1 购买第 1 个类型的巧克力。
接着,我们用成本 5 执行一次操作,巧克力的类型变更为 [2,0,1] 。我们可以用成本 1 购买第 0 个类型的巧克力。
然后,我们用成本 5 执行一次操作,巧克力的类型变更为 [1,2,0] 。我们可以用成本 1 购买第 2 个类型的巧克力。
因此,收集所有类型的巧克力需要的总成本是 (1 + 5 + 1 + 5 + 1) = 13 。可以证明这是一种最优方案。

示例 2:

输入:nums = [1,2,3], x = 4
输出:6
解释:我们将会按最初的成本收集全部三个类型的巧克力,而不需执行任何操作。因此,收集所有类型的巧克力需要的总成本是 1 + 2 + 3 = 6 。

提示:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 109
  • 1 <= x <= 109

枚举

https://leetcode.cn/problems/collecting-chocolates/solution/qiao-miao-mei-ju-pythonjavacgo-by-endles-5ws2/

模拟过程详解:https://leetcode.cn/problems/collecting-chocolates/solution/mo-ni-guo-cheng-xiang-jie-java-by-tailta-ie4f/

如果不操作,第i个巧克力必须花费 nums[i] 收集,总成本为所有nums[i] 之和

如果操作一次,第i个巧克力可以花费 min(nums[i],nums[(i +1) mod n])收集。注意在求和的情况下,把题意理解成循环左移还是循环右移,算出的结果都是一样的。(样例 1 解释中的类型变更是反过来的,但计算结果是正确的。)
如果操作两次,第 i 个巧克力可以花费 min(nums,nums[(i +1) mod n] , nums[(i + 2) mod n]) 收集依此类推。

class Solution {
    public long minCost(int[] nums, int x) {
        int n = nums.length;
        // sum数组:操作0次、1次、2次时得到的成本
        // 预处理 sum数组统计操作 i 次的总花费 sum[i]
        long[] sum = new long[n];
        for(int i = 0; i < n; i++){
            sum[i] = (long)i * x; // 操作i次的花费
        }
        for(int i = 0; i < n; i++){// 子数组左端点(枚举每一个类型,计算操作 0次、1次、2次时能购买的最小成本)
            //计算每个类型可以购买的最小成本, O(n^2)
            int mn = nums[i];
            for(int j = i; j < n+i; j++){ // 子数组右端点(把数组视作环形的)
                mn = Math.min(mn, nums[j % n]); // 从 nums[i] 到 nums[j%n] 的最小值
                sum[j - i] += mn; // 累加操作 j-i 次的成本
            }
        }
        long ans = Long.MAX_VALUE;
        for(long s : sum)
            ans = Math.min(ans, s);
        return ans;
    }
}

2736. 最大和查询

难度困难5

给你两个长度为 n 、下标从 0 开始的整数数组 nums1nums2 ,另给你一个下标从 1 开始的二维数组 queries ,其中 queries[i] = [xi, yi]

对于第 i 个查询,在所有满足 nums1[j] >= xinums2[j] >= yi 的下标 j (0 <= j < n) 中,找出 nums1[j] + nums2[j]最大值 ,如果不存在满足条件的 j 则返回 -1

返回数组 answer *,*其中 answer[i] 是第 i 个查询的答案。

示例 1:

输入:nums1 = [4,3,1,2], nums2 = [2,4,9,5], queries = [[4,1],[1,3],[2,5]]
输出:[6,10,7]
解释:
对于第 1 个查询:xi = 4 且 yi = 1 ,可以选择下标 j = 0 ,此时 nums1[j] >= 4 且 nums2[j] >= 1 。nums1[j] + nums2[j] 等于 6 ,可以证明 6 是可以获得的最大值。
对于第 2 个查询:xi = 1 且 yi = 3 ,可以选择下标 j = 2 ,此时 nums1[j] >= 1 且 nums2[j] >= 3 。nums1[j] + nums2[j] 等于 10 ,可以证明 10 是可以获得的最大值。
对于第 3 个查询:xi = 2 且 yi = 5 ,可以选择下标 j = 3 ,此时 nums1[j] >= 2 且 nums2[j] >= 5 。nums1[j] + nums2[j] 等于 7 ,可以证明 7 是可以获得的最大值。
因此,我们返回 [6,10,7] 。

示例 2:

输入:nums1 = [3,2,5], nums2 = [2,3,4], queries = [[4,4],[3,2],[1,1]]
输出:[9,9,9]
解释:对于这个示例,我们可以选择下标 j = 2 ,该下标可以满足每个查询的限制。

示例 3:

输入:nums1 = [2,1], nums2 = [2,3], queries = [[3,3]]
输出:[-1]
解释:示例中的查询 xi = 3 且 yi = 3 。对于每个下标 j ,都只满足 nums1[j] < xi 或者 nums2[j] < yi 。因此,不存在答案。 

提示:

  • nums1.length == nums2.length
  • n == nums1.length
  • 1 <= n <= 105
  • 1 <= nums1[i], nums2[i] <= 109
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • xi == queries[i][1]
  • yi == queries[i][2]
  • 1 <= xi, yi <= 109

题解:

关键一:答案要求与数组顺序无关,考虑从小到大排序

关键二:在知道所有询问的前提下, 可以从大到小(从小到大)进行回答

https://leetcode.cn/problems/maximum-sum-queries/solution/pai-xu-dan-diao-zhan-shang-er-fen-by-end-of9h/

class Solution {
    public int[] maximumSumQueries(int[] nums1, int[] nums2, int[][] queries) {
        int n = nums1.length;
        int q = queries.length;

        // 按 x 升序排序 nums1, nums2
        Integer[] ids = new Integer[n];
        for (int i = 0; i < n; i++) ids[i] = i;
        Arrays.sort(ids, Comparator.comparingInt(o -> nums1[o]));

        // 按 x 降序排序 queries
        for (int i = 0; i < q; i++) {
            queries[i] = new int[]{queries[i][0], queries[i][1], i};
        }
        Arrays.sort(queries, (o1, o2) -> Integer.compare(o2[0], o1[0]));

        int[] ans = new int[q];
        Arrays.fill(ans, -1);
        Deque<int[]> stack = new ArrayDeque<>();
        TreeMap<Integer, Integer> treeMap = new TreeMap<>();
        int idx = n - 1;
        for (int[] query : queries) {
            int x = query[0], y = query[1], qId = query[2];
            while (idx >= 0 && nums1[ids[idx]] >= x) { 
                int ax = nums1[ids[idx]], ay = nums2[ids[idx]];
                // ay >= stack.peek()[0]
                while (!stack.isEmpty() && stack.peek()[1] <= ax + ay) {
                    int[] pop = stack.pop();
                    treeMap.remove(pop[0]);
                }
                if (stack.isEmpty() || stack.peek()[0] < ay) {
                    stack.push(new int[]{ay, ax + ay});
                    treeMap.put(ay, ax + ay);
                }
                idx--;
            }
            // 单调栈上二分
            Map.Entry<Integer, Integer> entry = treeMap.ceilingEntry(y);
            if (entry != null) {
                ans[qId] = entry.getValue();
            }
        }
        return ans;
    }
}

你可能感兴趣的:(算法刷题记录,算法,leetcode,贪心算法)