一个算法面试题的5种不同解法(1)

通过上述用空间换时间的方式,我们可以直接将时间复杂度从O(n^3) 降低到O(n^2)

黄金-二分查找


细心的你可能已经发现了,因为给出的arr都是正整数,所以sumArr一定是递增且有序的,对于有序的数组,我们可以直接采用二分查找。对于这道题而已,我们可以遍历起点s,然在sumArr中二分去查找是否有终点e,如果s对于的e存在,那么sumArr[e]一定等于sumArr[s] + target,改造后的代码如下,相比于上面代码,增加了二分查找。

public int[] find(int[] arr, int target) {

int[] sumArr = new int[arr.length + 1];

for (int i = 1; i < sumArr.length; i++) {

sumArr[i] = sumArr[i-1] + arr[i-1];

}

for (int s = 0; s < arr.length; s++) {

int e = bSearch(sumArr, sumArr[s] + target);

if (e != -1) {

return new int[]{s, e};

}

}

return null;

}

// 二分查找

int bSearch(int[] arr, int target) {

int l = 1, r = arr.length-1;

while (l < r) {

int mid = (l + r) >> 1;

if (arr[mid] >= target) {

r = mid;

} else {

l = mid + 1;

}

}

if (arr[l] != target) {

return -1;

}

return l - 1;

}

由此,我们又继续将时间复杂从O(n^2)降低到了O(nlogn)。

钻石-HashMap优化


有序数组的查找除了可以用二分优化,还可以用hashMap来优化,借助HashMap O(1)的查询时间复杂度。我们又一次用空间来换取了时间。

public int[] find(int[] arr, int target) {

int[] sumArr = new int[arr.length + 1];

Map<Integer, Integer> map = new HashMap<>();

for (int i = 1; i < sumArr.length; i++) {

sumArr[i] = sumArr[i-1] + arr[i-1];

map.put(sumArr[i], i-1);

}

for (int s = 0; s < arr.length; s++) {

int e = map.getOrDefault(sumArr[s]+target, -1);

if (e != -1) {

return new int[]{s, e};

}

}

return null;

}

我们终于将时间复杂度降低到了O(n),这可是质的飞跃。

王者-尺取法


别急,还没结束,对于这道题还有王者解法。上文中我们通过不断的优化,将时间复杂度从O(n^3)一步步降低到了,但我们却一步步增加了存储的使用,从开始新增的sumArr数字,到最后的又增加的HashMap,空间复杂度从O(1)变为了O(n)。有没有办法把空间复杂度也给将下来?我能写到这那必然是有的。

这种算法叫做尺取法。尺取法,这个名字有点难理解。我们直接举个具体的例子,假设有n调长度不一的绳子并列放在一起,你需要找出其中连续的一部分绳子组成一条长度为target的绳子,这里需要注意是连续。这时候你可以找一个长度为target的尺子,然后把绳子一段段往尺子上放,如果发现短了就往后面再接一根,如果发现长了,就把最头上的一根扔掉,直到长度恰好合适。

在使用中我们并不需要这把尺子,只需要拿target作为标尺即可。说起来可能比较难理解,直接举个例子,下图演示了从数组中找到和为22的子数组的过程。

只要小了就右加,大了就左减,直到找到目标。

为什么尺取法是对的?我理解尺取法其实是解法二白银解法的一种优化,也是遍历了起点s,但是对终点e不做无效的遍历,如果e到某个位置后已经超了,因为数组里都是正数,再往后肯定是超的,也就没必要继续遍历e了。转而去调整s,如果s右移到某个位置后总和小了,s再往右总和只会更小,也就没必要继续调整s了…… 整个过程就像是先固定s去遍历e,然后固定e再去遍历s ……,直到得到结果。

尺取法可用的基础在于e往右移动总和一定是增的,s往右移总和一定是减的,也就是说数组中所有的数必须是正的。 没有完美的算法可以解决任何问题,但对于特定的问题一定有最完美的解法。

说完尺取法,我们来看下用尺取法是如何解决这道题的,代码比较简单,如下:

public int[] find(int[] arr, int target) {

int s = 0, e = 0;

int sum = arr[0];

while (e < arr.length) {

if (sum > target) {

sum -= arr[s++];

} else if (sum < target) {

sum += arr[++e];

} else {

return new int[]{s, e};

}

}

return null;

}

只有一层循环,时间复杂度O(n)。没有额外的空间占用,空间复杂度O(1),这就是最完美的解法。

总结


这道算法题乍看简单,细看其实真的不简单。可能你面试遇到,没办法一下子想到最优的解,但给出一个可行的解总比没有解强。我之前面试问别人这个题,他一上来就是想着怎么最优解决,反而连最简单的青铜解法都没写出来。记得下次面试,实在是解不出来就先给个60分的答案,然后再想办法把分数提升上去,别最后交了白卷。 给出一个可行解,然后再持续迭代优化,我觉得这也是解决一个复杂问题比较好的思路。

最后送大家一句鸡汤,没有人生下来就是王者,只是不断的努力成为了王者罢了。

作者:xindoo


来源:SegmentFault 思否社区


推荐阅读  点击标题可跳转

计算机网络

  • HTTP 缓存

  • 你知道 302 状态码是什么嘛?你平时浏览网页的过程中遇到过哪些 302 的场景?

  • HTTP 常用的请求方式,区别和用途?

  • HTTPS 是什么?具体流程

  • 三次握手和四次挥手

  • 你对 TCP 滑动窗口有了解嘛?

  • WebSocket与Ajax的区别

  • 了解 WebSocket 嘛?

  • HTTP 如何实现长连接?在什么时候会超时?

  • TCP 如何保证有效传输及拥塞控制原理。

  • TCP 协议怎么保证可靠的,UDP 为什么不可靠?

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

算法

  • 链表

  • 字符串

  • 数组问题

  • 二叉树

  • 排序算法

  • 二分查找

  • 动态规划

  • BFS

  • DFS

  • 回溯算法

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值