代码随想录算法训练营第七天|454.四数相加II 、383. 赎金信 、15. 三数之和、18. 四数之和

代码随想录算法训练营第七天|454.四数相加II 、383. 赎金信 、15. 三数之和、18. 四数之和

454.四数相加II

题目链接

问题简述:在四个数组中各选出一个元素,使其加和为0,返回能满足的四元组个数。

思考:我下面写的用到了两个map。但实际可以只用一个map,然后直接遍历数组即可。

算法思路:将两个数组元素加和的所有可能存入map1作为key,加和key出现的次数作为value。再将剩下两个数组进行同样操作存入map2。遍历map1的value,查看map2是否存在-value,如果存在,则将两个对应的key相乘存入计数器count。

时间复杂度 :O(n^2)

空间复杂度 :O(n)

import java.util.HashMap;
import java.util.Map;


class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4){
        Map<Integer, Integer> map1 = getMap(nums1, nums2);
        Map<Integer, Integer> map2 = getMap(nums3, nums4);
        //记录次数
        int count = 0;
        for (Integer key : map1.keySet()) {
            if(map2.containsKey(-key)){
                count += map1.get(key) * map2.get(-key);
            }
        }
        return count;
    }
    //将两个数组的加和存入map
    public Map getMap(int[] nums1, int[] nums2){
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                //如果加和不存在则存入map,如果加和存在则增加出现次数value
                if(map.containsKey(nums1[i] + nums2[j])){
                    map.put(nums1[i] + nums2[j], map.get(nums1[i] + nums2[j]) + 1);
                }else {
                    map.put(nums1[i] + nums2[j], 1);
                }
            }
        }
        return map;
    }
}

383. 赎金信

题目链接

问题简述:求一个字符串中的字符是否完全来自另一个字符串。

思考:这道题和昨天判断字符串顺序比较相似,较为简单。用到了数组,和昨天的区别是需要最后判断数组元素大于等于0,而不是等于0。

算法思路:用数组的0~25分别存储magazine中小写字母a~z出现的次数,再减去ransomNote中小写字母出现次数,最后看是否各个字母数量都为正。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] hash = new int[26];
      //将magazine字母存入数组
        for (int i = 0; i < magazine.length(); i++) {
            hash[magazine.charAt(i)-'a']++;
        }
      //数组剪去ransomNote中字母出现次数
        for (int i = 0; i  < ransomNote.length(); i++) {
            hash[ransomNote.charAt(i)-'a']--;
        }
      //遍历数组看是否各个字母数量都为正
        for (int i : hash){
            if(i < 0){
                return false;
            }
        }
        return true;
    }
}

15. 三数之和

题目链接

问题简述:给定一个数组,返回三个不同位置的加和为0的所有三元组,且三元素不得重复。

思考:这道和下一道题感觉是目前最难的题,用到了双指针,自己写加上看别人写的最后完成,需要注意的点超级超级多,以后要反复学习。

算法思路:先将数组排序,依次遍历每个元素作为a,将每个元素后的left指针指向b,rigth指针指向c。每次遍历时,如果 a > 0 则直接返回(去枝操作,为了提高运行效率)。同时每次遍历完a后进行去重操作,防止a这个数字遍历了多次。当a确定时,查看此时a、b、c的和是否为0,如果为0直接存入结果,并对b、c进行去重;如果大于0则c左移使加合变小,如果小于0则b右移使加合变大。最终返回三元组的集合。

时间复杂度 O(n^2)

空间复杂度 O(1)

import java.util.*;

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> listOfLists = new ArrayList<>();
        Arrays.sort(nums);
        //i遍历a, left:b, right:c
        for (int i = 0; i < nums.length; i++) {
            //如果后面的都为正数了,则不用继续了
            if (nums[i] > 0) {
                return listOfLists;
            }
            //保证a不重复,注意是要比较i和i - 1。因为三元组中允许有重复元素,在去重时,如果a有重复,第一次a出现时候,后面是存在与a相同的元素的,这样保证三元组中可以有重复元素
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;//跳过这轮循环
            }
            //取b为a后的的数,保证a、b索引不一样
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right){
                if (nums[i] + nums[left] + nums[right] == 0){
                    listOfLists.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    //进行b、c去重,即如果出现重复元素,将left和right向中间移动。因为我们在第一次遇到left和right指向元素时,比较的范围是包含left和right重复元素的,所以接下来遇到直接去重
                    while (left < right && nums[left + 1] == nums[left]) left++;
                    while (left < right && nums[right - 1] == nums[right]) right--;
                    //成立的三元组添加后,b和c一定都会改变
                    right--;
                    left++;
                    //加和大于0,c需要减小
                }else if (nums[i] + nums[left] + nums[right] > 0){
                    right--;
                    //加和小于0,b需要增大
                }else if (nums[i] + nums[left] + nums[right] < 0){
                    left++;
                }
            }
        }
        return listOfLists;
    }
}

18. 四数之和

题目链接

问题简述:给定一个数组,返回四个不同位置的加和为target的所有四元组,且四元素不得重复。

思考:三数之和的plus版,将三数之和的对a的一重遍历变为对a和b的二重遍历,值得注意的点还有很多,注意要去枝,去枝不到位可能会超时和报错。还有就是用sum表示了十个数字加和,不用也会超时。

算法思路:先将数组排序,依次二重遍历每个元素作为a和b,且b的初始位置为a的下一个元素,将b元素后的left指针指向c,rigth指针指向d。每次遍历时,如果 a > 0 且a还大于target,则直接返回(去枝操作,为了提高运行效率)。同时每次遍历完a后进行去重操作,防止a这个数字遍历了多次。当a确定时,此时遍历b,同样进行一次剪枝操作,如果 a + b > 0 且a还大于target,则跳出循环。最后判断四数之和是否为target,如果为target则直接将四元组存入集合,并对c、d进行去重;如果大于target则d左移使加合变小,如果小于target则c右移使加合变大。最终返回四元组的集合。

时间复杂度 O(n^3)

空间复杂度 O(1)

import java.util.*;

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> listOfLists = new ArrayList<>();
        Arrays.sort(nums);
        //i遍历a, j:b, left:c, right:d
        for (int i = 0; i < nums.length; i++) {
            //剪枝,注意和上一题的区别,负数可能越加越小
            if (nums[i] > 0 && nums[i] >target){
                return listOfLists;
            }
            //为a去重
            if (i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            for (int j = i + 1; j < nums.length; j++) {
                //剪枝
                if (nums[i] + nums[j] > 0 && nums[i] + nums[j] >target){
                    //注意这次剪枝不能直接返回,因为下次nums[i]可能再次变小
                    break;
                }
                //去重b,这里问题卡了很久,注意i要大于j+1,否则b就和a比大小了,实际上应该是下一个b和前一个b比大小
                if (j > i + 1 && nums[j] == nums[j - 1]){
                    continue;
                }
                int left = j + 1;
                int right = nums.length - 1;
                while (left < right){
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target){
                        listOfLists.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        //进行b、c去重,即如果出现重复元素,将left和right向中间移动
                        while (left < right && nums[left + 1] == nums[left]) left++;
                        while (left < right && nums[right - 1] == nums[right]) right--;
                        left++;
                        right--;
                        //加和大于0,c需要减小
                    }else if (sum > target){
                        right--;
                        //加和小于0,b需要增大
                    }else if (sum < target){
                        left++;
                    }
                }
            }
        }
        return listOfLists;
    }
}

感想

感觉今天的很有难度,特别是后两个,不过明天就是字符串了,坚持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值