子序列宽度之和
题目
原题链接:子序列宽度之和
一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。
给你一个整数数组 nums ,返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大,请返回对 10^9 + 7 取余 后的结果。
子序列 定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。例如,[3,6,2,7] 就是数组 [0,3,1,6,2,2,7] 的一个子序列。
示例 1:
输入: nums = [2,1,3]
输出: 6
解释: 子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 。
相应的宽度是 0, 0, 0, 1, 1, 2, 2 。
宽度之和是 6 。
示例 2:
输入: nums = [2]
输出: 0
提示:
1 <= nums.length <= 10^51 <= nums[i] <= 10^5
题解
暴力(时间超限)
思路:暴力的想法就是先枚举出该数组的全部序列,然后求出每个序列的最大值和最小值,最后求和取余,显然,该枚举过程是非常超时的,因为数组的长度是10^5,所以该方法不可取。
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> ans = new ArrayList<>();
public int sumSubseqWidths(int[] nums) {
dfs(0, nums);
long res = 0;
for (List<Integer> list: ans) {
//求最大值和最小值
int min = list.get(0);
int max = list.get(0);
for (int x: list) {
if (max < x) {
max = x;
}
if (min > x) {
min = x;
}
}
//求和并取余
res += max - min;
res %= 1e9 + 7;
}
return (int)res;
}
//枚举所有子序列
public void dfs(int index, int[]nums) {
if (index >= nums.length) {
if (path.size() > 0) {
ans.add(new ArrayList<>(path));
}
return;
}
//取或者不取
path.add(nums[index]);
dfs(index + 1, nums);
path.remove(path.size() - 1);
dfs(index + 1, nums);
}
}
排序 + 数学
思路来源:传送门
我们先对数组进行排序,因为是子序列,所以排序并不影响最终结果,我们计算每个元素对最终答案的贡献值。对于nums数组中的nums[i],其要么是最大值要么是最小值:
- 如果
nums[i]是最大值,那么对于nums[0]、nums[1]....nums[i - 1],一共有2^i种答案,因为其能够组成的子序列共有2^i种,那么作为最大值的贡献值为nums[i]*2^i。 - 如果
nums[i]是最小值,那么对于nums[i + 1]、nums[i + 2]...nums[n - 1],一共有2^(n-i-1)种答案,原因同上,其作为最小值的贡献值为nums[i]*2^(n-i-1)。 - 所以
nums[i]的最终贡献值就等于nums[i]*(2^i-2^(n-i-1))。
class Solution {
public int sumSubseqWidths(int[] nums) {
//排序
Arrays.sort(nums);
long mod = 1000000007;
long res = 0;
int len = nums.length;
long[] p = new long[len];
p[0] = 1;
//取模运算
for (int i = 1; i < len; i++) {
p[i] = p[i - 1] * 2 % mod;
}
//贡献值 = nums[i]*(2^i-2^(n-i-1))
for (int i = 0; i < len; i++) {
res = (res + nums[i] * (p[i] - p[len - i - 1])) % mod;
}
return (int)res;
}
}