算法总结-分治算法(正在更新)

参考:https://mp.weixin.qq.com/s/q9wKXNBxWCimCeKQNzygEw

简述

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

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; 
}

猜你喜欢

转载自blog.csdn.net/qq_37886086/article/details/90167898