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. ThinkPHP 3.2 自定义基类 Model

    ThinkPHP 提供了一个 Model 类,供其他的 Model 进行继承.Model 类中是 MVC 中的模型类,它是调用 持久层 的上层类.感觉这么描述问题很多,但是有什么办法呢?但是,这个 M ...

  2. js通过值获取数组对象对应下标

    var nn = [ { a: 'ss' },{ a: 'aa' },{ a : '11'},{ a: '33' },{ a: '88' } ] 我要怎么获取 a = 33的下标 var index ...

  3. python asyncio 协程调用task步骤

    import asyncio async def compute(x, y): print("Compute %s + %s ..." % (x, y)) await asynci ...

  4. div+css画一个小猪佩奇

    用DIV+CSS画一个小猪佩奇,挺可爱的,嘻嘻. HTML部分(全是DIV) <!-- 小猪佩奇整体容器 --> <div class="pig_container&quo ...

  5. Prometheus 监控K8S集群资源监控

    Prometheus 监控K8S集群中Pod 目前cAdvisor集成到了kubelet组件内,可以在kubernetes集群中每个启动了kubelet的节点使用cAdvisor提供的metrics接 ...

  6. MySQL for OPS 03:索引和执行计划

    写在前面的话 啥是索引?以一本书为例,如果想要找到某一指定章节的某一小节,书薄还好,如果书厚,可能就会找的头皮发麻.于是便出现了目录,让用户更容易查找到自己所需要的东西.索引就类似一张表的目录.其存在 ...

  7. dedecms5.7的文章详情页页面标题加载指定txt文本的随机关键字

    dedecms5.7的文章详情页加载指定txt文本的随机关键字 1 实现代码如下 {dede:name runphp='yes'} $file_path = "../test.txt&quo ...

  8. .NET使用本地outlook客户端发送邮件

    1.添加Microsoft.Office.Interop.Outlook引用 2.封装发送邮件方法 using System; using System.Configuration; using Sy ...

  9. Python collectioins

    collections是一个python的内建模块,提供了一些除了dict.list.tuble.等常见的数据类型之外的一些集合类 参考链接:https://www.liaoxuefeng.com/w ...

  10. LinuxShell——变量

    LinuxShell——变量 摘要:本文主要学习了Shell命令中的变量. 什么是变量 简单的说,变量就是让某一个特定字串代表不固定的内容. 变量是计算机内存的单元,其中存放的值可以改变.当Shell ...