Given an array of integers `A`, consider all non-empty subsequences of `A`.

For any sequence S, let the width of S be the difference between the maximum and minimum element of S.

Return the sum of the widths of all subsequences of A.

As the answer may be very large, return the answer modulo 10^9 + 7.

Example 1:

Input: [2,1,3]
Output: 6
Explanation: Subsequences are [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3].
The corresponding widths are 0, 0, 0, 1, 1, 2, 2.
The sum of these widths is 6.

Note:

  • 1 <= A.length <= 20000
  • 1 <= A[i] <= 20000

这道题给了我们一个数组,并且定义了一种子序列的宽度,就是非空子序列中最大值和最小值的差值,让我们算出所有的子序列的宽度之和,而且提示了结果可能是个超大数,要对 1e9+7 取余。由于要求是子序列,所以不必像子数组那样必须要连续,并且我们只关心最大值和最小值,其他的数字并不 care。由于子序列并不存在顺序之分,所以我们可以开始就对输入数组进行排序,并不会影响最终的结果。想到这里博主的思路就断了,难道要生成所有的子序列,然后一个一个的计算差值么,这种思路对得起 Hard 标签么?但一时半会又想不出什么其他好思路,其实这道题的最优解法相当的 tricky,基本有点脑筋急转弯的感觉了。在解题之前,我们首先要知道的是一个长度为n的数组,共有多少个子序列,如果算上空集的话,共有 2^n 个。那么在给数组排序之后,对于其中任意一个数字 A[i],其前面共有i个数是小于等于 A[i] 的,这i个数字共有 2^i 个子序列,它们加上 A[i] 都可以组成一个新的非空子序列,并且 A[i] 是这里面最大的数字,那么在宽度计算的时候,就要加上 A[i] x (2^i),同理,A[i] 后面还有 n-1-i 个数字是大于等于它的,后面可以形成 2^(n-1-i) 个子序列,每个加上 A[i] 就都是一个新的非空子序列,同时 A[i] 是这些子序列中最小的一个,那么结果中就要减去 A[i] x (2 ^ (n-1-i))。对于每个数字都这么计算一下,就是最终要求的所有子序列的宽度之和了。可能你会怀疑虽然加上了 A[i] 前面 2^i 个子序列的最大值,那些子序列的最小值减去了么?其实是减去了的,虽然不是在遍历 A[i] 的时候减去,在遍历之前的数字时已经将所有该数字是子序列最小值的情况减去了,同理,A[i] 后面的那些 2^(n-1-i) 个子序列的最大值也是在遍历到的时候才加上的,所以不会漏掉任何一个数字。在写代码的时候有几点需要注意的地方,首先,结果 res 要定义为 long 型,因为虽然每次会对 1e9+7 取余,但是不能保证不会在取余之前就已经整型溢出,所以要定义为长整型。其次,不能直接算 2^i 和 2^(n-1-i),很容易溢出,即便是长整型,也有可能溢出。那么解决方案就是,在累加i的同时,每次都乘以个2,那么遍历到i的时候,也就乘到 2^i 了,防止溢出的诀窍就是每次乘以2之后就立马对 1e9+7 取余,这样就避免了指数溢出,同时又不影响结果。最后,由于这种机制下的 2^i 和 2^(n-1-i) 不方便同时计算,这里又用了一个 trick,就是将 A[i] x (2^(n-1-i)) 转换为了 A[n-1-i] x 2^i,其实二者最终的累加和是相等的:

sum(A[i] * 2^(n-1-i)) = A[0]*2^(n-1) + A[1]*2^(n-2) + A[2]*2^(n-3) + ... + A[n-1]*2^0
sum(A[n-1-i] * 2^i) = A[n-1]*2^0 + A[n-2]*2^1 + ... + A[1]*2^(n-2) + A[0]*2^(n-1)

可以发现两个等式的值都是相等的,只不过顺序颠倒了一下,参见代码如下:

解法一:

class Solution {
public:
int sumSubseqWidths(vector<int>& A) {
long res = 0, n = A.size(), M = 1e9 + 7, c = 1;
sort(A.begin(), A.end());
for (int i = 0; i < n; ++i) {
res = (res + A[i] * c - A[n - i - 1] * c) % M;
c = (c << 1) % M;
}
return res;
}
};

我们也可以换一种写法,使用两个累加和 leftSum 和 rightSum,其中 leftSum[i] 表示数组范围 [0, i] 内的数字之和,rightSum[i] 表示数组范围 [i, n-1] 内的数字之和,然后每次在i位置,累加 (rightSum - leftSum) x 2^i 到结果 res,也能得到同样正确的结果,这是为啥呢?我们只要将 (rightSum - leftSum) x 2^i 展开,就能明白了:

sum((rightSum - leftSum) * 2^i) =
(A[n-1] - A[0]) * 2^0 +
(A[n-1] + A[n-2] - A[1] - A[0]) * 2^1 +
(A[n-1] + A[n-2] + A[n-3] - A[2] - A[1] - A[0]) * 2^2 +
... +
(A[n-1] + A[n-2] - A[1] - A[0]) * 2^(n-3)
(A[n-1] - A[0]) * 2^(n-2)
=
A[n-1] * (2^(n-1) - 2^0) + A[n-2] * (2^(n-2) - 2^1) + ... + A[0] * (2^0 - 2^(n-1))
=
sum(A[i] * 2^i - A[i] * 2^(n-1-i))

我们发现,最终还是转化成了跟解法一中一样的规律,参见代码如下:

