欢迎大家加QQ群:623375442,可以方便群里面交流。人不会做第四题,然后赛后试了AI的答案 prompt几次就能过。AI也太强了。

100571. Choose K Elements With Maximum Sum

给你两个整数数组,nums1 和 nums2,长度均为 n,以及一个正整数 k 。

对从 0 到 n - 1 每个下标 i ,执行下述操作:

  • 找出所有满足 nums1[j] 小于 nums1[i] 的下标 j 。
  • 从这些下标对应的 nums2[j] 中选出 至多 k 个,并 最大化 这些值的总和作为结果。

返回一个长度为 n 的数组 answer ,其中 answer[i] 表示对应下标 i 的结果。

测试样例:

输入:nums1 = [4,2,1,5,3], nums2 = [10,20,30,40,50], k = 2

输出:[80,30,0,80,50]

解释:

  • 对于 i = 0 :满足 nums1[j] < nums1[0] 的下标为 [1, 2, 4] ,选出其中值最大的两个,结果为 50 + 30 = 80 。
  • 对于 i = 1 :满足 nums1[j] < nums1[1] 的下标为 [2] ,只能选择这个值,结果为 30 。
  • 对于 i = 2 :不存在满足 nums1[j] < nums1[2] 的下标,结果为 0 。
  • 对于 i = 3 :满足 nums1[j] < nums1[3] 的下标为 [0, 1, 2, 4] ,选出其中值最大的两个,结果为 50 + 30 = 80 。
  • 对于 i = 4 :满足 nums1[j] < nums1[4] 的下标为 [1, 2] ,选出其中值最大的两个,结果为 30 + 20 = 50 。

解答:这题排序一下,从小到大遍历数组即可。用优先队列记录一下过程。

class Solution {
    public long[] findMaxSum(int[] nums1, int[] nums2, int k) {
        Integer[] sort = new Integer[nums1.length];
        for (int i = 0; i < nums1.length; ++i) {
            sort[i] = i;
        }
        Arrays.sort(sort, (a, b) -> (Integer.compare(nums1[a], nums1[b])));
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        List<Integer> buffer = new ArrayList<>();
        int last = -1;
        long sum = 0;
        long[] res = new long[nums1.length];
        for (int i : sort) {
            if (nums1[i] != last) {
                for (int n : buffer) {
                    queue.add(nums2[n]);
                    sum += nums2[n];
                    if (queue.size() > k) {
                        sum -= queue.poll();
                    }
                }
                buffer = new ArrayList<>();
            }
            res[i] = sum;
            last = nums1[i];
            buffer.add(i);
        }
        return res;
    }
}

100601. Fruits Into Baskets III

给你两个长度为 n 的整数数组,fruits 和 baskets,其中 fruits[i] 表示第 i 种水果的 数量,baskets[j] 表示第 j 个篮子的 容量。

你需要对 fruits 数组从左到右按照以下规则放置水果:

  • 每种水果必须放入第一个 容量大于等于 该水果数量的 最左侧可用篮子 中。
  • 每个篮子只能装 一种 水果。
  • 如果一种水果 无法放入 任何篮子,它将保持 未放置。

返回所有可能分配完成后,剩余未放置的水果种类的数量。

测试样例:

输入:fruits = [4,2,5], baskets = [3,5,4]

输出:1

解释:

  • fruits[0] = 4 放入 baskets[1] = 5。
  • fruits[1] = 2 放入 baskets[0] = 3。
  • fruits[2] = 5 无法放入 baskets[2] = 4。

由于有一种水果未放置,我们返回 1。

解答:线段树的板子题。利用折半搜索可以寻找第一个位置。

class Solution {
    public int numOfUnplacedFruits(int[] fruits, int[] baskets) {
        SegmentTree tree = new SegmentTree(baskets);
        int res = 0;
        for (int f : fruits) {
            int largest = tree.maxRange(0, baskets.length - 1);
            if (largest >= f) {
                int st = 0, ed = baskets.length;
                while (st < ed) {
                    int mid = (st + ed) / 2;
                    int tmp = tree.maxRange(0, mid);
                    if (tmp >= f) {
                        ed = mid;
                    } else {
                        st = mid + 1;
                    }
                }
                tree.update(st, 0);
                ++res;
            }
        }
        return fruits.length - res;
    }
}

