Java 8 中使用 Lambda 表达式和 Stream API 解决 LeetCode 的两数之和问题

news2024/12/23 4:22:31

Java 8 中使用 Lambda 表达式和 Stream API 解决 LeetCode 的两数之和问题

当我们在面对一个数列,需要查找其中两个元素的和为给定目标值时,可以使用两数之和(Two Sum)问题来解决。这个问题在 LeetCode 上有很高的重要性和普遍性,在各种面试中也经常会被考察。

最直接的方法是通过双重 for 循环来枚举所有可能的元素对,然后检查它们的和是否等于给定目标值。这个方法的时间复杂度是 O(n^2),并不太适用于大型数据集。

那么如何能够更快地解决这个问题呢?我们可以使用哈希表(Hash Table)来降低时间复杂度。具体来说,我们可以建立一个从数组元素到其下标的映射,然后遍历一遍数组,对于每个元素,查找其补数是否存在于哈希表中即可。

问题

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。


示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

以下是使用 Java 代码实现上述算法的标准解决方案:

public static int[] twoSum(int[] nums, int target) {

        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {

                if (nums[i] + nums[j] == target) {
                    System.out.println("i的值是==》" + i + "j的值是==》" + j);
                    System.out.println("---------------------------");
                    System.out.println("nums[i]的值是:" + nums[i] + "nums[j]的值是:" + nums[j] + ";target的值是=" + target);

                    int[] result = new int[2];
                    result[0] = i;
                    result[1] = j;
                    return result;
                }


            }

        }
        return new int[0];
    }

上面这段代码实现了使用双重循环来查找数组中两个元素的和等于目标值的下标。对于小型数据集,这种算法可以工作得很好,但对于大型数据集,它的时间复杂度为 O(n^2),效率较低。

相比之下,使用哈希表算法可以把时间复杂度降低到 O(n),提高程序的效率。这是因为哈希表可以在常量时间内完成对元素的查找操作,所以算法的总时间复杂度取决于遍历数组的时间复杂度,即线性的 O(n)。

除此之外,使用哈希表算法还具有以下优点:

可以处理包含重复元素的情况:如果输入数组中包含重复元素,那么双重循环的解法将会返回最后一组满足条件的元素下标。而哈希表算法可以正确地处理这种情况,返回第一组满足条件的元素下标。

可以处理无序数组的情况:如果输入数组是一个无序数组,那么双重循环的解法将需要进行排序操作,从而增加额外的时间和空间开销。而哈希表算法并不依赖数组的顺序,可以直接在原始数组上进行处理,减少了额外的开销。

可以处理不存在解的情况:如果输入数组中不存在满足条件的元素对,那么双重循环的解法将返回一个不正确的结果(即最后一组比较的元素对)。而哈希表算法可以检测到这种情况,并返回一个空数组。

因此,使用哈希表算法来解决两数之和问题是更加高效和可靠的方法。

以下是使用 Map代码实现上述算法的标准解决方案:

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

public class Solution {
    public static int[] t


woSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];

            if (map.containsKey(complement)) {
                return new int[] {map.get(complement), i};
            }

            map.put(nums[i], i);
        }

        return new int[0];
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int target = 9;

        int[] result = twoSum(nums, target);

        System.out.println(Arrays.toString(result)); // 输出 [0, 1]
    }
}

在上述代码中,我们使用了一个 Map 对象来存储数组中的元素及其下标。具体来说,在每次遍历数组中的元素时,我们检查它的补数(即目标值与当前元素之差)是否已经存在于 Map 中。如果是,那么我们已经找到了符合条件的两个元素,可以直接返回它们的下标。

如果补数不存在于 Map 中,则将当前元素及其下标添加到 Map 中,以便在查找后续元素时进行比较。

相比于双重 for 循环,这个算法的时间复杂度为 O(n),并且空间复杂度也只需要 O(n),因为我们需要存储数组中所有的元素和它们的下标。

除此之外,我们还可以使用 Java 8 的 Lambda 表达式和 Stream API 来简化上述算法的实现。具体来说,在使用 Lambda 表达式时,我们可以使用 IntStream.range() 方法来遍历数组元素的下标,并使用 Collectors.toMap() 方法将对应的元素和下标存储到一个 Map 对象中。然后,我们使用 filter() 方法和 findFirst() 方法来查找符合条件的元素对,并将它们转换为包含两个下标的数组。

以下是使用 Lambda 表达式实现上述算法的完整代码:

import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Solution {
    public static int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = IntStream.range(0, nums.length)
                .boxed()
                .collect(Collectors.toMap(i -> nums[i], i -> i));

        return IntStream.range(0, nums.length)
                .filter(i -> map.containsKey(target - nums[i]) && map.get(target - nums[i]) != i)
                .mapToObj(i -> new int[] {i, map.get(target - nums[i])})
                .findFirst()
                .orElse(new int[0]);
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int target = 9;

        int[] result = twoSum(nums, target);

        System.out.println(Arrays.toString(result)); // 输出 [0, 1]
    }
}

