原题如下
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
第一种解法:暴力方式 复杂度是O(n^2)
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
for i in range(len(nums)):
for j in range(len(nums) - i):
if nums[i] + nums[j] == target:
#因为题目说明一个解, 这里直接返回
return [i, j]
if __name__ == "__main__":
a = Solution()
print(a.twoSum([8,2,5,2,5,11], 10))
这里思考的是, 因为我们是要对比一个数组中的两个数之间的合的关系, 两次遍历同一个数组复杂度可以通过字典创建索引来解决其中的一次遍历问题
见代码: 复杂度就是O(n) + O(1)*O(N) == 2O(n) = O(N)
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
dictNums = {}
for i in range(len(nums)):
#这里循环同一个数组,创造字典,以便第二次循环对比的时候,能够O(1)级别的查找
dictNums[nums[i]] = i
for i in range(len(nums)):
if dictNums.get(target-nums[i], 0) != 0 and i != dictNums[target-nums[i]]:
#因为题目说条件是假设一个解,所以直接返回
return [i, dictNums[target-nums[i]]]
return False
if __name__ == "__main__":
a = Solution()
print(a.twoSum([8,2,5,2,5,11], 10))
第二种方法就从O(n^2)降到了O(n) , 但是发现上面第二种方式是否可以再优化一下呢? 可否把两次循环放在一次循环里面呢?
答案是肯定的, 看下面的数组假如我们要找和为10的两个数, 如果循环到了第三个数, 其实对于那么只需要前三个数创建的字典与后面的数进行匹配, 也就是说一次把数组倒置创建索引形成字典是存在一些浪费的, 因为有可能提前找到答案
1 2 5 2 9 6 3
那么在一次改进, 第三种方法:
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
dictNums = {}
for i in range(len(nums)):
if target-nums[i] in dictNums:
#因为题目说条件是假设一个解,所以直接返回
return [dictNums[target-nums[i]], i]
else:
dictNums[nums[i]] = i
return False
if __name__ == "__main__":
a = Solution()
print(a.twoSum([8,2,5,2,5,11], 10))
虽然复杂度还是同样的O(n) 但是比上面的算法代码上更加简洁, 且节约了构建索引的部分循环次数
以下是Java版本
/**
* 时间复杂度为O(n^2)
*
*/
public class TwoSum {
public int[] twoSum(int[] nums, int target) {
int indices[] = new int[2]; //定义一个数组,只有两个元素
//两次遍历数组
for(int i = 0; i < nums.length - 1; i++){
for(int j = i+1; j < nums.length; j++){
//判断相加结果是否为题目所求
if((i < j) && (nums[i] + nums[j] == target)){
indices[0] = i;
indices[1] = j;
}
}
}
return indices;
}
}