多数元素问题(非常详细,图文并茂)

 
多数元素问题指的是在包含 n 个元素的 nums 数组中,找到出现次数大于 ⌊ n/2 ⌋ 的元素。

例如,nums 数组是 {3, 2, 3},它包含 3 个元素,其中出现次数大于 1( ⌊ 3/2 ⌋ )次的元素是 3。

再例如, nums 数组是 {2, 2, 1, 1, 1, 2, 2},它包含 7 个元素,其中出现次数大于 3( ⌊ 7/2 ⌋ )次的元素是 2。

多数元素问题的解决思路有很多种,本节带领大家用分治算法解决这个问题。

分治算法解决多数元素问题

接下来以 {3, 3, 4, 2, 4, 4, 2, 4, 4} 数组为例,梳理分治算法解决多数元素问题的整个思路。

1) 首先,将整个数组不断地进行拆分,直至各个数组无法再分(只剩下一个元素)为止。拆分过程如下图所示:

多数元素问题--拆分整个数组
图1:拆分整个数组

最终,{3, 3, 4, 2, 4, 4, 2, 4, 4} 被拆分成了 9 个子数组,每个数组里只包含 1 个元素,它就是当前数组中出现次数大于 0( ⌊ 1/2 ⌋ )次的元素。

2) 经过第 1 步的拆分,每个子数组很轻易能找到出现次数最多的元素。在此基础上,每次合并两个子数组,直至将所有的子数组整合成完整的数组。对于要合并的两个子数组,已经得知了它们各自出现次数最多的元素,假设为 a 和 b:
  • 如果 a 和 b 相等,那么在合并后的数组中,出现次数最多的也一定是 a(或者 b);
  • 如果 a 和 b 不等,就需要在合并后的数组中分别统计 a 和 b 出现的次数,进而得知出现次数最多的元素。

所有子数组合并的过程如下图所示:

多数元素问题--合并所有的子数组
图2:合并所有的子数组

飘红的元素为各个数组中出现次数最多的元素。

结合图 1 和图 2 不难看出,合并各个数组的过程,其实是拆分数组的逆过程。最终,整个数组中出现次数最多的元素是 4。

下面的伪代码描述了分治算法解决多数元素问题的过程:
findMajorElement(nums, low, high):
    if low == high:
        return nums[low]

    // 递归方式拆分数组
    mid = (high - low) / 2 + low
    left = findMajorElement(nums, low, mid)
    right = findMajorElement(nums, mid + 1, high)

    //确定当前数组中出现次数最多的元素
    if left == right:
        return left
    // 统计 left 元素在当前数组中出现的次数
    leftCount = findMajorElementCount(nums, left, low, high)
    // 统计 right 元素在当前数组中出现的次数
    rightCount = findMajorElementCount(nums, right, low, high)
    // 确定当前数组中出现次数最多的元素
    if leftCount > rightCount:
       return left
    else:
       return right

分治算法实现多数元素问题

下面是用分治算法实现多数元素问题的 C 语言程序:
#include <stdio.h>

// 统计 target 元素在 nums 数组中出现的次数
int findMajorElementCount(int nums[], int target, int low, int high) {
    int count = 0;
    for (int i = low; i <= high; i++) {
        if (target == nums[i]) {
            ++count;
        }
    }
    return count;
}

int majorityElement(int nums[], int low, int high) {
    // 递归出口,当前数组拆分区域中只剩一个元素时,递归才结束
    if (low == high) {
        return nums[low];
    }

    // 递归方式拆分数组
    int mid = (high - low) / 2 + low;
    int left = majorityElement(nums, low, mid);
    int right = majorityElement(nums, mid + 1, high);
   
    // 确定当前数组中出现次数最多的元素
    if (left == right) {
        return left;
    }
    // 统计 left 元素在当前数组中出现的次数
    int leftCount = findMajorElementCount(nums, left, low, high);
    // 统计 right 元素在当前数组中出现的次数
    int rightCount = findMajorElementCount(nums, right, low, high);
    // 确定当前数组中出现次数最多的元素
    return (leftCount > rightCount) ? left : right;
}

int main() {
    int nums[] = { 3, 3, 4, 2, 2, 2, 2, 4, 4 };
    int numsSize = sizeof(nums) / sizeof(nums[0]);
   
    // 调用解决多数元素问题的函数
    int result = majorityElement(nums, 0, numsSize - 1);

    printf("出现次数最多的元素是: %d\n", result);
    return 0;
}

下面是用分治算法实现多数元素问题的 Java 程序:
public class MajorityElement {

    // 统计 target 元素在 nums 数组中出现的次数
    static int findMajorElementCount(int[] nums, int target, int low, int high) {
        int count = 0;
        for (int i = low; i <= high; i++) {
            if (target == nums[i]) {
                ++count;
            }
        }
        return count;
    }

    static int majorityElement(int[] nums, int low, int high) {
        // 递归出口,当前数组拆分区域中只剩一个元素时,递归才结束
        if (low == high) {
            return nums[low];
        }

        // 递归方式拆分数组
        int mid = (high - low) / 2 + low;
        int left = majorityElement(nums, low, mid);
        int right = majorityElement(nums, mid + 1, high);

        // 确定当前数组中出现次数最多的元素
        if (left == right) {
            return left;
        }
        // 统计 left 元素在当前数组中出现的次数
        int leftCount = findMajorElementCount(nums, left, low, high);
        // 统计 right 元素在当前数组中出现的次数
        int rightCount = findMajorElementCount(nums, right, low, high);
        // 确定当前数组中出现次数最多的元素
        return (leftCount > rightCount) ? left : right;
    }

    public static void main(String[] args) {
        int[] nums = {3, 3, 4, 2, 2, 2, 2, 4, 4};
        int numsSize = nums.length;

        // 调用解决多数元素问题的函数
        int result = majorityElement(nums, 0, numsSize - 1);

        System.out.println("出现次数最多的元素是: " + result);
    }
}

下面是用分治算法实现多数元素问题的 Python 程序:
def find_major_element_count(nums, target, low, high):
    count = 0
    for i in range(low, high + 1):
        if target == nums[i]:
            count += 1
    return count

def majority_element(nums, low, high):
    # 递归出口,当前数组拆分区域中只剩一个元素时,递归才结束
    if low == high:
        return nums[low]

    # 递归方式拆分数组
    mid = (high - low) // 2 + low
    left = majority_element(nums, low, mid)
    right = majority_element(nums, mid + 1, high)

    # 确定当前数组中出现次数最多的元素
    if left == right:
        return left
    # 统计 left 元素在当前数组中出现的次数
    left_count = find_major_element_count(nums, left, low, high)
    # 统计 right 元素在当前数组中出现的次数
    right_count = find_major_element_count(nums, right, low, high)
    # 确定当前数组中出现次数最多的元素
    return left if left_count > right_count else right

if __name__ == "__main__":
    nums = [3, 3, 4, 2, 2, 2, 2, 4, 4]
    nums_size = len(nums)

    # 调用解决多数元素问题的函数
    result = majority_element(nums, 0, nums_size - 1)

    print("出现次数最多的元素是:", result)

运行程序,输出结果为:

出现次数最多的元素是: 2