题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
暴力解法
时间复杂度为O(n2),空间复杂度O(1)。
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
int N = nums.length;
for(int i = 0; i<N-1; i++) {
for(int j = i+1;j<N; j++) {
if(nums[i]+nums[j] == target) {
result[0] = i;
result[1] = j;
return result;
}
}
}
return result;
}
两遍哈希表
对时间复杂度进行优化,以空间换取速度。时间复杂度O(n),空间复杂度O(n)。
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<Integer,Integer>();
for(int i= 0; i<nums.length; i++) {
map.put(nums[i],i);
}
for(int i = 0; i<nums.length; i++) {
int rest = target - nums[i];
if(map.containsKey(rest) && (Integer)map.get(rest) != i) { //两个数不能一样
return new int[]{i,(int)map.get(rest)};
}
}
return null;
}
一遍哈希表
边遍历边插入,在前几个数据插入哈希表时,一定查不着数据。
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<Integer,Integer>();
for(int i = 0; i<nums.length; i++) {
int rest = target - nums[i];
if(map.containsKey(rest)) {
return new int[]{(int)map.get(rest),i};
}else {
map.put(nums[i],i);
}
}
return null;
}
扩展
如果需要三个数呢???例如判断是否存在三个元素a,b,c,使得a+b+c = 0 ?这个数组中会有重复的数值,所以不能采用上述的哈希表来存储。
排序做法
利用排序之后,采用二分法,二分法是基于排序的。可快速的查找列表。复杂度为O(n2logn)。
public List<List<Integer>> threeSum(int[] nums) {
List list = new ArrayList<List>();
//先对数组进行排序
chooseSort(nums);
for(int i = 0; i < nums.length-2; i++) {
for(int j = i+1; j<nums.length-1; j++) {
int k = binarySearch(nums, -nums[i]-nums[j],j+1, nums.length-1);
if(k != -1 && k!= i && k!= j) {
List list2 = new ArrayList<Integer>();
list2.add(nums[i]);
list2.add(nums[j]);
list2.add(nums[k]);
list.add(list2);
}
}
}
return list;
}
private void chooseSort(int[] nums) {
for(int i = 0; i<nums.length; i++) {
int min = i; //记录最小值的角标
for(int j = i+1; j<nums.length; j++) {
if(min > nums[j]) {
min = j;
}
}
exch(nums, i, min);
}
}
private void exch(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private int binarySearch(int[] nums, int k,int lo,int hi) {
//其中nums必须是有序的
if(lo > hi)
return -1;
else {
int mid = lo +(hi-lo)/2;
if(nums[mid] == k)
return mid;
else if(nums[mid] > k) {
return binarySearch(nums, k, lo, mid-1);
}else {
return binarySearch(nums, k, mid+1, hi);
}
}
}
但是上面代码是有问题的,没有考虑重复的问题。这个问题怎么解决呢???想到可以用将访问到的次数利用哈希表可以存储,可是对于重复的问题依然没有解决,反倒增加了空间复杂度。
而且二分法对于有重复的数,就会给出的位置则是其中的一个,而不是精确定位。首先对二分法有重复的数进行优化。对于有重复的数,则在找到目标的第一个匹配的和最后一个匹配的。
private int binaryFirst(int[] nums, int k,int lo,int hi) {
//其中nums必须是有序的
if(lo > hi)
return -1;
else {
int mid = lo +(hi-lo)/2;
if(nums[mid] == k) {
if((mid > lo && nums[mid-1] != k) || mid == lo)
return mid;
else {
hi = mid - 1; //前一个数字也等于于当前数据
}
}else if(nums[mid] > k) {
hi = mid - 1;
}else {
lo = mid + 1;
}
return binaryFirst(nums, k, lo, hi);
}
}
private int binaryLast(int[] nums, int k,int lo,int hi) {
//其中nums必须是有序的
if(lo > hi)
return -1;
else {
int mid = lo +(hi-lo)/2;
if(nums[mid] == k) {
if((mid < hi && nums[mid+1] != k) || mid == hi)
return mid;
else {
lo = mid + 1; //后一个数字也等于于当前数据
}
}else if(nums[mid] > k) {
hi = mid - 1;
}else {
lo = mid + 1;
}
return binaryLast(nums, k, lo, hi);
}
}
由此得出使用二分查找会比较复杂,则换一种方法。依然先将数据排序,利用双指针解决重复的问题。
public List<List<Integer>> threeSum(int[] nums) {
List list = new ArrayList<List>();
//先对数组进行排序
Arrays.sort(nums);
for(int i = 0; i<nums.length-2 ;i++) {
if(i == 0 || (i>0 && nums[i] != nums[i-1])) {//跳过重复的答案,所以是往前判断是否相等
int l = i+1,r = nums.length - 1,sum = 0-nums[i];
while(l < r) {
if(nums[l] + nums[r] == sum) {
list.add(Arrays.asList(nums[i],nums[l],nums[r]));
while(l <r && nums[l] == nums[l+1])
l++;
while(l <r && nums[r] == nums[r-1])
r--;
l++;
r--;
}
else if(nums[l] + nums[r] < sum) { //对于比sum小的,则应该增加l,不可以减小r,否则就更小了
while(l <r && nums[l] == nums[l+1])
l++;
l++;
}else{ //对于比sum大,则需要降低r,不可以增大l
while(l <r && nums[r] == nums[r-1])
r--;
r--;
}
}
}
}
return list;
}