class SegmentTree {
    int[] tree;
    int n;

    public SegmentTree(int[] nums) {
        if (nums.length > 0) {
            n = nums.length;
            tree = new int[n * 2];
            buildTree(nums);
        }
    }

    private void buildTree(int[] nums) {
        for (int i = n, j = 0;  i < 2 * n; i++,  j++)
            tree[i] = nums[j];
        for (int i = n - 1; i > 0; --i)
            tree[i] = Math.max(tree[i * 2], tree[i * 2 + 1]);
    }

    void update(int pos, int val) {
        pos += n;
        tree[pos] = val;
        while (pos > 0) {
            int left = pos;
            int right = pos;
            if (pos % 2 == 0) {
                right = pos + 1;
            } else {
                left = pos - 1;
            }
            tree[pos / 2] = Math.max(tree[left], tree[right]);
            pos /= 2;
        }
    }

    public int maxRange(int l, int r) {
        l += n;
        r += n;
        int sum = Integer.MIN_VALUE;
        while (l <= r) {
            if ((l % 2) == 1) {
                sum = Math.max(sum, tree[l]);
                l++;
            }
            if ((r % 2) == 0) {
                sum = Math.max(sum, tree[r]);
                r--;
            }
            l /= 2;
            r /= 2;
        }
        return sum;
    }
}

100599. Maximize Subarrays After Removing One Conflicting Pair

给你一个整数 n,表示一个包含从 1 到 n 按顺序排列的整数数组 nums。此外,给你一个二维数组 conflictingPairs,其中 conflictingPairs[i] = [a, b] 表示 a 和 b 形成一个冲突对。

从 conflictingPairs 中删除 恰好 一个元素。然后,计算数组 nums 中的非空子数组数量,这些子数组都不能同时包含任何剩余冲突对 [a, b] 中的 a 和 b。

返回删除 恰好 一个冲突对后可能得到的 最大 子数组数量。

子数组 是数组中一个连续的 非空 元素序列。

测试样例:

输入:n = 4, conflictingPairs = [[2,3],[1,4]]

输出:9

解释:

  • 从 conflictingPairs 中删除 [2, 3]。现在,conflictingPairs = [[1, 4]]。
  • 在 nums 中,存在 9 个子数组,其中 [1, 4] 不会一起出现。它们分别是 [1],[2],[3],[4],[1, 2],[2, 3],[3, 4],[1, 2, 3] 和 [2, 3, 4]。
  • 删除 conflictingPairs 中一个元素后,能够得到的最大子数组数量是 9。

解答:不会做,赛后用AI做了一下。AI能过。。。顺便贴一个AI的解释。

预处理:

  • 我们初始化数组,使得如果一个索引没有冲突的配对,它的候选值被设置为 n+1
  • 对于每一对 [a, b] (其中 a < b),我们更新 candidate[a] (以及 candidate2[a] 作为第二个选项),并记录频率。

计算 fContributor

  • 后缀最小值数组 f 被计算,使得 f[i] 是从 in 的最小候选值。
  • contributor 数组记录,对于每个起始索引,提供 f[i] 的索引。

分组:

  • 我们将起始索引按照它们的 contributor 进行分组。
  • 对于 group[a] 中的每个起始索引 i,最初 f[i] = candidate[a]

模拟移除:

  • 对于在索引 a 处的候选值移除 (当 candidate[a] 是唯一的),我们将它的新候选值设置为 candidate2[a],并且令 r = (a < n ? min(candidate2[a], f[a+1]) : candidate2[a])
  • 然后,对于与 a 对应的组 (按照起始索引递增的顺序),我们重新计算新的运行最小值 m (它代表移除后这些索引的新 f)。
  • 改进值是 (m[j] – candidate[a]) 在该组上的总和。
