跳转至

算法专题 之 分治算法

原创

JAVA万维猿圈

2021-12-09 14:46:17

分治法,从字面上就能得到直接的解释,“分为治之”,即是处事的一种态度,也是解决问题的一种思维,很多地方都有体现,例如历史上秦国采用“远交近攻”战略逐个击破,也体现着分治思想的存在。那么接下来对分治算法的进行总结并对面试等场合中常出现的题目进行分析。

1、分治算法定义与理解

分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。(上述定义来自百度百科)

个人理解,分治算法就是将一个大问题或者说复杂的问题分解 成容易求解 的小问题,然后将求出的小规模问题的解合并 成一个更大规模问题的解,自底向上逐步求出原问题的解。

分治算法的核心包含3个元素:

(1)分解 :将要求解的问题划分成若干规模较小的同类问题。

(2)求解 :当子问题划分的足够小时,用较简单的方法解决。

(3)合并 :将子问题的解逐层合并,即可构成最终的解。

2、分治算法求解的经典问题汇总

(1)二分搜索

(2)二维矩阵搜索

(3)归并排序

(4)快速排序

(5)大数相乘

(6)汉诺塔

(7)最近点对问题

3、面试中常见的回溯算法问题

题目1:数组中的第K个最大元素

题目描述 :在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 : 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5

解法1:分治思想 ,可以先从大到小排序,arr[k-1]就是所要求的数组第K个最大元素。由于题目只需要找出第K大元素,不需要整个数组都进行排序,所以可以采用部分排序进行改进。

利用快速排序的思想,在进行排序过程中每次可以确定一个元素的最终位置,若此位置为第K个最大元素,则直接返回此索引,否则继续分治进行快速排序。不用排列全部,只要每次只排序一段,找到即可。时间复杂度为O(N*logk)。注:下面代码可左右滑动查看

public class FindKthLargest {

 public static int getKthLargest(int[] nums,int k){

 int begin = 0;

 int end = nums.length - 1;

 //此处将第K大转化为第(nums.length - k + 1)小

 k = nums.length - k + 1;

 while(begin < end){

int pos = partition(nums,begin,end);

if(k == pos + 1){

break;

}else if(k > pos + 1){

 begin = pos + 1;

 }else{

 end = pos - 1;

 }

 }

 return nums[k-1];

 }

 private static int partition(int[] nums, int begin, int end){

 int less = begin - 1;

 int more = end;

 while(begin < more){

if(nums[begin] < nums[end]){

 swap(nums, begin++, ++less);

 }else if(nums[begin] > nums[end]){

 swap(nums, begin, --more);

 }else{

 begin++;

 }

 }

 swap(nums, end, more);

 return less+1;

 }

 private static void swap(int[] nums, int l, int r){

 int temp = nums[l];

 nums[l] = nums[r];

 nums[r] = temp;

 }

 public static void main(String[] args){

 int[] nums = {3,2,1,5,6,4};

 int k = 2;

 System.out.println(getKthLargest(nums,k));

 }

}
解法2:小根堆

小顶堆解决 Top K 问题的思路,小顶堆维护K个数,其后每扫描一个数都与堆顶值进行比较,若大于堆顶,则删除堆顶元素,将当前值压入堆,循环往复,直至扫描完所有元素。

public static int findKthLargest(int[] nums, int k) {

 PriorityQueue<Integer> heap = new PriorityQueue<>(k);

 for(int i = 0; i < k; i++){

heap.add(nums[i]);

}

for(int i = k; i < nums.length; i++){

 if(heap.peek()<nums[i]){

heap.poll();

heap.add(nums[i]);

}

}

return heap.peek();

}
题目2:最接近原点的K个点

题目描述 :我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。(这里,平面上两点之间的距离是欧几里德距离。)你可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。

示例:输入:points = [[1,3],[-2,2]], K = 1 输出:[[-2,2]]

解释:(1, 3) 和原点之间的距离为 sqrt(10),(-2, 2) 和原点之间

的距离为 sqrt(8),由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更

近。我们只需要距离原点最近的 K = 1 个点,所以答案就是 [[-2,2]]。
解法1:分治思想 ,利用快速排序的思想,比较规则需要重新写。注:下面代码可左右滑动查看

class KClosest {