解法二:

class Solution {
public:
int sumSubseqWidths(vector<int>& A) {
long res = 0, n = A.size(), M = 1e9 + 7, c = 1;
int leftSum = 0, rightSum = 0, left = 0, right = n - 1;
sort(A.begin(), A.end());
while (left < n) {
leftSum += A[left++];
rightSum += A[right--];
res = (res + (rightSum - leftSum) * c) % M;
c = (c << 1) % M;
}
return res;
}
};

讨论:做完了这道题之后,博主就在想,如果将子序列变成子数组,其他不变,该怎么做?稍稍想了一下,发现是 totally different story,这道题的方法完全就不能使用了,因为子数组是不能排序的,也不能不连续,虽然只改了几个字,但是完全是两道题。博主能想到的就是建立一个类似分段树的结构,保存每个连续区间的最大值和最小值,从而解决问题。各位看官大神有什么好想法请留言讨论哈~


Github 同步地址:

https://github.com/grandyang/leetcode/issues/891

参考资料:

https://leetcode.com/problems/sum-of-subsequence-widths/

https://leetcode.com/problems/sum-of-subsequence-widths/discuss/162318/O(nlogn)-solution

https://leetcode.com/problems/sum-of-subsequence-widths/discuss/161267/C%2B%2BJava1-line-Python-Sort-and-One-Pass

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 891. Sum of Subsequence Widths 子序列宽度之和的更多相关文章

  1. 【leetcode】891. Sum of Subsequence Widths

    题目如下: 解题思路:题目定义的子序列宽度是最大值和最小值的差,因此可以忽略中间值.首先对数组排序,对于数组中任意一个元素,都可以成为子序列中的最大值和最小值而存在.例如数组[1,2,3,4,5,6] ...

  2. 891. Sum of Subsequence Widths

    Given an array of integers A, consider all non-empty subsequences of A. For any sequence S, let the  ...

  3. [Swift]LeetCode891. 子序列宽度之和 | Sum of Subsequence Widths

    Given an array of integers A, consider all non-empty subsequences of A. For any sequence S, let the  ...

  4. Sum of Subsequence Widths LT891

    Given an array of integers A, consider all non-empty subsequences of A. For any sequence S, let the  ...

  5. 子序列宽度求和 Sum of Subsequence Widths

    2019-10-14 17:00:10 问题描述: 问题求解: 如果暴力求解,时间复杂度是exponational的,因为这里是子序列而不是子数组.显然,直接枚举子序列是不太现实的了,那么可以怎么做呢 ...

  6. [LeetCode] Largest Sum of Averages 最大的平均数之和

    We partition a row of numbers A into at most K adjacent (non-empty) groups, then our score is the su ...

  7. [LeetCode] 633. Sum of Square Numbers 平方数之和

    Given a non-negative integer c, your task is to decide whether there're two integers a and b such th ...

  8. LeetCode 633. Sum of Square Numbers平方数之和 (C++)

    题目: Given a non-negative integer c, your task is to decide whether there're two integers a and b suc ...

  9. [leetcode]404. Sum of Left Leaves左叶子之和

    弄个flag记录是不是左节点就行 int res = 0; public int sumOfLeftLeaves(TreeNode root) { if (root==null) return res ...

随机推荐

  1. N!(hdu1042)

    N! Given an integer N(0 ≤ N ≤ 10000), your task is to calculate N! Input One N in one line, process ...

  2. Uboot启动流程分析(三)

    1.前言 在前面的文章Uboot启动流程分析(二)中,链接如下: https://www.cnblogs.com/Cqlismy/p/12002764.html 已经对_main函数的整个大体调用流程 ...

  3. java文件操作File类

    1.文件路径操作 测试方法 @Test public void test5() { StringBuffer succBuffer = new StringBuffer("D:\\home\ ...

  4. mysql百万级数据分页查询缓慢优化-实战

    作为后端攻城狮,在接到分页list需求的时候,内心是这样的 画面是这样的 代码大概是这样的 select count(id) from …       查出总数 select * from …. li ...

  5. hive on spark 释放session资源

    背景 启动hive时,可以看到2.0以后的版本,将要弃用mr引擎,官方建议使用spark,tez等引擎. spark同时支持批式流式处理,可以减少学习成本.所以选用了spark作为执行引擎. hive ...

  6. SpringCloud之Eureka详细的配置

    介绍 SpringCloud是一个完整的微服务治理框架,包括服务发现和注册,服务网关,熔断,限流,负载均衡和链路跟踪等组件. SpringCloud-Eureka主要提供服务注册和发现功能.本文提供了 ...

  7. Linux软件安装——安装软件的命令

    Linux软件安装——安装软件的命令 摘要:本文主要学习了如何在Linux系统中安装.更新.卸载软件. rpm命令 rpm命令用来在Linux系统上进行软件的安装. 基本语法 安装命令: rpm -i ...

  8. Winform中设置多条Y轴时新增的Y轴刻度不显示问题解决

    场景 Winform中实现ZedGraph的多条Y轴(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1001322 ...

  9. Linux 和 Windows多线程函数对应表

    Linux Pthread API Windows SDK 库对应 API 创建 pthread_create CreateThread 退出 pthread_exit ThreadExit 等待 p ...

  10. 从html富文本中提取纯文本

    其实从html富文本中提取纯文本很简单,富文本基本上是使用html标签给文本加上丰富多彩的样式. 所以只需要将富文本字符串中的“<.....>”标签剔除,即可得到纯文本.我们可以使用正则表 ...