算法知识、常用方法总结

一、算法常识

ASCII码

1-9 : 49 - 57; A-Z : 65 - 90; a-z : 97 - 122

小写 = 大写 + 32; 大写 = 小写 - 32

Integer最大/最小值

Integer.MAX_VALUE = 2147483647 Integer.MIN_VALUE = -2147483648

位运算符

& 按位与 有0则0,全1才1

| 按位或 有1则1,全0才0

^ 按位异或 相同为0,不同为1

~ 按位取反 全部位0变1、1变0

<< 左移 左移一位,等价于乘以2

>> 右移(带符号) 右移一位,等价于除以2向下取整

>>> 无符号右移(Java 特有) 无证正负,左边一律补零

二、算法类型思路

1. 动态规划

(1)定义dp数组,明确数组下标的含义

(2)明确每个元素的各种状态,推出递推公式

(3)dp数组如何初始化,如何遍历顺序

在这个基础上可以通过维护一些参数取代dp数组,降低空间复杂度

2. 贪心算法

核心:每一步都选当前最优,局部最优最终得到全局最优

(1)设计贪心策略(每一步怎么选最优)

(2)局部最优累加

(3)验证是否可行

3. 回溯算法

步骤:递归+循环,可通过剪枝优化

三、常用数据结构

1. 数组(Array)

数组是最基本的数据结构,大部分题目都会涉及。

常用方法如下:

复制代码
int[] arr = {3, 1, 4, 1, 5};

// 填充
Arrays.fill(arr, 0);  // 全部赋值 0

// 拷贝
int[] copy = Arrays.copyOf(arr, arr.length);

// 二分查找(需要升序数组, 不存在就返回负数)
int idx = Arrays.binarySearch(arr, 4);

一维数组排序:

复制代码
// 整型排序
int[] nums = {3, 1, 2};
Arrays.sort(nums); // 升序

// 局部排序
Arrays.sort(arr, 0, 3); 

// 字符排序
char[] chars = {'r', 'a', 'b'};
Arrays.sort(chars); // 升序

// 降序排序(对象类型)
Integer[] nums2 = {3, 1, 2};
Arrays.sort(nums2, (a, b) -> b - a);

二维数组排序:

复制代码
// 按第 0 列升序
Arrays.sort(arr, Comparator.comparingInt(a -> a[0]));


// 按第 0 列降序
Arrays.sort(arr, (a, b) -> Integer.compare(b[0], a[0]));

2. 字符串(String / StringBuilder)

算法中字符串处理也很多,尤其是子串、回文、哈希等。

常用方法如下:

复制代码
String s = "hello";

// 转数组
char[] chars = s.toCharArray();

// 截取
String sub = s.substring(1, 3); // "el"

// 查找
s.indexOf("ll");  // 2
s.contains("he"); // true

// 替换
s.replace("l", "x"); // hexxo

// 拼接(效率高用 StringBuilder)
StringBuilder sb = new StringBuilder();
sb.append("a").append("b");
sb.toString();

3. 集合框架(Collections)

(1)List
复制代码
List<Integer> list = new ArrayList<>();
list.add(1); list.add(2);
list.get(0);        // 1
list.remove(1);     // 按索引删
list.contains(2);   // false
Collections.sort(list); // 排序
(2)Set
  • 特点:不重复,适合去重、查找是否存在

    Set<Integer> set = new HashSet<>();
    set.add(1); set.add(2); set.add(2); // 只会保留一个 2
    set.contains(1); // true
    set.remove(1); // 删除

(3)Map
  • 键值对存储,查找效率高

    Map<Character, Integer> map = new HashMap<>();
    map.put('a', 1);
    map.get('a'); // 1
    map.containsKey('b'); // false
    map.remove('a');


4. 栈 & 队列

算法题常考栈和队列(括号匹配、BFS/DFS、单调栈、滑动窗口)。

复制代码
// 栈
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.pop();
stack.peek();  // 栈顶

// 队列
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.poll();  // 出队
queue.peek();  // 队首

// 双端队列(单调队列用得多)
Deque<Integer> deque = new LinkedList<>();
deque.offerFirst(1);
deque.offerLast(2);
deque.pollFirst();
deque.pollLast();

