算法训练Day7

2023-11-16

目录

LeetCode454. 四数相加

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考

 Leetcode383. 赎金信 

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考

Leetcode15. 三数之和

方法一:双指针法

1. 思路

 2. 代码实现

3. 复杂度分析

4. 思考

Leetcode18. 四数之和 

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考


LeetCode454. 四数相加

链接: 454. 四数相加 II - 力扣(LeetCode)

1. 思路

本题的暴力法是4个for loop循环,时间复杂度为O(N^4),不再赘述。

这道题目的一个很重要的点是不用考虑去重,例如A B C D 中的所有元素都为0的时候,就算找到的元组都是[ 0, 0, 0, 0 ]这种形式,但是他们仍然属于不同的元组,因为其中的0来自于数组中不同的位置。这大大简化了这道题的思路。

怎么想到用哈希法?

思考:在集合中判断一个元素是否出现过的时候,想到要用哈希法。

可以先遍历数组A和数组B,储存所有可能性在哈希表中,然后一一遍历数组C和数组D的组合;看0-(C+D)元素是否在哈希表中出现,如果出现,就找到了合适的元组,这里涉及到了在哈希表中判断元素是否出现过,所以想到用哈希法。

用哈希法的哪种数据结构?

数值大小不可控,不可以用array;题意要统计元组出现的次数,所以我们需要有key-value的键值对, 所以说用Map。

Map用来干啥?其中的Key-value分别是啥?

Map用来存AB数组元素相加的所有可能性,key为相加之和的数值,value为这个数值出现的次数。

具体执行思路

  1. 首先定义 一个map,key放a和b两数之和,value 放a和b两数之和出现的次数;
  2. 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中;
  3. 定义int变量count,用来统计 a+b+c+d = 0 出现的次数;
  4. 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来;
  5. 最后返回统计值 count 就可以了。

2. 代码实现

