前几天听同事分享了一道今日头条的算法面试题,感觉非常有趣,今天也分享给大家,题目是这样的:
给定一个有序数组,数组中有正数、负数或者0,对数组中所有的数求平方后问有多少个不同的值。
比如对于数组[-1,0,1,1,1,1],对数组求平方后为[1,0,1,1,1,1],那么最终的结果是2,因为最后只有0和1两个不同的数;
对于数组[-1,0,1,2,3],对数组求平方后为[1,0,1,4,9],那么最终的结果是4,因为最后数组中为0,1,4,9这四个不同的数;
同事提到只有时间复杂度为O(n),空间复杂度为O(1)的算法才能通过面试,在听到这个题目后首先想的就是如果在面试中自己遇到这个题目该如何解决?
我是如何思考的
如果在面试中遇到这个题目我会先给出一个最简单的解法,也就是直接对数组中的每一个数字求平方,然后放到map中,最后求一下map的大小就可以了,整个过程如图所示:
实际上这种解法有个问题,那就是对数字求平方和容易导致溢出,而实际上我们根本就无需求平方,实际上只需要求出绝对值就可以了,只要两个数字的绝对值相等,那么这两个数字的平方也一定相等。因此我们只需要将上图中的第一步由求平方改为求绝对值就可以了。
这个算法相对简单,也很容易想到,实际复杂度为O(n)满足要求,但是由于我们使用了map,因此空间复杂度为O(n),不能满足面试要求的O(1),那么该如何改进呢?
在往下看之前你要首先自己想一想该如何改进。
优化算法:利用数组有序
不要忘了题目给定的是一个有序数组,上图中的算法没有利用到这一性质。
我的想法是这样的,给定一个有序数组,如果这个数组中没有负数,那么这个题目是极其简单的,因为在一个有序数组中相同的数组一定是相邻的。
比如对于数组[0,1,1,1,1],其中四个1是相邻的,我们只需要在遍历数组的时候不计入重复的数字就可以了。
但是现在问题的关键在于数组中还有负数,对于负数该如何处理呢?很简单,我们只需要将负数部分转为正数就可以了,这样我们就得到了两个子数组,这两个子数组都是有序的,然后将这两个有序子数组原地合并,这样就得到了一个不包含负数的有序数组,问题圆满解决,如图所示:
问题
看上去看正确有没有,没有额外使用空间,而且时间复杂