在上述代码中,我们使用了 IntStream.range() 方法来遍历数组元素的下标,并使用 Collectors.toMap() 方法将对应的元素和下标存储到一个 Map 对象中。接着,我们使用 filter() 方法和 findFirst() 方法来查找符合条件的元素对,并将它们转换为包含两个下标的数组。

需要注意的是,在最终结果数组中,下标的顺序可能与之前提供的标准解决方案中的顺序不同,但它们仍然是符合条件的元素对的下标。

代码总计

package com.example.算法;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Solution {

    public static void main(String[] args) {


        int[] nums = {3, 2, 4};
        int target = 6;

        int[] result = twoSum(nums, target);

        System.out.println("输出的值1=>" + Arrays.toString(result));

        int[] result1 = twoSum1(nums, target);

        System.out.println("输出的值2=>" + Arrays.toString(result1));

        int[] result2 = twoSum2(nums, target);

        System.out.println("输出的值3=>" + Arrays.toString(result2));


        int[] result3 = twoSum3(nums, target);

        System.out.println("输出的值4=>" + Arrays.toString(result3));
    }


    public static int[] twoSum(int[] nums, int target) {

        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {

                if (nums[i] + nums[j] == target) {
                    System.out.println("i的值是==》" + i + "j的值是==》" + j);
                    System.out.println("---------------------------");
                    System.out.println("nums[i]的值是:" + nums[i] + "nums[j]的值是:" + nums[j] + ";target的值是=" + target);

                    int[] result = new int[2];
                    result[0] = i;
                    result[1] = j;
                    return result;
                }


            }

        }
        return new int[0];
    }

    /**
     * 在这个修改后的方法中,我们首先检查目标值是否等于两个下标都是 i 的元素之和。如果是,那么我们只需在后续的循环中查找另一个满足条件的元素即可。否则,我们可以按照之前回答的代码来查找符合条件的元素对。
     * <p>
     * 例如,在输入数组 {3, 2, 4} 和目标值 6 的情况下,下标为 0 的元素是 3,不等于目标值的一半。因此,我们直接进入第二个循环,在其中查找下标为 1 和 2 的元素之和等于目标值 6 的元素对。最终输出结果应该是 [1, 2]。
     *
     * @param nums
     * @param target
     * @return
     */
    public static int[] twoSum1(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == target / 2) { // 特殊情况:两个下标都是 i
                for (int j = i + 1; j < nums.length; j++) {
                    if (nums[j] == target / 2) {
                        int[] result = new int[2];
                        result[0] = i;
                        result[1] = j;
                        return result;
                    }
                }
            } else {
                for (int j = i + 1; j < nums.length; j++) {
                    if (nums[i] + nums[j] == target) {
                        int[] result = new int[2];
                        result[0] = i;
                        result[1] = j;
                        return result;
                    }
                }
            }
        }

        // 如果没有找到符合条件的元素对,返回空数组
        return new int[0];
    }


    /**
     * 在这个实现中,我们使用了一个 Map 对象来存储数组中的元素及其下标。具体来说,在每次遍历数组中的元素时,我们检查它的补数(即目标值与当前元素之差)是否已经存在于 Map 中。如果是,那么我们已经找到了符合条件的两个元素,可以直接返回它们的下标。
     * <p>
     * 如果补数不存在于 Map 中,则将当前元素及其下标添加到 Map 中,以便在查找后续元素时进行比较。
     *
     * @param nums
     * @param target
     * @return
     */
    public static int[] twoSum2(int[] nums, int target) {
        // 使用 Map 来存储数组中的元素及其下标
        Map<Integer, Integer> map = new HashMap<>();

        // 遍历数组中的所有元素
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];

            // 检查当前元素的补数是否已经存在于 Map 中
            if (map.containsKey(complement)) {
                // 如果已经存在,则返回它们的下标
                return new int[]{map.get(complement), i};
            }

            // 将当前元素及其下标添加到 Map 中
            map.put(nums[i], i);
        }

        // 如果没有找到符合条件的元素对,则返回空数组
        return new int[0];
    }

    /**
     * 就是把上面的写法换成lamdba的方式
     *
     * @param nums
     * @param target
     * @return
     */

    public static int[] twoSum3(int[] nums, int target) {
        // 使用 IntStream 和 Collectors.toMap() 方法来创建 Map

        //IntStream.range(0, nums.length) 方法生成一个包含从 0 到 nums.length - 1 (长度-1 就是下标的值)的整数序列的 IntStream 对象。然后,boxed() 方法将这个
        // IntStream 对象转换为一个装箱后的 Stream<Integer> 流。接着,Collectors.toMap() 方法将每个元素及其下标映射到一个键值对中,
        // 并返回一个 Map<Integer, Integer> 对象。
        Map<Integer, Integer> map = IntStream.range(0, nums.length)
                .boxed()
                .collect(Collectors.toMap(i -> nums[i], i -> i));

        // 使用 Stream API 来查找符合条件的元素对

        //接下来,我们可以使用 filter() 方法按照是否存在符合条件的元素对进行筛选
        //我们用 map.containsKey(target - nums[i]) 来判断目标值减去当前元素是否存在于 Map 中;用 map.get(target - nums[i]) != i 来判断得到的元素下标是否与当前元素下标相同,以避免重复计算。
        return IntStream.range(0, nums.length)
                .filter(i -> map.containsKey(target - nums[i]) && map.get(target - nums[i]) != i)
                .mapToObj(i -> new int[]{i, map.get(target - nums[i])})   //我们可以使用 mapToObj() 方法将其转换为包含两个下标的数组
                .findFirst()   //最后,我们可以使用 findFirst() 方法选择第一个符合条件的元素对,并使用 orElse(new int[0]) 方法在没有找到符合条件的元素对时返回一个空数组,如下所示:
                .orElse(new int[0]);
    }
}

