深入浅出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:岛屿数量

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

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui