HashMap 两数之和java

明天又上java(⊙﹏⊙)...眼前一黑,好怕挂科啊!

1 题目:

/*
给定一个整数数组 nums 和一个整数目标值 target,
请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
 */

2 哈希表思路及算法

注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。

使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。

这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

3实现代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
            if (hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]), i};
            }
            hashtable.put(nums[i], i);
        }
        return new int[0];
    }
}

作者:力扣官方题解


4 复杂度分析

时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。

空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。

5 Integer是什么意思?

在 Java 里,Integer [ˈɪntɪdʒər] 是一个包装类。

其作用是将基本数据类型 int 封装成对象。Java 是一种面向对象的编程语言,不过为了提升性能,它也包含了 intdouble 等基本数据类型。为了能在需要对象的场景中使用这些基本数据类型,Java 为每个基本数据类型都设计了对应的包装类,Integer 就是 int 类型的包装类。

每一个基本数据类型 -------------->有对应的包装类

(为什么需要包装类?)

Integer 类的主要功能

  1. 基本数据类型与对象之间的转换

    • 借助 Integer 类,能够把 int 类型的值封装成 Integer 对象(装箱,Boxing)。
    • 也可以将 Integer 对象转换为 int 类型的值(拆箱,Unboxing)。
  2. 处理 int 类型时的工具方法

    • Integer 类提供了一些实用方法,例如将字符串转换为 int 类型的 parseInt() 方法。
    • 还有获取 int 类型的最大值和最小值等常量,像 Integer.MAX_VALUE 和 Integer.MIN_VALUE

常见用法示例

java

public class IntegerExample {
    public static void main(String[] args) {
        // 装箱:将 int 转换为 Integer
        int primitiveInt = 42;
        Integer wrappedInt = Integer.valueOf(primitiveInt); // 显式装箱
        Integer autoWrapped = primitiveInt; // 自动装箱(Java 5 及以后版本支持)

        // 拆箱:将 Integer 转换为 int
        int unboxedInt = wrappedInt.intValue(); // 显式拆箱
        int autoUnboxed = wrappedInt; // 自动拆箱(Java 5 及以后版本支持)

        // 字符串转换
        String numberStr = "123";
        int parsedInt = Integer.parseInt(numberStr); // 字符串转 int
        Integer wrappedParsed = Integer.valueOf(numberStr); // 字符串转 Integer

        // 常量使用
        System.out.println("int 的最大值: " + Integer.MAX_VALUE);
        System.out.println("int 的最小值: " + Integer.MIN_VALUE);
    }
}

自动装箱与自动拆箱

在 Java 5 及以后的版本中,引入了自动装箱(Autoboxing)和自动拆箱(Auto-unboxing)功能,这使得基本数据类型和包装类之间的转换变得更加简便,无需手动调用 valueOf() 和 intValue() 方法。

Integer 与 int 的区别

  • 类型方面int 是基本数据类型,而 Integer 是引用类型。
  • 空值处理方面int 不能为 null但 Integer 可以被赋值为 null,所以在需要表示 “无值” 的情况下,使用 Integer 会更合适。
  • 性能方面:由于 int 是基本数据类型,不涉及对象的创建和垃圾回收,因此在性能上要优于 Integer。所以在对性能要求较高且不需要使用对象特性时,建议使用 int

典型应用场景

  • 在泛型中使用,例如 List<Integer> 不能使用 List<int>
  • 在 JavaBean 中作为属性类型,这样可以利用 null 值来表示属性未被设置。
  • 在集合框架中存储整数。
  • 在需要调用 Integer 类的静态方法时使用,比如 parseInt()

理解 Integer 类有助于你在 Java 编程中更好地处理基本数据类型和对象之间的关系,特别是在涉及泛型、集合和空值处理的场景中。

 6 那么写成 Map<Integer,Integer>是什么意思?

Map<Integer, Integer> 是 Java 中使用泛型定义的一个映射(Map)接口,它表示这个映射存储的键(Key)和值(Value)都是 Integer 类型。简单来说,这是一个用于存储整数对的容器,其中每个键对应一个唯一的值,且两者都是整数类型。

关键概念解析

  1. Map 接口
    Map 是 Java 集合框架中的一个接口,用于存储键值对(Key-Value Pairs),特点是:

    • 键唯一:每个键只能对应一个值,重复的键会覆盖原有值。
    • 无序性:默认不保证元素的顺序(如 HashMap),但可通过 LinkedHashMap 或 TreeMap 实现有序。
  2. 泛型参数 <Integer, Integer>

    • 键类型Integer(存储整数作为键)。
    • 值类型Integer(存储整数作为值)。
      例如,键可以是学生的学号(整数),值可以是对应的分数(整数)。

常见用法示例