# Map作哈希表
# time:O(n^2); space:O(n^2)
class Solution(object):
    def fourSumCount(self, nums1, nums2, nums3, nums4):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :type nums3: List[int]
        :type nums4: List[int]
        :rtype: int
        """
        record = {}
        count = 0
        for i in nums1:
            for j in nums2:
                Sum = i+j
                if Sum in record:
                    record[Sum] += 1
                else:
                    record[Sum] = 1
        for k in nums3:
            for l in nums4:
                target = 0-k-l 
                if target in record:
                    count += record[target]
        return count

3. 复杂度分析

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

我们使用了两次二重循环,时间复杂度均为 O(n^2);在循环中对哈希映射进行的修改以及查询操作的期望时间复杂度均为 O(1),因此总时间复杂度为 O(n^2);

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

即为哈希映射需要使用的空间。在最坏的情况下,A[i]+B[j]的值均不相同,因此值的个数为 n^2 ,也就需要 O(n^2) 的空间。

4. 思考

  1. 提出疑问,为什么不只遍历A,得到Map,然后再遍历B,C,D中所有组合的可能性?因为这样的话,时间复杂度和空间复杂度就提升为O(N^3);
  2. 注意count计数器在加的时候,并不是直觉上的count+= 1,因为出现的频率不一定是1,比如说map中储存的是5:3,对应三组AB元组,分别是,A[1] +B[2] = 5,A[2] +B[3] = 5,A[3] +B[4] = 5,然后在C+D中找到了一对相加等于-5的,所以说这样就找到了三组四元组,应该count+3;
  3. 和其他题目的比较思考:这道题和两数之和思路非常一致,只是创建Map和查找Map的时候,都变成了两个for loop一起遍历,value存的是下标;和有效字母的异位词处理也很类似,只是根据不同情况用到的元素不同,都是先创建容器,再利用容器找我们需要的结果;
  4. 题目乍眼看和三数之和,四数之和差不多,其实差很多;本题是使用哈希法的经典题目,而以上两道题不适合使用哈希法,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于题目18. 四数之和,题目15.三数之和,还是简单了不少!

Reference

1. 力扣

2.  代码随想录

本题学习时间:60分钟。


 Leetcode383. 赎金信 

链接:赎金信 - 赎金信 - 力扣(LeetCode)

1. 思路

暴力方法?

首先想到的第一思路是暴力枚举了,两层for循环,不断去寻找,首先遍历magazine,再遍历ransom,如果在ransom中找到了和magazine相同的字符,则再ransom中删掉这个字符,遍历结束之后,如果ransom为空了,说明,magazine的字符可以组成ransom,这里时间复杂度是比较高的,而且里面还有一个字符串删除也就是erase的操作,也是费时的。时间复杂度为O(N^2);空间复杂度为O(1),暴力解法只叙述思路,不再赘述。

这道题目和LeetCode242.有效的字母异位词很像,它相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。

如何想到用哈希法?

本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成。也就是看字符串ransom中的元素,是否在magazine中出现过。

在集合中判断一个元素是否出现过,则立即想到哈希法。

哈希表用哪种数据结构?其组成元素代表什么含义?

题目中还有两点细节需要注意:

  • 第一点“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思” 这里说明杂志里面的字母不可重复使用;
  • 第二点 “你可以假设两个字符串均只含有小写字母。” 说明只有小写字母,这一点很重要。

因为不可以重复使用,所以我们需要在建立哈希表的时候,统计每个元素出现的次数,因为字符串中只有小写字母,小写字母最多只有26种,所以可以用长度为26的数组来作哈希表,数组的下标索引是从“a”-”z”的各个字母,数组值是该字母出现的次数。

哈希解法实现思路

因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。

然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。依然是数组在哈希法中的应用。

2. 代码实现

# 数组作哈希表
# time:O(m+n);space:O(1)
class Solution(object):
    def canConstruct(self, ransomNote, magazine):
        """
        :type ransomNote: str
        :type magazine: str
        :rtype: bool
        """
        record = [0]*26
        # 通过recode数据记录 magazine里各个字符出现次数
        for element in magazine:
            index = ord(element) - ord("a")
            record[index] += 1
        # 遍历ransomNote,在record里对应的字符个数做--操作
        for item in ransomNote:
            index = ord(item) - ord("a")
            record[index] -= 1
        for i in record:
            # 如果小于零说明ransomNote里出现的字符,magazine没有
            if i <0:
                return False
        return True

 

3. 复杂度分析

时间复杂度:O(m + n)

其中 m 是字符串 ransomNote 的长度,n 是字符串 magazine 的长度,我们只需要遍历两个字符一次即可;

空间复杂度:O(|S|) = O(1)

S 是字符集,这道题中 S 为全部小写英语字母,因此 |S| = 26。

4. 思考

  1. 可以直接用Map吗?其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
  2. 本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题;
  3. 本题是 使用array 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。

Reference:

1. 力扣

2.  代码随想录

本题学习时间:60分钟。 


Leetcode15. 三数之和

链接: 15. 三数之和 - 力扣(LeetCode)

方法一:双指针法

1. 思路

这道题目可以像两数之和一样使用哈希法,但是有一个巨大的难点,就是元组不可以重复,所以说需要做去重操作,而去重的操作中有超级多的细节需要注意,在面试中很难写出bug free的代码。

虽然说哈希法也是时间复杂度为O(N^2)的算法,但是双指针法会比哈希法更加高效。

 

  1. 拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
  2. 处理一下边界的逻辑, 如果nums[i]已经大于0,那么不可能凑成三数和为0,直接结束这个for循环。
  3. i元素的去重逻辑,应该是从第二个元素开始,如果和前面的元素一样的话,就直接略过当前的循环,进入下一个循环中。但这里很细节,如果写成,if(nums[i] == nums[i+1] )这种情况的话,当数组为【-1,-1,0,0,2】这样子的时候,i = 0, nums[i] = -1; left = 1; right=4; 这种情况i比较后面数,就会自动i+ 1= 1开始考虑这个循环,会忽略掉-1 -1 2 这种情况。所以说i=0 不比较,从i=1开始,和前面的比较,如果相同,则跳过,这才是正确的i去重的办法,**我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!**所以这里是有两个重复的维度。那么应该这么写:if (i > 0 && nums[i] == nums[i - 1])这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。这是一个非常细节的思考过程。
  4. 依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i] b = nums[left] c = nums[right]。
  5. 接下来如何移动left 和right呢,循环条件也应该是while(left<right),因为如果两者相等,那相当于只有两个数了,不符合题意;
  6. 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
  7. 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
  8. 这个过程中,会找到三数之和等于0 的时刻,这时候,要先把它们加到result 里面去记录上,然后,在进行left和right的去重环节,这时候是拿下一个和现在的这个相比较,因为这个是已经加进去了,所以不会遗漏。

 2. 代码实现

# time:O(N^2); space:O(logN)/O(N)
class Solution(object):
    def threeSum(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """  
        nums.sort()
        result = []
        for i in range(len(nums)):
            # 如果第一个最小的元素都大于0的话,可以直接退出for loop
            if nums[i] > 0:
                break
            # 这里是元素i的去重,这里很细节,必须和前面的i作比较
            # 否则 -1 -1 0 0  2 ,就会漏掉 -1 -1 2 这种情况
            if i>0 and nums[i] == nums[i-1]:
                # 退出当前这个for loop ,i+1进入下一个loop
                continue
            # 初始化left和right
            left = i+1
            right = len(nums)-1
            # 这里不考虑left = right的情况,因为这种情况没有意义,
            # left and right 必须是不同的两个数才行
            while left<right:
                # 当和偏大的时候,让right左移变小
                if nums[i]+nums[left]+nums[right] >0:
                    right -= 1
                # 当和偏小的时候,让left右移变大
                elif nums[i]+nums[left]+nums[right] <0:
                    left += 1
                else:
                    # 先把这个结果给加上,再考虑left和right去重的问题
                    result.append([nums[i],nums[left],nums[right]])
                    # 找到一个结果之后left和right向中间聚拢一步
                    left += 1
                    right -= 1
                    # 开始处理left和right的去重细节
                    # 这里也类似前面的i,要和之前已经处理过的数据比较
                    # 这里同时要保证left是小于right的,不然容易越界,也没必要做任何操作了
                    # 违反这个会直接退出大的while(left<right)循环
                    # 这里要用while处理,因为可能有连续的好几个
                    while left<right and nums[left] == nums[left-1]:
                        left += 1
                    while left<right and nums[right] == nums[right+1]:
                        right -= 1
        return result

