本文涉及知识点
键值皆有序map 线段树 数学
LeetCode100240. 最小化曼哈顿距离
给你一个下标从 0 开始的数组 points ,它表示二维平面上一些点的整数坐标,其中 points[i] = [xi, yi] 。
两点之间的距离定义为它们的曼哈顿距离。
请你恰好移除一个点,返回移除后任意两点之间的 最大 距离可能的 最小 值。
示例 1:
输入:points = [[3,10],[5,15],[10,2],[4,4]]
输出:12
解释:移除每个点后的最大距离如下所示:
- 移除第 0 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间,为 |5 - 10| + |15 - 2| = 18 。
- 移除第 1 个点后,最大距离在点 (3, 10) 和 (10, 2) 之间,为 |3 - 10| + |10 - 2| = 15 。
- 移除第 2 个点后,最大距离在点 (5, 15) 和 (4, 4) 之间,为 |5 - 4| + |15 - 4| = 12 。
- 移除第 3 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间的,为 |5 - 10| + |15 - 2| = 18 。
在恰好移除一个点后,任意两点之间的最大距离可能的最小值是 12 。
示例 2:
输入:points = [[1,1],[1,1],[1,1]]
输出:0
解释:移除任一点后,任意两点之间的最大距离都是 0 。
提示:
3 <= points.length <= 105
points[i].length == 2
1 <= points[i][0], points[i][1] <= 108
数学
假定p1到p2的距离最大,则如果不删除p1或p2,最大距离一定不会变小。
我们只需要找到任意一对p1、p2,分别尝试删除p1和p2的最大距离。问题就转化成求:最大曼哈顿距离。
最大曼哈顿距离,应该有公式和模板。但我没有。这两天在研究线段树,就用线段树。结果11:59才完成编码。赛下,调试了一个小时才搞定。
线段树
将points 按x排序,i表示第一个点,j表示第二个点。则xi <= xj恒成立。只需要考虑yi和yj的大小。
我们只需要计算 (xi+yi)和(xi-yi) 的最小值。用两棵最小树,分别记录最小值,单点更新,区间查找。难点:先要离散化,否则空间复杂度会超。
键值皆有序映射
情况一: yi <= yj
(xi+yi)和yj都越小越好,故键yj 升序,值(xi+yi)降序,如果冲突,淘汰键大的。
情况二:yi >= yj
(xi- yi) 越小越好,yj越大越好。故键yj升序,值(xi- yi)升序。发生冲突,淘汰键小的。
我看题解才知道的
切比雪夫距离(Chebyshev Distance) 在二维空间中,切比雪夫距离是通过计算两个点在各个坐标轴上的差值的绝对值中的最大值来衡量它。
曼哈顿距离在坐标轴旋转 45 度后与切比雪夫距离等价。
代码
线段树实现
class CDiscretize //离散化 { public: CDiscretize(vector<int> nums) { sort(nums.begin(), nums.end()); nums.erase(std::unique(nums.begin(), nums.end()), nums.end()); m_nums = nums; for (int i = 0; i < nums.size(); i++) { m_mValueToIndex[nums[i]] = i; } } int operator[](const int value)const { auto it = m_mValueToIndex.find(value); if (m_mValueToIndex.end() == it) { return -1; } return it->second; } int size()const { return m_mValueToIndex.size(); } vector<int> m_nums; protected: unordered_map<int, int> m_mValueToIndex; }; template<class TSave, class TRecord> class CSingUpdateLineTree { public: CSingUpdateLineTree(int iEleSize, TSave tDefautl) :m_iEleSize(iEleSize), m_vSave(iEleSize * 4, tDefautl) { } void Update(int index, TRecord update) { Update(1, 1, m_iEleSize, index + 1, update); } void Query(int leftIndex, int leftRight) { Query(1, 1, m_iEleSize, leftIndex + 1, leftRight + 1); } void Init() { Init(1, 1, m_iEleSize); } const int m_iEleSize; protected: void Init(int iNodeNO, int iSaveLeft, int iSaveRight) { if (iSaveLeft == iSaveRight) { OnInit(m_vSave[iNodeNO], iSaveLeft); return; } const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; Init(iNodeNO * 2, iSaveLeft, mid); Init(iNodeNO * 2 + 1, mid + 1, iSaveRight); OnUpdateParent(m_vSave[iNodeNO], m_vSave[iNodeNO * 2], m_vSave[iNodeNO * 2 + 1], iSaveLeft, iSaveRight); } void Query(int iNodeNO, int iSaveLeft, int iSaveRight, int iQueryLeft, int iQueryRight) { if ((iSaveLeft >= iQueryLeft) && (iSaveRight <= iQueryRight)) { OnQuery(m_vSave[iNodeNO]); return; } if (iSaveLeft == iSaveRight) {//没有子节点 return; } const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (mid >= iQueryLeft) { Query(iNodeNO * 2, iSaveLeft, mid, iQueryLeft, iQueryRight); } if (mid + 1 <= iQueryRight) { Query(iNodeNO * 2 + 1, mid + 1, iSaveRight, iQueryLeft, iQueryRight); } } void Update(int iNodeNO, int iSaveLeft, int iSaveRight, int iUpdateNO, TRecord update) { if (iSaveLeft == iSaveRight) { OnUpdate(m_vSave[iNodeNO], iSaveLeft, update); return; } const int mid = iSaveLeft + (iSaveRight - iSaveLeft) / 2; if (iUpdateNO <= mid) { Update(iNodeNO * 2, iSaveLeft, mid, iUpdateNO, update); } else { Update(iNodeNO * 2 + 1, mid + 1, iSaveRight, iUpdateNO, update); } OnUpdateParent(m_vSave[iNodeNO], m_vSave[iNodeNO * 2], m_vSave[iNodeNO * 2 + 1], iSaveLeft, iSaveRight); } virtual void OnInit(TSave& save, int iSave) = 0; virtual void OnQuery(TSave& save) = 0; virtual void OnUpdate(TSave& save,int iSaveLeft, const TRecord& update) = 0; virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) = 0; vector<TSave> m_vSave; }; template<class TSave=pair<int,int>, class TRecord =int> class CMyLineTree : protected CSingUpdateLineTree<TSave, TRecord> { public: CMyLineTree(CDiscretize& dis,int iEleSize) : m_dis(dis),CSingUpdateLineTree<TSave, TRecord>(iEleSize,std::make_pair(1e9,-1)) { } void Update(int y, TRecord update) { CSingUpdateLineTree<TSave, TRecord>::Update(m_dis[y],update); } void QueryLessEqual(int y) { m_prMin = { 1e9,-1 }; CSingUpdateLineTree<TSave, TRecord>::Query(0, m_dis[y]); } void QueryMoreEqual(int y) { m_prMin = { 1e9,-1 }; CSingUpdateLineTree<TSave, TRecord>::Query(m_dis[y], m_dis.size()-1); } pair<int, int> m_prMin = { 1e9,-1 }; protected: virtual void OnInit(TSave& save, int iSave) override { } virtual void OnQuery(TSave& save) override { if (save < m_prMin) { m_prMin = save; } } virtual void OnUpdate(TSave& save, int iSaveLeft,const TRecord& update) override { TSave tmp = { update,m_dis.m_nums[iSaveLeft - 1] }; save = min(save,tmp); } virtual void OnUpdateParent(TSave& par, const TSave& left, const TSave& r, int iSaveLeft, int iSaveRight) override { par = min(left, r); } CDiscretize& m_dis; }; class Solution { public: int minimumDistance(vector<vector<int>>& points) { sort(points.begin(), points.end(), [](const auto& v1, const auto& v2) {return v1[0] < v2[0]; }); auto [tmp, i1, i2] = Max(points); auto pts1 = points, pts2 = points; pts1.erase(pts1.begin() + i1); pts2.erase(pts2.begin() + i2); auto [m1, tmp1, tmp2] = Max(pts1); auto [m2, tmp3, tmp4] = Max(pts2); return min(m1, m2); } std::tuple<int, int, int> Max(const vector<vector<int>>& pts) { vector<int> ys; for (const auto& pt : pts) { ys.emplace_back(pt[1]); } CDiscretize dis(ys); CMyLineTree mXAndY(dis,dis.size()), mXSubY(dis, dis.size()); const int n = pts.size(); int iRet = -1e9,i2; std::pair<int, int> xy; for (int i = 1; i < n; i++) { mXAndY.Update(pts[i - 1][1], pts[i - 1][0] + pts[i - 1][1]); mXSubY.Update(pts[i - 1][1], pts[i - 1][0] - pts[i - 1][1]); mXAndY.QueryLessEqual(pts[i][1]); const int iDis1 = pts[i][0] + pts[i][1] - mXAndY.m_prMin.first; if (iDis1 > iRet) { iRet = iDis1; xy = { mXAndY.m_prMin.first- mXAndY.m_prMin.second, mXAndY.m_prMin.second }; i2 = i; } mXSubY.QueryMoreEqual(pts[i][1]); const int iDis2 = pts[i][0] - pts[i][1] - mXSubY.m_prMin.first; if (iDis2 > iRet) { iRet = iDis2; xy = { mXSubY.m_prMin.first + mXSubY.m_prMin.second, mXSubY.m_prMin.second }; i2 = i; } } int i1 = 0; for (; (xy.first != pts[i1][0]) || (xy.second != pts[i1][1]);i1++); return { iRet,i1,i2 }; } };
测试用例
template<class T> void Assert(const T& t1, const T& t2) { assert(t1 == t2); } template<class T> void Assert(const vector<T>& v1, const vector<T>& v2) { if (v1.size() != v2.size()) { assert(false); return; } for (int i = 0; i < v1.size(); i++) { Assert(v1[i], v2[i]); } } int main() { vector<vector<int>> points; { points = { {9,8},{1,8},{3,1},{9,1},{7,7},{3,6} }; auto res = Solution().minimumDistance(points); Assert(13, res); } { points = { {3,10},{5,15},{10,2},{4,4} }; auto res = Solution().minimumDistance(points); Assert(12, res); } { points = { {3,2},{3,9},{7,10},{4,4},{8,10},{2,7} }; auto res = Solution().minimumDistance(points); Assert(10, res); } { points = { {1,1},{1,1},{1,1},{1,1} }; auto res = Solution().minimumDistance(points); Assert(0, res); } //CConsole::Out(res); }
键值皆有需map
template<class _Kty, class _Ty, bool bValueAsc, bool bOutSmallKey> class COrderValueMap { public: void Add(_Kty key, _Ty value) { if (bOutSmallKey) { if (bValueAsc) { AddOutSmall(key, value, std::less_equal<_Ty>(), std::greater_equal<_Ty>()); } else { AddOutSmall(key, value, std::greater_equal<_Ty>(), std::less_equal<_Ty>()); } } else { if (bValueAsc) { AddNotOutSmall(key, value, std::greater_equal<_Ty>(), std::less_equal<_Ty>()); } else { AddNotOutSmall(key, value, std::less_equal<_Ty>(), std::greater_equal<_Ty>()); } } }; std::map<_Kty, _Ty> m_map; protected: template<class _Pr1, class _Pr2> void AddOutSmall(_Kty key, _Ty value, _Pr1 pr1, _Pr2 pr2) { auto it = m_map.lower_bound(key); if ((m_map.end() != it) && pr1(it->second, value)) { return;//被旧值淘汰 } auto ij = it; while (it != m_map.begin()) { --it; if (pr2(it->second, value)) { it = m_map.erase(it); } else { break; } } m_map[key] = value; } template<class _Pr1, class _Pr2> void AddNotOutSmall(_Kty key, _Ty value, _Pr1 pr1, _Pr2 pr2) { auto it = m_map.upper_bound(key); if ((m_map.begin() != it) && pr1(std::prev(it)->second, value)) { return;//被淘汰 } auto ij = it; for (; (m_map.end() != ij) && pr2(ij->second, value); ++ij); m_map.erase(it, ij); m_map[key] = value; }; }; class Solution { public: int minimumDistance(vector<vector<int>>& points) { sort(points.begin(), points.end(), [](const auto& v1, const auto& v2) {return v1[0] < v2[0]; }); auto [tmp, i1, i2] = Max(points); auto pts1 = points, pts2 = points; pts1.erase(pts1.begin() + i1); pts2.erase(pts2.begin() + i2); auto [m1, tmp1, tmp2] = Max(pts1); auto [m2, tmp3, tmp4] = Max(pts2); return min(m1, m2); } std::tuple<int, int, int> Max(const vector<vector<int>>& pts) { COrderValueMap<int, int, false, false> mXAndY; COrderValueMap<int, int, true, true> mXSubY; const int n = pts.size(); int iRet = -1e9,i2; for (int i = 1; i < n; i++) { mXAndY.Add(pts[i - 1][1], pts[i - 1][0] + pts[i - 1][1]); mXSubY.Add(pts[i - 1][1], pts[i - 1][0] - pts[i - 1][1]); auto it1 = mXAndY.m_map.upper_bound(pts[i][1]); if (mXAndY.m_map.begin() != it1 ) { --it1; const int iDis1 = pts[i][0] + pts[i][1] - it1->second; if (iDis1 > iRet) { iRet = iDis1; i2 = i; } } auto it2 = mXSubY.m_map.lower_bound(pts[i][1]); if (it2 != mXSubY.m_map.end()) { const int iDis2 = pts[i][0] - pts[i][1] - it2->second; if (iDis2 > iRet) { iRet = iDis2; i2 = i; } } } int i1 = -1,iMinDis=-1; for (int i = 0; i < n; i++) { const int curDis = abs(pts[i][0] - pts[i2][0]) + abs(pts[i][1] - pts[i2][1]); if (curDis > iMinDis) { iMinDis = curDis; i1 = i; } } return { iRet,i1,i2 }; } };
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。