(化简复杂度) 离散化


离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。

通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。

(摘自百度百科)

直接举例:(0~n-1)

下标 idx01234
原数组103894
离散化数组40231

应用

如果问题不强调数值大小,只在乎数值的相对大小 。则可以通过离散化大大降低空间和时间复杂度。

如:在一个图论题目中, 给出 1,1000两个点。

如果常规的使用邻接矩阵在存储,则需要1000*1000的大小。

而通过离散化变为 1,2后,在空间上的开销直接成指数级的降低!

模板题:力扣:1331. 数组序号转换

力扣: 506. 相对名次

力扣:剑指 Offer 51. 数组中的逆序对

一维

实现思路

这过程如果听的不清晰,最好自己手动模拟一遍过程

  • 将原数据(数据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

下标 idx0123456789
原数组103894355113
离散化数组4023102260
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;
}
下标 idx0123456789
原数组103894355113
离散化数组4023102260

二分写法(重复数值)

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;
    }
};



END

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值