3. 复杂度分析

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

首先排序的时间复杂度为O(NlogN);第一个循环,遍历元素i,在每个元素i中,两个指针left和right会把数组也遍历一遍,因此总共的遍历的复杂度是O(N^2),加起来就是O(N^2);

空间复杂度:O(1)/O(N)

输出答案的空间不会有很多,可以认为是O(1),排序所需要的空间复杂度也为O(1);但如果题意不能原地排序的话,需要创建一个新的数组,那么排序的空间复杂度变为O(N)。

4. 思考

  1. 本题的双指针逻辑相对还比较清晰明了,但是去重的过程有很多容易出错的地方和细节;
  2. 既然三数之和可以使用双指针法,我们之前讲过的两数之和可不可以使用双指针法呢?两数之和 就不能使用双指针法,因为两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了,如果要求返回的是数值的话,就可以使用双指针法了。

本题学习时间:120分钟。


Leetcode18. 四数之和 

链接:18. 四数之和 - 力扣(LeetCode)

1. 思路

四数之和,和三数之和是一个思路,都是使用双指针法, 基本解法就是在它的基础上再套一层for循环。

 

三数之和的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。

四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。

但是有一些细节需要注意

1.  一级剪枝操作有所变化,不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(亲自写代码就能感受出来)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法训练Day7 的相关文章

