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. 有关tab页的

    有关tab页的 1.静态的 2.动态的可以删除的 3.删除右侧,左侧,全部,除了自己以外的. 4.多了可以自动伸缩. 5.带shown事件.可以反向影响菜单去.

  2. POJ 2594 (传递闭包 + 最小路径覆盖)

    题目链接: POJ 2594 题目大意:给你 1~N 个点, M 条有向边.问你最少需要多少个机器人,让它们走完所有节点,不同的机器人可以走过同样的一条路,图保证为 DAG. 很明显是 最小可相交路径 ...

  3. pandas使用大全--数据与处理

    1.首先导入pandas库,一般都会用到numpy库,所以我们先导入备用: import numpy as np import pandas as pd 导入CSV或者xlsx文件: df = pd. ...

  4. vue 移动端上传图片结合localResizeIMG插件进行图片压缩

    localResizeIMG插件的功能是将图片进行压缩,然后转换成base64传给后台. 首先, npm i lrz -save 然后,再main.js里面引入lrz import lrz from ...

  5. .Net轻松处理亿级数据--clickhouse及可视化界面安装介绍

    该篇内容由个人博客点击跳转同步更新!转载请注明出处! 前言 我是在17年就听说过Clickhouse,那时还未接触过亿数据的运算,那时我在的小公司对于千万数据的解决方案还停留在分库分表,最好的也是使用 ...

  6. vue中使用Ajax(axios)、vue函数中this指向问题

    Vue.js 2.0 版本推荐使用 axios 来完成 ajax 请求.Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 node.js 中. axios中文文档库:http ...

  7. Kafka学习笔记之Kafka性能测试方法及Benchmark报告

    0x00 概述 本文主要介绍了如何利用Kafka自带的性能测试脚本及Kafka Manager测试Kafka的性能,以及如何使用Kafka Manager监控Kafka的工作状态,最后给出了Kafka ...

  8. JDK1.8 Stream

    Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据. Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达 ...

  9. JavaScript中的 JSON 和 JSONP

    JSON 和 JSONP JSONP是一种发送JSON数据的方法,无需担心跨域问题.JSONP不使用该XMLHttpRequest对象.JSONP使用<script>标签代替.由于跨域策略 ...

  10. SharpGL之透视投影和摄像机

    当三维体放在世界坐标系中后,由于显示器只能用二维图像显示三维休,因此必须要依赖投影来把三维体降低维数. 投影变换的目的就是定义了一个视景体,使得视景体外多余的部分不会显示. 投影包括透视投影(pers ...