四数相加 2
题目描述
解题思路
-
首先定义 一个HashMap,key放i和j两数之和,value 放i和j两数之和出现的次数。
-
遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中。
-
定义int变量count,用来统计 i+j+k+l = 0 出现的次数。
-
在遍历nums3和nums4数组,找到如果 0-(k+l) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
-
最后返回统计值 count
自己解题
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int n = nums1.length;
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
map.put(nums1[i] + nums2[j], map.getOrDefault(nums1[i] + nums2[j], 1));
}
}
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (map.containsKey(-nums3[i] - nums4[j])) {
count += map.get(-nums3[i] - nums4[j]);
}
}
}
return count;
}
}
- 时间复杂度: O(n^2)
- 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
value 放i和j两数之和出现的次数
map.put(nums1[i] + nums2[j], map.getOrDefault(nums1[i] + nums2[j], 1));
getOrDefault(Object key,Object defaultValue)
返回指定键映射到的值,如果此映射不包含键的映射,则返回defaultValue
key-要返回其关联值的键
defaultValue-密钥的默认映射
参考解题
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int count = 0;//用来计算符合的情况次数
Map<Integer, Integer> map = new HashMap<>();
//统计两个数组中的元素之和,同时统计出现的次数,放入map
for (int i : nums1) {
for (int j : nums2) {
int sum = i + j;
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
}
//统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
for (int i : nums3) {
for (int j : nums4) {
count += map.getOrDefault(0 - i - j, 0);
}
}
return count;
}
}
补充
1. HashMap常用方法
2. HashMap的getOrDefault方法
赎金信
题目描述
解题思路
哈希解法
因为题目说只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为26的数组来记录magazine里字母出现的次数。
然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的,数据量大的话就能体现出来差别了。 所以数组更加简单直接有效
自己解题
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
int[] arr = new int[26];
for (char c : magazine.toCharArray()) arr[c - 'a']++;
for (char c : ransomNote.toCharArray()) {
arr[c - 'a']--;
if (arr[c - 'a'] < 0) {
// 如果数组arr中存在负数,说明ransomNote字符串总存在magazine中没有的字符
return false;
}
}
return true;
}
}
-
时间复杂度: O(n)
-
空间复杂度: O(1)
加上剪枝
if (ransomNote.length() > magazine.length()) { return false; }
参考解题
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
// shortcut
if (ransomNote.length() > magazine.length()) {
return false;
}
// 定义一个哈希映射数组
int[] record = new int[26];
// 遍历
for(char c : magazine.toCharArray()){
record[c - 'a'] += 1;
}
for(char c : ransomNote.toCharArray()){
record[c - 'a'] -= 1;
}
// 如果数组中存在负数,说明ransomNote字符串总存在magazine中没有的字符
for(int i : record){
if(i < 0){
return false;
}
}
return true;
}
}
补充
1. 加上剪枝
if (ransomNote.length() > magazine.length()) {
return false;
}
2. String类常用方法
charAt(index) 返回值类型char —>返回指定索引处的char值
contains(CharSequence s) 返回值类型boolean —>当且仅当此字符串包含指定的char值序列时,返回true
hashCode() 返回值类型int —>返回此字符串的哈希码
isEmpty() 返回值boolean —>当且仅当length()为0时 返回true
length() 返回值类型int —>返回此字符串的长度
replace(char oldChar, char newChar) 返回值类型String —>返回一个新的字符串,它是通过用newChar替换此字符串中出现的所有oldChar得到的新字符串(替换)
split(String regex) 返回值类型String() —>根据给定正则表达式的匹配拆分此字符串(切割)
substring(int beginIndex,int endIndex) 返回值类型String—>返回一个新字符串,他是此字符串的一个子字符串(截取)
toCharArray() 返回值类型char[] —>将此字符串转换为一个新的字符数组
三数之和
题目描述
不可包含重复的三元组
nums[i] + nums[j] + nums[k] == 0
i、j、k都不相同
返回所有和为 0
且不重复的三元组
解题思路
哈希解法较复杂
自己解题
import java.util.*;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();//创建一个集合res,里面存放的是 整数集合
Arrays.sort(nums);//调用工具类对数组nums排序
int n = nums.length;//获取数组长度
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
//注意,这个变量k要写在第二层for循环的外面
//如果写在里面的话,那么j每加1,k都要从数组的最后一个元素重新遍历,导致执行时间能达到1000ms以上
int k = n - 1;// int k=nums.length-1
int target = -nums[i];//另外两个数之和
for (int j = i + 1; j < n; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {//去重b
continue;
}
while (j < k && nums[j] + nums[k] > target) {
k--;
}
if (j == k) {
break;
}
if (nums[j] + nums[k] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
}
return res;
}
}
参考解题
import java.util.*;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();// 创建一个集合res,里面存放的是 整数集合
Arrays.sort(nums);// 调用工具类对数组nums排序
int n = nums.length;// 获取数组长度n
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
// 注意,这个变量k要写在第二层for循环的外面
// 如果写在里面的话,那么j每加1,k都要从数组的最后一个元素重新遍历,导致执行时间能达到1000ms以上
int k = n - 1;// int k=nums.length-1
int target = -nums[i];// 另外两个数之和
for (int j = i + 1; j < n; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {// 去重b
continue;
}
while (j < k && nums[j] + nums[k] > target) {
k--;
}
if (j == k) {
break;
}
if (nums[j] + nums[k] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
res.add(list);
}
}
}
return res;
}
}
补充
bc的去重应该放在下面,应该至少获取一个结果
相关内容的复习
1. List接口
List接口的特点:存取有序、有索引、可以重复存储
ArrayList
内部是基于动态数组实现的,它通过一个数组来存储元素,当数组空间不足时会自动扩容。LinkedList
内部是基于双向链表实现的,它通过节点之间的引用来存储元素
2.List集合的遍历方式
Iterator迭代器、增强for、foreach方法、普通for 循环、ListIterator迭代器(复习一下)
3. ArrayLis长度可变原理
数组:存储的元素个数固定不变
集合:存储的元素个数经常发生改变
4. ArrayList常用方法
5.LinkedList常用方法
6.LinkedList独有方法
7.List接口的两个实现类 ArrayList与LinkedList的不同之处
讲的很清楚的一篇文章
a.内部实现:
ArrayList
内部是基于动态数组实现的,它通过一个数组来存储元素,当数组空间不足时会自动扩容。LinkedList
内部是基于双向链表实现的,它通过节点之间的引用来存储元素。
b. 随机访问性能:
ArrayList
支持高效的随机访问,因为它可以通过索引直接访问数组中的元素,时间复杂度为 O(1)。LinkedList
的随机访问性能较差,因为要遍历链表从头部或尾部找到目标元素,时间复杂度为 O(n)。
c. 插入和删除操作性能:
- 在
ArrayList
中,插入和删除操作涉及到元素的移动,如果在中间插入或删除元素,需要将后续的元素向后或向前移动,时间复杂度为 O(n)。 - 在
LinkedList
中,插入和删除操作无需移动其他元素,只需调整节点的引用即可,因此在列表两端执行插入和删除操作的性能较好,时间复杂度为 O(1),而在中间插入和删除操作的时间复杂度为 O(n)。
d.空间占用:
ArrayList
在添加新元素时,可能需要重新分配内存空间,因为它是基于数组实现的,而且可能会有一部分预留空间没有被使用,因此可能会存在一定的空间浪费。LinkedList
中每个元素都是一个节点,节点对象的创建和维护会占用额外的空间,因此相比于ArrayList
,它可能会占用更多的内存空间。
当需要频繁进行随机访问操作时,应该选择 ArrayList
;而当需要频繁执行插入和删除操作,并且操作主要集中在列表的两端时,LinkedList
可能更适合。
8.容器的选择
参考文章
a.需要保证存储的元素唯一性,且不关心顺序,则选择 Set
。
b.需要按照顺序存储元素,并且可能包含重复元素,则选择 List
。
c.需要通过键来快速检索对应的值,则选择 Map
。
四数之和
题目描述
给你一个由 n
个整数组成的数组 nums
,和一个目标值 target
。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案
解题思路
剪枝
三数之和 可以通过 nums[i] > 0
就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]
,target
是-10
,不能因为-4 > -10
而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)
就可以了。
自己解题
未解出,网上参考代码如下:
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();// 定义结果集
int len = nums.length;
if (len < 4)
return res; // 如果长度小于4,则直接返回
Arrays.sort(nums); // 进行排序
for (int i = 0; i < len - 3; i++) {
// if(nums[i]>target) break; target可能为负数,不可以这样
if (i > 0 && nums[i] == nums[i - 1])//i去重
continue;// 重复则跳过,跳过本次循环i进行下次循环
for (int j = i + 1; j < len; j++) {
if (j > i + 1 && nums[j] == nums[j - 1])//j去重
continue;// 重复则跳过,跳过本次j循环进行下次循环
int l = j + 1;
int r = len - 1;
while (l < r) {
long sum = (long) nums[i] + nums[j] + nums[l] + nums[r];//数字可能太大 long
if (sum == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[l], nums[r]));//先取一个结果出来
while (l < r && nums[l] == nums[l + 1]) {//l去重
l++;
}
while (l < r && nums[r] == nums[r - 1]) {//r去重
r--;
}
l++;
r--;
} else if (sum < target) {
l++;
} else if (sum > target) {
r--;
}
}
}
}
return res;
}
}
参考解题
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// nums[i] > target 直接返回, 剪枝操作
if (nums[i] > 0 && nums[i] > target) {
return result;
}
if (i > 0 && nums[i - 1] == nums[i]) { // 对nums[i]去重
continue;
}
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) { // 对nums[j]去重
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target int会溢出
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
// 对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;
}
}
补充
今日题目较难,同时注意对相关内容的复习
ps:部分图片和代码来自代码随想录和Leetcode官网