学习目标:
第三章哈希表
-
1. 两数之和
-
454.四数相加II
-
15. 三数之和
-
18. 四数之和
学习内容:
1. 两数之和
思路:遍历数组,每次将数据插入到map中时需要进行判断:map中是否存在需要的数据是他们相加等于target目标值,提前设置结果数组用来存储答案所需的下标。map中数组元素设置为key,数组下标设置为value,因为最终要求的是下标并且是通过判断元素来确定下标在哪的,也就是说在map中原数组中的数据变成下标,原数组中的下标变成数据。
public int[] twoSum(int[] nums, int target) {
//遍历数组,每次将数据插入到map中时需要进行判断:map中是否存在需要的数据是他们相加等于target目标值,提前设置结果数组用来存储答案所需的下标
//map中数组元素设置为key,数组下标设置为value,因为最终要求的是下标并且是通过判断元素来确定下标在哪的,也就是说在map中原数组中的数据变成下标,原数组中的下标变成数据
Map<Integer,Integer> map = new HashMap<>();
int[] res = new int[2];
//如果数组长度为0或者为空的时候直接放回空的结果数组
if(nums.length == 0 || nums == null){
return res;
}
for (int i = 0; i < nums.length; i++) {
int temp = target - nums[i];//设置临时变量计算出需要的值
if(map.containsKey(temp)){//去map中查找需要的值
res[0] = map.get(temp);//若找到则获取它的value也就是原数组下标
res[1] = i;//再获取当前数据的下标
break;
}
map.put(nums[i],i);//若没找到则存入当前数据的值和下标
}
return res;
}
454.四数相加II
思路:将四个数组分为两组分别计算和,将nums1和nums2的和的值存储到map中,其中他们的和为key,也就是map的下标,出现的次数为value,此处value每次自增一,使用map.getOrDefault方法可以设置在某个key中对应的value默认值为0,每次存入则value自增一。随后计算nums+nums4的值,每次进行一次计算就要计算出对应的target = 0 - nums3[i] - nums4[j],然后去map中查询是否存在这个值。若查到存在则在次数count相加value也就是出现的次数,否则继续向后遍历,最终返回count就是四个数值和为0 的总次数
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//将四个数组分为两组分别计算和,将nums1和nums2的和的值存储到map中,其中他们的和为key,也就是map的下标,出现的次数为value
//此处value每次自增一,使用map.getOrDefault方法可以设置在某个key中对应的value默认值为0,每次存入则value自增一
//随后计算nums+nums4的值,每次进行一次计算就要计算出对应的target = 0 - nums3[i] - nums4[j],然后去map中查询是否存在这个值
//若查到存在则在次数count相加value也就是出现的次数,否则继续向后遍历,最终返回count就是四个数值和为0 的总次数
Map<Integer,Integer> map = new HashMap<>();
int count = 0;
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
map.put(nums1[i] + nums2[j],map.getOrDefault(nums1[i] + nums2 [j],0) + 1);
}
}
for (int i = 0; i < nums3.length; i++) {
for (int j = 0; j < nums4.length; j++) {
int target = 0 - nums3[i] - nums4[j];
if(map.containsKey(target)){
count += map.get(target);
}
}
}
return count;
}
15. 三数之和
思路: 双指针法
设置左右两个指针,每次遍历数组中的一个数据,左右指针都会不断向数组中间移动知道左右指针相遇,然后求他们三个数据的和是否等于0。
具体逻辑是:若和小于0则left++,若和大于0则right–,若等于0则满足条件可以存入结果集合中,但同时需要进行去重操作,也就是判断left+1和right-1 的值是否分别与left 和right的值相等。
public List<List<Integer>> threeSum(int[] nums) {
//双指针法
//思路:设置左右两个指针,每次遍历数组中的一个数据,左右指针都会不断向数组中间移动知道左右指针相遇,然后求他们三个数据的和是否等于0。
//具体逻辑是:若和小于0则left++,若和大于0则right--,若等于0则满足条件可以存入结果集合中,但同时需要进行去重操作,也就是判断left+1和right-1 的值是否分别与left 和right的值相等
//若相等则继续向后搜索也就是left++或right--知道left和right相遇,以此完成降重操作,最终得出的集合也就是所有合法且非冗余的集合答案。
List<List<Integer>> result = new ArrayList<>();//将每次得出的三个数据当作一个集合,再将整个集合作为另外一个集合的元素
Arrays.sort(nums);//将数组从小到大进行排序
for (int i = 0; i < nums.length; i++) {
int left = i + 1;//设置左指针指向i的后面一位
int right = nums.length - 1;//设置右指针指向数组最右侧
if(nums[i] > 0){//因为完成从小到大的排序因此若第一个数据就大于零说明不可能和为0
return result;
}
if(i > 0 && nums[i] == nums[i - 1]){//去重操作若后面的数据等于前一个数据则进行跳过处理,避免得到相同答案冗余
//若上述判断条件携程nums[i + 1] == nums[i]会出现误删一些答案例如:[-1,-1,2],因为left = i + 1,这样写也就说明i和left的值不能相等,但实际上是可以相等的,只剩i和i之间不能相等
continue;
}
while(right > left){//左右指针不相遇
int sum = nums[i] + nums[left] + nums[right];//求和
if(sum > 0)
{
right--;
}
else if(sum < 0){
left++;
}
else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));//强行将asList中的数组转换成集合,此方法得到的集合长度是不可变的,只适用于遍历查询操作
while (right > left && nums[right] == nums[right - 1]) {//右指针去重
right--;
}
while (right > left && nums[left] == nums[left + 1]) {//左指针去重
left++;
}
//左右指针分别向中间移动
right--;
left++;
}
}
}
return result;
}
18. 四数之和
思路: 双指针法
与上述三数之和逻辑大体上一致,只是再在外层加一层for循环作为第四个数据,然后对其内部细节做出相应改变即可,内部依旧是三数之和思路。
public List<List<Integer>> fourSum(int[] nums, int target) {
//双指针法:与上述三数之和逻辑大体上一致,只是再在外层加一层for循环作为第四个数据,然后对其内部细节做出相应改变即可,内部依旧是三数之和思路
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if(nums[i] > target && nums[i] > 0){//一级剪枝,因为是手动定义一个target,因此不确定正负,若依旧采用nums[i]大于target来直接判断是否直接返回结果集合依旧不准确了
//因为负数加负数会变得更小,因此需要再添加从小到大排序后的数组的nums[0]>0再nums[0]>target才有效
return result;
}
if(i > 0 && nums[i] == nums[i - 1]){//去重
continue;
}
for (int j = i + 1; j < nums.length; j++) {//完全就是三数之和逻辑
long sum01 = nums[i] + nums[j];//将前两位之和看为一个整体
int left = j + 1;
int right = nums.length - 1;
if(sum01 > target && target > 0){//二级剪枝,与一级同理
break;
}
if(j > i + 1 && nums[j] == nums[j-1]){//去重
continue;
}
while(right > left){
long sum = sum01 + nums[left] + nums[right];//由于自己定义的target会很大,因此若数组中每个数据都是接近int 的最大值则会溢出,因此需要使用long长整型
if(sum > target){
right--;
}
else if(sum < target){
left++;
}
else{
result.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
while(right > left && nums[right] == nums[right - 1]){
right--;
}
while(right > left && nums[left] == nums[left + 1]){
left++;
}
left++;
right--;
}
}
}
}
return result;
}
学习时间:
下午四小时。