mobile wallpaper 1
mobile wallpaper 2
mobile wallpaper 3
mobile wallpaper 4
1120 字
3 分钟
离散化与区间合并
2026-03-31

离散化与区间合并#

离散化#

什么是离散化? 当题目的数值范围非常大(例如坐标在 -10⁹ ~ 10⁹),但实际用到的数值个数很少(例如只有 10⁵ 个)时,无法开一个覆盖整个值域的数组。离散化把所有”实际用到”的数值排序去重后,映射到从 1 开始的连续下标上,之后就可以像处理小数组一样处理它们。

保序离散化的三步:

  1. 收集: 把所有需要离散化的数值(加点位置、询问端点等)全部放进一个数组 alls;
  2. 排序去重: 排序后把重复元素去掉(见下面”数组排序去重”代码,去重函数返回新长度 len);
  3. 二分查找映射: 查询数值 x 时,在 alls 中二分出 x 的下标(一般 +1 使下标从 1 开始),这个下标就是 x 离散化后的”新坐标”。

为什么下标要从 1 开始? 因为后续通常要配合前缀和数组 s 使用,s[0] = 0 可以省去左端点越界的特判,与”前缀和”篇的做法保持一致。

数组排序去重

import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
int[] arr = {1,2,1,3,2,5,6,4,1,9,2,2};
//排序
Arrays.sort(arr);
int len = unique(arr);
System.out.println(len);
//截取非重复部分
int[] b = Arrays.copyOf(arr,len);
for (int i = 0; i < b.length; i++) {
System.out.print(b[i] + " ");
}
}
public static int unique(int[] a){
int j = 0;
//满足的条件
//1,数组中的第一个元素
//2,a[i] != a[i-1]
for (int i = 0; i < a.length; i++) {
if(i == 0 || a[i] != a[i-1]) a[j++] = a[i];
}
return j;
}
}

双指针去重的原理: 数组已有序,相等的元素一定相邻。慢指针 j 指向”下一个要写入的位置”,快指针 i 从头扫到尾;a[i] != a[i-1](或 i == 0)说明 a[i] 是第一次出现,写入 a[j] 并 j++。扫描结束后前 j 个元素互不相同,j 即新长度。

例题:区间和#

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。

现在,我们首先进行 n 次操作,每次操作将某一位置上的数加 c。

接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l, r] 之间的所有数的和。

输入格式: 第一行包含两个整数 n 和 m。 接下来 n 行,每行包含两个整数 x 和 c。 再接下来 m 行,每行包含两个整数 l 和 r。

输出格式: 共 m 行,每行输出一个询问中所求的区间内数字和。

import java.util.*;
public class Main {
static ArrayList<Integer> alls = new ArrayList<>();
static ArrayList<Map.Entry<Integer,Integer>> add = new ArrayList<>();
static ArrayList<Map.Entry<Integer,Integer>> query = new ArrayList<>();
public static void main(String[] args) {
Scanner sc= new Scanner(System.in);
int n =sc.nextInt();
int m = sc.nextInt();
int[] a = new int[300010];
int[] s = new int[300010];
for (int i = 0; i < n; i++) {
int x =sc.nextInt();
int c= sc.nextInt();
add.add(new AbstractMap.SimpleEntry<>(x,c));
alls.add(x);
}
for (int i = 0; i < m; i++) {
int l = sc.nextInt();
int r = sc.nextInt();
query.add(new AbstractMap.SimpleEntry<>(l,r));
alls.add(l);
alls.add(r);
}
Collections.sort(alls);
int len = unique(alls);
List<Integer> newList = alls.subList(0,len + 1);
for (Map.Entry<Integer, Integer> integerIntegerEntry : add) {
int x = find(integerIntegerEntry.getKey());
a[x] += integerIntegerEntry.getValue();
}
//预处理前缀和
for (int i = 1; i <= newList.size(); i++) {
s[i] = s[i-1] + a[i];
}
//处理询问
for (Map.Entry<Integer, Integer> integerIntegerEntry : query) {
int l = find(integerIntegerEntry.getKey());
int r = find(integerIntegerEntry.getValue());
System.out.println(s[r] - s[l -1]);
}
}
public static int find(int x){
int l = 0;
int r = alls.size()-1;
while (l < r ){
int mid = (l+r)>> 1;
if(alls.get(mid) >= x){
r = mid;
}else{
l = mid + 1;
}
}
return r + 1;
}
public static int unique(ArrayList<Integer> arrayList){
int j = 0;
for (int i = 0; i < arrayList.size(); i++) {
if(i == 0 || arrayList.get(i) != arrayList.get(i-1)){
arrayList.set(j++,arrayList.get(i));
}
}
return j;
}
}

