【算法】已知必存在众数,求该众数 -- 分治法

分治法应用场景之一:大学选毕业生代表

  • 【分】

提出【大范围问题 】 校长告诉大四级长:从所有大四学生中选出毕业生代表

处理为【小范围问题】大四级长告诉每个系主任:从各自的系里面选一个系学生代表

再处理为【最小范围问题】系主任告诉每个班主任:从各自的班里选一个班级学生代表

  • 【治】

最先反馈【最小范围问题】每个班主任分别告诉系主任:这是我们班级学生代表。每个系主任从班级代表里面选出最好的成为系学生代表

再反馈【小范围问题】每个系主任分别告诉大四级长:这是我们的系学生代表。大四级长从系学生代表里面选出最好的成为毕业生代表

解决【大范围问题】大四级长告诉校长:我已经筛选出最好学生成为毕业生代表

  • 分治法

强调治理分离。宏观来讲,分治法一个大范围的问题可以切割成若干个小范围的问题。若干个小范围问题处理后一定要反馈给大范围的问题。这种从大范围分割问题,再通过小范围问题返回结果的模式,很适合使用递归的方法。

算法题

在这里插入图片描述

  • 思路

提出【大范围问题】在数组中找到出现过最多的数

处理为【小范围问题】分别从数组的左半部分,右半部分找一个众数。

再处理为【最小范围问题】数组已不可再分,当前的数就是众数。左右半边都选了个众数,等待上头来决定吧。

以上可以写出代码:

/**
 * 分治法 -- 使用系统栈
 * @param nums 数组 a[6, 2, 3, 6, 6]
 * @param lo 数组低位边界 初始lo = 0
 * @param hi 数组高位边界 初始hi = a.length - 1
 * @return
 */
private int majorityElement(int[] nums, int lo, int hi) {
		// 数组已不可再分
		if (lo == hi) {
            // 让上头决定的逻辑
        }
		// 每次都找中间边界
        int mid = (hi-lo)/2 + lo;
        // 左边的区间找众数
        int left = majorityElemeec(nums, lo, mid);
        // 右边的区间找众数
        int right = majorityElemeec(nums, mid+1, hi);
}

最先反馈【最小范围问题】

private int majorityElement(int[] nums, int lo, int hi) {
		// 数组已不可再分
		if (lo == hi) {
			// 单个数,不需要比较,直接返回
			return nums[lo];
		}
		
		// 每次都找中间边界
        int mid = (hi-lo)/2 + lo;
        // 左边的区间找众数
        int left = majorityElemeec(nums, lo, mid);
        // 右边的区间找众数
        int right = majorityElemeec(nums, mid+1, hi);
}

再反馈【小范围问题】当前左右半边的众数已经找到,告诉范围大的数组:需要选举确定那个最合适的代表。

private int majorityElement(int[] nums, int lo, int hi) {
		// 数组已不可再分
		if (lo == hi) {
			// 单个数,不需要比较,直接返回
			return nums[lo];
		}
		
		// 每次都找中间边界
        int mid = (hi-lo)/2 + lo;
        // 左边的区间找众数
        int left = majorityElemeec(nums, lo, mid);
        // 右边的区间找众数
        int right = majorityElemeec(nums, mid+1, hi);
       
       // ------------以下处理小范围问题------------------------------------------
		
		// 左右区间返回的众数都一样了,这里不用再选举了,直接返回左边的给上头。
        if (left == right) {
            return left;
        }
        // 左右区间拿到的众数不一样,需要选举。这次选举一定要公平,所以合并左右区间进行选举
        int leftCount = countInRange(nums, left, lo, hi);
        int rightCount = countInRange(nums, right, lo, hi);
        // 返回选票多的区间的众数,如果选举票数一样,返回右边区间众数
        return leftCount > rightCount ? left : right;
}

选举函数:

    private int countInRange(int[] nums, int num, int lo, int hi) {
        int count = 0;
        for (int i = lo; i <= hi; i++) {
            if (nums[i] == num) {
            	// 区间内出现过一次选票增加1
                count++;
            }
        }
        return count;
    }

解决【大范围问题】本数组的左右半边都以选出众数,我认为x是最好的代表。

// 上面代码已经蕴含了这个逻辑
        int leftCount = countInRange(nums, left, lo, hi);
        int rightCount = countInRange(nums, right, lo, hi);
        // 由于题目:给定的数组总是存在众数,那么在最后一次大范围问题是遍历整个数组进行二选一,必定有结果。
        return leftCount > rightCount ? left : right;

最终代码

public class Solution2 {
    /**
     * 分治法 -- 使用系统栈
     * @param nums 数组
     * @param lo 数组低位边界
     * @param hi 数组高位边界
     * @return
     */
    private int majorityElemeec(int[] nums, int lo, int hi) {
        // 左右值重合,则说明该数组大小只有 1, 直接返回元素,属于递归到底的情况
        if (lo == hi) {
            return nums[lo];
        }
   
        int mid = (hi-lo)/2 + lo;
        int left = majorityElemeec(nums, lo, mid);
        int right = majorityElemeec(nums, mid+1, hi);

        // 左右区间拿到的众数一样直接返回
        if (left == right) {
            return left;
        }

        // 左右区间拿到的众数不一样,需要选举。这次选举一定要公平,所以合并左右区间进行选举
        int leftCount = countInRange(nums, left, lo, hi);
        int rightCount = countInRange(nums, right, lo, hi);
        // 如果选举票数一样,返回右边区间众数
        return leftCount > rightCount ? left : right;
    }

    /**
     * 选举方法
     * @param nums 数组
     * @param num 待选举的数
     * @param lo  数组低位边界
     * @param hi  数组高位边界
     * @return
     */
    private int countInRange(int[] nums, int num, int lo, int hi) {
        int count = 0;
        for (int i = lo; i <= hi; i++) {
            if (nums[i] == num) {
                count++;
            }
        }
        return count;
    }

	// 给最上层调用
    public int majorityElement(int[] nums) {
        return majorityElemeec(nums, 0, nums.length-1);
    }

}
  • 优化的点
    分治法的时间复杂度:O(nlogn); 空间复杂度:O(logn);
    哈希算法的时间复杂度:O(n);空间复杂度O(n)

    此题使用分治法是使用时间换空间的解决方案。

  • 不足
    Boyer-Moore 投票算法,可以不牺牲时间复杂度就降低空间复杂度,是这道题的最佳解法。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页