前言
LeetCode是一个刷题的网站,它上面收录了许多互联网公司的面试算法题。不论是对于准备校招的应届生和准备跳槽的社招人员都是帮助非常大的。把LeetCode上面的算法题好好刷刷就能轻松应对各大公司面试中的编程环节。
虽然网上已经有了很多LeetCode刷题的题解。但是很少有针对每道题给出细致,深度的探讨和解释。因此,我准备推出一个LeetCode超越99系列。针对每一道题,进行细致的探讨,最终给出一个超过99%的答案的解决方案。通过这种循序渐进的方式和对性能极致追求的态度来加深对相应算法的理解,提高编程能力和解题思路。这样如果在面试过程中面试官对题目进行变形也难不倒各位了。
当然了,由于LeetCode也在不断的收录新的面试题,截至目前已经有一千多道题目。我很难对每一道题去作答。我准备先从200多道的Easy级别题目入手。今天是第一题 Two Sum。
题目: Two Sum
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
给定一个整形数组,找出其中两个数,它们的值相加等于给定值。
每个数组中有且只有两个数满足要求,并且这两个数必须是数组中两个不同的元素,不能一个数字使用两次。
解题思路
两层循环法
首先,最直观也是最暴力的方法就是使用两层循环搞定。
第一个循环从头到尾遍历数组,里面的循环从外面循环的当前位置开始往数组末尾进行遍历。如果两个循环当前位置的值相加等于给定值,则找到答案退出循环。
这种解法的时间复杂度o(n^2),只算是一个保底的解法,不是面试官想要的结果。一般面试官会继续问还有没有效率更高的方法。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result=new int[2];
for(int i=0;i<nums.length;i++)
{
for(int j=i+1;j<nums.length;j++)
{
if(target==(nums[i]+nums[j]))
{
result[0]=i;
result[1]=j;
return result;
}
}
}
return null;
}
}
从运行结果可以看到,这个解法只击败了39.15%的答案。并且该答案处于一个波峰的位置,说明这么干的人还不少。同时也可以看到还有另外一个波峰,大多数答案处于那个位置。
Hash法
下面给出一个借助HashMap来实现的近似O(n)的解决方案。
这个方案使用一层循环来遍历数组,如果当前数和给定值之间的差不在Map中,则将当前值加入道Map中。该方案的关键点就是借助了HashMap的containsKey 这个方法。这个方法的时间复杂读可以近似为O(1)。关于这个containsKey为啥时间复杂度近似为O(1),后面会另外写一篇文章解释,有兴趣的可以先看下它的源码。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result=new int[2];
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++)
{
Integer value=target-nums[i];
if(map.containsKey(value))
{
result[0]=map.get(value);
result[1]=i;
return result;
}
map.put(nums[i], i);
}
return result;
}
}
运行时间 8 ms。排在91.85%。可以看到这里也是一个峰值点,说明很多人也是这么做的。想进99%,还得有更优化的算法。
这种解法击败了91.85%的答案,离99%只有一步之遥了,但这也是真正见功夫的地方。
对撞指针法
下面是我找到的一个击败了99.61%的答案。它使用了对撞指针法。先将数组升序排序,再使用两个指针分别指向数组的开头(头指针)和结尾(尾指针)。如果当前两个指针所指向的元素相加大于给定值,则将尾指针往头部挪一位,如果小于给定值则把头指针向尾部挪一位。
如果等于给定值则找到了答案。
对撞指针的原理是:如果头指针和尾指针所指向的元素相加大于给定值,那么头指针和尾指针之间的区域里任何一个元素加上尾指针所指向的元素相加都会大于给定值,因此将尾指针往头指针方向挪一个元素,缩小寻找区域。反之类似。
其实这边还有优化的空间。如果数组很大,我们可以先快速的找到第一个比给定值大的元素作为尾指针的起始位置,这样也能减少一些比较。
class Solution {
public int[] twoSum(int[] nums, int target) {
if(nums == null)
return null;
int[] nums2 = Arrays.copyOf(nums, nums.length);
Arrays.sort(nums2);
int a = 0, b = 0;
int start = 0, end = nums2.length-1;
while(start<end){
int sum = nums2[start] + nums2[end];
if(sum < target)
start++;
else if(sum > target)
end--;
else{
a = nums2[start]; b = nums2[end];
break;
}
}
int[] res = new int[2];
for(int i = 0; i < nums.length; i++){
if(nums[i] == a){
res[0] = i;
break;
}
}
if(a != b){
for(int i = 0; i < nums.length; i++){
if(nums[i] == b){
res[1] = i;
break;
}
}
} else{
for(int i = 0; i < nums.length; i++){
if(nums[i] == b && i != res[0]){
res[1] = i;
break;
}
}
}
return res;
}
}
关注公众号,第一时间接收原创文章
欢迎加入微信群,一起交流