class Solution {
    public long maxSubarrays(int n, int[][] conflictingPairs) {
        // total number of subarrays = n*(n+1)/2.
        long totalSubarrays = (long) n * (n + 1) / 2;

        // Use 1-indexed arrays; allocate size n+2.
        int[] candidate = new int[n+1];   // candidate[a] = smallest b among pairs with first element a.
        int[] candidate2 = new int[n+1];    // second-best candidate for a.
        int[] freq = new int[n+1];          // frequency of candidate value for a.
        for (int i = 1; i <= n; i++) {
            candidate[i] = n + 1;
            candidate2[i] = n + 1;
        }

        // Process each conflicting pair, ensuring a < b.
        for (int[] pair : conflictingPairs) {
            int a = pair[0], b = pair[1];
            if(a > b) {
                int temp = a; a = b; b = temp;
            }
            if(b < candidate[a]){
                candidate2[a] = candidate[a]; // previous best becomes second best.
                candidate[a] = b;
                freq[a] = 1;
            } else if(b == candidate[a]) {
                freq[a]++;
            } else {
                candidate2[a] = Math.min(candidate2[a], b);
            }
        }

        // Build the suffix–minimum array f:
        // f[i] = min(candidate[i], candidate[i+1], ..., candidate[n]).
        int[] f = new int[n+2];
        f[n] = candidate[n];
        for (int i = n - 1; i >= 1; i--) {
            f[i] = Math.min(candidate[i], f[i+1]);
        }
        f[n+1] = n + 1;

        // Build contributor array:
        // contributor[i] = the first index j in [i, n] with candidate[j] = f[i].
        int[] contributor = new int[n+1];
        contributor[n] = n;
        for (int i = n - 1; i >= 1; i--) {
            if(candidate[i] <= f[i+1]) {
                contributor[i] = i;
            } else {
                contributor[i] = contributor[i+1];
            }
        }

        // Compute the original union area (i.e. total invalid subarrays):
        long unionArea = 0;
        for (int i = 1; i <= n; i++) {
            if(f[i] <= n) {
                unionArea += (n - f[i] + 1);
            }
        }
        long originalValid = totalSubarrays - unionArea;

        // Group starting indices by their contributor.
        List<Integer>[] groups = new ArrayList[n+1];
        for (int i = 1; i <= n; i++) {
            groups[i] = new ArrayList<>();
        }
        for (int i = 1; i <= n; i++) {
            groups[contributor[i]].add(i);
        }

        long maxImprovement = 0;

        // Try candidate removals. Removal is only possible if candidate[a] <= n and is unique.
        for (int a = 1; a <= n; a++) {
            if(candidate[a] <= n && freq[a] == 1) {
                // Removal at a changes candidate[a] to candidate2[a].
                int newCandidate = candidate2[a];  // if no second candidate, equals n+1.
                int r = (a < n ? Math.min(newCandidate, f[a+1]) : newCandidate);

                // For all starting indices i with contributor a, originally f[i] = candidate[a].
                // After removal, we recompute the new running minimum along the group.
                List<Integer> groupList = groups[a];
                if(groupList.isEmpty()) continue;
                int k = groupList.size();
                int[] X = new int[k];
                // X[j] will be candidate[ i ] for i in the group.
                for (int j = 0; j < k; j++) {
                    int idx = groupList.get(j);
                    X[j] = candidate[idx];
                }
                // Compute new running minimum array m:
                int[] m = new int[k];
                m[k-1] = r;  // the contributor's new candidate becomes r.
                for (int j = k - 2; j >= 0; j--) {
                    m[j] = Math.min(X[j], m[j+1]);
                }
                long sumM = 0;
                for (int j = 0; j < k; j++) {
                    sumM += m[j];
                }
                // Originally, each starting index in this group had f = candidate[a].
                long improvement = sumM - (long) k * candidate[a];
                maxImprovement = Math.max(maxImprovement, improvement);
            }
        }

        return originalValid + maxImprovement;
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *