md_files/Java/力扣Hot 100题.md
2025-03-21 13:56:48 +08:00

24 KiB
Raw Blame History

力扣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
    

Collections 类本身没有直接提供类似 Arrays.equals 的方法来比较两个集合的内容是否相等。不过Java 中的集合类(如 ListSetMap)已经实现了 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) 方法返回从 beginIndexendIndex - 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}
        }
    }
    

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
        }
    }
    

二维数组

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(); // 换行,便于输出格式化
    }
}
import java.util.ArrayList;
import java.util.List;

int rows = 3;
int cols = 3;
List<List<Integer>> list = new ArrayList<>();


for (List<Integer> row : list) {
    for (int num : row) {
        System.out.print(num + " ");
    }
    System.out.println(); // 换行
}
for (int i = 0; i < list.size(); i++) {
    List<Integer> 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)
  • 常用方法

    1. add(E e) / offer(E e)
      • 将元素插入队列。
      • add 在队列满时会抛出异常,offer 返回 false
    2. remove() / poll()
      • 移除并返回队首元素。
      • remove 在队列为空时会抛出异常,poll 返回 null
    3. element() / peek()
      • 查看队首元素,但不移除。
      • element 在队列为空时会抛出异常,peek 返回 null
    4. size()
      • 返回队列中的元素数量。
    5. isEmpty()
      • 检查队列是否为空。
    6. 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
        }
    }
    

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);
    }
}

Deque(双端队列+栈)

支持在队列的两端(头和尾)进行元素的插入和删除。这使得 Deque 既能作为队列FIFO又能作为栈LIFO使用。

建议在需要栈操作时使用 Deque 的实现

Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);   // 入栈
Integer top1=stack.peek()
Integer top = stack.pop();   // 出栈

双端队列

在队头操作

  • addFirst(E e):在队头添加元素,如果操作失败会抛出异常。
  • offerFirst(E e):在队头插入元素,返回 truefalse 表示是否成功。
  • peekFirst():查看队头元素,不移除;队列为空返回 null
  • removeFirst():移除并返回队头元素;队列为空会抛出异常。
  • pollFirst():移除并返回队头元素;队列为空返回 null

在队尾操作

  • addLast(E e):在队尾添加元素,若失败会抛出异常。
  • offerLast(E e):在队尾插入元素,返回 truefalse 表示是否成功。
  • 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<Integer> 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

  • HashMapHashSetArrayListPriorityQueue 都实现了 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<Integer> 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<Integer> 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<Person> 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)。