光说不练假把式,光练不说傻把式,又练又说真把式。
前边忘忧给大家总结了一下常用的数据结构的知识,但是光单纯的掌握这些知识点是不够用的,还需要在实际问题中灵活发挥,自由组合。
从今天开始,忘忧将围绕leetcode,展开漫长的刷题过程,除了leetcode之外,也会穿插一些蓝桥杯、ACM的练习题。
今天,就从leetcode的第一题——两数之和,这个算法界的“hello world”来开始练练手!
题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
- 给定 nums = [2, 7, 11, 15], target = 9
- 返回 [0, 1]
- 因为 nums[0] + nums[1] = 2 + 7 = 9
思路一
理解完题意后,最直观也最容易想到的方式,就是依次计算target与每一个元素的差值,然后遍历整个nums数组,观察nums里边是否包含这个差值,如果包含,则表示找到了答案,如果不包含,则继续计算下一个元素。
/**
* 暴力解法
* @param nums 输入的数组
* @param target 目标值
* @return 返回记过
*/
public static int[] twoSum(int[] nums, int target) {
//遍历nums数组,依次计算target与当前元素的差值
for (int i = 0; i < nums.length; i++) {
int diff = target - nums[i];
//遍历nums数组,判断数组中是否包含差值
for (int j = 0; j < nums.length; j++) {
//使用i != j来限制同一个元素不被重复利用
if(diff == nums[j] && i != j){
return new int[]{i, j};
}
}
}
return new int[]{0, 0};
}
时间复杂度:O(n * n)
空间复杂度:O(1)
提交答案后:
运行通过,代码没毛病,但是运行时间貌似有些不太妥,理应有更优的解法。
思路二
猜想:寻找数组中存不存在这个差值的时候,如果不通过遍历数组的方式便可以直接确定数组中有没有这个值的话,则可以将算法的时间复杂度降低到O(n)。
有了这个猜想,对哈希表有过了解的朋友们应该已经想到了,可以通过hash的方式存储数组的值,因为hash在查询一个元素的时候,被设计为接近O(1)的复杂度了,具体hash表的原理我会在后续专门写一篇文章做介绍,在此之前没有了解过hash表的朋友们可以暂且将hash表的这个特性当作一个结论。
使用hash法的实现代码如下:
/**
* hash法解题过程
* @param nums 输入的数组
* @param target 目标值
* @return 返回记过
*/
public static int[] twoSum3(int[] nums, int target) {
//用于存放值->索引的映射关系,其中的key为hash结构,只存储已经遍历过的元素
Map<Integer, Integer> map = new HashMap<>();
//遍历所有愿随
for (int i = 0; i < nums.length; i++) {
//计算差值
int diff = target - nums[i];
//查看遍历过的元素中是否包含这个diff
if(map.containsKey(diff)){
return new int[]{map.get(diff), i};
}
//如果当前元素与已遍历过的元素没法配对达到target的要求,将当前元素放入已遍历过的hash表中
map.put(nums[i], i);
}
return new int[]{0, 0};
}
时间复杂度:O(n)
空间复杂度:O(n)
与思路一比较,典型的空间换时间的方式,牺牲少量的空间,换取时间上的优化。
提交后,与之前的运行时间有了明显的上升,内存消耗也没收到太大的影响。:
总结
对编程我们要始终保持敬畏之心,有些算法的求解过程不难,难点在于如何最大程度的提高算法性能,怎么寻求最优解。
趁阳光正好,趁微风不噪,趁繁花还未开至荼蘼,去看尽世间美好。