运行结果为
在这里插入图片描述

总之,通过这篇博客,我们学习了如何使用哈希表求解两数之和(Two Sum)问题,并且介绍了如何使用 Java 8 的 Lambda 表达式和 Stream API 来实现这个算法。通过掌握这些技能,我们可以更加高效地解决这个问题,并在各种面试中展示出我们的编程能力。

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

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

相关文章

入行IC| 数字IC设计和验证哪个好?(内含薪资对比)

网上有一个很火的问题那就是数字IC设计和验证哪个好&#xff1f;接下来我们从以下几个维度来对比一下这两个岗位。 入行门槛 从上述对比就可以看出&#xff0c;IC设计要比验证门槛高。 具体工作内容有哪些&#xff1f; 数字IC前端设计 1.配合芯片架构的设计&#xff1b;一…

技术干货|直流电源自动测试系统功能介绍

直流电源是一种将交流电转换为恒定电压或电流输出的电子设备。在实际生产生活中&#xff0c;直流电源被广泛应用于各种场合。但由于各种原因&#xff0c;包括工艺、质量等因素&#xff0c;直流电源存在一定的出厂偏差。为了确保直流电源的精度和稳定性&#xff0c;在生产过程中…

layui框架学习(23:代码文本修饰模块)

Layui中的代码文本修饰模块layui.code主要用于修饰代码区域或文本行&#xff0c;其基本用法是使用预设类layui-code标识包含代码或文本的元素&#xff0c;然后调用layui.code函数渲染样式。Layui官网教程及示例中主要使用pre元素包含带修饰的代码或文本&#xff08;pre元素可定…

Myslq架构和原理

这里写自定义目录标题 Myslq体系架构连接层存储引擎 MYSQL原理单表访问连接查询&#xff08;原理&#xff09; Myslq体系架构 连接层 存储引擎 查看日志文件&#xff1a;show variables like ‘log_error’\G; BIN LOG 日志&#xff1a; show variables like ‘log_bin’; MYS…

Leetcode 622. 设计循环队列

文章目录 1.题目描述2.原题链接3.思路分析4.接口实现 &#xff1a;FrontRearenQueue(value):deQueue():isEmpty(): 检查循环队列是否为空isFull():myCircularQueueFree 5.代码实现 1.题目描述 设计你的循环队列实现。 循环队列是一种线性数据结构&#xff0c;其操作表现基于 FI…

16.泛型

泛型 一、什么是泛型 泛型的本质是参数化类型&#xff0c;即给类型指定一个参数&#xff0c;然后在使用时再指定此参数具体的值&#xff0c;那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中&#xff0c;分别被称为泛型类、泛型接口、泛型方法。 二…

【ONE·C++ || 模板进阶】

总言 主要介绍模板相关内容&#xff1a;非类型模板参数、类模板特化、模板的分离编译。 文章目录 总言1、非类型模板参数1.1、主要介绍1.2、std::array 简要说明 2、模板的特化2.1、基本介绍2.2、函数模板特化2.3、类模板特化2.3.1、基本说明2.3.2、用途举例2.3.3、分类&#…

统信UOS 20 安装达梦数据库V8

统信UOS 20 安装达梦数据库V8 1、安装教程2、启动数据库实例服务失败解决方法3、使用dm管理工具连接数据库 1、安装教程 https://blog.csdn.net/OceanWaves1993/article/details/129936878 此教程进行到启动数据库实例步骤时 使用下面命令启动数据库实例服务时&#xff0c;报…

