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

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

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]
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11
• 12
• 13
• 14
• 15
• 16
• 17
• 18
• 19

以下是使用 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 来实现这个算法。通过掌握这些技能,我们可以更加高效地解决这个问题,并在各种面试中展示出我们的编程能力。


相关文章
|
2月前
|
JSON Java API
【干货满满】分享拼多多API接口到手价,用Java语言实现
本方案基于 Java 实现调用拼多多开放平台商品详情 API,通过联盟接口获取商品到手价(含拼团折扣与优惠券),包含签名生成、HTTP 请求及响应解析逻辑,适用于电商比价、导购系统集成。
|
2月前
|
JSON Java API
【干货满满】分享京东API接口到手价,用Java语言实现
本示例使用 Java 调用京东开放平台商品价格及优惠信息 API,通过商品详情和促销接口获取到手价(含优惠券、满减等),包含签名生成、HTTP 请求及响应解析逻辑,适用于比价工具、电商系统集成等场景。
|
2月前
|
JSON Java API
【干货满满】分享淘宝API接口到手价,用Java语言实现
本文介绍了如何使用 Java 调用淘宝开放平台 API 获取商品到手价,涵盖依赖配置、签名生成、HTTP 请求与响应解析等核心实现步骤。
|
3月前
|
JSON JavaScript 前端开发
Python+JAVA+PHP语言,苏宁商品详情API
调用苏宁商品详情API,可通过HTTP/HTTPS发送请求并解析响应数据,支持多种编程语言,如JavaScript、Java、PHP、C#、Ruby等。核心步骤包括构造请求URL、发送GET/POST请求及解析JSON/XML响应。不同语言示例展示了如何获取商品名称与价格等信息,实际使用时请参考苏宁开放平台最新文档以确保兼容性。
|
7月前
|
缓存 监控 负载均衡
如何提升 API 性能:来自 Java 和测试开发者的优化建议
本文探讨了如何优化API响应时间,提升用户体验。通过缓存(如Redis/Memcached)、减少数据负载(REST过滤字段或GraphQL精确请求)、负载均衡(Nginx/AWS等工具)、数据压缩(Gzip/Brotli)、限流节流、监控性能(Apipost/New Relic等工具)、升级基础设施、减少第三方依赖、优化数据库查询及采用异步处理等方式,可显著提高API速度。快速响应的API不仅让用户满意,还能增强应用整体性能。
|
7月前
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
7月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
155 11
|
8月前
|
数据采集 存储 Java
Java爬虫获取微店店铺所有商品API接口设计与实现
本文介绍如何使用Java设计并实现一个爬虫程序,以获取微店店铺的所有商品信息。通过HttpClient发送HTTP请求,Jsoup解析HTML页面,提取商品名称、价格、图片链接等数据,并将其存储到本地文件或数据库中。文中详细描述了爬虫的设计思路、代码实现及注意事项,包括反爬虫机制、数据合法性和性能优化。此方法可帮助商家了解竞争对手,为消费者提供更全面的商品比较。
|
8月前
|
缓存 Java 应用服务中间件
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
java语言后台管理若依框架-登录提示404-接口异常-系统接口404异常如何处理-登录验证码不显示prod-api/captchaImage 404 (Not Found) 如何处理-解决方案优雅草卓伊凡
1350 5
|
10月前
|
JSON Java Apache
Java基础-常用API-Object类
继承是面向对象编程的重要特性,允许从已有类派生新类。Java采用单继承机制,默认所有类继承自Object类。Object类提供了多个常用方法,如`clone()`用于复制对象,`equals()`判断对象是否相等,`hashCode()`计算哈希码,`toString()`返回对象的字符串表示,`wait()`、`notify()`和`notifyAll()`用于线程同步,`finalize()`在对象被垃圾回收时调用。掌握这些方法有助于更好地理解和使用Java中的对象行为。
123 8

热门文章

最新文章