454.四数相加II
建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。
题目链接/文章讲解/视频讲解:代码随想录
常用的 `Map` 接口方法:
1. `V get(Object key)`:返回指定键所映射的值,如果键不存在,则返回 `null`。
2. `V put(K key, V value)`:将指定的键值对添加到 `Map` 中,如果键已经存在,则会用新值替换旧值,并返回旧值。
3. `boolean containsKey(Object key)`:判断 `Map` 是否包含指定的键。
4. `boolean containsValue(Object value)`:判断 `Map` 是否包含指定的值。
5. `int size()`:返回 `Map` 中键值对的数量。
6. `V remove(Object key)`:从 `Map` 中删除指定键对应的键值对,并返回该键对应的值。
7. `void clear()`:清空 `Map` 中的所有键值对。
8. `Set<K> keySet()`:返回包含所有键的集合。
9. `Collection<V> values()`:返回包含所有值的集合。
10. `Set<Map.Entry<K, V>> entrySet()`:返回包含所有键值对的集合。
11. `default V getOrDefault(Object key, V defaultValue)`:返回指定键所映射的值,如果键不存在,则返回一个默认值。
12. `default void forEach(BiConsumer<? super K, ? super V> action)`:对 `Map` 中的每个键值对执行指定的操作。
这些 `Map` 接口中常用的方法,可以用于对键值对进行增、删、改、查等操作。
public static int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
// 不仅要考虑a+b在这个集合里有没有出现过,还要统计a+b出现过多少次,所以用map
// key:a+b的数值 value:a+b数值出现的次数
HashMap<Integer, Integer> map = new HashMap<>();
for (int a : nums1) {
for (int b : nums2) {
int sum=a+b;
// V getOrDefault(Object key,V defaultValue)
// key:要获取键的值
// defaultValue:如果键不存在,则返回的默认值
// getOrDefault()是map接口中的一个方法,用于获取指定键对应的值,如果键不存在,则返回一个默认的值
// 将sum作为key,出现的次数作为value,保存在map中
map.put(sum, map.getOrDefault(sum, 0)+1);
}
}
// 计算c+d元素的和,并在map中查找其相反数的出现次数
int count=0;
for (int c : nums3) {
for (int d : nums4) {
int sum=c+d;
// 如果相反数在map中存在,则将其出现次数累加到count变量中
count+=map.getOrDefault(-sum,0);
}
}
return count;
}
383. 赎金信
建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题
题目链接/文章讲解:代码随想录
对表格特性解释:
1.可变性:StringBuilder和StringBuffer是可变的,可以通过方法修改其中的内容,而String是不可变的,一旦创建就不能被修改。
2.线程安全性:StringBuilder是非线程安全的,不适合在多线程下使用。StringBuffer和String是线程安全的,可以在多线程环境下使用。
3.性能:StrignBuilder的性能最高,因为它不需要进行头同步操作。String的性能最低,因为每次修改都会创建一个新的String对象。String的性能居中,因为它需要进行同步操作,但比String要快。
4.内存使用:StringBuilder的内存使用较低,因为它不需要创建新的对象,String的内存使用较高,因为每次修改都会创建新的String对象。
5.使用场景:SrtingBuilder适用于单线程环境下需要频繁修改字符串的场景,String适用于无需频繁修改字符串的场景,StringBuffer适用于多线程环境下需要频繁修改字符串的场景。
思路①:暴力法
两层for循环遍历,如果a字符串中和b字符串中的字符相等,就删掉a(子)字符串中的字符。
如果删到最后a字符串中的长度为0咯就说明::a字符串中的字符在b中都有能与之匹配的。
public static boolean canConstruct(String ransomNote, String magazine) {
StringBuilder sb = new StringBuilder(ransomNote);
for (int i = 0; i < magazine.length(); i++) {
for (int j = 0; j < sb.length(); j++) {
// 在ransomNote中找到和magazine相同的字符
if(magazine.charAt(i)==sb.charAt(j)){
// substring()可以从原始字符串中提取出指定位置的子字符串
sb.deleteCharAt(j);//删除这个字符
break;
}
}
}
if(sb.length()==0){
return true;
}
return false;
}
思路②:哈希法
遍历a字符串
hash[c-'a']+=1;
遍历b(子)字符串
hash[c-'a']-=1;
如果到最后b字符串有很多负数,就说明a中不存在或者少一些与b相对于的字符,我们这道题要求的是a的字符串囊括的要多些?所以最后结果小于0就说明不正确。
public static boolean canConstruct(String ransomNote, String magazine) {
// 数组在哈希法中的应用
// 用一个长度为26的数组记录magazine里字母出现的次数
int[] hash = new int[26];
// 再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母
// toCharArray()将字符串转换为字符数组
for (char c : magazine.toCharArray()) {
hash[c-'a']+=1;
}
for(char c: ransomNote.toCharArray()){
hash[c-'a']-=1;
}
// 如果字符串存在负数,说明randomNote字符串中总存在magazine中没有的字符
for(int i: hash){
if(i<0){
return false;
}
}
return true;
}
15. 三数之和
建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。
题目链接/文章讲解/视频讲解:代码随想录
问题:给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?
思路:双指针法
收集的结果集里不能有重复的三元组,所有对a,b,c进行去重。 对数组进行排序。
第一个i的元素的位置是不变的,将left定义在i后的位置,而right定义在最后的位置。要求的结果是nums[i]+nums[left]+nums[right]=0 。如果nums[i]+nums[left]+nums[right]>0,就把right--,向前移动一位,nums[i]+nums[left]+nums[right]<0,就把left++,向后移动,这就体现了排序的好处。
要求的是不能重复的三元组,但三元组里面的元素是可以重复的。
这两种写法截然不同。
边界问题的判断。如果left==right的话,不满足三元组的条件。
去重的逻辑,一定要放在收获结果的下面。至少要收获符合一个条件的结果。
public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a+b+c=0
// a=nums[i],b=nums[left],c=nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经>0,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if(nums[i]>0){
return result;
}
// 去重a
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]+nums[left]+nums[right];
if(sum>0){
right--;
}else if(sum<0){
left++;
}else {
// asList():将指定数组转换为List
result.add(Arrays.asList(nums[i],nums[left],nums[right]));
// 去重逻辑应该放在一个三元组之后,对b和c去重
// 通过while循环移动指针到下一个不重复的位置
while (right>left && nums[right]==nums[right-1]) right--;
while (right>left && nums[left]==nums[left+1]) left++;
// 把重复的删了移到原本想要的位置,原本想要的位置应该就是结果考虑了去重的。
right--;
left++;
}
}
}
return result;
}
18. 四数之和
建议: 要比较一下,本题和 454.四数相加II 的区别,为什么 454.四数相加II 会简单很多,这个想明白了,对本题理解就深刻了。 本题 思路整体和 三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意,建议先看视频。
题目链接/文章讲解/视频讲解:代码随想录
思路:和三数之和相同,就多了一层for循环
public static List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
for (int k = 0; k < nums.length; k++) {
if(nums[k]> target && target>0){
break;
}
if (k>0 && nums[k]==nums[k-1]){
continue;
}
for (int i = k+1; i < nums.length; i++) {
if(nums[k]+nums[i]>target && target>0){
break;
}
if(i>k+1 && nums[i]==nums[i-1]){
continue;
}
int left=i+1;
int right=nums.length-1;
while (left<right){
long sum=(long)nums[k]+nums[i]+nums[left]+nums[right];
if(sum >target){
right--;
}else if(sum<target){
left++;
}else {
list.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
while (left<right && nums[left]==nums[left+1]) left++;
while (left<right && nums[right]==nums[right-1]) right--;
left++;
right--;
}
}
}
}
return list;
}
总结
四数相加:哈希法。key放两数之和,value放a和b两数之和出现的次数。两个双层for循环。
赎金信:和字母异位词相同,定义一个数组记录字符串出现的次数。一个字符串对数组+1,另一个字符串-1,如果最后数组里面的元素有负数,说明大数组的元素没有包含小数组的元素,小数组不能由大数组组成。
三/四数之和:双指针法