(1) 优先队列(堆)
  • 优先队列:出队顺序由元素优先级(通常用最小/最大堆实现)决定,而不是按入队顺序。

  • Java 提供 PriorityQueue<E>,底层是 最小堆(默认升序)。

  • 常用于 堆排序、TopK 问题、Dijkstra 最短路、合并 K 个有序链表 等。

    PriorityQueue<Integer> pq = new PriorityQueue<>(); // 默认小根堆
    pq.offer(3);
    pq.offer(1);
    pq.offer(2);
    pq.poll(); // 1

    // 大根堆
    PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);

✅ 时间复杂度

  • 插入:O(log n) (堆调整)

  • 删除(poll):O(log n)

  • 获取堆顶(peek):O(1)

  • TopK 问题中,每次操作堆大小为 K,复杂度 O(n log k)

(2) 双端队列
  • 双端队列(Deque,Double Ended Queue):队头和队尾都可以插入、删除。

  • Java 提供接口 Deque<E>,常用实现类有:ArrayDeque(推荐,效率高)、LinkedList

    Deque<Integer> deque = new ArrayDeque<>();
    deque.addFirst(1); // 队头加
    deque.addLast(2); // 队尾加
    deque.removeFirst(); // 队头删
    deque.removeLast(); // 队尾删

✅ 时间复杂度

  • 插入 / 删除:O(1)

  • 取队头/队尾元素:O(1)

  • 滑动窗口最大值 算法中,每个元素进出队列最多一次,总复杂度 O(n)

四、常用方法

字符串

判断回文字符串

双指针法

复制代码
public static boolean isPalindrome(String s) {
        if (s == null) return false;
        int left = 0, right = s.length() - 1;
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }

🟢 特点:时间复杂度:O(n) 空间复杂度:O(1)

dp数组

复制代码
boolean[][] dp = new boolean[n][n];
        for (int right = 0; right < n; right++) {
            for (int left = 0; left <= right; left++) {
                if (s.charAt(left) == s.charAt(right) &&
                   (right - left <= 2 || dp[left + 1][right - 1])) {
                    dp[left][right] = true;
                }
            }
        }

要是需要多次判断,使用dp数组预处理,降低整体时间复杂度

数组

数组中两数转换
复制代码
private void swap(int[] nums, int a, int b) {
        int ans = nums[a];
        nums[a] = nums[b];
        nums[b] = ans;
    }
数组反转
复制代码
private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            // 交换 nums[start] 和 nums[end]
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            
            // 移动指针
            start++;
            end--;
        }
矩阵数组基于主对角线反转

就是把元素 (i, j)(j, i) 交换

复制代码
public static void reverseMainDiagonal(int[][] matrix) {
    int n = matrix.length;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
    }
}
矩阵数组基于副对角线反转

副对角线下标特点:i + j = n - 1,要交换 (i, j)(n-1-j, n-1-i)

复制代码
public static void reverseSecondaryDiagonal(int[][] matrix) {
    int n = matrix.length;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n - i; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[n - 1 - j][n - 1 - i];
            matrix[n - 1 - j][n - 1 - i] = temp;
        }
    }
}
原地哈希法

将数组中的元素和数组索引按照某种规则一一对应。比如将数组中的元素 i 存储到nums[i - 1]中,空间复杂度为常数级别,可解决缺失的第一个正数问题

复制代码
public int method(int[] nums) {
        int n = nums.length;
        
        // Step 1: 原地哈希排序,把每个数放到正确的位置 nums[i] -> nums[nums[i] - 1]
        for (int i = 0; i < n; i++) {
            while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1]) {
                swap(nums, i, nums[i] - 1);
            }
        }
    }
二分查找算法
复制代码
public int binarySearch(int[] nums, int target) {
        int left = 0, right = nums.length - 1;

        while (left <= right) {
            int mid = left + (right - left) / 2; // 避免溢出
            if (nums[mid] == target) {
                return mid;  // 找到目标
            } else if (nums[mid] < target) {
                left = mid + 1;  // 目标在右边
            } else {
                right = mid - 1; // 目标在左边
            }
        }
        return -1; // 未找到
    }

链表

反转链表

定义三个指针,每次迭代将 curr.next 指向 prev,然后三个指针往后移动,直到 curr 为 null。

