ID: 170
TITLE: 两数之和 III - 数据结构设计
TAG: Java,Python
这是第一题两数之和的后续问题之一,两数之和要求一个返回列表中两个数字的索引,这两个数字的和等于目标值。
我们可以从两数之和中汲取灵感,把所有的输入数字保存在一个列表中。
两数之和的一个解决方法是双指针法,指针从两个方向迭代,互相逼近。
但是,双指针法的前提条件之一是输入列表有序。
现在有几个问题:
add(number)
在列表中插入新数字时,是否应该保证列表有序。find(value)
时按需进行排序?我们将在后面的算法部分讨论上述两个问题。
算法:
先给出双指针在有序列表中找到两数之和的算法。
low
和 high
分别指向列表的头尾。low
指针向前移动获得更大的值。high
向 low
靠近来减少总和。class TwoSum(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.nums = []
self.is_sorted = False
def add(self, number):
"""
Add the number to an internal data structure..
:type number: int
:rtype: None
"""
# Inserting while maintaining the ascending order.
# for index, num in enumerate(self.nums):
# if number <= num:
# self.nums.insert(index, number)
# return
## larger than any number
#self.nums.append(number)
self.nums.append(number)
self.is_sorted = False
def find(self, value):
"""
Find if there exists any pair of numbers which sum is equal to the value.
:type value: int
:rtype: bool
"""
if not self.is_sorted:
self.nums.sort()
low, high = 0, len(self.nums)-1
while low < high:
currSum = self.nums[low] + self.nums[high]
if currSum < value:
low += 1
elif currSum > value:
high -= 1
else: # currSum == value
return True
return False
import java.util.Collections;
class TwoSum {
private ArrayList<Integer> nums;
private boolean is_sorted;
/** Initialize your data structure here. */
public TwoSum() {
this.nums = new ArrayList<Integer>();
this.is_sorted = false;
}
/** Add the number to an internal data structure.. */
public void add(int number) {
this.nums.add(number);
this.is_sorted = false;
}
/** Find if there exists any pair of numbers which sum is equal to the value. */
public boolean find(int value) {
if (!this.is_sorted) {
Collections.sort(this.nums);
}
int low = 0, high = this.nums.size() - 1;
while (low < high) {
int twosum = this.nums.get(low) + this.nums.get(high);
if (twosum < value)
low += 1;
else if (twosum > value)
high -= 1;
else
return true;
}
return false;
}
}
我们会发现,add(number)
函数将被频繁调用,而 find(value)
将不被那么频繁调用。
这样的使用模式下,意味着我们应该减少 add(number)
函数的时间消耗,因而我们是在 find(value)
对列表进行排序,而不是在 add(number)
。
在哪个函数进行排序,都是可行的,只是对应该使用模式下在 add(number)
下进行排序就不是最佳的方案了。
并且,我们在 find(value)
中是按需排序,也就是当列表更新时,才进行排序。
复杂度分析
add(number)
: O ( 1 ) \mathcal{O}(1) O(1)find(value)
: O ( N ⋅ log ( N ) ) \mathcal{O}(N \cdot \log(N)) O(N⋅log(N)),在最坏的情况下,我们需要对列表进行排序和遍历整个列表,这需要 O ( N ⋅ log ( N ) ) \mathcal{O}(N \cdot \log(N)) O(N⋅log(N)) 和 O ( N ) \mathcal{O}(N) O(N) 的时间。因此总的时间复杂度为 O ( N ⋅ log ( N ) ) \mathcal{O}(N \cdot \log(N)) O(N⋅log(N))。两数之和的另一个解决方案是用哈希表存储值到索引的映射关系。
给定一个目标值 S
,对于每个数字 a
,我们只需要验证哈希表中是否存在 S - a
。
众所周知,哈希表可以提供快速的查找和插入操作,非常适合上述要求。
算法:
add(number)
函数中:在哈希表中添加 number
到 number
频数之间的映射关系。find(value)
函数中:遍历哈希表,对于每个键值(number
),我们检查哈希表中是否存在 value - number
。如果存在,我们终止循环并返回结果。number = value - number
时,在哈希表中 number
对应的值应大于 2
。我们在下图中演示了算法:
class TwoSum(object):
def __init__(self):
"""
Initialize your data structure here.
"""
self.num_counts = {}
def add(self, number):
"""
Add the number to an internal data structure..
:type number: int
:rtype: None
"""
if number in self.num_counts:
self.num_counts[number] += 1
else:
self.num_counts[number] = 1
def find(self, value):
"""
Find if there exists any pair of numbers which sum is equal to the value.
:type value: int
:rtype: bool
"""
for num in self.num_counts.keys():
comple = value - num
if num != comple:
if comple in self.num_counts:
return True
elif self.num_counts[num] > 1:
return True
return False
import java.util.HashMap;
class TwoSum {
private HashMap<Integer, Integer> num_counts;
/** Initialize your data structure here. */
public TwoSum() {
this.num_counts = new HashMap<Integer, Integer>();
}
/** Add the number to an internal data structure.. */
public void add(int number) {
if (this.num_counts.containsKey(number))
this.num_counts.replace(number, this.num_counts.get(number) + 1);
else
this.num_counts.put(number, 1);
}
/** Find if there exists any pair of numbers which sum is equal to the value. */
public boolean find(int value) {
for (Map.Entry<Integer, Integer> entry : this.num_counts.entrySet()) {
int complement = value - entry.getKey();
if (complement != entry.getKey()) {
if (this.num_counts.containsKey(complement))
return true;
} else {
if (entry.getValue() > 1)
return true;
}
}
return false;
}
}
复杂度分析
add(number)
中: O ( 1 ) \mathcal{O}(1) O(1),只进行了哈希表的更新。find(value)
中: O ( N ) \mathcal{O}(N) O(N),其中 N N N 指的是哈希表中键值对的数量。在最坏的情况下,会遍历整个表。