找高清图片素材,这8个网站就够了

相信很多设计师、自媒体都为找素材而烦恼&#xff0c;很多朋友不知道去哪里找图片素材&#xff0c;找到了版权还不明确&#xff0c;怕造成侵权&#xff0c;今天我就把我独家珍藏的8个图片素材网站分享给大家&#xff0c;免费下载&#xff0c;还可以商用&#xff0c;建议收藏起来…

APIs -- DOM浏览器

1. Window对象 1.1 BOM&#xff08;浏览器对象模型&#xff09; BOM(Browser Object Model)是浏览器对象模型 window对象是一个全局对象&#xff0c;也可以说是JavaScript中的顶级对象像document、alert()、console.log()这些都是window的属性&#xff0c;基本BOM的属性和方…

crackme例子1

样本 jadx 静态分析 其中v2为查v5表得到&#xff0c;v3为用户输入index 下面就分别分析这几个值是啥 abcdefghddddd 得到v5和v4 解压assets下abcdefghddddd 拖入010 editor查看&#xff0c;实际是一个带相关数据的png文件 v5为图片位置89473开始&#xff0c;长度768字节&am…

Web安全 SQL注入漏洞测试.(可以 防止恶意用户利用漏洞)

Web安全 SQL注入漏洞测试 SQL注入就是 有些恶意用户在提交查询请求的过程中 将SQL语句插入到请求内容中&#xff0c;同时程序的本身对用户输入的内容过于相信&#xff0c;没有对用户插入的SQL语句进行任何的过滤&#xff0c;从而直接被SQL语句直接被服务端执行&#xff0c;导致…

AutoSAR软件组件开发的两类工作流程(Matlab/Simulink)

目录 前面 自顶向下 导入arxml文件 生成模型框架 搭建算法模型 生成代码 自下向上 前面 如何在Matlab进行AutoSAR软件组件SWC的开发&#xff1f;也就是下图红框标识出来的部分。 常规的有两种方式自顶向下与自下而上&#xff1a; 从上往下&#xff1a;从软件组件描述文…

改bug神器ChatGPT AI测试将取代人工吗?

最近ChatGPT大火&#xff0c;各大论坛中都会出现它的关键词。 机器和人对话本不是什么新鲜事&#xff0c;而ChatGPT上线仅5天&#xff0c;用户数量就超百万&#xff0c;之所以能在短时间吸引到这么多用户尝鲜&#xff0c;是因为它比“人工智障”的AI前辈们聪明多了~ 玩了一会…

Vue 3 组件通信

本文采用<script setup />的写法&#xff0c;比options API更自由。那么我们就来说说以下七种组件通信方式&#xff1a; props emit v-model refs provide/inject eventBus vuex/pinia 举个例子 本文将使用下面的演示&#xff0c;如下图所示&#xff1a; 上图中…

继承下的类型转换

一、私有/保护继承下的向上类型转换 示例&#xff1a; 图中蓝色、黄色代码均不允许使用&#xff0c;原因是在私有继承下&#xff0c;派生类和基类的public接口完全不搭界&#xff08;所实现的功能没有重叠&#xff09;&#xff0c;因此不允许强制转换&#xff0c;也无任何意义…

04-waf绕过权限控制

WAF绕过-权限控制之代码混淆及行为造轮子 思维导图 后门工具介绍: 菜刀&#xff0c;蚁剑&#xff0c;冰蝎优缺点 菜刀&#xff1a;未更新状态&#xff0c;无插件&#xff0c;单向加密传输 蚁剑&#xff1a;更新状态&#xff0c;有插件&#xff0c;拓展性强&#xff0c;单向加…

远程虚拟桌面解决方案 OpenText™ Exceed™ TurboX(ETX)的优势有哪些?

远程虚拟桌面解决方案 OpenText™ Exceed™ TurboX&#xff08;ETX&#xff09;的优势有哪些&#xff1f; 为 Windows、Linux 和 UNIX 实施精益、经济高效的虚拟化&#xff1b;提供完整的远程 Windows 可用性&#xff1b;以类似本地的性能远程工作&#xff1b;安全地保护系统和…

【Redis7】 Redis7 哨兵(重点:哨兵运行流程和选举原理)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 哨兵&#xff0c;重点&#xff1a;哨兵运行流程和选举原理。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 上一篇文…

第一章 webpack与构建发展简史

官方loader和插件 Loaders | webpack Plugins | webpack 为什么需要构建工具&#xff1f; 初识webpack webpack默认配置文件&#xff1a;webpack.config.js 可以通过webpack --config <config_file_name>指定配置文件 rules是个数组&#xff0c;一个打包配置可以有多…