24 KiB
力扣Hot 100题
杂项
- 最大值:
Integer.MAX_VALUE
- 最小值:
Integer.MIN_VALUE
数组集合比较
Arrays.equals(array1, array2)
-
用于比较两个数组是否相等(内容相同)。
-
支持多种类型的数组(如
int[]
、char[]
、Object[]
等)。 -
int[] arr1 = {1, 2, 3}; int[] arr2 = {1, 2, 3}; boolean isEqual = Arrays.equals(arr1, arr2); // true ```text
Collections
类本身没有直接提供类似 Arrays.equals
的方法来比较两个集合的内容是否相等。不过,Java 中的集合类(如 List
、Set
、Map
)已经实现了 equals
方法
-
List<Integer> list1 = Arrays.asList(1, 2, 3); List<Integer> list2 = Arrays.asList(1, 2, 3); List<Integer> list3 = Arrays.asList(3, 2, 1); System.out.println(list1.equals(list2)); // true System.out.println(list1.equals(list3)); // false(顺序不同)
要实现接口自定义排序,必须实现 Comparator<T>
接口的 compare(T o1, T o2)
方法。
Comparator
接口中定义的 compare(T o1, T o2)
方法返回一个整数(非布尔值!!),这个整数的正负意义如下:
- 如果返回负数,说明
o1
排在o2
前面。 - 如果返回零,说明
o1
等于o2
。 - 如果返回正数,说明
o1
排在o2
后面。
public class TestComparator {
// 定义一个升序排序的 Comparator
static Comparator<Integer> ascComparator = new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b; // 如果 a < b, 则返回负数
}
};
public static void main(String[] args) {
// 创建一个整数列表
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(3);
numbers.add(8);
numbers.add(1);
numbers.add(9);
numbers.add(2);
// 使用 Collections.sort 进行排序,并指定 Comparator
Collections.sort(numbers, ascComparator);
// 输出排序后的列表
System.out.println("排序后的列表: " + numbers);
}
}
假设有两个参数a=3,b=5,那么返回负数,表示第一个参数a排在第二个参数b前面,因此是升序;
自定义比较器排序二维数组 用Lambda表达式实现Comparator<int[]>接口
import java.util.Arrays;
public class IntervalSort {
public static void main(String[] args) {
int[][] intervals = { {1, 3}, {2, 6}, {8, 10}, {15, 18} };
// 自定义比较器,先比较第一个元素,如果相等再比较第二个元素
Arrays.sort(intervals, (a, b) -> {
if (a[0] != b[0]) {
return a[0] - b[0];
} else {
return a[1] - b[1];
}
});
// 输出排序结果
for (int[] interval : intervals) {
System.out.println(Arrays.toString(interval));
}
}
}
逻辑比较
boolean flag = false;
if (!flag) { //! 是 Java 中的逻辑非运算符,只能用于对布尔值取反。
System.out.println("flag 是 false");
}
if (flag == false) { //更常用!
System.out.println("flag 是 false");
}
//java中没有 if(not flag) 这种写法!
常用数据结构
String
子串:字符串中连续的一段字符。
子序列:字符串中按顺序选取的一段字符,可以不连续。
异位词:字母相同、字母频率相同、顺序不同,如"listen"
和 "silent"
排序:
需要String先转为char [] 数组,排序好之后再转为String类型!!
char[] charArray = str.toCharArray();
Arrays.sort(charArray);
String sortedStr = new String(charArray);
取字符:
charAt(int index)
方法返回指定索引处的char
值。char
是基本数据类型,占用 2 个字节,表示一个 Unicode 字符。- HashSet set = new HashSet();
取子串:
substring(int beginIndex, int endIndex)
方法返回从beginIndex
到endIndex - 1
的子字符串。- 返回的是
String
类型,即使子字符串只有一个字符。
HashMap
-
基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。
-
不保证元素的顺序。
-
import java.util.HashMap; import java.util.Map; public class HashMapExample { public static void main(String[] args) { // 创建 HashMap Map<String, Integer> map = new HashMap<>(); // 添加键值对 map.put("apple", 10); map.put("banana", 20); map.put("orange", 30); // 获取值 int appleCount = map.get("apple"); //如果获取不存在的元素,返回null System.out.println("Apple count: " + appleCount); // 输出 10 // 遍历 HashMap for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } // 输出: // apple: 10 // banana: 20 // orange: 30 // 检查是否包含某个键 boolean containsBanana = map.containsKey("banana"); System.out.println("Contains banana: " + containsBanana); // 输出 true // 删除键值对 map.remove("orange"); //删除不存在的元素也不会报错 System.out.println("After removal: " + map); // 输出 {apple=10, banana=20} } } ```text
ArrayList
-
基于数组实现,支持动态扩展。
-
访问元素的时间复杂度为 O(1),在末尾插入和删除的时间复杂度为 O(1)。
-
在指定位置插入和删除O(n)
add(int index, E element)
remove(int index)
-
import java.util.ArrayList; import java.util.List; public class ArrayListExample { public static void main(String[] args) { // 创建 ArrayList List<Integer> list = new ArrayList<>(); // 添加元素 list.add(10); list.add(20); list.add(30); int size = list.size(); // 获取列表大小 System.out.println("Size of list: " + size); // 输出 3 // 获取元素 int firstElement = list.get(0); System.out.println("First element: " + firstElement); // 输出 10 // 修改元素 list.set(1, 25); // 将第二个元素改为 25 System.out.println("After modification: " + list); // 输出 [10, 25, 30] // 遍历 ArrayList for (int num : list) { System.out.println(num); } // 输出: // 10 // 25 // 30 // 删除元素 list.remove(2); // 删除第三个元素 System.out.println("After removal: " + list); // 输出 [10, 25] } }
数组(Array)
数组是一种固定长度的数据结构,用于存储相同类型的元素。数组的特点包括:
-
固定长度:数组的长度在创建时确定,无法动态扩展。
-
快速访问:通过索引访问元素的时间复杂度为 O(1)。
-
连续内存:数组的元素在内存中是连续存储的。
-
public class ArrayExample { public static void main(String[] args) { // 创建数组 int[] array = new int[5]; // 创建一个长度为 5 的整型数组 // 添加元素 array[0] = 10; array[1] = 20; array[2] = 30; array[3] = 40; array[4] = 50; // 获取元素 int firstElement = array[0]; System.out.println("First element: " + firstElement); // 输出 10 // 修改元素 array[1] = 25; // 将第二个元素改为 25 System.out.println("After modification:"); for (int num : array) { System.out.println(num); } // 输出: // 10 // 25 // 30 // 40 // 50 // 遍历数组 System.out.println("Iterating through array:"); for (int i = 0; i < array.length; i++) { System.out.println("Index " + i + ": " + array[i]); } // 输出: // Index 0: 10 // Index 1: 25 // Index 2: 30 // Index 3: 40 // Index 4: 50 // 删除元素(数组长度固定,无法直接删除,可以通过覆盖实现) int indexToRemove = 2; // 要删除的元素的索引 for (int i = indexToRemove; i < array.length - 1; i++) { array[i] = array[i + 1]; // 将后面的元素向前移动 } array[array.length - 1] = 0; // 最后一个元素置为 0(或其他默认值) System.out.println("After removal:"); for (int num : array) { System.out.println(num); } // 输出: // 10 // 25 // 40 // 50 // 0 // 数组长度 int length = array.length; System.out.println("Array length: " + length); // 输出 5 } } ```text
二维数组
int rows = 3;
int cols = 3;
int[][] array = new int[rows][cols];
// 填充数据
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j + 1;
}
}
//创建并初始化
int[][] array = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 遍历二维数组,不知道几行几列
public void setZeroes(int[][] matrix) {
// 遍历每一行
for (int i = 0; i < matrix.length; i++) {
// 遍历当前行的每一列
for (int j = 0; j < matrix[i].length; j++) {
// 这里可以处理 matrix[i][j] 的元素
System.out.print(matrix[i][j] + " ");
}
System.out.println(); // 换行,便于输出格式化
}
}
```text
import java.util.ArrayList; import java.util.List;
int rows = 3; int cols = 3; List<List> list = new ArrayList<>();
for (List row : list) { for (int num : row) { System.out.print(num + " "); } System.out.println(); // 换行 } for (int i = 0; i < list.size(); i++) { List row = list.get(i); for (int j = 0; j < row.size(); j++) { System.out.print(row.get(j) + " "); } System.out.println(); // 换行 }
**如果事先不知道数组的大小呢?**
List<int[]> merged = new ArrayList<>();
merged.add(current);
return merged.toArray(new int[merged.size()][]);
#### **`HashSet`**
- 基于哈希表实现,查找、插入和删除的平均时间复杂度为 O(1)。
- 不保证元素的顺序!!因此不太用iterator迭代,而是用contains判断是否有xx元素。
- ```
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
// 创建 HashSet
Set<Integer> set = new HashSet<>();
// 添加元素
set.add(10);
set.add(20);
set.add(30);
set.add(10); // 重复元素,不会被添加
// 检查元素是否存在
boolean contains20 = set.contains(20);
System.out.println("Contains 20: " + contains20); // 输出 true
// 遍历 HashSet
for (int num : set) {
System.out.println(num);
}
// 输出:
// 20
// 10
// 30
// 删除元素
set.remove(20);
System.out.println("After removal: " + set); // 输出 [10, 30]
}
}
PriorityQueue
-
基于优先堆(最小堆或最大堆)实现,元素按优先级排序。
-
默认是最小堆,即队首元素是最小的。
-
支持自定义排序规则,通过
Comparator
实现。 -
常用操作的时间复杂度:
- 插入元素:
O(log n)
- 删除队首元素:
O(log n)
- 查看队首元素:
O(1)
- 插入元素:
-
常用方法
add(E e)
/offer(E e)
:- 将元素插入队列。
add
在队列满时会抛出异常,offer
返回false
。
remove()
/poll()
:- 移除并返回队首元素。
remove
在队列为空时会抛出异常,poll
返回null
。
element()
/peek()
:- 查看队首元素,但不移除。
element
在队列为空时会抛出异常,peek
返回null
。
size()
:- 返回队列中的元素数量。
isEmpty()
:- 检查队列是否为空。
clear()
:- 清空队列。
-
import java.util.PriorityQueue; import java.util.Comparator; public class PriorityQueueExample { public static void main(String[] args) { // 创建 PriorityQueue(默认是最小堆) PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // 添加元素 minHeap.add(10); minHeap.add(20); minHeap.add(30); minHeap.add(5); // 查看队首元素 System.out.println("队首元素: " + minHeap.peek()); // 输出 5 // 遍历 PriorityQueue(注意:遍历顺序不保证有序) System.out.println("遍历 PriorityQueue:"); for (int num : minHeap) { System.out.println(num); } // 输出: // 5 // 10 // 30 // 20 // 移除队首元素 System.out.println("移除队首元素: " + minHeap.poll()); // 输出 5 // 再次查看队首元素 System.out.println("队首元素: " + minHeap.peek()); // 输出 10 // 创建最大堆(通过自定义 Comparator) PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); maxHeap.add(10); maxHeap.add(20); maxHeap.add(30); maxHeap.add(5); // 查看队首元素 System.out.println("最大堆队首元素: " + maxHeap.peek()); // 输出 30 // 清空队列 minHeap.clear(); System.out.println("队列是否为空: " + minHeap.isEmpty()); // 输出 true } } ```text
Queue
队尾插入,队头取!``
import java.util.Queue;
import java.util.LinkedList;
public class QueueExample {
public static void main(String[] args) {
// 创建一个队列
Queue<Integer> queue = new LinkedList<>();
// 添加元素到队列中
queue.add(10); // 使用 add() 方法添加元素
queue.offer(20); // 使用 offer() 方法添加元素
queue.add(30);
System.out.println("队列内容:" + queue);
// 查看队头元素,不移除
int head = queue.peek();
System.out.println("队头元素(peek): " + head);
// 移除队头元素
int removed = queue.poll();
System.out.println("移除的队头元素(poll): " + removed);
System.out.println("队列内容:" + queue);
// 再次移除队头元素
int removed2 = queue.remove();
System.out.println("移除的队头元素(remove): " + removed2);
System.out.println("队列内容:" + queue);
}
}
```text
#### `Deque`(双端队列+栈)
支持在队列的两端(头和尾)进行元素的插入和删除。这使得 Deque 既能作为队列(FIFO)又能作为栈(LIFO)使用。
建议在需要栈操作时使用 `Deque` 的实现
**栈**
Deque stack = new ArrayDeque<>(); stack.push(1); // 入栈 Integer top1=stack.peek() Integer top = stack.pop(); // 出栈
**双端队列**
**在队头操作**
- `addFirst(E e)`:在队头添加元素,如果操作失败会抛出异常。
- `offerFirst(E e)`:在队头插入元素,返回 `true` 或 `false` 表示是否成功。
- `peekFirst()`:查看队头元素,不移除;队列为空返回 `null`。
- `removeFirst()`:移除并返回队头元素;队列为空会抛出异常。
- `pollFirst()`:移除并返回队头元素;队列为空返回 `null`。
**在队尾操作**
- `addLast(E e)`:在队尾添加元素,若失败会抛出异常。
- `offerLast(E e)`:在队尾插入元素,返回 `true` 或 `false` 表示是否成功。
- `peekLast()`:查看队尾元素,不移除;队列为空返回 `null`。
- `removeLast()`:移除并返回队尾元素;队列为空会抛出异常。
- `pollLast()`:移除并返回队尾元素;队列为空返回 `null`。
**添加元素**:调用 `add(e)` 或 `offer(e)` 时,实际上是调用 `addLast(e)` 或 `offerLast(e)`,即在**队尾**添加元素。
**删除或查看元素**:调用 `remove()` 或 `poll()` 时,则是调用 `removeFirst()` 或 `pollFirst()`,即在队头移除元素;同理,`element()` 或 `peek()` 则是查看队头元素。
import java.util.Deque; import java.util.LinkedList;
public class DequeExample { public static void main(String[] args) { // 使用 LinkedList 实现双端队列 Deque deque = new LinkedList<>();
// 在队列头部添加元素
deque.addFirst(10);
// 在队列尾部添加元素
deque.addLast(20);
// 在队列头部插入元素
deque.offerFirst(5);
// 在队列尾部插入元素
deque.offerLast(30);
System.out.println("双端队列内容:" + deque);
// 查看队头和队尾元素,不移除
int first = deque.peekFirst();
int last = deque.peekLast();
System.out.println("队头元素:" + first);
System.out.println("队尾元素:" + last);
// 从队头移除元素
int removedFirst = deque.removeFirst();
System.out.println("移除队头元素:" + removedFirst);
// 从队尾移除元素
int removedLast = deque.removeLast();
System.out.println("移除队尾元素:" + removedLast);
System.out.println("双端队列最终内容:" + deque);
}
}
#### `Iterator`
- **`HashMap`、`HashSet`、`ArrayList` 和 `PriorityQueue`** 都实现了 `Iterable` 接口,支持 `iterator()` 方法。
`Iterator` 接口中包含以下主要方法:
1. `hasNext()`:如果迭代器还有下一个元素,则返回 `true`,否则返回 `false`。
2. `next()`:返回迭代器的下一个元素,并将迭代器移动到下一个位置。
3. `remove()`:从迭代器当前位置删除元素。该方法是可选的,不是所有的迭代器都支持。
import java.util.ArrayList; import java.util.Iterator;
public class Main { public static void main(String[] args) { // 创建一个 ArrayList 集合 ArrayList list = new ArrayList<>(); list.add(1); list.add(2); list.add(3);
// 获取集合的迭代器
Iterator<Integer> iterator = list.iterator();
// 使用迭代器遍历集合并输出元素
while (iterator.hasNext()) {
Integer element = iterator.next();
System.out.println(element);
}
}
}
### 排序
排序时间复杂度:O(nlog(n))
求最大值:O(n)
#### **数组排序**
import java.util.Arrays;
public class ArraySortExample { public static void main(String[] args) { int[] numbers = {5, 2, 9, 1, 5, 6}; Arrays.sort(numbers); // 对数组进行排序 System.out.println(Arrays.toString(numbers)); // 输出 [1, 2, 5, 5, 6, 9] } }
#### 集合排序
import java.util.ArrayList; import java.util.Collections; import java.util.List;
public class ListSortExample { public static void main(String[] args) { List numbers = new ArrayList<>(); numbers.add(5); numbers.add(2); numbers.add(9); numbers.add(1); numbers.add(5); numbers.add(6);
Collections.sort(numbers); // 对 List 进行排序
System.out.println(numbers); // 输出 [1, 2, 5, 5, 6, 9]
}
}
#### **自定义排序**
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List;
class Person { String name; int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class ComparatorSortExample { public static void main(String[] args) { List people = new ArrayList<>(); people.add(new Person("Alice", 25)); people.add(new Person("Bob", 20)); people.add(new Person("Charlie", 30));
// 使用 Comparator 按姓名排序
Collections.sort(people, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.name.compareTo(p2.name); // 按姓名升序排序
}
});
System.out.println(people); // 输出 [Alice (25), Bob (20), Charlie (30)]
}
}
### 题型
#### 哈希
**问题分析**:
- 确定是否需要快速查找或存储数据。
- 判断是否需要统计元素频率或检查元素是否存在。
**适用场景**
1. **快速查找**:
- 当需要频繁查找元素时,哈希表可以提供 O(1) 的平均时间复杂度。
2. **统计频率**:
- 统计元素出现次数时,哈希表是常用工具。
3. **去重**:
- 需要去除重复元素时,`HashSet` 可以有效实现。
#### 双指针
1. 问题分析:
- 确定问题是否涉及数组或链表的遍历。
- 判断是否需要通过两个指针的协作来缩小搜索范围或比较元素。
2. 选择双指针类型:
- **同向双指针**:两个指针从同一侧开始移动,通常用于**滑动窗口**或链表问题。
- **对向双指针**:两个指针从两端向中间移动,通常用于有序数组或回文问题。重点是考虑**移动哪个指针**可能优化结果!!!
- **快慢指针**:两个指针以不同速度移动,通常用于链表中的环检测或中点查找。
3. 适用场景
**有序数组的两数之和**:
- 在对向双指针的帮助下,可以在 O(n) 时间内找到两个数,使它们的和等于目标值。
**滑动窗口**:
- 用于解决**子数组或子字符串**问题,如同向双指针可以在 O(n) 时间内找到满足条件的最短或最长子数组。
**链表中的环检测**:
- 快慢指针可以用于检测链表中是否存在环,并找到环的起点。
**回文问题**:
- 对向双指针可以用于判断字符串或数组是否是回文。
**合并有序数组或链表**:
- 双指针可以用于合并两个有序数组或链表,时间复杂度为 O(n)。
### 前缀和
1. **前缀和的定义**
定义前缀和 `preSum[i]` 为数组 `nums` 从索引 0 到 i 的元素和,即
$$
\text{preSum}[i] = \sum_{j=0}^{i} \text{nums}[j]
$$
2. **子数组和的关系**
对于任意子数组 `nums[i+1..j]`(其中 `0 ≤ i < j < n`),其和可以表示为
$$
\text{sum}(i+1,j) = \text{preSum}[j] - \text{preSum}[i]
$$
当这个子数组的和等于 k 时,有
$$
\text{preSum}[j] - \text{preSum}[i] = k
$$
即
$$
\text{preSum}[i] = \text{preSum}[j] - k
$$
$\text{preSum}[j] - k$表示 "以当前位置结尾的子数组和为k"
3. **利用哈希表存储前缀和**
我们可以使用一个哈希表 `prefix` 来存储每个**前缀和**出现的**次数**。
- 初始时,`prefix[0] = 1`,表示前缀和为 0 出现一次(对应空前缀)。
- 遍历数组,每计算一个新的前缀和 `preSum`,就查看 `preSum - k` 是否在哈希表中。如果存在,则说明之前有一个前缀和等于 `preSum - k`,那么从该位置后一个位置到**当前索引**的子数组和为 k,累加其出现的次数。
4. **时间复杂度**
该方法只需要遍历数组一次,时间复杂度为 O(n)。