深入浅出Java算法 排序与搜索

深入浅出Java算法 排序与搜索

排序和搜索是算法中最基础也最常考的两大类问题,今天我就用最通俗的大白话,给大家拆解这些高频考题的解题套路。

一、排序算法三板斧

1. 快速排序(快排)

核心思想:挑个基准值,小的放左边,大的放右边,递归处理两边

java 复制代码
public void quickSort(int[] arr, int low, int high) {
    if (low < high) {
        int pivot = partition(arr, low, high); // 找分割点
        quickSort(arr, low, pivot - 1);  // 排左边
        quickSort(arr, pivot + 1, high); // 排右边
    }
}

private int partition(int[] arr, int low, int high) {
    int pivot = arr[high]; // 选最后一个当基准
    int i = low; // 小于基准的边界
    
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            swap(arr, i, j); // 小的扔到左边
            i++;
        }
    }
    swap(arr, i, high); // 基准归位
    return i;
}

解释

  1. 随便抓个人当基准(比如最后一个)
  2. 比他矮的站左边,高的站右边
  3. 让基准站到中间
  4. 对左右两队重复这个过程

2. 归并排序

核心思想:先拆到最小,再两两合并

java 复制代码
public void mergeSort(int[] arr, int left, int right) {
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(arr, left, mid);   // 排左边
        mergeSort(arr, mid + 1, right); // 排右边
        merge(arr, left, mid, right); // 合并
    }
}

private 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) {
        if (arr[i] <= arr[j]) {
            temp[k++] = arr[i++];
        } else {
            temp[k++] = arr[j++];
        }
    }
    
    // 处理剩下的
    while (i <= mid) temp[k++] = arr[i++];
    while (j <= right) temp[k++] = arr[j++];
    
    // 拷贝回原数组
    System.arraycopy(temp, 0, arr, left, temp.length);
}

解释

  1. 把数组对半切
  2. 切到只剩一个元素时开始合并
  3. 合并时像军训排队,两个排好序的队伍,谁小谁先出列

3. 堆排序

核心思想:先把数组堆化,然后逐个取出最大值

java 复制代码
public void heapSort(int[] arr) {
    // 1. 构建大顶堆
    for (int i = arr.length/2 - 1; i >= 0; i--) {
        heapify(arr, arr.length, i);
    }
    
    // 2. 逐个提取元素
    for (int i = arr.length - 1; i > 0; i--) {
        swap(arr, 0, i); // 把当前最大的放到最后
        heapify(arr, i, 0); // 调整剩下的堆
    }
}

private void heapify(int[] arr, int n, int i) {
    int largest = i; // 父节点
    int left = 2 * i + 1;
    int 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) {
        swap(arr, i, largest);
        heapify(arr, n, largest); // 递归调整
    }
}

解释

  1. 先把数组变成大顶堆(父节点比子节点大)
  2. 堆顶是最大的,把它和最后一个元素交换
  3. 剩下的元素重新堆化
  4. 重复直到全部有序

二、搜索算法两大利器

1. 二分查找

适用场景:有序数组中找特定值

java 复制代码
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; // 没找到
}

变种题型

  • 找第一个等于target的
  • 找最后一个等于target的
  • 找第一个大于等于target的
java 复制代码
// 找第一个等于target的
public int firstEqual(int[] nums, int target) {
    int left = 0, right = nums.length - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        
        if (nums[mid] >= target) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    
    if (left < nums.length && nums[left] == target) {
        return left;
    }
    return -1;
}

解释

  1. 每次猜中间那个数
  2. 如果猜大了就往左找,猜小了就往右找
  3. 直到找到或者确定不存在

2. 深度优先搜索(DFS)和广度优先搜索(BFS)

DFS模板(递归版):

java 复制代码
void dfs(TreeNode root) {
    if (root == null) return;
    
    // 前序遍历
    System.out.println(root.val);
    dfs(root.left);
    dfs(root.right);
    
    // 中序和后序调整打印位置即可
}

BFS模板

java 复制代码
void bfs(TreeNode root) {
    if (root == null) return;
    
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        System.out.println(node.val);
        
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
    }
}

解释

  • DFS:一条路走到黑,撞墙再回头
  • BFS:一层一层扫荡,像水波纹扩散

三、高频考题解题套路

