算法题中求解绝对值最值的技巧
作者:互联网
引言
现在算法题中,有时会遇到求解绝对值最值的问题,比如给定一个数组,求解abs∣ai−aj∣的最大值。诸如此类问题,暴力解法是用O(n2)时间复杂度遍历i,j。而一种常用优化是将绝对值拆开,比如abs∣ai−aj∣=max(ai−aj,aj−ai),将问题拆成两个子问题,而每个子问题通过维护当前最小值,即O(n)的时间可解出。最后通过O(1)取二者最值即可。
下面分享三道利用此技巧的算法题,难度按升序排列。
Leetcode 1131
题意:
给定两个数组arr1和arr2,让找两个下标i和j,使得∣arr1[i]−arr1[j]∣+∣arr2[i]−arr2[j]∣+∣i−j∣最大。
思路:
这里直接拆开三个绝对值,得到以下8个子问题:
然后我们把有关于下标i和下标j的项提出来放在一起,发现其实子问题只有以下四个:1&8;2&7;3&6;4&5(比如1和8,它们只不过把两个下标i和j顺序调换了下)。
而这四个子问题的差别就是它们中的符号为加减法的排列组合:++; +−; −+; −−。这样通过四个O(n)的循环即可求出最优解。
代码:
const int INF = 0x3f3f3f3f;
class Solution {
public:
int maxAbsValExpr(vector<int>& x, vector<int>& y) {
int res = 0, n = x.size();
for (int sign1=-1; sign1<=1; sign1+=2)
for (int sign2=-1; sign2<=1; sign2+=2) {
int mx = -INF, mn = INF;
for (int i=0; i<n; i++) {
int val = x[i] + sign1*y[i] + sign2*i;
mx = max(mx, val);
mn = min(mn, val);
}
res = max(res, mx - mn);
}
return res;
}
};
Leetcode 1330
题意:
给定一个数组nums,它的value值被定义为所有∣nums[i]−nums[i+1]∣的和(其中0<=i<=n)。我们有一个操作,能使它某一段连续的子数组翻转一次,问翻转前/后这个数组的value值最大能为多少?
思路:
- 如果我们选择不翻转子数组,那么它的value值可以通过O(n)时间遍历得到。这个值我们记录为sum1。
- 如果我们翻转子数组[L,R],这时新数组的value为:
sum1+∣a[R]−a[L−1]∣+∣a[L]−a[R+1]∣−∣a[L]−a[L−1]∣−∣a[R]−a[R+1]∣,这里sum1是不变的就可以不管它,然后又出现了绝对值最值问题。
此时我们只展开前两个绝对值,因为展开绝对值的目的是把与L相关的放一起,与R相关的放一起,而后两个绝对值里只包含L/R了。
展开后,得到这二者:
1.(a[R]−a[R+1]−∣a[R]−a[R+1]∣)−(a[L−1]−a[L]+∣a[L]−a[L−1]∣)
2.(a[R]+a[R+1]−∣a[R]−a[R+1]∣)−(a[L−1]+a[L]+∣a[L]−a[L−1]∣)
最后,取二者最大值即可。
代码:
class Solution {
public:
int solve(const vector<int>& A) {
int n = A.size(), base = 0;
for (int i=0; i<n-1; i++)
base += abs(A[i] - A[i+1]);
int inc = 0;
for (int sign=-1; sign<=1; sign+=2) {
int mnVal = A[0] + sign*A[1] + abs(A[0]-A[1]);
for (int i=1; i<n-1; i++) {
int biggerVal = A[i] + sign*A[i+1] - abs(A[i]-A[i+1]);
inc = max(inc, biggerVal - mnVal);
mnVal = min(mnVal, A[i] + sign*A[i+1] + abs(A[i]-A[i+1]));
}
}
return base + inc;
}
int maxValueAfterReverse(vector<int>& nums) {
if (nums.size() <= 1) return 0;
int res1 = solve(nums);
reverse(nums.begin(), nums.end());
int res2 = solve(nums);
return max(res1, res2);
}
};
Google Kick Start Round A 2019 - Parcels
题意:
给定一个R∗C的01地图,标记为1的位置为快递站点,标记为0的位置为收快递的区域。假如我们站在某个0位置,那么送快递的时间为距离我最近的快递站到我当前位置的曼哈顿距离。为了减小送快递时间,我们可以在某个0的位置新建一个快递站点,问建立完后大家收快递所需要的最长时间最快是多少?
思路:
这个题暴力解法是,对于每个潜在位置,都试图建立一个快递站,然后BFS求一遍最长时间,时间复杂度是O((RC)2)。
由于新建快递站点的位置很难确定,我们可以先用二分法把最优化问题转化为判定性问题,即,给定一个最长时间T,我们能否找到新增一个新快递站,使得所有快递的运输时间都小于等于T?
那么我们二分枚举T的值,然后对于某个T,遍历一边整个地图找到所有运输时间大于T的位置,记录下来(假设有m个,m<RC)。现在问题存不存在一个新快递快递站点,使得这些位置的运输时间小于T。
这里曼哈顿距离是绝对值形式表示的,于是两点之间的距离可以被表示为:dist((x1,y1),(x2,y2))=max(abs(x1+y1−(x2+y2)),abs(x1−y1−(x2−y2)))。这里假设(x1,y1)为快递点的位置,(x2,y2)为运输时间大于T的位置,我们遍可以利用O(m)的方式求出(x2+y2)和(x2−y2)的最值,然后再遍历一边地图检验是否存在这一快递点(x1,y1)即可。
这种解法利用了二分和绝对值优化,它总时间复杂度为O(RC∗log(R+C))。本题还有一个O(RC)的方法,在此不做介绍(不会…)。
关键代码:
bool ok(int K) {
vector<pair<int, int> > focusBlanks;
int mx1 = -INF, mn1 = INF, mx2 = -INF, mn2 = INF;
for (int i=0; i<row; i++) {
for (int j=0; j<col; j++) if (dis[i][j] > K) {
focusBlanks.push_back(make_pair(i, j));
mx1 = max(mx1, i + j); mn1 = min(mn1, i + j);
mx2 = max(mx2, i - j); mn2 = min(mn2, i - j);
}
}
if (focusBlanks.size() == 0) return true;
for (int i=0; i<blanks.size(); i++) {
int x2 = blanks[i].first, y2 = blanks[i].second;
int dis1 = max(abs(mx1 - (x2 + y2)), abs(mn1 - (x2 + y2)));
int dis2 = max(abs(mx2 - (x2 - y2)), abs(mn2 - (x2 - y2)));
if (max(dis1, dis2) <= K) return true;
}
// printf("%d : %d\n", K, false);
return false;
}
Site1997
发布了40 篇原创文章 · 获赞 44 · 访问量 9万+
私信
关注
标签:x1,题中,int,y1,abs,绝对值,x2,y2,最值 来源: https://blog.csdn.net/Site1997/article/details/104177941