复制代码
public ListNode reverseList(ListNode head) {
        // 初始化前一个节点为 null
        ListNode prev = null;
        // 当前节点从 head 开始
        ListNode curr = head;

        // 遍历链表,直到 curr 为 null
        while (curr != null) {
            // 保存当前节点的下一个节点
            ListNode next = curr.next;

            // 反转当前节点的指针指向前一个节点
            curr.next = prev;

            // 向后移动 prev 和 curr 指针
            prev = curr;
            curr = next;
        }

        // 最终 prev 指向反转后的头节点
        return prev;
    }
找链表中点

使用快慢指针

复制代码
private ListNode findCenter(ListNode head) {
    if(head == null || head.next.next == null) {
        return null;
    }

    ListNode fast = head;
    ListNode slow = head;
    while(fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
    }
    return slow;
}
合并两个有序链表
复制代码
private ListNode merge(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0); // 虚拟头节点
    ListNode tail = dummy;

    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            tail.next = l1;   // 添加较小节点
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;     // 移动指针
    }

    // 接上剩余部分
    tail.next = (l1 != null) ? l1 : l2;

    return dummy.next;
}
链表排序

链表排序适合使用 归并排序(Merge Sort),思路是分而治之

步骤如下:

  1. 使用快慢指针找到链表的中点;

  2. 递归地将左右链表进行归并排序;

  3. 合并两个有序链表。

复制代码
public ListNode sortList(ListNode head) {
    // 递归结束条件:空链表或只有一个节点
    if (head == null || head.next == null) {
        return head;
    }

    // 快慢指针找中点
    ListNode slow = head, fast = head, prev = null;
    while (fast != null && fast.next != null) {
        prev = slow;         // 记录 slow 前一个节点
        slow = slow.next;    // slow 每次走一步
        fast = fast.next.next; // fast 每次走两步
    }
    prev.next = null; // 断开链表,分为左右两部分

    // 对左右两部分递归排序
    ListNode l1 = sortList(head);
    ListNode l2 = sortList(slow);

    // 合并两个有序链表
    return merge(l1, l2);
}

// 合并两个有序链表
private ListNode merge(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0); // 虚拟头节点
    ListNode tail = dummy;

    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            tail.next = l1;   // 添加较小节点
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;     // 移动指针
    }

    // 接上剩余部分
    tail.next = (l1 != null) ? l1 : l2;

    return dummy.next;
}

二叉树

中序遍历

中序遍历左中右

复制代码
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    inorder(root, result); // 从根节点开始中序遍历
    return result;
}

// 递归方法:中序遍历
private void inorder(TreeNode node, List<Integer> result) {
    if (node == null) {
        return; // 如果当前节点为空,直接返回
    }

    inorder(node.left, result);       // 递归遍历左子树
    result.add(node.val);             // 访问当前节点(根节点)
    inorder(node.right, result);      // 递归遍历右子树
}
树的最大深度/高度

最大深度 = max(左子树深度, 右子树深度) + 1

复制代码
public int maxDepth(TreeNode root) {
    // 如果当前节点为空,深度为0
    if (root == null) {
        return 0;
    }

    // 递归获取左子树的最大深度
    int leftDepth = maxDepth(root.left);

    // 递归获取右子树的最大深度
    int rightDepth = maxDepth(root.right);

    // 当前树的最大深度 = 左右子树最大深度的较大者 + 1(加上当前节点)
    return Math.max(leftDepth, rightDepth) + 1;
}
二叉树DFS

二叉树的层序遍历用队列实现

复制代码
public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            int size = queue.size(); // 当前层节点数量
            List<Integer> level = new ArrayList<>();

            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);

                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }

            res.add(level);
        }

        return res;
    }

拓扑排序

拓扑排序(Topological Sorting) 是针对 有向无环图(DAG, Directed Acyclic Graph) 的一种 线性排序

它要求:

若图中存在一条边 u → v

则在排序结果中 u 一定出现在 v 之前。

拓扑排序常用 两种算法

方法 思想 关键数据结构
1️⃣ Kahn 算法(BFS) 每次取入度为 0 的节点 队列(Queue)
2️⃣ DFS 递归算法 深度遍历 + 栈逆序输出 栈(Stack)
🧩 Kahn 算法(基于入度的 BFS)

