• 优先队列题目:多次求和构造目标数组


    题目

    标题和出处

    标题:多次求和构造目标数组

    出处:1354. 多次求和构造目标数组

    难度

    7 级

    题目描述

    要求

    给你一个由 n \texttt{n} n 个整数组成的数组 target \texttt{target} target。初始数组 arr \texttt{arr} arr n \texttt{n} n 1 \texttt{1} 1 组成,你可以执行以下操作:

    • x \texttt{x} x 为你的数组中的所有元素之和。
    • 选择下标 i \texttt{i} i,满足 0 ≤ i < n \texttt{0} \le \texttt{i} < \texttt{n} 0i<n,将 arr \texttt{arr} arr 的下标 i \texttt{i} i 处的值设为 x \texttt{x} x
    • 你可以重复该过程任意次。

    如果可以从 arr \texttt{arr} arr 开始构造出目标数组 target \texttt{target} target,返回 true \texttt{true} true,否则返回 false \texttt{false} false

    示例

    示例 1:

    输入: target   =   [9,3,5] \texttt{target = [9,3,5]} target = [9,3,5]
    输出: true \texttt{true} true
    解释:从 arr   =   [1,   1,   1] \texttt{arr = [1, 1, 1]} arr = [1, 1, 1] 开始。
    [1,   1,   1] \texttt{[1, 1, 1]} [1, 1, 1],和为 3 \texttt{3} 3,选择下标 1 \texttt{1} 1
    [1,   3,   1] \texttt{[1, 3, 1]} [1, 3, 1],和为 5 \texttt{5} 5,选择下标 2 \texttt{2} 2
    [1,   3,   5] \texttt{[1, 3, 5]} [1, 3, 5],和为 9 \texttt{9} 9,选择下标 0 \texttt{0} 0
    [9,   3,   5] \texttt{[9, 3, 5]} [9, 3, 5] 完成。

    示例 2:

    输入: target   =   [1,1,1,2] \texttt{target = [1,1,1,2]} target = [1,1,1,2]
    输出: false \texttt{false} false
    解释:不可能从 [1,1,1,1] \texttt{[1,1,1,1]} [1,1,1,1] 出发构造目标数组。

    示例 3:

    输入: target   =   [8,5] \texttt{target = [8,5]} target = [8,5]
    输出: true \texttt{true} true

    数据范围

    • n = target.length \texttt{n} = \texttt{target.length} n=target.length
    • 1 ≤ n ≤ 5 × 10 4 \texttt{1} \le \texttt{n} \le \texttt{5} \times \texttt{10}^\texttt{4} 1n5×104
    • 1 ≤ target[i] ≤ 10 9 \texttt{1} \le \texttt{target[i]} \le \texttt{10}^\texttt{9} 1target[i]109

    解法

    思路和算法

    这道题最容易想到的思路是从初始数组开始模拟所有的可能性,但是可能性太多,会超出时间限制,因此需要考虑其他思路。

    由于初始数组的元素都是 1 1 1,都是正整数,每次操作都是将数组中的一个元素替换成数组中的所有元素之和,因此每次操作的结果都是将数组中的一个元素值增加,且变化后的元素一定是数组中的最大元素。只要找到数组中的最大元素,即可知道在最后一次操作之前的数组中的所有元素之和,从而将数组恢复到最后一次操作之前的状态。

    由此可以反向思考,即从目标数组开始,每次计算上一个状态,判断是否可以得到初始数组。

    为了能得到数组中的最大元素,可以使用基于大根堆的优先队列存储数组中的所有元素,优先队列的队首元素即为数组中的最大元素。遍历数组 target \textit{target} target,将所有元素加入优先队列,并计算所有元素之和,记为 sum \textit{sum} sum,然后进行反向操作。

    将优先队列的队首元素取出,记为 curr \textit{curr} curr,数组中的其他元素之和为 remainSum = sum − curr \textit{remainSum} = \textit{sum} - \textit{curr} remainSum=sumcurr,则在最后一次操作之前,数组中的所有元素之和为 curr \textit{curr} curr,因此 curr \textit{curr} curr 元素的上一个值为 prev = curr − remainSum \textit{prev} = \textit{curr} - \textit{remainSum} prev=currremainSum。将 sum \textit{sum} sum 的值减去 curr − prev \textit{curr} - \textit{prev} currprev,将 prev \textit{prev} prev 加入优先队列,则 sum \textit{sum} sum 为最后一次操作之前的数组中的所有元素之和,优先队列中的元素为最后一次操作之前的数组中的所有元素。重复上述反向操作,如果能到达初始数组,即数组中的所有元素都是 1 1 1,则返回 true \text{true} true,如果出现数组中的元素小于 1 1 1 的情况,则返回 false \text{false} false

    当数组中的最大元素远大于数组中的其他元素之和时,上述反向操作的做法仍然会超时。注意到当 curr > remainSum \textit{curr} > \textit{remainSum} curr>remainSum 时,数组中的最大元素一定是 curr \textit{curr} curr,且 remainSum \textit{remainSum} remainSum 的值不会变化,因此每次反向操作都会使 curr \textit{curr} curr 的值减少 remainSum \textit{remainSum} remainSum,直到 curr ≤ remainSum \textit{curr} \le \textit{remainSum} currremainSum 时数组中的最大元素才可能不是 curr \textit{curr} curr。因此可以一步计算 prev \textit{prev} prev,令 prev \textit{prev} prev curr \textit{curr} curr 减去若干个 remainSum \textit{remainSum} remainSum 之后的值且满足 1 ≤ prev ≤ remainSum 1 \le \textit{prev} \le \textit{remainSum} 1prevremainSum,具体计算方法如下:

    • 如果 curr   m o d   remainSum = 0 \textit{curr} \bmod \textit{remainSum} = 0 currmodremainSum=0,则 prev = remainSum \textit{prev} = \textit{remainSum} prev=remainSum

    • 如果 curr   m o d   remainSum ≠ 0 \textit{curr} \bmod \textit{remainSum} \ne 0 currmodremainSum=0,则 prev = curr   m o d   remainSum \textit{prev} = \textit{curr} \bmod \textit{remainSum} prev=currmodremainSum

    得到 prev \textit{prev} prev 之后,将 sum \textit{sum} sum 的值减去 curr − prev \textit{curr} - \textit{prev} currprev,将 prev \textit{prev} prev 加入优先队列,即达到用 prev \textit{prev} prev 更新 curr \textit{curr} curr 的效果。

    上述反向操作的过程必须保证数组中的全部元素都大于 0 0 0。如果在某一步反向操作之后,数组中的全部元素之和等于 n n n,则恢复到初始数组,返回 true \text{true} true

    如果在反向操作的过程中出现 remainSum = 0 \textit{remainSum} = 0 remainSum=0 或者 curr − remainSum < 1 \textit{curr} - \textit{remainSum} < 1 currremainSum<1 的情况,则说明反向操作会导致数组中出现小于 1 1 1 的元素,返回 false \text{false} false

    代码

    class Solution {
        public boolean isPossible(int[] target) {
            PriorityQueue<Long> pq = new PriorityQueue<Long>(new Comparator<Long>() {
                public int compare(Long num1, Long num2) {
                    return num2.compareTo(num1);
                }
            });
            long sum = 0;
            for (int num : target) {
                sum += num;
                pq.offer((long) num);
            }
            int n = target.length;
            while (sum > n) {
                long curr = pq.poll();
                long remainSum = sum - curr;
                if (remainSum == 0 || curr - remainSum < 1) {
                    return false;
                }
                long prev = curr % remainSum == 0 ? remainSum : curr % remainSum;
                sum -= curr - prev;
                pq.offer(prev);
            }
            return sum == n;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    复杂度分析

    • 时间复杂度: O ( n log ⁡ n × log ⁡ m ) O(n \log n \times \log m) O(nlogn×logm),其中 n n n 是数组 target \textit{target} target 的长度, m m m 是数组 target \textit{target} target 的最大元素。
      由于每次反向操作都使数组的最大元素至少减少一半,因此数组中的每个元素最多需要 O ( log ⁡ m ) O(\log m) O(logm) 反向操作即可减少到 1 1 1,将 n n n 个元素都减少到 1 1 1 需要的反向操作次数是 O ( n log ⁡ m ) O(n \log m) O(nlogm)
      由于每次反向操作需要对优先队列进行取出元素和添加元素操作,优先队列中有 n n n 个元素,每次将元素从优先队列中取出和加入优先队列的时间复杂度是 O ( log ⁡ n ) O(\log n) O(logn),因此总时间复杂度是 O ( n log ⁡ n × log ⁡ m ) O(n \log n \times \log m) O(nlogn×logm)

    • 空间复杂度 O ( n ) O(n) O(n),其中 n n n 是数组 target \textit{target} target 的长度。空间复杂度主要取决于优先队列,优先队列中的元素个数不会超过 n n n

  • 相关阅读:
    10 索引优化与查询优化
    注解详解系列 - @EnableAspectJAutoProxy:启用AspectJ自动代理
    SpringBoot整合SSMP
    【C++】模板:了解泛型编程
    Web 前端汇总
    Docker仓库harbor私服搭建
    搭建第一个区块链网络
    numpy函数总结
    【笔试强训day02】倒置字符串 &&排序子序列
    jQuery_按键变色/keyCode/text
  • 原文地址:https://blog.csdn.net/stormsunshine/article/details/121255456