1. 合并两个有序数组

问题:把nums2合并到nums1中(nums1有足够空间)

java 复制代码
public void merge(int[] nums1, int m, int[] nums2, int n) {
    int p1 = m - 1, p2 = n - 1, p = m + n - 1;
    
    while (p1 >= 0 && p2 >= 0) {
        nums1[p--] = nums1[p1] > nums2[p2] ? nums1[p1--] : nums2[p2--];
    }
    
    // 如果nums2还有剩余
    System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
}

解释:从后往前放,谁大谁先占坑

2. 寻找两个正序数组的中位数

解题思路:转化成找第k小的数

java 复制代码
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int m = nums1.length, n = nums2.length;
    int left = (m + n + 1) / 2;
    int right = (m + n + 2) / 2;
    return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}

private int findKth(int[] nums1, int i, int[] nums2, int j, int k) {
    if (i >= nums1.length) return nums2[j + k - 1];
    if (j >= nums2.length) return nums1[i + k - 1];
    if (k == 1) return Math.min(nums1[i], nums2[j]);
    
    int midVal1 = (i + k/2 - 1 < nums1.length) ? nums1[i + k/2 - 1] : Integer.MAX_VALUE;
    int midVal2 = (j + k/2 - 1 < nums2.length) ? nums2[j + k/2 - 1] : Integer.MAX_VALUE;
    
    if (midVal1 < midVal2) {
        return findKth(nums1, i + k/2, nums2, j, k - k/2);
    } else {
        return findKth(nums1, i, nums2, j + k/2, k - k/2);
    }
}

解释:每次排除掉k/2个不可能的数,逐步逼近中位数

3. 前K个高频元素

解题思路:哈希表统计频率 + 最小堆

java 复制代码
public int[] topKFrequent(int[] nums, int k) {
    // 统计频率
    Map<Integer, Integer> freqMap = new HashMap<>();
    for (int num : nums) {
        freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
    }
    
    // 最小堆保存前k大
    PriorityQueue<Map.Entry<Integer, Integer>> heap = 
        new PriorityQueue<>((a, b) -> a.getValue() - b.getValue());
    
    for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
        heap.offer(entry);
        if (heap.size() > k) {
            heap.poll();
        }
    }
    
    // 收集结果
    int[] res = new int[k];
    for (int i = k - 1; i >= 0; i--) {
        res[i] = heap.poll().getKey();
    }
    return res;
}

解释

  1. 先数每个数字出现的次数
  2. 用一个小顶堆来保存出现次数最多的k个
  3. 堆满了就把最小的踢出去

四、实战技巧

  1. 排序选择

    • 数据量小用插入排序
    • 需要稳定用归并
    • 一般情况用快排
    • 找前K大/小用堆排序
  2. 二分查找变种

    • 找边界时,先确定搜索方向
    • 结束时检查left/right是否越界
    • 不确定时画图模拟
  3. 搜索优化

    • DFS考虑剪枝
    • BFS考虑双向BFS
    • 矩阵搜索可以DFS+记忆化
  4. 必刷题目

    • LeetCode 215:数组中的第K个最大元素
    • LeetCode 347:前K个高频元素
    • LeetCode 4:寻找两个正序数组的中位数
    • LeetCode 33:搜索旋转排序数组
    • LeetCode 200:岛屿数量

记住这些套路,排序搜索题就能见招拆招。最重要的是多写多练,培养对算法复杂度的直觉!

相关推荐
时光少年2 小时前
Android 副屏录制方案
android·前端
时光少年2 小时前
Android 局域网NIO案例实践
android·前端
alexhilton2 小时前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
流浪汉kylin2 小时前
Android TextView SpannableString 如何插入自定义View
android
火柴就是我3 小时前
git rebase -i,执行 squash 操作 进行提交合并
android
你说你说你来说4 小时前
安卓广播接收器(Broadcast Receiver)的介绍与使用
android·笔记
你说你说你来说4 小时前
安卓Content Provider介绍及使用
android·笔记
RichardLai885 小时前
[Flutter学习之Dart基础] - 类
android·flutter
_一条咸鱼_5 小时前
深度解析 Android MVI 架构原理
android·面试·kotlin
火柴就是我6 小时前
git rebase -i 修改某次提交的message
android