分治算法是一种重要的算法思想,它将一个复杂的问题分解成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这种技术是多项基础计算问题的基石,包括排序算法(如快速排序和归并排序)、傅里叶变换(FFT)等。
分治算法的三个主要步骤:
- 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
- 解决:若子问题规模足够小,则直接解决。否则,递归地解决各个子问题。
- 合并:将各个子问题的解合并为原问题的解。
分治算法的特点:
- 递归性:分治算法通常通过递归的方式进行子问题的分解和解决。
- 问题规模缩小:在分治算法中,子问题是原问题的一个较小部分,解决了子问题也就是解决了原问题的一部分。
- 子问题独立性:在理想情况下,分治算法分解出的子问题应该是相互独立的,这意味着子问题之间没有相互影响。
分治算法的应用示例:
1. 归并排序
归并排序是分治思想的典型应用。它将待排序的数组分成两半,递归地对两半进行归并排序,然后将排序好的两半合并成一个最终的排序数组。
Java实现:
public void mergeSort(int[] array, int left, int right) {
if (left < right) {
// Find the middle point
int middle = (left + right) / 2;
// Sort first and second halves
mergeSort(array, left, middle);
mergeSort(array, middle + 1, right);
// Merge the sorted halves
merge(array, left, middle, right);
}
}
// Merges two subarrays of array[].
void merge(int arr[], int l, int m, int r) {
// Find sizes of two subarrays to be merged
int n1 = m - l + 1;
int n2 = r - m;
/* Create temp arrays */
int L[] = new int[n1];
int R[] = new int[n2];
/*Copy data to temp arrays*/
for (int i = 0; i < n1; ++i)
L[i] = arr[l + i];
for (int j = 0; j < n2; ++j)
R[j] = arr[m + 1 + j];
/* Merge the temp arrays */
// Initial indexes of first and second subarrays
int i = 0, j = 0;
// Initial index of merged subarray array
int k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
/* Copy remaining elements of L[] if any */
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
/* Copy remaining elements of R[] if any */
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
2. 快速排序
快速排序也是一个分治算法。它从数组中选取一个元素作为基准,重新排列数组,所有比基准小的元素摆放在基准前面,所有比基准大的元素摆在基准的后面。然后递归地在基准前后的子数组上重复这个过程。
Java实现:
public void quickSort(int[] arr, int low, int high) {
if (low < high) {
// pi is partitioning index, arr[pi] is now at right place
int pi = partition(arr, low, high);
// Recursively sort elements before
// partition and after partition
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1); // index of smaller element
for (int j = low; j < high; j++) {
// If current element is smaller than the pivot
if (arr[j] < pivot) {
i++;
// swap arr[i] and arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// swap arr[i+1] and arr[high] (or pivot)
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
分治算法通过逐步分解问题,解决子问题再合并结果来解决整体问题,是解决许多计算问题的有效策略。理解分治策略的关键在于如何将问题分解为可以管理的小问题,以及如何合并这些小问题的解来得到原问题的解。
1. 二叉树的右视图
题目描述:给定一个二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回你能看到的节点值。
示例:
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:
1 <---
/ \
2 3 <---
\ \
5 4 <---
Java解法:
public class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
rightView(root, result, 0);
return result;
}
public void rightView(TreeNode curr, List<Integer> result, int currDepth){
if(curr == null){
return;
}
if(currDepth == result.size()){
result.add(curr.val);
}
rightView(curr.right, result, currDepth + 1);
rightView(curr.left, result, currDepth + 1);
}
}
2. LRU缓存机制
题目描述:设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作:获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
Java解法(使用LinkedHashMap):
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache extends LinkedHashMap<Integer, Integer> {
private int capacity;
public LRUCache(int capacity) {
super(capacity, 0.75F, true);
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
}
3. 合并区间
题目描述:以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
示例:
输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
Java解法:
import java.util.Arrays;
import java.util.LinkedList;
public class Solution {
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
LinkedList<int[]> merged = new LinkedList<>();
for (int[] interval : intervals) {
if (merged.isEmpty() || merged.getLast()[1] < interval[0]) {
merged.add(interval);
} else {
merged.getLast()[1] = Math.max(merged.getLast()[1], interval[1]);
}
}
return merged.toArray(new int[merged.size()][]);
}
}
这三个问题分别涉及二叉树的深度优先搜索、设计数据结构以及排序和合并区间的算法,是面试中常见的题型。掌握这些题目的解法可以帮助你在面试中更好地展示你的编程和问题解决能力。