代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和

news2024/11/27 22:38:48

代码随想录算法训练营第七天| 哈希表理论基础 ,454.四数相加II, 383. 赎金信, 15. 三数之和, 18. 四数之和

454.四数相加II

建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。

题目链接/文章讲解/视频讲解:

在这里插入图片描述

看到题目的第一想法:

超时的想法:用map存储其中的一个数组,然后三层for循环求得三个整数数组的和sum,最后判断map中是否存在0-sum,如果存在result加上它的value值,如果不存在就下层循环。显然这种想法的时间复杂度为O(n^3).
重新思考后的想法:用map的key存储其中两个数组的所有和,value存储其个数,然后三层for循环求得另外两个数组的sum,最后判断map中是否存在0-sum,如果存在result加上它的value值,如果不存在就下层循环。这种想法的时间复杂度为O(n^2),减少了一个数量级。

package com.second.day7;

import java.util.HashMap;

public class FourSumCount_454 {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums3.length; i++) {
            int num3 = nums3[i];
            for(int j = 0; j < nums4.length; j++) {
                int num4 = nums4[j];
                map.put(num3 + num4, map.getOrDefault(num3 + num4, 0) + 1);
            }
        }
        int result = 0;
        for(int i = 0; i < nums1.length; i++) {
            int num1 = nums1[i];
            for(int j = 0; j < nums2.length; j++) {
                int target = 0 - num1 - nums2[j];
                if(map.containsKey(target)) {
                    result += map.get(target);
                }
            }
        }
        return result;
    }
}

看完代码随想录之后的想法:

思路是一样的,难怪leetcode的执行时间还挺快的。

自己实现过程中遇到哪些困难:

无,看到时间超了,能很快想出另外一种想法不错的。

383. 赎金信

建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题

题目链接/文章讲解:
在这里插入图片描述

看到题目的第一想法:

1.设置一个map,map的key表示magazine的字符,value表示字符出现的次数。
2.遍历magazine,将magazine的字符存入map中
3.遍历ransomNote,如果map中有不存在的字符,或map的字符的value小于0就返回false,直到遍历完。

package com.second.day7;

import java.util.HashMap;

public class FourSumCount_454 {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums3.length; i++) {
            int num3 = nums3[i];
            for(int j = 0; j < nums4.length; j++) {
                int num4 = nums4[j];
                map.put(num3 + num4, map.getOrDefault(num3 + num4, 0) + 1);
            }
        }
        int result = 0;
        for(int i = 0; i < nums1.length; i++) {
            int num1 = nums1[i];
            for(int j = 0; j < nums2.length; j++) {
                int target = 0 - num1 - nums2[j];
                if(map.containsKey(target)) {
                    result += map.get(target);
                }
            }
        }
        return result;
    }
}

看完代码随想录之后的想法:

思路是一样的,但卡哥用的是数组存储,比我直接用map的空间少,时间更快。以后涉及只有小写字母或者大写字母的,用哈希法先用数组存储。

自己实现过程中遇到哪些困难:

15. 三数之和

建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。

题目链接/文章讲解/视频讲解:
在这里插入图片描述

看到题目的第一想法:

1.设置一个map,map的key表示nums[i]的值,value表示出现的次数
2.用map存储其中的一个数组
3.两层for循环,分别求得nums[i]和nums[j],然后判断map中是否有0-nums[i]-nums[j]
4.如果map中存在,放入list中,为了去重,排序后放入set集合中

package com.second.day7;

import java.util.*;

