建模回来了,杂七杂八的事情处理完竟然耗费了大概两周emmm。

2019.4.16更新一遍,重新看了一下以前写的,有些地方还是naive了一点,这次重新过一遍题目,在原先c++的基础上,加上python3和java语言的解法。

0.问题重述

给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9。
所以返回 [0, 1]

1.题目分析

乍一看这个题目就是在一堆数中找到唯一的符合条件的两个数(此为题目为我们做好的简化)。一开始想到的办法是进行一次排序,然后暴力搜索出答案。这样算法的复杂度就是O(n^2)。Too Young too simple。这么暴力的算法自然是不能通过长者的法眼。运行下来的结果是Time Limit Exceeded。那么只能想出来一个O(n)复杂度的算法。

原算法是将数组遍历两次,加上排序的遍历就不止是两次。这样做虽然节省了空间,但是时间上增加了消耗。因此,想要复杂度更低的算法自然是需要使用一定的空间。思路是这样的,要得到一个复杂度为O(n)的算法,遍历最多也只能是1次。一次遍历只能找到一个数,那么就要将第二个数和第一个数之间先建立起联系。自然而然的就想到使用key-value的方式进行查询。

2.整体思路

anyway,思路就是:首先遍历一遍数组,建立每一个元素和下标的联系。你可能会问这功能数组也有,为啥要多于遍历一遍?

naive

当这一遍建立起元素与下标的关系后,之后的查找仅发生在map内部,而map内部的查找是常数级的查找。大大缩短了时间。在完成遍历后,第二次遍历数组,这次直接用target-当前值去找对应的值。需要注意的是,有可能是两个相同的数相加,所以要排除寻找的数是当前的数的可能性。

第二遍看到这里的时候心情复杂,当年戏可真多…

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		vector<int> res;
		int i;
		map<int, int> m;

		for (i = 0; i < nums.size(); i++) {
			m[nums[i]] = i;
		}
		int left = 0;
		for (i = 0; i < nums.size(); i++) {
			left = target - nums[i];
			if (m.count(left) && m[left] != i) {
				res.push_back(i);
				res.push_back(m[left]);
                break;
			}
		}
        return res;
	}
};

最终要求返回的是元素的位置,而不是元素的值。所以push_back的时候注意不要搞错(第一遍submit的时候就错了)。
and finally,第一道leetcode成功的AC了。
Leetcode笔记-1.twoSum C++&Java&Python

3.优化代码

实际上,在上一个版本的代码中,我们的循环做了1-2遍,在这之中荡漾,呃,摆动。我们可以将两个循环合并成一个,每遇到一个数,先把它扔进map里,同时立刻进行一次判断,看有没有符合条件的值(因为再本题的描述中,是不存在多个解的)。如果没有,就在进行下一次的循环,如果有直接结束。
如此一来循环可以做的比一次更少。

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		//也可以不用新的vector,直接返回{}
		int i;
		map<int, int> m;

		for (i = 0; i < nums.size(); i++) {
			m[nums[i]] = i;
			if (m[target - nums[i]] != i && m.count(target - nums[i])) {
				return { i,m[target - nums[i]] };
			}
		}
		return {};
	}
};

然鹅我们会发现这样死活都通不过,但是把下面这句话调换一下位置后就AC了。

map<int, int> m;

换到循环后面,如下

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		//也可以不用新的vector,直接返回{}
		int i;
		map<int, int> m;
		for (i = 0; i < nums.size(); i++) {
			if (m.count(target - nums[i])&&m[target-nums[i]]!=i) {
				return { m[target - nums[i]] ,i};
			}
             m[nums[i]] = i;
		}
		return {};
	}
};

debug之后明白了,因为map一一对应的特性决定了,不能存在两个key值相同的元素,所以再出现[3,3]这样的情况时,函数就布恩那个得到正确的结果。所以要先判断,再扔进map里。

4.python3解法

还是使用key-value的思想,只不过要熟悉一下接下来两种语言的数据结构。

对于python3而言,直接使用dict字典即可。字典类似于c++中的map,我们将在另外一篇文章中讨论c++、java、python三种语言中key-value数据结构的区别。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        dict = {}
        for i, v in enumerate(nums):
            if target - v in nums_dict:
                return [dict[target - v], i]
            nums_dict[v] = i
        return None

另外一定要注意缩进。

5.java解法

java解法写起来不是很顺利,对java的数据结构不熟悉,hashmap报了几个错。但思路和c++十分相似,只是数据结构内部的方法不同。我相关的hashmap的方法集放在几种数据结构的讨论中。

另外值得注意的是,在生成哈希表的时候,要么:

  • 遍历两次,先生成一次,再判断一次;
  • 遍历一次,但是要先判断,后加入到哈希表里去。否则很容易在重复的数上报错,比如以下:
    Leetcode笔记-1.twoSum C++&Java&Python
class Solution {
    public int[] twoSum(int[] nums, int target) {
       Map<Integer,Integer> map = new HashMap<Integer, Integer>();
        int num2 = 0;
        for(int i=0;i<nums.length;i++){
           num2  = target - nums[i];
            if(map.containsKey(num2) && map.get(num2)!=i){
                return new int[]{map.get(num2), i};
            }
            map.put(nums[i],i);
        }
        return null;
    }
}

最后,关于不同语言key-value的数据结构的区别,我们放到另一篇文章中说明。

相关文章:

  • 2022-12-23
  • 2021-08-04
  • 2021-11-11
  • 2021-08-29
  • 2021-05-25
  • 2021-11-24
  • 2021-05-03
  • 2021-04-15
猜你喜欢
  • 2021-10-22
  • 2021-08-04
  • 2022-02-28
  • 2021-08-11
  • 2021-09-22
  • 2021-07-19
  • 2021-09-22
相关资源
相似解决方案