前言
毕业一段时间了,但是在工作中数据结构和算法好像不是使用的特别广泛,甚至夸张地说大多时候用不上,不过这又面试必考,而且也是作为一个程序猿必要掌握的技能,可以用不上但是不能不会,我自我觉得这方面能力差一些,为了锻炼自己的多语言代码能力,打算使用C++、Java、Scala、Python、Julia,从头开始刷一遍LeetCode。使用博客记录就当打卡了。当然水平有限,如果有不对的地方,希望大家不吝赐教。
两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
问题分析
如果是面试过程中我们遇到问题,第一应该做的是与面试官沟通各种边界情况,及问题的模棱两可的地方。等一切都确定了,再开始做。否则上来就做,做到一半的时候才发现各种问题,大大浪费时间,而且给人的印象也不好。
这道题说白了就是给你一个数,一个数组,这个数组里面有唯一的一对数的和等于之前的这个数。
暴力破解
对我来说这种题上来不管三七二十一,就先暴力来一发,从头到尾遍历数组,指定一个数后,拿目标值减去这个数得到差值,然后再遍历数组看是否有等于差值的元素,如果有得到答案返回结果,如果没有遍历下一个数重复执行此过程。
Java暴力解法
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length; i ++) {
for(int j = i + 1; j < nums.length; j ++) {
if(nums[j] == target - nums[i]) {
return new int[]{i, j};
}
}
}
System.out.printf("输入数组没有两数之和等于目标数")
return null;
}
}
Python解法
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
for index_i, value_i in enumerate(nums):
if target - value_i in nums and nums.index(target - value_i) != index_i:
return [index_i, nums.index(target - value_i)]
else:
continue
print("检查输入,没有目标结果")
return None
Scala暴力解法
object Solution {
def twoSum(nums: Array[Int], target: Int): Array[Int] = {
for(i <- 0 until nums.length) {
for (j <- i + 1 until nums.length) {
if (target - nums(i) == nums(j))
return Array(i, j)
}
}
return Array.empty
}
}
优化
既然暴力解法已经完成,那我们考虑接下来的优化。我们之前是遍历数组,然后得到目标数减去这个数的差值,再去遍历数组找到这个差值的下标。两次循环。那么可不可以减少第二次查找差值的时间?就是可以在O(1)的复杂度下,完成第二次查找。什么数据结构可以在O(1)复杂度下查找?我的第一反应就是哈希表。用空间换时间,可是哈希表就有另一个问题。就是哈希冲突。知识点来了,解决哈希冲突的方法都有什么。1.开放地址法,2.拉链法。其中开放地址法又分为1.线性探测法2.线性探测补偿法3.伪随机法。具体这里就不展开说明了。好吧,写到这里,大家会发现,上面的python版本只有一次遍历,但是python的index方法内部实现是O(N)。
Java 第一次优化版本
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i =0; i < nums.length; i++) {
map.put(nums[i], i);
}
for(int i = 0; i < nums.length; i++) {
int secondNum = target - nums[i];
if (map.containsKey(secondNum) && map.get(secondNum) != i) {
return new int[] { i, map.get(secondNum) };
}
}
return null;
}
}
再次优化
我们可以看到,我们事先将map建立完再去遍历,这个的空间复杂度是O(N),时间复杂度是O(N),但是我们可以在遍历的同时,一边建立map一边去map里面查。
终极优化版本
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
int secondNum = target - nums[i];
if (map.containsKey(secondNum) && map.get(secondNum) != i) {
return new int[] { i, map.get(secondNum) };
}
}
return null;
}
}
Golang版本
func TwoSumResForTarget(nums []int, target int) []int {
tmpMap := make(map[int]int, len(nums))
for i, v := range nums {
tmpVal := target - v
if index, ok := tmpMap[tmpVal]; ok {
return []int{i, index}
}
tmpMap[v] = i
}
return nil
}
总结
我觉得面试遇到手写算法题(如果是这种简单地问题,还是很幸运的),最开始就不用先考虑那些优化的问题,就大力出奇迹就行,反正你写完了,还会让你优化的,和面试官交流的过程也是一个成长的过程,但是如果你上来在很短的时间内就写的明明白白的,而且是最优解,那么面试官会有两种想法,第一种:卧艹,牛逼啊,那我赶紧加大难度,要不然我岂不是很没有面子。第二种,这道题他做过。这两种想法对你来说都不是好事。
其中有参考LeetCode的实现,如果侵权请及时联系我。
谢谢