✅ 思想步骤:

  1. 统计每个节点的 入度

  2. 将所有 入度为 0 的节点加入队列(表示没有前置依赖)。

  3. 不断出队节点,并将其邻接点的入度减 1。

  4. 如果某个节点入度变为 0,则入队。

  5. 当队列为空时,若输出的节点数等于图中节点数 → 无环;否则有环。

💻 代码示例(Java)

复制代码
public List<Integer> topologicalSort(int numCourses, int[][] prerequisites) {
    List<List<Integer>> graph = new ArrayList<>();
    for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
    int[] indegree = new int[numCourses];

    for (int[] pre : prerequisites) {
        int a = pre[0], b = pre[1];
        graph.get(b).add(a);
        indegree[a]++;
    }

    Queue<Integer> queue = new LinkedList<>();
    for (int i = 0; i < numCourses; i++) {
        if (indegree[i] == 0) queue.offer(i);
    }

    List<Integer> res = new ArrayList<>();
    while (!queue.isEmpty()) {
        int cur = queue.poll();
        res.add(cur);
        for (int next : graph.get(cur)) {
            indegree[next]--;
            if (indegree[next] == 0) queue.offer(next);
        }
    }

    return res.size() == numCourses ? res : new ArrayList<>();
}
🧮 DFS 实现(递归检测环 + 栈保存顺序)

✅ 思想步骤:

  1. 对每个未访问节点执行 DFS。

  2. 递归过程中:

    • 标记当前节点为"访问中"。

    • 对所有相邻节点递归访问。

    • 如果遇到"访问中"的节点 → 存在环。

  3. 访问完一个节点后,将其压入栈。

  4. 最后栈中元素逆序即为拓扑序。

💻 代码示例(Java)

复制代码
public List<Integer> topologicalSortDFS(int numCourses, int[][] prerequisites) {
    List<List<Integer>> graph = new ArrayList<>();
    for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
    for (int[] pre : prerequisites) graph.get(pre[1]).add(pre[0]);

    int[] visited = new int[numCourses]; // 0=未访问,1=访问中,2=已完成
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i < numCourses; i++) {
        if (!dfs(graph, visited, stack, i)) return new ArrayList<>(); // 有环
    }

    List<Integer> res = new ArrayList<>();
    while (!stack.isEmpty()) res.add(stack.pop());
    return res;
}

private boolean dfs(List<List<Integer>> graph, int[] visited, Stack<Integer> stack, int node) {
    if (visited[node] == 1) return false; // 环
    if (visited[node] == 2) return true;

    visited[node] = 1;
    for (int next : graph.get(node)) {
        if (!dfs(graph, visited, stack, next)) return false;
    }
    visited[node] = 2;
    stack.push(node);
    return true;
}

📈 复杂度分析

项目 复杂度
时间复杂度 O(V + E),遍历所有节点和边
空间复杂度 O(V + E),用于图结构与辅助队列/栈

排序

🧮 一、冒泡排序(Bubble Sort)

💡 思想:

两层for循环,内层是逐个比较相邻两个元素,如果前者大于后者则交换,外层每次遍历都把当前最大的数迁移到末尾。优化点:如果内层都没有交换则证明原数组是升序的,时间复杂度从O(n^2)降为O(n)

✅ Java实现:

复制代码
public static void bubbleSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        boolean swapped = false;
        for (int j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = true;
            }
        }
        if (!swapped) break; // 优化:如果一轮无交换则提前结束
    }
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n) O(n²) O(n²) O(1) ✅ 稳定

🪄 二、选择排序(Selection Sort)

💡 思想:

每轮从未排序部分选出最小元素,与未排序部分头元素交换位置

✅ Java实现:

复制代码
public static void selectionSort(int[] arr) {
    int n = arr.length;
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) minIndex = j;
        }
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n²) O(n²) O(n²) O(1) ❌ 不稳定
⚙️ 三、插入排序(Insertion Sort)

💡 思想:

将数组分为"已排序"和"未排序"部分,每次取出未排序的第一个元素,插入到前面合适的位置。

复制代码
public static void insertionSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n) O(n²) O(n²) O(1) ✅ 稳定

⚡ 四、快速排序(Quick Sort)

💡 思想:

选择一个"基准"(pivot),将数组分为两部分:比pivot小的在左,比pivot大的在右。递归排序两部分。

