当前位置: 首页 > news >正文

8.30美团技术岗算法第三题

题目:

给定顺序数组a,计算所有子数组的权值之和

         * 权值定义:使子数组成为连续整数序列需要插入的最少元素个数

说明:

题目很好理解由题意得 权值公式:(max - min + 1) - 子数组长度= (max - min + 1) - (j-i+1)

代码:

解法一:暴力法 不用想时间肯定超

public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int n = in.nextInt();int[] a = new int[n];for (int i = 0; i < n; i++) {a[i] = in.nextInt();}long result = 0;// 暴力遍历所有子数组for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {// 找出子数组[i,j]的最大值和最小值int minVal = a[i];int maxVal = a[i];for (int k = i; k <= j; k++) {minVal = Math.min(minVal, a[k]);maxVal = Math.max(maxVal, a[k]);}// 计算当前子数组的权值int weight = maxVal - minVal + 1 - (j - i + 1);result += weight;}}System.out.println(result);in.close();}
}

结果超时,想到该问题满足重叠子问题,尝试使用动态规划法求解:

解法二:动态规划

public class Main {public static void main(String[] args) {Scanner in = new Scanner(System.in);int n = in.nextInt();int[] a = new int[n];for (int i = 0; i < n; i++) {a[i] = in.nextInt();}// DP数组:dp[i][j]表示子数组[i,j]的权值int[][] dp = new int[n][n];// 辅助数组:记录子数组[i,j]的最小值和最大值int[][] minVal = new int[n][n];int[][] maxVal = new int[n][n];long result = 0;// 初始化:长度为1的子数组for (int i = 0; i < n; i++) {minVal[i][i] = a[i];maxVal[i][i] = a[i];dp[i][i] = 0; // 单个元素的权值为0result += dp[i][i];}// 动态规划:按子数组长度递增计算for (int len = 2; len <= n; len++) {for (int i = 0; i <= n - len; i++) {int j = i + len - 1;// 利用已知的子数组[i, j-1]的信息minVal[i][j] = Math.min(minVal[i][j-1], a[j]);maxVal[i][j] = Math.max(maxVal[i][j-1], a[j]);// 计算当前子数组的权值dp[i][j] = maxVal[i][j] - minVal[i][j] - (j - i + 1);result += dp[i][j];}}System.out.println(result);in.close();}
}

但是 这个的空间复杂度超出了限制

解法三:数学分解与算法优化

核心思想
权值公式 : 权值 = max - min - length + 1

数学分解 :将总和分解为四个独立的贡献:
最大值贡献 :每个元素作为某些子数组最大值的贡献
最小值贡献 :每个元素作为某些子数组最小值的贡献
长度贡献 :所有子数组长度的负贡献
常数贡献 :每个子数组+1的贡献

换句话说就是分别计算每个数字作为最大值和最小值对最后权值总数的贡献,所以就要找出每个数在所有子数组中可能出现为最大值/最小值的次数

代码如下:

import java.util.*;
import java.io.*;public class SubarrayWeightSum {/*** 高效计算所有子数组权值之和* 权值定义:max - min - length + 1* 使用数学方法:计算每个元素作为最大值和最小值的贡献* 时间复杂度:O(n),空间复杂度:O(n)*/public static long solve(int[] arr) {int n = arr.length;if (n == 0) return 0;// 计算每个元素作为最大值的贡献long maxContribution = calculateMaxContribution(arr);// 计算每个元素作为最小值的贡献long minContribution = calculateMinContribution(arr);// 计算长度的贡献(每个子数组长度的负贡献)long lengthContribution = calculateLengthContribution(n);// 计算常数项贡献(每个子数组+1的贡献)long constantContribution = (long) n * (n + 1) / 2;return maxContribution - minContribution - lengthContribution + constantContribution;}/*** 使用单调栈计算每个元素作为最大值的贡献*/private static long calculateMaxContribution(int[] arr) {int n = arr.length;long contribution = 0;// 计算每个元素左边第一个大于等于它的元素位置int[] leftGreater = new int[n];Stack<Integer> stack = new Stack<>();for (int i = 0; i < n; i++) {while (!stack.isEmpty() && arr[stack.peek()] < arr[i]) {stack.pop();}leftGreater[i] = stack.isEmpty() ? -1 : stack.peek();stack.push(i);}// 计算每个元素右边第一个大于它的元素位置int[] rightGreater = new int[n];stack.clear();for (int i = n - 1; i >= 0; i--) {while (!stack.isEmpty() && arr[stack.peek()] <= arr[i]) {stack.pop();}rightGreater[i] = stack.isEmpty() ? n : stack.peek();stack.push(i);}// 计算每个元素作为最大值的贡献for (int i = 0; i < n; i++) {long leftCount = i - leftGreater[i];// 计算当前元素左边有多少个位置可以作为子数组的 起始位置long rightCount = rightGreater[i] - i;//表示:从位置 i 到位置 rightGreater[i] - 1 ,有多少个位置可以作为子数组的 结束位置contribution += (long) arr[i] * leftCount * rightCount;}return contribution;}/*** 使用单调栈计算每个元素作为最小值的贡献*/private static long calculateMinContribution(int[] arr) {int n = arr.length;long contribution = 0;// 计算每个元素左边第一个小于等于它的元素位置int[] leftSmaller = new int[n];Stack<Integer> stack = new Stack<>();for (int i = 0; i < n; i++) {while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {stack.pop();}leftSmaller[i] = stack.isEmpty() ? -1 : stack.peek();stack.push(i);}// 计算每个元素右边第一个小于它的元素位置int[] rightSmaller = new int[n];stack.clear();for (int i = n - 1; i >= 0; i--) {while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {stack.pop();}rightSmaller[i] = stack.isEmpty() ? n : stack.peek();stack.push(i);}// 计算每个元素作为最小值的贡献for (int i = 0; i < n; i++) {long leftCount = i - leftSmaller[i];// 计算当前元素左边有多少个位置可以作为子数组的 起始位置long rightCount = rightSmaller[i] - i;//位置 i 到位置 rightSmaller[i] - 1 ,有多少个位置可以作为子数组的 结束位置contribution += (long) arr[i] * leftCount * rightCount;}return contribution;}/*** 计算所有子数组长度的总和* 对于长度为len的子数组,有(n-len+1)个* 总和 = Σ(len * (n-len+1)) for len from 1 to n*/private static long calculateLengthContribution(int n) {long total = 0;for (int len = 1; len <= n; len++) {total += (long) len * (n - len + 1);}return total;}/*** 优化版本:直接计算长度贡献的数学公式*/private static long calculateLengthContributionOptimized(int n) {// 使用数学公式:n*(n+1)*(n+2)/6return (long) n * (n + 1) * (n + 2) / 6;}public static void main(String[] args) throws IOException {// BufferedReader br = new BufferedReader(new InputStreamReader(System.in));// int n = Integer.parseInt(br.readLine().trim());// StringTokenizer st = new StringTokenizer(br.readLine());int[] arr = new int[]{3, 1, 4};// for (int i = 0; i < n; i++) {//     arr[i] = Integer.parseInt(st.nextToken());// }long result = solve(arr);System.out.println(result);//br.close();}
}

备注:

### 1. 常数贡献公式:n * (n + 1) / 2
问题 :权值公式中的常数项 +1 对总和的贡献是多少?

推导过程 :

- 每个子数组都会贡献 +1 ,所以需要计算总共有多少个子数组
- 对于长度为 n 的数组,子数组总数为:
```
起始位置0:可以形成 n 个子数组 [0,
0], [0,1], ..., [0,n-1]
起始位置1:可以形成 n-1 个子数组 [1,
1], [1,2], ..., [1,n-1]
起始位置2:可以形成 n-2 个子数组 [2,
2], [2,3], ..., [2,n-1]
...
起始位置n-1:可以形成 1 个子数组 
[n-1,n-1]
```
- 总数 = n + (n-1) + (n-2) + ... + 1 = 1 + 2 + 3 + ... + n
- 这是经典的等差数列求和: n * (n + 1) / 2
### 2. 长度贡献公式:n * (n + 1) * (n + 2) / 6
问题 :权值公式中的 -(j-i+1) 对总和的贡献是多少?

推导过程 :

- 需要计算所有子数组长度的总和
- 按长度分组:
```
长度1的子数组:n 个,贡献 1 × n
长度2的子数组:n-1 个,贡献 2 × 
(n-1)  
长度3的子数组:n-2 个,贡献 3 × 
(n-2)
...
长度n的子数组:1 个,贡献 n × 1
```
- 总长度 = Σ(k=1 to n) k × (n-k+1)
- 展开: = Σ(k=1 to n) k × (n+1) - k²
- = (n+1) × Σ(k=1 to n) k - Σ(k=1 to n) k²
- 使用已知公式:
- Σ(k=1 to n) k = n(n+1)/2
- Σ(k=1 to n) k² = n(n+1)(2n+1)/6
- 代入并化简得到: n * (n + 1) * (n + 2) / 6

http://www.dtcms.com/a/390039.html

相关文章:

  • CentOS 7 一键安装 vsftpd 并创建可登录 FTP 用户 test
  • k8s自定义调度器实现路径
  • 服务器数据恢复—RAIDZ硬盘“惹祸”导致服务器崩溃的数据恢复过程
  • 20250917_车辆定位系统aidata-01Apache Doris数据库备份+恢复+清理 流程操作文档
  • Redis 7.0 ACL实战:RBAC模型实现精细化权限控制
  • Lightrag 文档处理不成功(httpx.ReadTimeout 为主)的解决步骤与方法总结
  • Spring Boot + MySQL MCP 集成标准流程
  • 基于RK3588与ZYNQ7045的ARM+FPGA+AI实时系统解决方案
  • 基于Linux,看清C++的动态库和静态库
  • 多导睡眠五大PSG数据集统一格式化处理|SHHS
  • ZeroMQ基础
  • 【JavaGuide学习笔记】什么,Java中 native 也是一个关键字?
  • 【LWIP】STM32F429 + LWIP + DP83848 热插拔问题总结
  • RGWRados::Object::Write::_do_write_meta()
  • Shopify 集合页改造:增加 Banner 图片 + 点击加载更多功能
  • 泛函 Φ(u) = ∫[(u″)² + u² + 2f(x)u]dx − (u′(0))² 在 u(0)=u(1) 下的驻点方程与边界条件
  • JAVA高频面试题汇总:Java+ 并发 +Spring+MySQL+ 分布式 +Redis+ 算法 +JVM 等
  • 构建与运营“爬虫 IP 代理池”的方法论
  • 【文献笔记】Point Transformer
  • Linux | i.MX6ULL Modbus 移植和使用(第二十一章)
  • 几种微前端框架的沙箱策略
  • 黑盒测试:测试用例设计之边界值设计方法(边界值分析法)(上点、离点、内点)健壮性测试、单缺陷假设理论
  • 【题解】P1548 [NOIP 1997 普及组] 棋盘问题
  • scala中for推导式详细讲解
  • React学习 ---- 基础知识学习
  • C语言实现MATLAB中的Fir1带通滤波器
  • 微信小程序开发教程(十七)
  • 9月18日星期四今日早报简报微语报早读
  • SqlSugar 问题记录
  • 记一次宝塔+nginx+php8+thinkphp8多应用下某个应用报错404的问题 - nginx、php日志全无 - 无法追踪