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月前
|
Java API 数据处理
Java新特性:使用Stream API重构你的数据处理
Java新特性:使用Stream API重构你的数据处理
|
2月前
|
Java 大数据 API
Java Stream API:现代集合处理与函数式编程
Java Stream API:现代集合处理与函数式编程
242 100
|
2月前
|
Java API 数据处理
Java Stream API:现代集合处理新方式
Java Stream API:现代集合处理新方式
271 101
|
2月前
|
并行计算 Java 大数据
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
243 101
|
1月前
|
索引 容器
两数之和(每天刷力扣hot100系列)
本题要求在数组中找出两数之和等于目标值的下标。解法一为暴力枚举,时间复杂度O(N²),空间复杂度O(1);解法二利用哈希表,将查找时间降为O(1),总时间复杂度O(N),空间复杂度O(N),实现以空间换时间的优化。
|
2月前
|
安全 Java API
使用 Java 构建强大的 REST API 的四个基本技巧
本文结合探险领域案例,分享Java构建REST API的四大核心策略:统一资源命名、版本控制与自动化文档、安全防护及标准化异常处理,助力开发者打造易用、可维护、安全可靠的稳健API服务。
203 2
|
2月前
|
存储 数据可视化 Java
Java Stream API 的强大功能
Java Stream API 是 Java 8 引入的重要特性,它改变了集合数据的处理方式。通过声明式语法,开发者可以更简洁地进行过滤、映射、聚合等操作。Stream API 支持惰性求值和并行处理,提升了代码效率和可读性,是现代 Java 开发不可或缺的工具。
Java Stream API 的强大功能
|
3月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
3月前
|
安全 Java API
Java日期时间API:从Date到Java.time
本文深入解析了Java 8中引入的全新日期时间API,涵盖LocalDate、LocalTime、LocalDateTime、ZonedDateTime等核心类的使用,以及时间调整、格式化、时区处理和与旧API的互操作。通过实例对比,展示了新API在可变性、线程安全与易用性方面的显著优势,并提供迁移方案与实战技巧,助你掌握现代Java时间处理的最佳实践。