java

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

public class MapExample {
    public static void main(String[] args) {
        // 创建一个键值都是 Integer 的 Map
        Map<Integer, Integer> scoreMap = new HashMap<>();

        // 添加键值对
        scoreMap.put(1001, 95);  // 学号 1001 的分数是 95
        scoreMap.put(1002, 88);
        scoreMap.put(1003, 92);

        // 获取值
        int score = scoreMap.get(1001);  // 返回 95

        // 检查键是否存在
        boolean hasKey = scoreMap.containsKey(1002);  // true

        // 遍历 Map
        for (Map.Entry<Integer, Integer> entry : scoreMap.entrySet()) {
            int key = entry.getKey();
            int value = entry.getValue();
            System.out.println("学号: " + key + ", 分数: " + value);
        }
    }
}

常用实现类

  1. HashMap

    • 无序存储,基于哈希表实现,性能最优(平均时间复杂度 O(1))。
    • 允许 null 键和 null 值(但键只能有一个 null)。
  2. TreeMap

    • 按键的自然顺序(或自定义比较器)排序,基于红黑树实现。
    • 不允许 null 键。
  3. LinkedHashMap

    • 按插入顺序或访问顺序存储,基于哈希表 + 链表实现。
    • 保留插入顺序,性能略低于 HashMap

为什么使用 Integer 而不是 int

  • 泛型限制:Java 泛型要求使用引用类型(如 Integer),不能直接使用基本类型(如 int)。
  • 自动装箱 / 拆箱:Java 会自动转换 int 和 Integer,例如:

    java

    scoreMap.put(1001, 95);  // 自动将 int 95 装箱为 Integer
    int score = scoreMap.get(1001);  // 自动将 Integer 拆箱为 int
    

常见应用场景

  • 统计频率:例如统计数组中每个数字出现的次数。
  • 缓存数据:用 ID 作为键,缓存对应的数值结果。
  • 映射关系:如学生 ID 与成绩、商品 ID 与库存数量等。

注意事项

  1. 键的唯一性:重复的键会覆盖原有值。

    java

    scoreMap.put(1001, 95);
    scoreMap.put(1001, 98);  // 键 1001 的值被更新为 98
    
  2. 空指针异常:若键不存在,get(key) 返回 null,拆箱时可能引发 NullPointerException

    java

    Integer score = scoreMap.get(1004);  // 返回 null
    int s = score;  // 报错:NullPointerException
    

    建议使用 getOrDefault(key, defaultValue) 避免空值。

通过 Map<Integer, Integer>,你可以高效地管理整数键值对,适用于各种需要快速查找和存储数值关系的场景。

7 为什么不写<int,int>?

在 Java 中,泛型(如 Map<Integer, Integer>)要求使用引用类型(如 Integer),而不能直接使用基本类型(如 int)。这是由 Java 泛型的设计原则决定的,主要原因如下:

1. 泛型的类型擦除机制

Java 的泛型是通过类型擦除(Type Erasure)实现的。在编译时,泛型类型参数会被擦除为它们的上限类型(通常是 Object)。例如:

java

List<Integer> list = new ArrayList<>();
// 编译后等价于:
List list = new ArrayList();  // 泛型信息被擦除,只保留 Object

由于 Object 是所有引用类型的基类,但不是基本类型的基类,因此泛型无法直接使用 intdouble 等基本类型。

2. 基本类型与引用类型的差异

  • 基本类型(如 intchar):直接存储值,不继承自 Object,没有方法和属性。
  • 引用类型(如 IntegerString):是类的实例,继承自 Object,可以调用方法(如 Integer.parseInt())。

泛型设计的初衷是为了提供类型安全的集合操作,而集合框架(如 MapList)只能存储对象。因此,泛型要求使用引用类型。

3. 自动装箱与拆箱的折中方案

为了简化基本类型和引用类型的转换,Java 5 引入了自动装箱 / 拆箱(Autoboxing/Unboxing)机制:

java

Map<Integer, Integer> map = new HashMap<>();
map.put(1, 100);  // 自动装箱:int → Integer
int value = map.get(1);  // 自动拆箱:Integer → int

虽然底层仍需创建 Integer 对象,但语法上允许直接使用基本类型,掩盖了装箱 / 拆箱的开销。

4. 性能考虑

自动装箱 / 拆箱会带来一定的性能开销(如对象创建和垃圾回收)。在对性能要求极高的场景下,可以使用第三方库(如 TroveFastUtil)提供的原始类型集合,例如:

java

// Trove 库的原始类型 Map
TIntIntMap map = new TIntIntHashMap();
map.put(1, 100);  // 直接存储 int,无需装箱
int value = map.get(1);  // 直接返回 int

