归并排序巧解计算数组的小和问题
这道题如果用暴力的解法,对每一个元素遍历它左边的所有元素,累加比它更小的数,代码逻辑当然非常简单,但是时间复杂度会达到O(n2)O(n^2)O(n2),如果数组的长度n达到1e5时,我们估算一下啊,1e10次的运算很明显会超时,这么做会通不过测试系统。
而借助归并排序的思想,是可以将这道题的时间复杂度优化到O(nlogn)O(nlogn)O(nlogn)的。
下面介绍代码逻辑:
1.首先定义全局变量用于存储数据与临时数组
public static int MAXN = 100001; // 数组最大长度
public static int[] arr = new int[MAXN]; // 存储原始数组
public static int[] help = new int[MAXN]; // 合并时的临时数组
public static int n; // 数组实际长度
2.输入输出的处理
public static void main(String[] args)throws IOException{// 读入BufferedReader br = new BufferedReader(new InputStreamReader(System.in));StreamTokenizer in = new StreamTokenizer(br);// 输出PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));// 处理多组输入(直到文件结束)while(in.nextToken()!=StreamTokenizer.TT_EOF){n = (int) in.nval; // 读取数组长度// 读取数组元素for(int i=0;i<n;i++){in.nextToken();arr[i]=(int)in.nval;}// 计算小和并输出out.println(smallSum(0,n-1));}out.flush(); // 刷新缓冲区,确保输出out.close(); // 关闭输出流
}
3.分治函数
public static long smallSum(int l,int r){if(l==r){ // base casereturn 0;}int m = l+((r-l)>>1); // 中间位置,将数组分成 [l,m] 和 [m+1,r]// 分治逻辑:左半部分小和 + 右半部分小和 + 合并时的小和return smallSum(l,m) + smallSum(m+1,r) + merge(l,m,r);
}
4.合并函数
public static long merge(int l,int m,int r){long ans = 0; // 存储当前合并阶段的小和// 统计小和for(int j=m+1, i=l, sum=0; j<=r; j++){ // 累积左数组 L 中比当前 R[j] 小的元素和while(i<=m && arr[i] <= arr[j]){sum += arr[i++];}ans += sum;}// 合并两个有序子数组到 help 数组int i = l; int a = l; int b = m+1; while(a <= m && b <= r){ // 两个数组都没遍历完// 取较小的元素放入 helphelp[i++] = arr[a] <= arr[b] ? arr[a++] : arr[b++];}// 处理左数组剩余元素while(a <= m){help[i++] = arr[a++];}// 处理右数组剩余元素while(b <= r){help[i++] = arr[b++];}// 将 help 中的有序数组拷贝回 arrfor(i=l; i<=r; i++){arr[i] = help[i];}return ans;
}
代码链接
链接:小和
提取码:2JMV