public class ThreeSum_15 {
    /**
     * 一顿操作猛如虎,点击提交超时了。
     *
     * 二话不说翻题解,评论区里全人才。
     *
     * 反反复复终得道,再次尝试却报错。
     *
     * 行行检查字字改,击败用户百分五。
     * @param nums
     * @return
     */
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Set<List<Integer>> set = new HashSet<>();
        //key存储nums[i]的值,value存储值的个数
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for(int i = 0; i < nums.length - 1; i++) {
            int num1 = nums[i];
            map.put(num1, map.get(num1) - 1);
            for(int j = i + 1; j < nums.length; j++) {
                int num2 = nums[j];
                map.put(num2, map.get(num2) - 1);
                int target = -num1 - num2;
                List<Integer> list = new ArrayList<>();
                if(map.containsKey(target) && map.get(target) > 0) {
                    list.add(num1);
                    list.add(num2);
                    list.add(target);
                    Collections.sort(list);
                    set.add(list);
                }
                map.put(num2, map.get(num2) + 1);
            }
            map.put(num1, map.get(num1) + 1);
        }
        for(List<Integer> lists : set) {
            result.add(lists);
        }
        return result;
    }
}

看完代码随想录之后的想法:

排序后去重的效率是很低的,然后我的时间复杂度就很高,Colletion.sort()用的归并排序或者快速排序时间复杂度为O(nlogn),然后加两层for循环时间复杂度就到了O(n^3logn).
卡哥有去重的哈希法:先对数组进行排序,两层for循环,然后每层for循环分别去重nums[i],nums[j],在第一层for循环设置一个set用来存储nums[k],如果set中存在target = nums[i] - nums[j],则符合题目要求,放入result中,并对nums[k]去重。剪枝后的时间复杂度是O(n^2)。

public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0; i < nums.length; i++) {
            //排序后的第一数都大于0,之后的肯定不满足三数之和为0
            if(nums[i] > 0)
                break;
            //对a去重
            if(i > 0 && nums[i] == nums[i - 1])
                continue;
            HashSet<Integer> set = new HashSet<>();
            for(int j = i + 1; j < nums.length; j++) {
                //对三元组b去重如[0, 0, 0, 0]
                if(j > i + 2 && (nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]))
                    continue;
                int target = -nums[i] - nums[j];
                if(set.contains(target)) {
                    res.add(Arrays.asList(nums[i], nums[j],target));
                    //对c去重
                    set.remove(target);
                }
                else {
                    set.add(nums[j]);
                }
            }
        }
        return res;
    }

双指针法
1.首先对数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
2.依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
3.如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
4.如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
先不看代码实现一下吧!

public List<List<Integer>> threeSum2(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for(int i = 0; i < nums.length; i++) {
            //第一个数大于0了,就不会有满足题目要求的数了
            if(nums[i] > 0)
                break;
            //对数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 {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while(left < right && nums[left + 1] == nums[left])
                        left++;
                    while(left < right && nums[right - 1] == nums[right])
                        right--;
                    left++;
                    right--;
                }
            }
        }
        return res;
    }

啊,竟然一摸一样,时间复杂度进一步降低了!代码的世界真奇妙!这次的时间复杂度小于O(n^2)了。

自己实现过程中遇到哪些困难:

将数组转换为List的函数不熟悉,不会运用。Arrays.asList().

454.四数相加II

建议: 要比较一下,本题和 454.四数相加II 的区别,为什么 454.四数相加II 会简单很多,这个想明白了,对本题理解就深刻了。 本题 思路整体和 三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意,建议先看视频。

'题目链接/文章讲解/视频讲解:
在这里插入图片描述

看到题目的第一想法:

和之前的三数之和的想法类似
1.设置一个map,map的key表示nums[i]的值,value表示出现的次数
2.用map存储其中的一个数组
3.三层for循环,分别求得nums[i],nums[j]和nums[k],然后判断map中是否有0-nums[i]-nums[j]
4.如果map中存在,放入list中,为了去重,排序后放入set集合中
可想而知这个时间复杂度是很高的!但在leetcode上能够通过。期间我也遇到了一个整数溢出的问题。是之前三数之和没出现的。

package com.second.day7;

import java.util.*;