为什么不能直接支持 <int, int>

  • 类型系统限制:泛型的类型擦除机制与基本类型不兼容,无法将 int 擦除为 Object
  • 向后兼容性:Java 的泛型是在 JDK 5 引入的,为了兼容已有代码,设计时选择了类型擦除方案,导致无法直接支持基本类型。
  • 语法一致性:如果允许 <int, int>,需要为泛型单独设计两套语法规则(引用类型和基本类型),增加语言复杂度。

总结

  • 必须使用引用类型:Java 泛型要求类型参数必须是引用类型,因此 Map<int, int> 会编译错误。
  • 自动装箱 / 拆箱:通过 Integer 配合自动装箱 / 拆箱,可以在语法上简化基本类型的使用。
  • 性能优化:对性能敏感的场景可使用第三方库的原始类型集合。

例如,正确的写法是:

java

Map<Integer, Integer> map = new HashMap<>();  // ✅ 正确
// Map<int, int> map = new HashMap<>();      ❌ 错误:不支持基本类型

8 java为什么需要包装类? 

Java 引入包装类(如 IntegerDoubleBoolean 等)主要是为了在面向对象的环境中统一处理基本数据类型和对象,解决基本类型无法满足的场景需求。以下是包装类的核心作用及必要性:

1. 泛型系统的强制要求

Java 泛型(如 List<T>Map<K,V>)要求类型参数必须是引用类型,不能直接使用基本类型。包装类是基本类型的对象形式,因此是泛型的唯一选择:

java

// 正确:使用 Integer 包装类
List<Integer> list = new ArrayList<>();
list.add(10);  // 自动装箱 int → Integer

// 错误:不能直接使用基本类型
// List<int> list = new ArrayList<>(); ❌

2. 满足面向对象编程的需求

  • 统一 API 设计:包装类允许基本类型通过对象形式参与面向对象的操作,例如调用方法、实现接口:

    java

    Integer num = 10;
    String binary = num.toBinaryString();  // 调用对象方法
    Comparable<Integer> comp = num;        // 实现 Comparable 接口
    
  • 集合框架的兼容性:Java 集合(如 ArrayListHashMap)只能存储对象,包装类使基本类型可以存入集合:

    java

    List<Integer> numbers = new ArrayList<>();
    numbers.add(42);  // 自动装箱为 Integer
    

3. 支持 null 值表示缺失状态

基本类型(如 intboolean)有默认值(如 0false),无法表示 “无值” 状态。包装类可以赋值为 null,适用于需要显式表示缺失值的场景:

java

Integer score = null;  // 表示分数未设置
if (score != null) {
    System.out.println("分数: " + score);
} else {
    System.out.println("分数未知");
}

4. 提供实用工具方法

包装类包含静态方法和常量,用于基本类型的转换和操作:

java

// 字符串转整数
int num = Integer.parseInt("123");

// 最大值/最小值常量
System.out.println(Integer.MAX_VALUE);  // 2147483647

// 进制转换
String hex = Integer.toHexString(255);  // "ff"

5. 反射与序列化的需要

  • 反射机制:Java 反射只能操作对象,包装类使基本类型可以通过反射处理:

    java

    Class<?> intClass = Integer.TYPE;  // 获取 int 的 Class 对象
    
  • 序列化:需要将基本类型转换为对象以便在网络或文件中传输:

    java

    Integer num = 10;
    ObjectOutputStream oos = new ObjectOutputStream(...);
    oos.writeObject(num);  // 序列化 Integer 对象
    

6. 自动装箱与拆箱的便利性

Java 5 引入的自动装箱 / 拆箱机制(Autoboxing/Unboxing)掩盖了基本类型和包装类的转换细节,简化了代码:

java

Integer wrapper = 10;  // 自动装箱:int → Integer
int primitive = wrapper;  // 自动拆箱:Integer → int

但需注意避免空指针异常(NullPointerException):

java

Integer nullable = null;
int value = nullable;  // 报错:NullPointerException

性能权衡

虽然包装类提供了灵活性,但对象创建和垃圾回收会带来性能开销。在性能敏感的场景(如大量数据计算),建议优先使用基本类型:

java

// 基本类型数组(性能优)
int[] arr = new int[1000];

// 包装类集合(性能略低)
List<Integer> list = new ArrayList<>(1000);

总结

Java 的包装类是为了填补基本类型在面向对象系统中的不足:

  • 泛型支持:使基本类型能参与泛型操作。
  • 对象特性:提供方法调用、null 值支持等。
  • 工具功能:包含类型转换、进制处理等实用方法。
  • 系统兼容性:适配反射、序列化等需要对象的场景。

自动装箱 / 拆箱机制平衡了代码简洁性和类型安全,但在性能关键场景需谨慎使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值