随机推荐

  • 树莓派gpio接ttl转usb串口调试

    树莓派设置修改 以下教程只在树莓派3B 验证测试通过 其它版本未经测试仅供参考 1 gt 修改config txt enable uart 1 找到这行 将值改为1 dtoverlay pi3 miniuart bt 在config txt
  • uni-apph5 端获取当前位置坐标及地理位置逆解析

    1 uni app getLocation在浏览器端获取的地理位置坐标是你电脑里面ip地址位置的坐标 2 调用百度地图api逆解析地址对坐标解析详细地址 代码如下 经纬度 记得改成活的 测试用写死了 uni getLocation type
  • 详解 MySQL InnoDB 实现原理

    MySQL InnoDB 引擎现在广为使用 它提供了事务 行锁 日志等一系列特性 本文分析下 InnoDB 的内部实现机制 MySQL 版本为 5 7 24 操作系统为 Debian 9 1 InnoDB 架构 Innodb 架构图 Inn
  • Java基于Selenium动态抓取页面

    Java基于Selenium动态抓取页面 前情介绍 解决 思路 编码 解决过程中遇到的问题一 解决过程中遇到的问题二 总结 前情介绍 前段时间开发了一个功能 通过HttpClient访问某个页面 获取页面的全部html内容 之后通过抓取过来
  • 【Spring Boot 集成应用】 OAUTH2统一认证单点登录中的各种模式说明

    1 OAUTH2统一认证介绍 OAuth 2 0 是一个行业的标准授权协议 OAuth 2 0 专注于简化客户端开发人员 同时为 Web 应用程序 桌面应用程序 手机等各种设备接入提供特定的授权流程 2 传统登陆认证 传统登陆方式是在每个服
  • python基础笔记(三)_Matplotlib基础语法

    图表绘制工具 Matplotlib 概念 一个python版的matlab绘图接口 以2D为主 支持python numpy pandas基本数据结构 有较丰富的图表库 图表窗口 plt show 直接生成图表 matplotlib inl
  • seate底层原理_Seate

    Seata是阿里开源的一个分布式事务框架 Seata主要有两种分布式事务实现方案 AT及TCC AT模式主要关注多 DB 访问的数据一致性 当然也包括多服务下的多 DB 数据访问一致性问题 TCC 模式主要关注业务拆分 在按照业务横向扩展资
  • Jmeter(三十七) - 从入门到精通进阶篇 - 输出HTML格式的性能测试报告(详解教程)

    1 简介 相对于Loadrunner Jmeter其实也是可以有测试报告产出的 虽然一般都不用 没有Loadrunner的报告那么强大是一方面 但是有小伙伴们私下问 那宏哥还是顺手写一下吧 今天我们就来学习下 如何输入HTML格式的JMet
  • nginx下面完美配置解决404 file not found(让nginx支持PATHINFO路由模式)

    老朱亲自写的 最完美Nginx 配置文件 server listen 80 server name xxxx com if host d d d d return 404 禁IP访问 root var www index index htm
  • C++ 中 operator< 运算符重载来实现 sort 排序的简单理解

    先上代码 Struct Student string id int grade bool operator lt const Student t const if grade t grade return grade gt t grade
  • 面试常见问题

    最失败的案例 复盘经验和反思 有分量的事情 最成功 印象最深的案例 网络架构较为复杂 用户端为移动手持 即无线网络 服务端为固网私有网络 中间为办公网 即服务端网络 办公网 用户端 故障排查 分别在服务端 服务器 网络中转端 防火墙 用户端
  • PLSQL的使用

    目录 1 PLSQLl的安装 配置文件配置教程地址 2 PLSQL建表出现乱问题 Oracle PLSQL 表中字段 注释时为乱码 解决方式 1 PLSQLl的安装 配置文件配置教程地址 https blog csdn net master
  • java垃圾回收机制

    今天算是对java的gc有了一定的了解 三篇文章做个标记 配合上篇文章来看 http www daniel journey com archives 139 另外推荐三篇很棒的文章 JVM调优总结 Java 6 JVM参数选项大全 一次Ja
  • ng-class的几种用法

    方法一 div div checker disabled checker 是CSS样式 selectAllButton是判断条件 值为 true or false 方法二 div div item disab是判断条件 值为 true or
  • linux rootfs制作

    作一个嵌入式Linux rootfs 并且实现 web 服务 1 文件系统简介 理论上说一个嵌入式设备如果内核能够运行起来 且不需要运行用户进程的话 是不需要文件系统的 文件系统简单的说就是一种目录结构 由于 linux操作系统的设备在系统
  • 数组切片[1::2]怎么理解

    python中数组切片 在数组a中 有三个地方可以设置参数a 位置 列表初始索引 默认为0 位置 列表结束索引 默认到最后一个元素 包含最后一个元素 位置 为步长 默认为1 a np arange 1 10 print a 1 2 3 4
  • kafka创建话题遇到的错误

    确定Kafka安装和启动正确 ZooKeeper可以查到所有的Brokers 但执行 kafka topics sh create zookeeper localhost 2181 replication factor 3 partitio
  • Linux iperf3:网络性能测试工具

    文章目录 iperf3简介 安装 详细命令参数 Server 端参数 Client 端参数 示例 服务端 先启动 客户端 iperf3简介 iPerf3是用于主动测试IP网络上最大可用带宽的工具 它支持时序 缓冲区 协议 TCP UDP S
  • 点云旋转平移(二)—python open3d点云平移

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 点云旋转平移介绍 请
  • 算法训练Day7

    目录 LeetCode454 四数相加 1 思路 2 代码实现 3 复杂度分析 4 思考 Leetcode383 赎金信 1 思路 2 代码实现 3 复杂度分析 4 思考 Leetcode15 三数之和 方法一 双指针法 1 思路 2 代码