第一种方法(for循环嵌套暴力求解)
根据题目描述:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
这样大多数人的思路都很明确,就是从头遍历数组找到两个数和目标数相同则返回下标。
核心代码如下:
for (int i =0;i<num.length;i++){
for (int j=i+1;j<num.length;j++ ){
if ((num[i]+num[j])==target){
System.out.println("序号为:["+i+","+j+"]");
}
}
}
这样确实可以得到结果,但是用时较长,在数组较大时,速度较慢。也就是时间复杂度很高O(n^2),一般在我们的程序中,时间复杂度不能为指数递增。这个方法虽然方便,大多数人都能想到,但是程序不是最优。
程序执行结果:
提交程序用时
第二种方法-(map)
在第一种方法的基础上,我们不能允许程序中有指数递增的时间复杂度存在,我们得进行优化。怎么优化呢?我们想到java中有Map接口,Map接口中键和值一一映射. 可以通过键来获取值。给定一个键和一个值,你可以将该值存储在一个Map对象. 之后,你可以通过键来访问对应的值。
我们可以将数组中的元素和下标存储到map中,因为map键值对映射的时间复杂度为O(1),这样的话就大大增加了程序的执行速度。
核心代码如下
Map<Integer,Integer> map = new HashMap ();
for (int i=0;i<num.length;i++){
map.put ( num[i] ,i);
}
for (int i = 0;i<num.length;i++){
int shuzhi = target-num[i];//对应map中的键
//这个判断当目标数减去数组里的数时,有可能在数组中不存在,也就是没有对应的建
if (map.containsKey ( shuzhi )&&map.get ( shuzhi)>i){
System.out.println("序号为:["+i+","+map.get ( shuzhi )+"]");
}
}
这样得到的结果用的时间比第一种大大减少,时间复杂度为O(n).这样就对第一种方法进行了优化。提高了效率。
程序执行结果:
提交程序用时
第三种方法-终极方法(时间复杂度最低)
有人可能会想,第二种方法已经很快了,还有比这个更快的方法吗,当然。
观察第二种方法我们不难发现,他将数组元素存储map和与目标元素的具体比较分别进行了处理。我们是不是可以这样想,将他们放到一起,在一个for中进行处理,这样就减少了一个步骤。速度能比第二种方法较快一点。时间复杂度与第二种相同都是O(n).
核心代码如下
for (int i=0;i<num.length;i++){
int shuzi = target-num[i];
if (map.containsKey ( shuzi )){
System.out.println("序号为:["+map.get ( shuzi )+","+i+"]");
}
map.put ( num[i],i );
}
代码量相比第二种减少了一些,效率也更高。这个代码将找找两个数的和等于目标数的操作和数组元素存储到map中的操作放到了一起,先进行判断,再进行存储。
程序执行结果:
提交程序用时
第四种方法-先排序在根据位置进行比较
第四种方法用时介于第一中和第二种方法之间,时间复杂度是log级别的。
具体思路就是:首先得进行排序(排序方法有好多,冒泡,快速,选择,或者用java自带的排序函数),然后标记排好序数组元素的首位置和末位置,将两者对应元素相加与目标数进行比较,大于目标数则将末尾值向前移动,小于目标数首位置向后移动,直到找到等于目标数的两个元素停止,输出下标。
需要一个存储下标的容器,我选择map。但是map中的key是不能重复的,所以,我们可以想到,让一个key拥有多个value。例如[3,3]数组,存储到map中就是,key:3,value:[0,1]。这样就可以解决遇到数组中重复元素时,map键不能重复的问题。
核心代码如下
int a=0;
int b=num.length-1;
Map<Integer,List<Integer>> map = new HashMap();
for (int i = 0; i < num.length; i++) {
//当不存在键时,正常插入
if (!map.containsKey ( num[i] )){
List<Integer> list1 = new ArrayList <> ( );
list1.add ( i );
map.put ( num[i],list1 ); // 将值和下标存入Map
}
else {
//当存在键时,将键对应的值添加道列表里,这样就形成了一个键对应多个值
List<Integer> list2 = map.get ( num[i]);
list2.add ( i );
map.put ( num[i],list2 );
}
}
System.out.println(map);
Arrays.sort (num);//用自带的排序函数
for (int i=0;i<num.length;i++){
if ((num[a]+num[b])>target){
b=b-1;
}
else if((num[a]+num[b])<target)
{
a=a+1;
}
else {
if (num[a]!=num[b]){
System.out.println("序号为:"+map.get (num[a])+"<><><>"+map.get (num[b]));
}else if(a!=b){
System.out.println("序号为:"+map.get (num[a]));
}
a=a+1;
}
}
程序执行结果:
这个结果测试了极端问题,数组中有重复元素时,map一个键
对应多个值,可以实现。其他的测试数据也与前三种相同。时间复杂度O(nlogn)级别。
总结
leetcode第一题,两数之和,看似不难,但背后需要考虑很多问题。时间与空间转换问题,数组极端值问题等等。通过这一道题我收获很多,明白了考虑问题得全面。本博客由于本人能力有限,肯定有很多的问题,再此大家谅解。
2019-1-15记录