 public static int[][] getKClosest(int[][] points, int K) {

 int l = 0,r = points.length -1;

 while(l < r){

int pos = partition(points,l,r);

if(pos == K - 1){

break;

}else if(pos < K - 1){

 l = pos + 1;

 }else{

 r = pos -1;

 }

 }

 int[][] resPoints = new int[K][];

 for(int i = 0 ; i < K ; i++)

resPoints[i] = points[i];

return resPoints;

}

private static int partition(int[][] points, int l, int r) {

int left = l - 1, right = r;

int pivot = points[r][0]*points[r][0] + points[r][1]*points[r][1];

while (l < right){

 if(points[l][0]*points[l][0] + points[l][1]*points[l][1] > pivot){

 swap(points, l, --right);

 }else if(points[l][0]*points[l][0] + points[l][1]*points[l][1] <= pivot){

swap(points, l++, ++left);

}else{

l++;

}

}

swap(points, right, r);

return left + 1;

}

private static void swap(int[][] points ,int a,int b){

int[] tmp = points[a];

points[a] = points[b];

points[b] = tmp;

}

public static void main(String[] args){

int[][] points = {{3,3},{5,-1},{-2,4}};

int K = 2;

int[][] res = getKClosest(points,K);

for(int i = 0; i < res.length; i++){

 System.out.println("["+res[i][0]+","+res[i][1]+"]");

 }

 }

}
解法2:小根堆 ,建立一个小根堆,这里的由于是二维点坐标,所以比较规则注意重写,不能使用默认的小根堆。首先往小根堆里压入所有点;然后输出堆中前K个元素即可。注:下面代码可左右滑动查看

public static int[][] getKClosest(int[][] points, int K) {

 int [][] res = new int [K][2];

 //构建一个小根堆

 PriorityQueue<int[]> queue = new PriorityQueue<int[]>(K,new Comparator<int[]>(){

 public int compare(int[] t1,int[] t2) {

 return (t1[0]*t1[0] + t1[1]*t1[1]) - (t2[0]*t2[0] + t2[1]*t2[1]);

 }

 });

 //将所有元素入堆

 for(int i = 0; i < points.length; i++) {

queue.offer(points[i]);

}

//前K个元素出堆

for(int i = 0; i < K; i++) {

 res[i] = queue.poll();

 }

 return res; 

}

评论0

相关文章

算法系列之分治算法

分治算法(Divide and Conquer)是一种解决复杂问题的非常实用的策略,广泛应用于计算机科学中的各个领域。它的核心思想是将一个复杂的问题分解成若干个相同或相似的子问题,递归地解决这些子问题,然后将子问题的解合并,最终得到原问题的解。分治算法的典型应用包括归并排序、快速排序、二分查找等。

归并算法:分治而治的高效算法大揭秘(图文详解)

归并算法是我们算法中最常见的算法之一,其思想非常巧妙。本身归并是只能归并有序数组但是当我们利用了二路归并分治法之后,就可以使用归并的思想来帮我们排序其算法性能属于第一梯队

高效算法设计:递归、动态规划、贪心、分治与回溯

在计算机科学领域,算法的设计与分析是构建高效系统的关键。本文将深入探讨五种核心算法策略:递归算法、动态规划、贪心算法、分治法以及回溯算法。我们将通过理论解释、示例代码和性能比较,帮助读者理解每种算法的特点和适用场景。递归算法递归是一种直接或间接调用自身的函数。它通常用于解决可以分解为相似子问题的问题。示例:斐波那契数列def fibonacci(n): if n <= 1:

算法专题 之 回溯算法

实例分析和讲解回溯算法,助力理解回溯思想

算法--分治算法

分治算法分而治之,把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。如:二分法、快速排序、归并排序,二叉树遍历(先遍历左子树再遍历右子树)等。步骤:分解:将原有问题分解为若干规模较小,相对独立,与原问题形式相同的子问题;解决:若子问题容易解决,则直接解;否则继续分解为更小的子问题,直到容易解决;合并:将

【算法】分治算法

#in

分治算法之芯片测试

VLSI芯片测试 1. 芯片测试 在讲解具体的芯片测试的分治策略算法之前,先来了解芯片测试的意思。 1.1 一次测试的过程 如上图,A、B为芯片。测试方法为:将2片芯片(A和B)置于测试台上,互相进行测试,测试报告是“好”或者“坏”,只取其一。 假设:好芯片的报告一定是正确的,坏芯片的报告是不确定的

分治算法

一个装有 16 枚硬币的袋子,16 枚硬币中有一个是伪造的,伪造的硬币和普通硬币从表面上看不出有任何差别,但是那个伪造的硬币比真的硬币要轻。现有给你一台天平,请你在尽可能最短的时间内找出那枚伪造的硬币分治:我们先将 16 枚硬币分为左右两个部分,各为 8 个硬币,分别称重,必然会有一半轻一半重,而我们要的就是轻的那组,重的舍去。接下来我们继续对轻的进行五五分,直至每组剩下一枚或者两枚硬币

算法专题 之 树

分析并实例讲解典型的数据结构(树)

算法专题 之 栈

详细分析栈的特点及应用场景,并实例讲解