题目来自https://leetcode.com/problems/two-sum/description/
题目描述
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.//不能用同一个元素两次
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
给定一个整数数组和一个目标整数,返回使数组中的两个数的和为目标整数的两个数的下标。
解答一
最直观的做法,暴力破解。
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i=0; i<nums.length-1; i++)
for(int j=i+1; j<nums.length; j++){
if( i != j && target == nums[i] + nums[j]) //题目要求不能用同一个元素两次,即i,j不能相等
return new int[]{i,j};
}
return null;
}
}
解答二
利用HashMap的查询效率,缩减时间复杂度。
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++){
if(map.containsKey(target-nums[i]) && i != map.get(target-nums[i]))
return new int[]{i,map.get(target-nums[i])};
}
return null;
}
}
结果被评了个Wrong Answer。TestCases如下:
Input:
[3,3]
6
Output:
null
Expected:
[0,1]
意思是不能用同一个元素两次,但是原本数组中不同元素可以相等。这样的话不能把数组元素作为key,元素下标作为value。因为同一个key用put操作,会用新的value覆盖旧的value,旧元素下标会丢失。但是官方solution也是将数组元素作为key(如下),此处很疑惑。
官方Solution
Approach 1: Brute Force
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 };
}
}
}
throw new IllegalArgumentException("No two sum solution");
}
Time complexity : O(n2)
Space complexity: O(1). (由于不需要另行开辟空间,只是常数的变量申请,时间复杂度为O(1))
Approach 2: Two-pass Hash Table
What is the best way to maintain a mapping of each element in the array to its index? A hash table.
第一次循环把元素值作为key,元素下标作为value。
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 complement = target - nums[i];
if (map.containsKey(complement) && map.get(complement) != i) {
return new int[] { i, map.get(complement) };
}
}
throw new IllegalArgumentException("No two sum solution");
}
用空间换时间
Time complexity : O(n).(如果hash函数完全无冲突,则查询时间复杂度为O(1),n次循环时间复杂度为O(n))
Space complexity: O(n).
Approach 3: One-pass Hash Table
仅用一次循环就解决问题,在循环的同时将元素放入map。我们只需要在新元素进map之前在map内查找元素的complement,如果没有再放进map。
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
Time complexity : O(n).
Space complexity: O(n).
HashMap操作的的时间复杂度
put操作
可以看到如果当前散列位置无元素,即散列完全有效,则时间复杂度为O(1)
/*@return the previous value associated with key, null if there was no mapping for key. (A null return can also indicate that the map previously associated null with key.)*/
//如之前无此key则返回null;如之前有此key,则覆盖旧value,返回旧value
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;//map为空时resize()复杂度为O(1)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);//如果当前key的散列位置为空,则新建结点。
else {//如果当前key的散列位置已有元素
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))//如果当前key的散列位置不为空且key相同
e = p;//e指向当前散列位置p
else if (p instanceof TreeNode)//如果当前散列位置p为TreeNode
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//当前key的散列位置不为空且key不相同,并且当前散列位置为链表存储
for (int binCount = 0; ; ++binCount) {//循环直到找到当前散列位置为key的结点
if ((e = p.next) == null) {//当前散列位置的后继结点为空
p.next = newNode(hash, key, value, null);//新建结点在当前位置之后
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);//大于7就树化
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))//如果找到了当前结点的key跟参数的key相同,即指向了已有的key结点
break;
p = e;//当前结点指像e,即下一个结点,直到为空,或者找到key结点,必要时树化
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
get操作
通过key来取value,可以看到如果当前散列位置无元素,即散列完全有效,则时间复杂度为O(1)
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
containsKey操作
可以看到如果当前散列位置无元素,即散列完全有效,则时间复杂度为O(1)
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
比较的经典语句
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
containsValue操作
直接用for循环扫描table,时间复杂度为O(n)。
如果散列的不好,即一个结点有若干个元素,则还要更长时间。
public boolean containsValue(Object value) {
Node<K,V>[] tab; V v;
if ((tab = table) != null && size > 0) {
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next) {
if ((v = e.value) == value ||
(value != null && value.equals(v)))
return true;
}
}
}
return false;
}
总结
利用java包含的数据结构提高算法效率
如HashSet、HashMap
熟悉各种数据结构的各种操作的执行效率
如排序算法