代码流程梳理: 先把所有”加点坐标 x”和”询问端点 l、r”收集进 alls;排序去重后,用二分 find(x) 把每个坐标映射成从 1 开始的紧凑下标;在紧凑数组上做加点、前缀和;最后每个询问同样通过 find 映射后用前缀和 O(1) 求区间和。总复杂度 O((n + m) log(n + m)),与坐标值域大小无关。

区间合并#

区间合并就是将多个区间中存在交集的区间合并为一个区间

1.按区间左端点排序

2.扫描整个区间,把所有可能合并的区间合并

算法思路: 把所有区间按左端点从小到大排序;维护一个”当前正在合并的区间” [st, ed](初始设为一个不可能出现的值,如 st = ed = -2e9)。扫描每个区间 [l, r],分两种情况:

  • l > ed: 新区间与当前区间没有交集,把当前区间 [st, ed] 存入答案,然后把 [l, r] 作为新的当前区间;
  • l ≤ ed: 有交集(注意端点相交也算交集),只需把右端点扩大为 ed = max(ed, r)

扫描结束后别忘了把最后一个区间也存入答案。答案列表的大小就是合并后的区间个数。

给定 n 个区间 [l, r],要求合并所有有交集的区间。 注意:如果在端点处相交,也算有交集。 输出合并完成后的区间个数。

例如:[1,3] 和 [2,6] 可以合并为一个区间 [1,6]。

输入格式:

  • 第一行包含整数 n。
  • 接下来 n 行,每行包含两个整数 l 和 r。

输出格式:

共一行,包含一个整数,表示合并区间完成后的区间个数。

import java.util.*;
public class Main {
public static List<Map.Entry<Integer, Integer>> merge(List<Map.Entry<Integer,Integer>> segs){
segs.sort(Map.Entry.<Integer,Integer>comparingByKey().thenComparing(Map.Entry.comparingByValue()));
List<Map.Entry<Integer,Integer>> res = new ArrayList<>();
int st = (int)-2e9;
int ed = (int)-2e9;
for (Map.Entry<Integer, Integer> seg : segs) {
if(ed < seg.getKey()){
if(st != -2e9) res.add(new AbstractMap.SimpleEntry<>(st,ed));
st = seg.getKey();
ed = seg.getValue();
}else{
ed = Math.max(ed , seg.getValue());
}
}
if(st != -2e9) res.add(new AbstractMap.SimpleEntry<>(st,ed));
for (Map.Entry<Integer, Integer> re : res) {
System.out.println(re.getKey() + "---" + re.getValue());
}
return res;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
List<Map.Entry<Integer,Integer>> segs = new ArrayList<>();
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
int l = sc.nextInt();
int r= sc.nextInt();
segs.add(new AbstractMap.SimpleEntry(l,r));
}
List<Map.Entry<Integer,Integer>> res = merge(segs);
System.out.println(res.size());
}
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

离散化与区间合并
https://blogstella.xyz/posts/数据结构之离散化与区间合并/
作者
Stella
发布于
2026-03-31
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

目录