727 字
4 分钟
【算法笔记】(五)归并分治
1、归并分治的原理
思考一个问题在大范围上的答案,是否等于:左部分的答案 + 右部分的答案 + 跨越左右产生的答案。
计算“跨越左右产生的答案”时,如果加上 左、右各自有序 这个设定,会不会获得计算的便利性。
如果以上两点都成立,那么该问题很可能被 归并分治 解决。
求解答案的过程中只需要加入 归并排序 的过程即可,因为要让左、右各自有序来获得计算的便利性。
2、例题一:小和问题

测试链接:https://www.nowcoder.com/practice/edfe05a1d45c4ea89101d936cac32469
package org.example;
import java.io.*;
// 小和问题public class Main {
public static int n; public static int[] arr = new int[100100]; public static int[] help = new int[100100]; // 结果可能数据比较大,用int会溢出 public static long result = 0;
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; } gB(0, n - 1); out.println(result); result = 0; }
out.flush(); out.close(); br.close(); }
private static void gB(int l, int r) { if (l == r) return; int mid = (l + r) >> 1; gB(l, mid); gB(mid + 1, r); smallSum(l, mid, r); }
private static void smallSum(int l, int m, int r) {
int left = l, right = m + 1, flag = l; while (left <= m && right <= r) { if (arr[left] <= arr[right]) { result += (long) arr[left] * (r - right + 1); // 当发现左边区间的值小于右边区间的某值时,此时因为两个区间都是有序的。 // 那么对于右边区间的当前值来说,其右手的所有剩下的值都将大于左边区间的当前值。 // 所以对于其右手的所有剩下的值来说左边区间的当前值就是它们各自小和的一部分。 help[flag++] = arr[left++]; } else { help[flag++] = arr[right++]; } }
while (left <= m) help[flag++] = arr[left++]; while (right <= r) help[flag++] = arr[right++];
for (int i = l; i <= r; i++) { arr[i] = help[i]; } }}3、例题二:翻转对数量

测试链接:https://leetcode.cn/problems/reverse-pairs/description/
static int[] help = new int[50100];
public int reversePairs(int[] nums) { if (nums.length <= 1) return 0; return gB(0, nums.length - 1, nums);}
private int gB(int l, int r, int[] nums) { if (l == r) { return 0; } int mid = (l + r) / 2; return gB(l, mid, nums) + gB(mid + 1, r, nums) + merge(l, mid, r, nums);}
private int merge(int l, int mid, int r, int[] nums) { int ans = 0;
// 统计跨越左右的翻转对数量 for (int i = l, j = mid + 1; i <= mid; i++) { while (j <= r && (long) nums[i] > (long) nums[j] * 2) j++; ans += j - mid - 1; }
// 归并排序:合并两个有序区间 int left = l, right = mid + 1, index = l; while (left <= mid && right <= r) { if (nums[left] <= nums[right]) help[index++] = nums[left++]; else help[index++] = nums[right++]; }
while (left <= mid) help[index++] = nums[left++]; while (right <= r) help[index++] = nums[right++];
for (int i = l; i <= r; i++) nums[i] = help[i];
return ans;}部分信息可能已经过时









