引言

现在算法题中,有时会遇到求解绝对值最值的问题,比如给定一个数组,求解 a b s ∣ a i − a j ∣ abs|a_i - a_j| abs∣ai​−aj​∣的最大值。诸如此类问题,暴力解法是用 O ( n 2 ) O(n^2) O(n2)时间复杂度遍历 i , j i,j i,j。而一种常用优化是将绝对值拆开,比如 a b s ∣ a i − a j ∣ = m a x ( a i − a j , a j − a i ) abs|a_i - a_j| = max(a_i - a_j, a_j - a_i) abs∣ai​−aj​∣=max(ai​−aj​,aj​−ai​),将问题拆成两个子问题,而每个子问题通过维护当前最小值,即 O ( n ) O(n) O(n)的时间可解出。最后通过 O ( 1 ) O(1) O(1)取二者最值即可。

下面分享三道利用此技巧解题的算法题,难度按升序排列。

Leetcode 1131

题意:
给定两个数组 a r r 1 arr1 arr1和 a r r 2 arr2 arr2,让找两个下标 i i i和 j j j,使得 ∣ a r r 1 [ i ] − a r r 1 [ j ] ∣ + ∣ a r r 2 [ i ] − a r r 2 [ j ] ∣ + ∣ i − j ∣ |arr1[i] - arr1[j]| + |arr2[i] - arr2[j]| + |i - j| ∣arr1[i]−arr1[j]∣+∣arr2[i]−arr2[j]∣+∣i−j∣最大。

思路:
这里直接拆开三个绝对值,得到以下8个子问题:

然后我们把有关于下标 i i i和下标 j j j的项提出来放在一起,发现其实子问题只有以下四个:1&8;2&7;3&6;4&5(比如1和8,它们只不过把两个下标 i i i和 j j j顺序调换了下)。
而这四个子问题的差别就是它们中的符号为加减法的排列组合: + + ++ ++; + − + - +−; − + - + −+; − − - - −−。这样通过四个 O ( n ) O(n) 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

题意:
给定一个数组 n u m s nums nums,它的 v a l u e value value值被定义为所有 ∣ n u m s [ i ] − n u m s [ i + 1 ] ∣ |nums[i]-nums[i+1]| ∣nums[i]−nums[i+1]∣的和(其中 0 < = i < = n 0 <= i <= n 0<=i<=n)。我们有一个操作,能使它某一段连续的子数组翻转一次,问翻转前/后这个数组的 v a l u e value value值最大能为多少?

思路:

  • 如果我们选择不翻转子数组,那么它的 v a l u e value value值可以通过 O ( n ) O(n) O(n)时间遍历得到。这个值我们记录为 s u m 1 sum_1 sum1​。
  • 如果我们翻转子数组 [ L , R ] [L, R] [L,R],这时新数组的 v a l u e value value为:
    s u m 1 + ∣ a [ R ] − a [ L − 1 ] ∣ + ∣ a [ L ] − a [ R + 1 ] ∣ − ∣ a [ L ] − a [ L − 1 ] ∣ − ∣ a [ R ] − a [ R + 1 ] ∣ sum_1 + |a[R] - a[L-1]| + |a[L] - a[R+1]| - |a[L] - a[L-1]| - |a[R] - a[R+1]| sum1​+∣a[R]−a[L−1]∣+∣a[L]−a[R+1]∣−∣a[L]−a[L−1]∣−∣a[R]−a[R+1]∣,这里 s u m 1 sum_1 sum1​是不变的就可以不管它,然后又出现了绝对值最值问题。
    此时我们只展开前两个绝对值,因为展开绝对值的目的是把与 L L L相关的放一起,与 R R R相关的放一起,而后两个绝对值里只包含 L / R 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 ] ∣ ) (a[R]-a[R+1] - |a[R] - a[R+1]|) - (a[L-1] - a[L] + |a[L] - a[L-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 ] ∣ ) (a[R]+a[R+1] - |a[R] - a[R+1]|) - (a[L-1] + a[L] + |a[L] - a[L-1]|) (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 R*C R∗C的01地图,标记为1的位置为快递站点,标记为0的位置为收快递的区域。假如我们站在某个0位置,那么送快递的时间为距离我最近的快递站到我当前位置的曼哈顿距离。为了减小送快递时间,我们可以在某个0的位置新建一个快递站点,问建立完后大家收快递所需要的最长时间最快是多少?

思路:
这个题暴力解法是,对于每个潜在位置,都试图建立一个快递站,然后BFS求一遍最长时间,时间复杂度是 O ( ( R C ) 2 ) O((RC)^2) O((RC)2)。

由于新建快递站点的位置很难确定,我们可以先用二分法把最优化问题转化为判定性问题,即,给定一个最长时间 T T T,我们能否找到新增一个新快递站,使得所有快递的运输时间都小于等于 T T T?

那么我们二分枚举 T T T的值,然后对于某个 T T T,遍历一边整个地图找到所有运输时间大于 T T T的位置,记录下来(假设有 m m m个, m < R C m < RC m<RC)。那么现在问题变成了,存不存在建立一个新快递快递站点的方式,能使得这些位置的运输时间小于T。

这里曼哈顿距离是绝对值形式表示的,于是两点之间的距离可以被表示为: d i s t ( ( x 1 , y 1 ) , ( x 2 , y 2 ) ) = m a x ( a b s ( x 1 + y 1 − ( x 2 + y 2 ) ) , a b s ( x 1 − y 1 − ( x 2 − y 2 ) ) ) dist((x1, y1), (x2, y2)) = max(abs(x1 + y1 - (x2 + y2)), abs(x1 - y1 - (x2 - y2))) dist((x1,y1),(x2,y2))=max(abs(x1+y1−(x2+y2)),abs(x1−y1−(x2−y2)))。这里假设 ( x 1 , y 1 ) (x1, y1) (x1,y1)为快递点的位置, ( x 2 , y 2 ) (x2, y2) (x2,y2)为运输时间大于 T T T的位置,我们遍可以利用 O ( m ) O(m) O(m)的方式求出 ( x 2 + y 2 ) (x2 + y2) (x2+y2)和 ( x 2 − y 2 ) (x2 - y2) (x2−y2)的最值,然后再遍历一边地图检验是否存在这一快递点 ( x 1 , y 1 ) (x1, y1) (x1,y1)即可。

这种解法利用了二分和绝对值优化,它总时间复杂度为 O ( R C ∗ l o g ( R + C ) ) O(RC *log(R+C)) O(RC∗log(R+C))。本题还有一个 O ( R C ) O(RC) 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;
}

