赞
踩
递归和分治是两种常见的问题解决方法,它们是不同的概念,但它们经常在一起使用,有时甚至是互相支持的。
递归是一种解决问题的方法,其中一个函数通过不断调用自身来解决更小规模的子问题,直到达到基本情况为止。递归函数通常包含两个部分:
递归通常用于解决具有自相似性质的问题,例如树结构、图结构等。经典的递归算法包括斐波那契数列的计算、二叉树的遍历等。
分治是一种算法设计策略,它将问题分解成若干个规模较小且相互独立的子问题,然后解决这些子问题,最后将子问题的解合并起来得到原问题的解。分治算法一般包括三个步骤:
分治算法通常用于解决可以被分割成互相独立的子问题的问题。经典的分治算法包括归并排序、快速排序等。
尽管递归和分治是不同的概念,但它们经常在一起使用。在分治策略中,递归用于解决子问题。分治算法将原问题分解成更小的规模,然后通过递归地解决这些子问题来实现。因此,可以说分治算法通常会使用递归来实现子问题的解决。
递归是一种解决问题的方法,而分治是一种算法设计策略。它们经常一起使用,尤其是在解决可以分解成子问题的问题时。
递归乘法。 写一个递归函数,不使用 * 运算符, 实现两个正整数的相乘。可以使用加号、减号、位移,但要吝啬一些。
示例1:
输入:A = 1, B = 10
输出:10
示例2:
输入:A = 3, B = 4
输出:12
multiply(A, B-1)
,这相当于将 B 逐步减一,继续进行相乘操作。时间复杂度为 O(n)。
class Solution {
public int multiply(int A, int B) {
if(B == 0) return 0;
if(B == 1) return A;
return A + multiply(A, B-1);
}
}
时间复杂度为 O(log(n))
class Solution { public int multiply(int A, int B) { // 基本情况 if (A == 0 || B == 0) { return 0; } else if (A == 1) { return B; } else if (B == 1) { return A; } // 将问题分解为更小的子问题 int half_A = A >> 1; int double_B = B << 1; // 递归求解 int product = multiply(half_A, double_B); // 合并子问题的解 if (A % 2 == 0) { return product; } else { return product + B; } } }
归并排序采用分治策略,将待排序的数组分成两部分,分别进行排序,然后将两个已排序的子数组合并成一个有序的数组。
举例说明:
让我们来看一下归并排序对给定数组 [21, 12, 39, 14, 16, 10, 42, 6, 34, 34]
的执行过程:
初始状态:原始数组为 [21, 12, 39, 14, 16, 10, 42, 6, 34, 34]
。
第一次分解:
[21, 12, 39, 14, 16]
和 [10, 42, 6, 34, 34]
。对子数组进行排序:
[21, 12, 39, 14, 16]
和 [10, 42, 6, 34, 34]
分别进行排序。递归排序子数组:
对第一个子数组 [21, 12, 39, 14, 16]
进行递归排序:
[21, 12]
和 [39, 14, 16]
。[21, 12]
和 [39, 14, 16]
分别进行排序。对第二个子数组 [10, 42, 6, 34, 34]
进行递归排序:
[10, 42]
和 [6, 34, 34]
。[10, 42]
和 [6, 34, 34]
分别进行排序。合并排序后的子数组:
[21, 12]
和 [39, 14, 16]
进行合并,得到 [12, 14, 16, 21, 39]
。[10, 42]
和 [6, 34, 34]
进行合并,得到 [6, 10, 34, 34, 42]
。最终合并:
[12, 14, 16, 21, 39]
和 [6, 10, 34, 34, 42]
进行合并,得到最终排序后的数组 [6, 10, 12, 14, 16, 21, 34, 34, 39, 42]
。这样,我们就完成了对给定数组 [21, 12, 39, 14, 16, 10, 42, 6, 34, 34]
的归并排序过程。
归并排序
归并排序与二叉树后序遍历的递归顺序是一致的。
import java.util.Arrays; public class MergeSort { public static 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); } } // 归并函数:将两个已排序的数组合并成一个有序数组 public static void merge(int[] arr, int left, int mid, int right) { // 计算左右两部分的长度 int n1 = mid - left + 1; int n2 = right - mid; // 创建临时数组 int[] L = new int[n1]; int[] R = new int[n2]; // 将数据复制到临时数组 for (int i = 0; i < n1; i++) { L[i] = arr[left + i]; } for (int j = 0; j < n2; j++) { R[j] = arr[mid + 1 + j]; } // 合并左右两部分 int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将剩余的元素复制到数组中 while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } public static void main(String[] args) { int[] arr = {21, 12, 39, 14, 16, 10, 42, 6, 34, 34}; mergeSort(arr, 0, arr.length - 1); System.out.println("排序完成: " + Arrays.toString(arr)); } }
在这个示例中,我们通过 mergeSort
方法来实现归并排序,其中 merge
方法用于合并两个已排序的子数组。在 main
方法中,我们提供了一个示例数组,并调用 mergeSort
方法对其进行排序。
通过归并排序的执行过程,我们可以清晰地了解递归的应用和工作原理。
随机快速排序是快速排序的一种变体,它在选择基准元素时采用了随机化的方法,以提高算法的平均性能。通过随机选择基准元素,我们可以减少最坏情况下的概率,并使算法更具有鲁棒性。
举例说明:
让我们来分析对数组 [21, 12, 39, 14, 16, 10, 42, 6, 34, 34]
进行随机快速排序的执行过程:
初始状态:原始数组为 [21, 12, 39, 14, 16, 10, 42, 6, 34, 34]
。
选择基准元素:随机选择数组中的一个元素作为基准元素,假设选择的是 39
。
分解:
39
,将数组分成两部分:
[21, 12, 14, 16, 10, 6, 34, 34]
[42]
递归排序:
[21, 12, 14, 16, 10, 6, 34, 34]
进行随机快速排序。[42]
进行随机快速排序。递归过程继续:
左侧子数组 [21, 12, 14, 16, 10, 6, 34, 34]
:
14
。14
的放在左边,大于 14
的放在右边:
[12, 10, 6]
[21, 16, 34, 34]
右侧子数组 [42]
:
重复递归过程:
对左侧子数组 [12, 10, 6]
:
10
。10
的放在左边,大于 10
的放在右边:
[6]
[12]
对左侧子数组 [21, 16, 34, 34]
:
21
。21
的放在左边,大于 21
的放在右边:
[16]
[34, 34]
递归结束:
合并:
最终,数组经过随机快速排序后的顺序为 [6, 10, 12, 14, 16, 21, 34, 34, 39, 42]
。
随机快速排序
import java.util.Arrays; import java.util.Random; public class RandomQuickSort { public static void randomQuickSort(int[] arr, int low, int high) { if (low < high) { // 随机选择基准元素 int pivotIndex = randomPartition(arr, low, high); // 递归排序左侧子数组 randomQuickSort(arr, low, pivotIndex - 1); // 递归排序右侧子数组 randomQuickSort(arr, pivotIndex + 1, high); } } public static int randomPartition(int[] arr, int low, int high) { // 生成 low 和 high 之间的随机索引作为基准元素索引 Random random = new Random(); int randomIndex = random.nextInt(high - low + 1) + low; // 将基准元素交换到数组末尾 swap(arr, randomIndex, high); return partition(arr, low, high); } public static int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; swap(arr, i, j); } } // 将基准元素放到正确的位置 swap(arr, i + 1, high); return i + 1; } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } public static void main(String[] args) { int[] arr = {21, 12, 39, 14, 16, 10, 42, 6, 34, 34}; randomQuickSort(arr, 0, arr.length - 1); System.out.println("Sorted array: " + Arrays.toString(arr)); } }
题目描述:
将整数 n 分成 k 份,且每份不能为空,任意两份不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5; 1,5,1; 5,1,1
问有多少种不同的分法。
示例 :
输入
7 3
输出
4
题解:
public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); int k = scanner.nextInt(); System.out.println(partition(n, k)); } public static int partition(int n, int k) { if (n == 0 || k == 0 || n < k) { return 0; } if (k == 1 || n == k){ return 1; } else { return partition(n - k, k) + partition(n - 1, k-1); } } }
当我们调用 partition(7, 3)
时,我们希望计算将整数 7 分成 3 份的不同分法数量。下面是递归过程的详细解释:
第一次调用: partition(7, 3)
进入 partition
方法,我们检查条件,n = 7
不等于 0,k = 3
不等于 0,因此继续执行。
不满足 k == 1
或 n == k
,所以我们进入 else
分支。
我们计算 partition(7 - 3, 3) + partition(7 - 1, 3 - 1)
。
第一次递归调用: partition(4, 3)
进入 partition
方法,同样检查条件,n = 4
不等于 0,k = 3
不等于 0,因此继续执行。
不满足 k == 1
或 n == k
,所以我们进入 else
分支。
我们计算 partition(4 - 3, 3) + partition(4 - 1, 3 - 1)
。
第二次递归调用: partition(1, 3)
进入 partition
方法,检查条件,n = 1
不等于 0,k = 3
不等于 0,因此继续执行。
不满足 k == 1
或 n == k
,所以我们进入 else
分支。
我们计算 partition(1 - 3, 3) + partition(1 - 1, 3 - 1)
。
注意到 n - k
为负数,所以第一个递归调用返回 0。
第二个递归调用变成了 partition(0, 2)
。
第三次递归调用: partition(0, 2)
进入 partition
方法,检查条件,n = 0
等于 0,k = 2
不等于 0,因此继续执行。
因为 k == 1
,所以返回 1。
回到第二次递归调用,partition(1 - 1, 3 - 1)
也返回 1。
返回到第一次递归调用,partition(4 - 1, 3 - 1)
为 partition(3, 2)
,进入下一轮递归。
第四次递归调用: partition(3, 2)
进入 partition
方法,检查条件,n = 3
不等于 0,k = 2
不等于 0,因此继续执行。
不满足 k == 1
或 n == k
,所以我们进入 else
分支。
我们计算 partition(3 - 2, 2) + partition(3 - 1, 2 - 1)
。
第五次递归调用: partition(1, 2)
进入 partition
方法,检查条件,n = 1
不等于 0,k = 2
不等于 0,因此继续执行。
不满足 k == 1
或 n == k
,所以我们进入 else
分支。
我们计算 partition(1 - 2, 2) + partition(1 - 1, 2 - 1)
。
注意到 n - k
为负数,所以第一个递归调用返回 0。
第二个递归调用变成了 partition(0, 1)
。
第六次递归调用: partition(0, 1)
进入 partition
方法,检查条件,n = 0
等于 0,k = 1
等于 1,因此返回 1。
返回到第五次递归调用,partition(1 - 1, 2 - 1)
也返回 1。
返回到第四次递归调用,partition(3 - 1, 2 - 1)
为 partition(2, 1)
,进入下一轮递归。
第七次递归调用: partition(2, 1)
进入 partition
方法,检查条件,n = 2
不等于 0,k = 1
等于 1,因此返回 1。
返回到第四次递归调用,partition(2, 1)
为 partition(2, 1)
的结果加上 partition(1, 1)
的结果,所以返回 2。
返回到第一次递归调用,partition(4 - 3, 3)
为 partition(1, 3)
的结果加上 partition(3, 2)
的结果,所以返回 3。
返回到初始调用,partition(7, 3)
为 partition(4, 3)
的结果加上 partition(6, 2)
的结果,所以返回 4。
最终,我们得到了将整数 7 分成 3 份的不同分法数量为 4。
划分数字的两种分法
k份里面包含1的分法,首先就给分配 1 ,然后就剩下n-1个数分成 k -1 份
k份里面不包含1的分法,在每一个位置上都先分配 1 ,相当于把 n-k 个数分成 k 份之后再在每个位置上加上1
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。