public class FourSum_18 {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Set<List<Integer>> set = new HashSet<>();
        //key存储nums[i]的值,value存储值的个数
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for(int i = 0; i < nums.length - 2; i++) {
            map.put(nums[i], map.get(nums[i]) - 1);
            for(int j = i + 1; j < nums.length - 1; j++) {
                map.put(nums[j], map.get(nums[j]) - 1);
                for(int k = j + 1; k < nums.length; k++) {
                    map.put(nums[k], map.get(nums[k]) - 1);
                    long num4 = (long)target - ((long)nums[i] + nums[j] + nums[k]);
                    if(num4 < Integer.MIN_VALUE || num4 > Integer.MAX_VALUE) {
                        map.put(nums[k], map.get(nums[k]) + 1);
                        continue;
                    }
                    List<Integer> list = new ArrayList<>();
                    if(map.containsKey((int)num4) && map.get((int)num4) > 0) {
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[k]);
                        list.add((int)num4);
                        Collections.sort(list);
                        set.add(list);
                    }
                    map.put(nums[k], map.get(nums[k]) + 1);
                }
                map.put(nums[j], map.get(nums[j]) + 1);
            }
            map.put(nums[i], map.get(nums[i]) + 1);
        }
        for(List<Integer> lists : set) {
            result.add(lists);
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums = new int[]{1, 0, -1, 0, -2, 2};
        FourSum_18 demo = new FourSum_18();
        demo.fourSum(nums, 0);
    }
}

看完代码随想录之后的想法:

双指针法:
(1)和三树之和的双指针法思想是一致的,但在一些细节上是不同的。因为这个题目的target是不固定的,不能直接在第一次循环的时候nums[i] > target就可以不循环,因为如果target为-10,-4>-10,nums[i]后面还有负数的话就有可能满足题目要求。
(2)四数之和相对于三数之和是有两层for循环,同样也设置left和right,然后找nums[i]+nums[j]+nums[left]+nums[right]=target的情况。
(3)用双指针法,三数之和的时间复杂度从O(n ^ 3) 降到了O(n ^ 2),四数之和的时间复杂度从O(n ^ 4) 降到了O(n ^ 3).之后的五数之和,六数之和都可以用同样的方法。
先不看卡哥代码,自己先实现一下吧。