✅ Java实现:

复制代码
public static void quickSort(int[] arr, int left, int right) {
    if (left >= right) return;
    int pivot = arr[left];
    int i = left, j = right;
    while (i < j) {
        while (i < j && arr[j] >= pivot) j--;
        while (i < j && arr[i] <= pivot) i++;
        if (i < j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    arr[left] = arr[i];
    arr[i] = pivot;
    quickSort(arr, left, i - 1);
    quickSort(arr, i + 1, right);
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n log n) O(n log n) O(n²) O(log n) ❌ 不稳定

🧩 五、归并排序(Merge Sort)

💡 思想:

分治法:将数组分成两半,分别排序,再合并两个有序数组。

✅ Java实现:

复制代码
public static void mergeSort(int[] arr, int left, int right) {
    if (left >= right) return;
    int mid = (left + right) / 2;
    mergeSort(arr, left, mid);
    mergeSort(arr, mid + 1, right);
    merge(arr, left, mid, right);
}

private static void merge(int[] arr, int left, int mid, int right) {
    int[] temp = new int[right - left + 1];
    int i = left, j = mid + 1, k = 0;
    while (i <= mid && j <= right) {
        temp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];
    }
    while (i <= mid) temp[k++] = arr[i++];
    while (j <= right) temp[k++] = arr[j++];
    System.arraycopy(temp, 0, arr, left, temp.length);
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n log n) O(n log n) O(n log n) O(n) ✅ 稳定

🏗️ 六、堆排序(Heap Sort)

💡 思想:

利用大顶堆(或小顶堆)特性,每次取堆顶元素(最大/最小),调整堆。

✅ Java实现:

复制代码
public static void heapSort(int[] arr) {
    int n = arr.length;
    // 建堆
    for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i);
    // 逐个取出堆顶元素
    for (int i = n - 1; i > 0; i--) {
        int temp = arr[0];
        arr[0] = arr[i];
        arr[i] = temp;
        heapify(arr, i, 0);
    }
}

private static void heapify(int[] arr, int n, int i) {
    int largest = i;
    int left = 2 * i + 1, right = 2 * i + 2;
    if (left < n && arr[left] > arr[largest]) largest = left;
    if (right < n && arr[right] > arr[largest]) largest = right;
    if (largest != i) {
        int temp = arr[i];
        arr[i] = arr[largest];
        arr[largest] = temp;
        heapify(arr, n, largest);
    }
}

📈 复杂度:

最好 平均 最坏 空间复杂度 稳定性
O(n log n) O(n log n) O(n log n) O(1) ❌ 不稳定

排序的 稳定性 指的是:

当有多个相同关键字的元素时,排序后它们的相对顺序是否保持不变

✅ 举个例子

假设有一组学生成绩表,按"成绩"排序:

姓名 成绩
小明 90
小红 80
小刚 90

我们要按成绩升序排列。

  • 如果排序后还是:小红(80), 小明(90), 小刚(90)

    ------✅ 稳定排序(小明和小刚原来顺序没变)

  • 如果排序后成了:小红(80), 小刚(90), 小明(90)

    ------❌ 不稳定排序(两个90的顺序被打乱)


相关推荐
萧逸才2 小时前
【learn-claude-code】S07TaskSystem - 任务系统:大目标拆成小任务,持久化到磁盘
java·人工智能·ai
paeamecium2 小时前
【PAT甲级真题】- Talent and Virtue (25)
数据结构·c++·算法·pat
Mr_Xuhhh2 小时前
蓝桥杯复习清单真题(C++版本)
c++·算法·蓝桥杯
tankeven2 小时前
HJ163 时津风的资源收集
c++·算法
Rsun045512 小时前
MessageUtils.message(“user.jcaptcha.expire“)
java
Boop_wu2 小时前
[Java 算法] 动态规划(4)
数据结构·算法·leetcode
旖-旎2 小时前
分治(计算右侧小于当前元素的个数)(7)
c++·学习·算法·leetcode·排序算法·归并排序
zaim12 小时前
计算机的错误计算(二百二十六)
java·python·c#·c·错数·mpmath
cxr8282 小时前
细胞球运动追踪的卡尔曼滤波与力场插值算法 —— 活体内微米级颗粒实时定位与轨迹预测系统
算法