Bootstrap

代码随想录算法训练Day7|LeetCode454-四数相加 2、LeetCode 383-赎金信、LeetCode15-三数之和、LeetCode18-四数之和

四数相加 2

题目描述

力扣454-四数相加 2

解题思路

  1. 首先定义 一个HashMap,key放i和j两数之和,value 放i和j两数之和出现的次数。

  2. 遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放到map中

  3. 定义int变量count,用来统计 i+j+k+l = 0 出现的次数。

  4. 在遍历nums3和nums4数组,找到如果 0-(k+l) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。

  5. 最后返回统计值 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方法

赎金信

题目描述

力扣383

解题思路

代码随想录

哈希解法

因为题目说只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为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 且不重复的三元组

力扣15

解题思路

哈希解法较复杂

代码随想录

自己解题

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

四数之和

题目描述

力扣18

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • 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官网

;