离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。
(摘自百度百科)
直接举例:(0~n-1)
下标 idx | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
原数组 | 10 | 3 | 8 | 9 | 4 |
离散化数组 | 4 | 0 | 2 | 3 | 1 |
应用
如果问题不强调数值大小,只在乎数值的相对大小 。则可以通过离散化大大降低空间和时间复杂度。
如:在一个图论题目中, 给出 1,1000
两个点。
如果常规的使用邻接矩阵在存储,则需要1000*1000
的大小。
而通过离散化变为 1,2
后,在空间上的开销直接成指数级的降低!
模板题:力扣:1331. 数组序号转换
力扣: 506. 相对名次
一维
实现思路
这过程如果听的不清晰,最好自己手动模拟一遍过程
- 将原数据(数据val+下标idx)重新存储到一个数据结构中
- 将新的数据结构按照存储的数值val进行排序
- 便利新排序好的数据结构
- 创建离散化数组
- 根据原先的下标来搜寻需要放入的数组的单元(关键)
在一些算法竞赛书本或者是视频中大都是使用结构体+排序
处理的
这里我运用自带排序效果的map
来处理
用自带排序的map
实现
vector<int> toDiscretization(const vector<int>& arr) {
int n = arr.size();
// 使用map省去排序步骤
map<int, int>mp;
for (int i = 0; i < n; i++) {
// key 为数值(按此排序)
// val 为下标
mp[ arr[i] ] = i;
}
vector<int>discretization(n);
int idx = 0;
// map自动按照从小到大的排序
for (auto &[key, val] : mp) {
// 根据idx的递增来排放
// 这里表面上是数组赋值
// 实际上是根据序号来找对应的数组的那个空间
discretization[val] = idx;
// idx+1操作的先后顺序
// 决定是0~n-1 还是1~n
// 当然也可以初始化为1
idx++;
}
return discretization;
}
可以根据自己的需要 改变需要结果的形式 如:
改变起始点
0 ~ n-1
1 ~ n
根据分值大小获得名次排名
n-1 ~ 0
n ~ 1
重复数值的处理情况
如果需要处理有重复数值出现的情况,则只需要像处理数组中时候有连续相同数值的处理方式相同即可
而普通的map 对于同一键值的值是会覆盖的,因此这里可以用到multimap
下标 idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
原数组 | 10 | 3 | 8 | 9 | 4 | 3 | 5 | 5 | 11 | 3 |
离散化数组 | 4 | 0 | 2 | 3 | 1 | 0 | 2 | 2 | 6 | 0 |
vector<int> toDiscretization(const vector<int>& arr) {
int n = arr.size();
// 健值可重复型map
multimap<int, int>mp;
for (int i = 0; i < n; i++) {
// key 为数值(按此排序)
// val 为下标
// 注意multimap不能用中括号[]
mp.insert({arr[i], i});
}
vector<int>discretization(n);
int idx = 0;
auto it = mp.begin();
int pre = it->first; // 记录前驱数值
discretization[it->second] = idx;
for (it++; it != mp.end(); it++) {
int key = it->first;
int val = it->second;
if (key == pre){
// 数值相等 idx不变
discretization[val] = idx;
} else {
// 数值不等 idx增加
idx++;
discretization[val] = idx;
}
pre = key;
}
return discretization;
}
下标 idx | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
原数组 | 10 | 3 | 8 | 9 | 4 | 3 | 5 | 5 | 11 | 3 |
离散化数组 | 4 | 0 | 2 | 3 | 1 | 0 | 2 | 2 | 6 | 0 |
二分写法(重复数值)
void toDiscretization(vector<int>& arr, int start = 1) {
vector<int> tmp = arr;
sort(tmp.begin(), tmp.end());
for (int& num: arr) {
num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + start;
}
return ;
}
练习题:力扣:506. 相对名次
只需要按照高分排名次即可
class Solution {
public:
vector<string> findRelativeRanks(vector<int>& score) {
int n = score.size();
map<int, int, less<int>>mp;
for (int i = 0; i < n; i++) {
mp[score[i]] = i;
}
vector<string>ans(n);
int idx = 0;
for (auto &[key, val] : mp) {
// 注意这里是按高分排名次
int num = n-idx++;
switch(num){
case 1:
ans[val] = "Gold Medal";
break;
case 2:
ans[val] = "Silver Medal";
break;
case 3:
ans [val] = "Bronze Medal";
break;
default:
ans[val] = to_string(num);
break;
}
}
return ans;
}
};
二维
二维不要想的太复杂,直接两次一维操作
然后两条路一起映射就完事了
在映射过程中,我们遵循三个规则(以行举例)
- 若当前行是上一行的同一层则合并,不用处理占用零层空间
- 若当前行是上一行的下一层则接着后面占用一层空间
- 若当前行是上一行的间隔层则接着后面占用两层空间(第一层是压缩两层间所有的空白,第二层是记录当前层)
- 特殊的
- 若首位置前面有空白也要预留一层空间
- 若末位置后面有空白也要预留一层空间
力扣:1036. 逃离大迷宫 (二维离散化+普通BFS)
class Solution {
private:
const int BOUNDARY = 1e6;
const vector<vector<int>>MOV = {{1,0},{-1,0},{0,1},{0,-1}};
// 离散化后的二维数组长度
int rowDiscreteLen;
int colDiscreteLen;
public:
bool isEscapePossible(vector<vector<int>>& blocked, vector<int>& source, vector<int>& target) {
int pointNum = blocked.size()+2;
// 存储行列的数组
vector<int>rowArr;
rowArr.reserve(pointNum);
vector<int>colArr;
colArr.reserve(pointNum);
// 墙壁
for (auto &arr : blocked) {
rowArr.push_back(arr[0]);
colArr.push_back(arr[1]);
}
// 起点,终点
rowArr.push_back(source[0]);
colArr.push_back(source[1]);
rowArr.push_back(target[0]);
colArr.push_back(target[1]);
unordered_map<int,int> rowMap;
this->rowDiscreteLen = getDiscretization(rowArr, rowMap);
unordered_map<int,int> colMap;
this->colDiscreteLen = getDiscretization(colArr, colMap);
// 构造离散化后的邻接矩阵地图
vector<vector<bool>> grid(rowDiscreteLen+1, vector<bool>(colDiscreteLen+1));
for (auto &arr : blocked) {
// 获得离散化的映射
int& x = rowMap[ arr[0] ];
int& y = colMap[ arr[1] ];
grid[x][y] = true;
}
pair<int,int> START = {rowMap[source[0]], colMap[source[1]]};
pair<int,int> END = {rowMap[target[0]], colMap[target[1]]};
return bfs(grid, START, END);
}
private:
int getDiscretization(vector<int>& arr, unordered_map<int,int>& mp) {
sort(arr.begin(), arr.end());
int len = arr.size();
// 离散化后的数组长度,不断的更新累计后缀
int lastIdx = (arr[0] == 0 ? 0 : 1);
// 循环中不会处理起点
mp[arr[0]] = lastIdx;
// 前一个元素
int pre = arr[0] + (INT_MAX/2);
for (int i = 0; i < len; i++) {
int &cur = arr[i];
if (cur == pre) {
// 重复值合并,不用处理
continue;
} else if (cur == pre + 1) {
// 连续无间隔情况
lastIdx += 1;
mp[cur] = lastIdx;
} else if (cur > pre + 1) {
// 连续有间隔情况
// 需要多给空白位置预留
lastIdx += 2;
mp[cur] = lastIdx;
}
pre = cur;
}
// 末端位置没有占用的话,需要为末端位置预留空间
lastIdx += (arr[len-1] == BOUNDARY-1 ? 0 : 1);
return lastIdx;
}
inline bool check(const int x, const int y) {
if (x < 0 || x > rowDiscreteLen) {
return false;
}
if (y < 0 || y > colDiscreteLen){
return false;
}
return true;
}
bool bfs(vector<vector<bool>>& grid, const pair<int,int> START, const pair<int,int> END) {
queue<pair<int,int>> q;
q.push(START);
grid[START.first][START.second] = true;
while (!q.empty()) {
pair<int,int> cur = q.front();
q.pop();
if (cur == END){
return true;
}
for (auto& mov : MOV){
int nexX = cur.first + mov[0];
int nexY = cur.second + mov[1];
if (!check(nexX, nexY)) {
continue;
}
if (!grid[nexX][nexY]) {
q.push({nexX, nexY});
grid[nexX][nexY] = true;
}
}
}
return false;
}
};