題目描述:
給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和為目標值的那兩個整數,并返回他們的數組下標。
你可以假設每種輸入只會對應一個答案。但是,你不能重復利用這個數組中同樣的元素。
示例:
給定 nums = [2, 7, 11, 15], target = 9
因為 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/two-sum
著作權歸領扣網絡所有。商業轉載請聯系官方授權,非商業轉載請注明出處。
題目分析:
題目中有以下條件是我們正確解答題目的關鍵:
- “假設每種輸入只會對應一個答案”,說明數組中如果有重復數字并且重復數字是答案的一部分,那么重復數字有且僅有兩個并且這兩個就是答案,如:
給定 nums = [3, 3, 1, 9], target = 6
因為 nums[0] + nums[1] = 3 + 3 = 6
所以返回 [0, 1]
- “不能重復利用這個數組中同樣的元素”,無法使用雙層循環這樣的暴力解法。
解答:
為了便于說明我們還是先使用暴力解法:
使用雙層循環,第一層循環找到當前值與目標值的差值tempJ。第二層循環尋找tempJ是否存在,如果存在則返回其位置
public int[] twoSum(int[] nums, int target) {
int i = 0, j = 0;
for (; i < nums.length; i++) {
int tempJ = target - nums[i];
j = getNumJ(nums,tempJ,i);
if(j != -1 ){
break;
}
}
return new int[]{i, j};
}
/**
* @param nums
* @param tempJ
* @param i
* @return 如何元素存在則返回元素的位置,否則返回-1
*/
private int getNumJ(int[] nums, int tempJ ,int i) {
for (int j = i+1; j < nums.length; j++) {
if(nums[j] == tempJ){
return j;
}
}
return -1;
}
我們可以看到在上述解法的時候,找tempJ的時候元素被使用了多次,我們應當尋找快速定位到tempJ的方式;我們可以想到利用HashMap來存儲數據,從而快速定位。
最終我們得到的解決方案如下:
public class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> numMap = new HashMap<>();
int i = 0, j = 0;
for (; i < nums.length; i++) {
int tempJ = target - nums[i];
Integer position = numMap.get(tempJ);
if(position != null){
j = position;
break;
}
numMap.put(nums[i],i);
}
return new int[]{j, i};
}
}
那么HashMap在get數據難道不是通過遍歷的方式么?當然不是,HashMap是Java中較為基礎的數據接構,感興趣的同學可以去閱讀下源碼。這里簡單的說明下他是如何通過hash存儲和取值的。
在jdk1.8之前hashmap是使用數組+鏈表的形式存儲的,數據結構如下圖
當我們向該數據結構中存鍵值對(key-value)時,會獲取“key”的hashCode(不同類型key的hashCode的生成方式可以參看該類的hashCode方法,如Integer的hashCode為Integer的value),獲取到的每一個hashCode對應數組上的一個位置,插入的時候直接將value放到該位置上,如果當前位置不為空,則直接放到該位置對應的鏈表中。至于取值的方式就顯而易見了,直接獲取key的hashCode,找到它在數組中的位置,獲取該位置的值即可,如果當前位置的key不是我們想要的那個key,則需要遍歷該位置對應的鏈表獲取到對應的值。到這里你可能會說,這里還是會遍歷,是否能滿足只遍歷一次數據的需求?當然能,理由在“題目分析”的第一點已經說了,我們需要的答案的數據最多只有兩個重復的,并且我們在找j的時候,j還沒有被插入,因此不會存在hash沖突,不需要遍歷。