leetcode第1题,两数之和
很容易能够让人想到通过二重循环的方式依次组合数组所有数字组合情况,但是时间复杂度为O(n*n)。
优化:
- 通过
HashMap
记录当前每个数字信息
key: 当前数字
,value: 当前数字的位置
- 重新遍历数组,计算当前数字
a
与target
的差值target-a
,然后通过在上面构建的HashMap
中通过key
查找与该差值target-a
相同的数字b
,这样就可以获取b
的位置,此时a+b=target
并且两者的索引均可以获取到。
需要注意题目要求:你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素
。因此构建条件hash_map.get(target-a)!=index
代码
class Solution {
public int[] twoSum(int[] nums, int target) {
// 结果集
int[] res = new int[2];
// 构建HashMap
HashMap<Integer,Integer> num_map = new HashMap<>();
int index=0;
for(int item : nums){
// `key: 当前数字`,`value: 当前数字的位置`
num_map.put(item,index);
index++;
}
// 重新遍历
index=0;
for(int item : nums){
// 如果存在两数之和=target,并且该数并不是使用两次构成的target
if(num_map.get(target-item)!=null&&num_map.get(target-item)!=index){
// 题目已知只有一种答案,如果找到了直接输出即可
res[0] = index;
res[1]=num_map.get(target-item);
return res;
}
index++;
}
return res;
}
}
leetcode第15题,三数之和
HashMap法
可以采用与两数之和相同的方式。
- 首先对数组进行排序,这样在之后遍历过程中便于去重
- 通过
HashMap
记录每个数字出现的位置。 - 再次通过双指针模拟另外两个数字,然后从
HashMap
中寻找剩余数字的位置。 - 如果位置存在,那么直接收集结果。
这种方案去重及其麻烦,正常情况不采用。
1. 指针i,指向的位置存放的数字如果和上次相同,那么直接向后移
2. 指针j,指向的位置存放的数字如何和上次相同,也直接向后移动
3. 在hashmap
中查到对应目标值的下标后,要求下标必须大于指针j,这样避免产生与前面相同的组合。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> rst = new ArrayList();
// 构建HashMap
HashMap<Integer,Integer> num_map = new HashMap<>();
for(int i=0;i<nums.length;i++){
// `key: 当前数字`,`value: 当前数字的位置`
num_map.put(nums[i],i);
}
for(int i=0;i<nums.length-1;i++){
// 当前最小值已经是正数了
if(nums[i]>0)return rst;
// 第一个值与上次相同
if(i>0&&nums[i]==nums[i-1])continue;
// 第二个值
for(int j=i+1;j<nums.length;j++){
// 第二个值与上次相同
if(j-1>i&&nums[j]==nums[j-1])continue;
// 目标值
int target = -(nums[i]+nums[j]);
// 从hashmap中获取目标值的下标
Integer find_index = num_map.get(target);
// 如果是在第二个数后面,才进行收集,避免重复
if(find_index!=null&&find_index>j)
{
rst.add(Arrays.asList(
nums[i],
nums[j],
target
));
}
}
}
return rst;
}
}
双指针法
如果采用双指针分别指向最左侧和最右侧的话,可以不需要构建HashMap
,并且去重操作更为简单,
- 仍然先进行排序操作
- 遍历目标数组,计算当前剩余两数所需要构成的目标和
target_sum
- 分别使用
left
,right
指向剩余内容的最左侧和最右侧的数据- 假设
nums[left]+nums[right]>target_sum
,那么只需要将右侧的指针向左移动 - 假设
nums[left]+nums[right]<target_sum
,那么只需要将左侧的指针向右移动 - 假设
nums[left]+nums[right]==target_sum
,那么就是当前结果
- 假设
去重操作:
- 遍历数组的时候,如果与上次相同,那么就不用查找了,因为已经收集过结果了
- 在收集结果时,将左右侧指针一直指向下一个不相同的数字的位置。这样在之后就避免的重复收集。
(注意:也可以在左右侧移动后,查看与上一个位置的数字是否相同
不推荐
因为这样需要判断跳过的相同的数字是与最外侧i相同的还是与自己上次相同的,例如(-1,-1,-1,2),假设left在指向第二个-1时,发现与上个相同,然后就会跳过,一直跳到2,显然是不符合的,需要单独判断当前的left是不是i之后的第一个——如果是相同但是是i后的第一个,那么就不需要跳过),
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> rst = new ArrayList();
for(int i=0;i<nums.length;i++){
// 当前最小值已经是正数了
if(nums[i]>0)return rst;
// 与上次相同
if(i>0&&nums[i]==nums[i-1])continue;
int left = i+1;
int right = nums.length-1;
while(left<right){
int sum = -nums[i];
int sum_ = nums[right]+nums[left];
if(sum_>sum){
right--;
}else if(sum_<sum){
left++;
}else{
List<Integer> k = Arrays.asList(nums[i],nums[left],nums[right]);
rst.add(k);
while(left<right&&nums[left+1]==nums[left])left++;
while(left<right&&nums[right-1]==nums[right])right--;
right--;
left++;
}
}
}
return rst;
}
}
比较
第一种和第二种时间复杂度均为O(n*n),但是由于方法一有查询HashMap的操作在,单次的时间耗费要大于方法二
第二种方式的空间复杂度为O(1),但是第一种的空间复杂度为O(n)
leetcode第18题,四数之和
这道题就是对三数之和的应用,依次遍历数组,然后寻找剩余三数之和 = target-nums[i],然后加入结果集中。
但是这道题比较坑的就是需要转换为长整型,因为可能会出现溢出。
这道题没什么好做的,看看就行
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> rst = new ArrayList();
if(nums[0]>0&&nums[0]>target)return rst;
if(nums[nums.length-1]<0&&target>0) return rst;
for(int i=0;i<nums.length-3;i++){
if(nums[i]>0&&nums[i]>target)continue;
List<List<Integer>> three_rst =
threeSum(Arrays.copyOfRange(nums, i+1,nums.length),(long)target-nums[i]);
for(List<Integer> item:three_rst){
rst.add(new ArrayList<>(Collections.singletonList(nums[i])) {{
addAll(item);
}});
}
while(i<nums.length-3&&nums[i]==nums[i+1]){
i++;
}
}
return rst;
}
/**
三数之和
**/
public List<List<Integer>> threeSum(int[] nums, long target){
List<List<Integer>> rst = new ArrayList();
int length = nums.length;
for(int i=0;i<length-2;i++){
if(i>0&&nums[i]==nums[i-1])continue;
int left = i+1;
int right = length-1;
long two_target = target-nums[i];
while(left<right){
if(nums[left]>two_target&&nums[left]>0)return rst;
long two_sum = (long)(nums[left]+nums[right]);
if(two_sum>two_target)right--;
else if(two_sum<two_target)left++;
else if(two_sum==two_target){
rst.add(Arrays.asList(nums[i],nums[left],nums[right]));
while(left<right&&nums[left+1]==nums[left])left++;
while(left<right&&nums[right-1]==nums[right])right--;
right--;
left++;
}
}
}
return rst;
}
}
leetcode第16题:最接近的三数之和
与上述的方案都很类似,都是通过双指针夹紧的方案,收集与当前目标值相距最近的结果,也就是与目标值差的绝对值的最小值。
只要学会了三数之和,这道题就很简单了
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int rst = nums[0]+nums[1]+nums[2];
for(int i=0;i<nums.length;i++){
int left = i+1, right = nums.length-1;
while(left<right){
// 比较目标值和当前收集结果的距离
int divide1 = Math.abs(rst-target);
// 当前三个数的和
int sum = nums[i]+nums[left]+nums[right];
// 比较目标值和当前本次遍历总和的距离
int divide2 = Math.abs(sum-target);
// 如果本次遍历的总和,与target距离更近,就修改结果
if(divide1>divide2)rst = sum;
// 如果收集的结果小于目标值,将左侧指针右移
if(sum<target)left++;
// 如果收集的结果大于目标值,将有侧指针左移
else if(sum>target)right--;
// 如果等于就可以直接返回结果了。
else return target;
}
}
return rst;
}
}