算法题中求解绝对值最值的技巧相关推荐

  1. c算法题中各种输入和输出方法技巧详解!

    文章目录 引言 导入io库 输入 各种输入方法 `scanf` 格式说明符 基本示例 读入整数 读入其他类型的数字 读入单个字符 读入字符串 扫描字符集合 `getchar()` `gets()` ` ...

  2. 算法题中关于去重问题的解法(不同的值)

    算法题中关于去重问题的解法(不同的值):这类问题最好利用C++的map或set来做. 1087 有多少不同的值 (20 分) 当自然数 n 依次取 1.2.3.--.N 时,算式 ⌊n/2⌋+⌊n/3 ...

  3. 【算法思想】数学归纳法在算法题中的应用(含例题举例)

    [算法思想]数学归纳法在算法题中的应用(含例题举例) 前言 数学归纳法 应用举例 1. 前n项和 2. 区域计数 3. 着色问题 4. 金字塔求和 5. 简单不等式 6. 欧拉公式 7. 有路可达 8 ...

  4. 技术图文:排序技术在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第五篇图文. 理论部分 C# 中的排序 对集合类的排序,我们通常使用位于 System.Core 程序集,System.Lin ...

  5. Java实现算法导论中求解模线性方程解(基于最大公约数欧几里得扩展算法)

    基于最大公约数欧几里得扩展算法求解算法导论中模线性方程解.具体要结合算法导论中的有关数论算法章节理解,具体代码如下: package cn.ansj;/*假设方程ax=b(mod n)有解,且x0是方 ...

  6. 技术图文:集合技术在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第四篇图文. 理论部分 HashSet C# 语言中 HashSet<T> 是包含不重复项的无序列表,称为&quo ...

  7. 技术图文:双指针在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第三篇图文. 理论部分 Python list 的源码地址: https://github.com/python/cpytho ...

  8. 技术图文:字典技术在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第二篇图文. 在总结这篇图文的时候,顺便总结了 C# 中Dictionary类的实现,大家可以参考一下: 浅析 C# Dict ...

  9. 技术图文:位运算技术在求解算法题中的应用

    背景 前段时间,在知识星球立了一个Flag,这是总结Leetcode刷题的第一篇图文. 在总结这篇图文的时候,顺便把遇到的坑写了两篇辅助的图文,大家可以参考一下: 有符号整型的数据范围为什么负数比正数 ...

最新文章

  1. python3:利用SMTP协议发送QQ邮件+附件
  2. webdriver(python)学习笔记一
  3. JAVA中Final的用法
  4. 网站后台的lnmp启动与重启
  5. onlyOfice取消上传文件大小的限制
  6. Java虚拟机必学之四大知识要点,附学习资料
  7. android 安装应用程序apk安装不了
  8. 我那个37岁的大神朋友,后续
  9. ie系列浏览器_2020下半年河北教师资格准考证打印只能用ie浏览器吗
  10. 计算机科学与技术探索,计算机科学与技术的发展趋势探索
  11. c语言const限制什么,[C语言]类型限定词const解析
  12. Plants vs. Zombies(二分好题+思维)
  13. 多个JVM之间,能否共用同样的类?
  14. EPSON-LQ 300K II驱动安装问题
  15. Go测试远控免杀学习
  16. 正大国际琪貨纯手召:期货交易中的五大忌
  17. 目标检测 YOLOv5 - 模型的样子
  18. DTI-ATS入门(2):DTI协议纵览
  19. Hinton 深度学习论文总结
  20. 目前最火的人工神经网络,神经网络未来发展趋势

热门文章

  1. windows10下的浏览器userAgent
  2. Linux开发之Makefile简明教程及示例
  3. 推荐一个免费翻译接口
  4. voltdb mysql_VoltDB安装
  5. 思科云服务器是干什么的,思科的服务器困局:投资增长,还是退出?
  6. 相关系数评价标准的相关知识
  7. 前端常用的CDN静态资源库
  8. 用HTML+CSS+JS做一个漂亮简单的游戏网页——全屏游戏美术大赛作品(4个滚动页面)
  9. 实战:使用 Flutter 仿开眼视频App
  10. create table ,create table as 与create like三种建表方式的使用详解