public List<List<Integer>> fourSum1(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        //对数组进行排序
        Arrays.sort(nums);
        for(int i = 0; i < nums.length - 3; i++) {
            //剪枝
            if(nums[i] > target && (nums[i] > 0 || target > 0))
                break;
            //去重
            if(i > 0 && nums[i] == nums[i - 1])
                continue;
            for(int j = i + 1; j < nums.length - 2; j++) {
                //剪枝
                if((nums[i] + nums[j]) > target && (nums[i] + nums[j] > 0 || target > 0))
                    continue;
                //去重
                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) {
                        right--;
                    }
                    else if(sum < target) {
                        left++;
                    }
                    else {
                        result.add(Arrays.asList(nums[i], nums[j], 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 result;
    }

注意:上面的代码没有做整数溢出处理也能过得原因是剪枝过程中规避了溢出。最好还是要像卡哥一样,求sum是把他转为long

自己实现过程中遇到哪些困难:

[-1000000000,-1000000000,-1000000000,-1000000000]
294967296
整数溢出处理:
我第一做的时候是先把他转为long,然后判断这个数是否在int的范围内,没有在的话就不符合条件,跳过去。

long num4 = (long)target - ((long)nums[i] + nums[j] + nums[k]);
if(num4 < Integer.MIN_VALUE || num4 > Integer.MAX_VALUE) {
	map.put(nums[k], map.get(nums[k]) + 1);
	continue;
}

好吧这种方法很笨。
还是双指针法好。

今日收获,记录一下自己的学习时长:

今天的题我虽然都硬写出来了,真就是硬写出来的,时间复杂度都太高了,好吧多数之和没有想到可以用双指针法进行剪枝与去重。今天的博客写了我两天。脚踏实地的,不骗自己。
今天写了四个题:
454.四数相加II , 383. 赎金信, 15. 三数之和 ,18. 四数之和
代码:4h
博客:1.5h
想干就干,要干就干的漂亮,即使没有人为你鼓掌,至少还能够勇敢的自我欣赏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/88425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【洞察人性】 理解行为背后的动机

《洞察人性》 关于作者 阿尔弗雷德•阿德勒&#xff0c;奥地利精神病学家&#xff0c; 人本主义心理学先驱&#xff0c;曾经在美国哥伦比亚大学任客座教授。同时他也是个体心理学的创始人&#xff0c;在学术界拥有重要的地位。著作有《自卑与超越》《人性的研究》 《洞察人性…

DPDK源码分析之rte_eal_init

EAL是什么 环境抽象层(EAL)负责获得对底层资源(如硬件和内存空间)的访问。对于应用程序和其他库来说&#xff0c;使用这个通用接口可以不用关系具体操作系统的环境细节。rte_eal_init初始化例程负责决定如何分配操作系统的这些资源(即内存空间、设备、定时器、控制台等等)。 …

【IVIF:搜索架构】

Searching a Hierarchically Aggregated Fusion Architecture for Fast Multi-Modality Image Fusion &#xff08;搜索用于快速多模态图像融合的分层聚合融合架构&#xff09; 现有的基于CNN的方法使主要点在于设计各种体系结构&#xff0c;以端到端的方式实现这些任务。但是…

JSP ssh美食娱乐分享网站系统myeclipse开发oracle数据库MVC模式java编程计算机网页设计

一、源码特点 JSP ssh美食娱乐分享网站系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采 用B/S模式开发。开发环境为TOMCA…

学习二叉树必须要了解的各种遍历方式及节点统计

哈喽&#xff0c;大家好&#xff0c;我是小林。今天给大家分享一下对二叉树的一些常规操作。 愿我们都能保持一颗向上的心。 目录一、前序遍历二、中序遍历三、后序遍历四、 统计节点个数五、统计叶子节点个数六、第K层的节点个数七、二叉树的深度八、查找值为x的节点九、层序遍…

TensorFlow TFRecords简介

TensorFlow TFRecords简介 这篇博客将介绍TensorFlow的TFRecords&#xff0c;提供有关TFRecords的所有信息的一应俱全的介绍。从如何构建基本TFRecords到用于训练 SRGAN 和 ESRGAN 模型的高级TFRecords的所有内容。包括什么是TFRecords&#xff0c;如何序列化&#xff0c;反序…

SQL 语句练习03

目录 一、建表 二、插入数据 三、查询 一、建表 这里先建好我们下面查询需要的表&#xff0c;方便后续查询。 建立如下学生表(命名格式“姓名拼音_三位学号_week5s”&#xff0c; 如LBJ_023_week5s&#xff09;create table LYL_116_week5s(SNO varchar(4) primary key,SNA…

【Kubernetes】DashBoard部署

kubernetes&#xff0c;是一个全新的基于容器技术的分布式架构领先方案&#xff0c;是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本&#xff0c;于2014年9月发布第一个版本&#xff0c;2015年7月发布第一个正式版本。 kubernetes的本质是一组服务器集群&#xff0…

数字孪生智慧水务建设综述

随着新时期治水方针的逐步落实&#xff0c;水利现代化、智能化建设已全面开启&#xff0c;数字孪生等新技术的成熟&#xff0c;也为智慧水务体系的搭建提供了技术保障&#xff0c;新时代治水新思路正逐步得到落实。本文将重点对智慧水务的内涵及建设内容进行解读&#xff0c;力…

2022年航空与物流行业研究报告

第一章 行业概况 航空与物流行业是指以各种航空飞行器为运输工具&#xff0c;以空中运输的方式运载人员或货物的企业。航空公司是以各种航空飞行器为运输工具为乘客和货物提供民用航空服务的企业。航空公司使用的飞行器可以是他们自己拥有的&#xff0c;也可以是租来的&#x…

物联网通信技术原理-作业汇总(更新ing)

第一章 第二章 第三章 第四章 第五章 1. 移动通信中典型的多址接入方式有哪些&#xff1f;简要说明其工作原理2. 分集技术的基本原理是什么&#xff1f;简要说明空间、频率和时间分集、合并的异同。 1&#xff09;分集技术的基本原理 通过多个信道&#xff08;时间、频率或…

25.访客功能

访客功能 一、需求分析 用户在浏览我的主页时&#xff0c;需要记录访客数据&#xff0c;访客在一天内每个用户只记录一次。 首页展示最新5条访客记录 我的模块&#xff0c;分页展示所有的访客记录 二、数据库表 visitors&#xff08;访客记录表&#xff09; { “_id”: …

尚医通 (三十五) --------- 预约下单

目录一、预约下单前端1. 封装 api 请求2. 页面修改二、后端逻辑1. 需求分析2. 搭建 service-order 模块3. 添加订单基础类4. 封装 Feign 调用获取就诊人接口5. 封装 Feign 调用获取排班下单信息接口6. 实现下单接口7. 预约成功后处理逻辑① rabbit-util 模块封装② 封装短信接口…

C++ Reference: Standard C++ Library reference: Containers: map: map: cend

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/cend/ 公有成员函数 <map> std::map::cend const_iterator cend() const noexcept;返回指向结束的const_iterator 返回一个指向容器结束后元素的const_iterator。 const_iterator是指向const内容的it…

正弦交流电物理量表征

前言 这一讲主要来表征正弦交流电的物理量 文章目录前言一、周期和频率二、最大值、有效值和平均值一、周期和频率 周期&#xff1a;正弦交流电每重复变化1次所需要的时间称为周期&#xff0c;用符号T表示&#xff0c;单位是秒&#xff08;s&#xff09;。 频率&#xff1a;正…

web前端期末大作业 绿色环境保护(4个页面) HTML5网站模板农业展示网站 html5网页制作代码 html5网页设计作业代码 html制作网页案例代码

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

体育馆场地预约管理系统/球馆管理系统

摘 要 随着体育馆规模的不断扩大&#xff0c;人流数量的急剧增加&#xff0c;有关体育馆的各种信息量也在不断成倍增长。面对庞大的信息量&#xff0c;就需要有体育馆场地预约管理系统来提高体育馆工作的效率。通过这样的系统&#xff0c;我们可以做到信息的规范管理和快速查询…

TCP/IP网络原理 【IP篇】

&#x1f389;&#x1f389;&#x1f389;写在前面&#xff1a; 博主主页&#xff1a;&#x1f339;&#x1f339;&#x1f339;戳一戳&#xff0c;欢迎大佬指点&#xff01; 目标梦想&#xff1a;进大厂&#xff0c;立志成为一个牛掰的Java程序猿&#xff0c;虽然现在还是一个…

聚观早报 | 马斯克丢掉世界首富宝座;加密货币FTX创始人被捕

今日要闻&#xff1a;马斯克丢掉世界首富宝座&#xff1b;加密货币FTX创始人被捕&#xff1b;美团推出高峰打车极速版&#xff1b;魔兽制作组正研发新功能&#xff1b;SpaceX出售公司内部股票马斯克丢掉世界首富宝座 12 月 13 日消息&#xff0c;据国外媒体报道&#xff0c;受特…

7-54 孤岛营救问题——状压bfs+三维标记

1944 年&#xff0c;特种兵麦克接到国防部的命令&#xff0c;要求立即赶赴太平洋上的一个孤岛&#xff0c;营救被敌军俘虏的大兵瑞恩。瑞恩被关押在一个迷宫里&#xff0c;迷宫地形复杂&#xff0c;但幸好麦克得到了迷宫的地形图。迷宫的外形是一个长方形&#xff0c; 其南北方…