【刷题之路】LeetCode 169 多数元素

一、题目描述

原题连接: LeetCode 169 多数元素

题目描述:
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:
输入:nums = [3,2,3]
输出:3

示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2

提示:
n == nums.length
1 <= n <= 5 * 104
-109 <= nums[i] <= 109

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

二、解题

1、方法1——暴力法

1.1、思路分析

对于每个元素,我们都向它的后面遍历,找到与它相同的元素,用一个变量count保存,若count大于⌊ n/2 ⌋则直接返回;
因为是要找到出现次数大于⌊ n/2 ⌋,所以当我们遍历到⌊ n/2 ⌋ + 1个元素时,就不用再往后找了,因为就算⌊ n/2 ⌋ + 2个元素是第一次出现的,那即使它后面的元素都与它相等,那他出现的次数也不可能大于⌊ n/2 ⌋。就比如下面这个例子:
在这里插入图片描述

故外层循环我们只需要执行⌊ n/2 ⌋ + 1次。

1.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int majorityElement1(int* nums, int numsSize) {
	assert(nums);
	int N = numsSize / 2;
	int i = 0;
	int j = 0;
	for (i = 0; i < N + 1; i++) {
		int count = 1;
		for (j = i + 1; j < numsSize; j++) {
			if (nums[j] == nums[i]) {
				count++;
			}
		}
		if (count > N) {
			break;
		}
	}
	return nums[i];
}

时间复杂度:O(n^2),n为数组中元素的个数,两层循环的次数都是有n决定,故时间复杂度为O(O ^2)。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

但这个方法的复杂度过大,像LeetCode这样“斯文”的网站是不给过的。

2、方法2——排序

2.1、思路分析

如果将数组中的元素按升序或降序的顺序排序,那么下下标为⌊ n/2 ⌋(数组的下标从0开始,那么下标为⌊ n/2 ⌋相当于第⌊ n/2 ⌋ + 1个元素)的元素就一定是出现次数超过⌊ n/2 ⌋的元素,因为在排序后相同的元素一定是紧挨在一起的,因为那一串出现次数大于⌊ n/2 ⌋的元素的长度一定大于数组长度的一半,所以不管那一串是排在开头、中间、还是末尾。都会把下标⌊ n/2 ⌋给占了,就如下图:
在这里插入图片描述

2.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int majorityElement2(int* nums, int numsSize) {
	assert(nums);
	// 用冒泡排序先对数组进行排序
	int i = 0;
	int j = 0;
	for (i = 0; i < numsSize - 1; i++) {
		int flag = 1;
		for (j = 0; j < numsSize - 1 - i; j++) {
			if (nums[j] > nums[j + 1]) {
				int temp = nums[j];
				nums[j] = nums[j + 1];
				nums[j + 1] = temp;
				flag = 0;
			}
		}
		if (flag) {
			break;
		}
	}
	return nums[numsSize / 2];
}

时间复杂度:O(n^2),其中n为数组元素的个数。
空间复杂度:O(1),我们只需要用到常数级额外空间。

3、方法3——一对一打擂台

3.1、思路分析

我们可以先假设有这样一个擂台赛:
有若干个队在争抢一块“令牌”,比赛开始先把令牌交给第一个上台的人,若下一个上台的人是自己队的就可以留在台上一起守护“令牌”,若不是,就只能一对一打,但最后都同归于尽。“令牌”交给下一个上台的人。
最后留在台上手持“令牌”人是哪一队的,哪一队就获胜。
显而易见,一定是人数最多的那一队获胜。

这个方法扩展到数组中就是:
我们可以吧每个元素的数组的不同把它们分成不同的队伍,上台的顺序就是遍历数组的顺序。用一个变量token来表示令牌,用一个变量count来表示在台上的某一队的人数。当下一个上台的元素与台上的是同一队时,count++,否则count–,当count=0时,就将令牌交给下一个要遍历到的元素。

3.2、代码实现

有了以上分析,那我们写起代码来也就水到渠成了:

int majorityElement3(int* nums, int numsSize) {
	int token = nums[0]; // 先把令牌给第一个上台的人
	int i = 0;
	int count = 0; // 统计台上的一共有多少个某队的人
	for (i = 0; i < numsSize; i++) {
		if (nums[i] == token) {
			count++;
		}
		else {
			count--;
			if (count == 0) {
				token = nums[i + 1];
			}
		}
	}
	return token;
}

时间复杂度:O(n),其中n为数组元素个数,我们只需要遍历一遍数组即可。
空间复杂度:O(1),我们只需要用到常量级的额外空间。

4、方法4——分治法

4.1、思路分析

如果a是数组nums的众数,那如果我们将nums分成两部分,那么a一定至少是一部分的众数。
那我们分别求出这两部分各自的众数,再将这两个众数进行比较,如果这两个众数不同,那就看看哪个众数在整个区间内出现的频次更高,更高的那个就是整个区间的众数,如果这两个众数相同,则默认返回任意一个都行。
例如:
在这里插入图片描述
左边区间的众数是2,右边的众数也是2,所以整个区间的众数就是2。

那么左右区间的众数又是怎么求的呢?
其实思路和上面也是一样的,我们可以把左右区间也各自看做一个完整的区间,再重复上面找众数的过程即可:
在这里插入图片描述
如此往复,知道区间长度都为1,那其众数就是它本身。其实整个过程就是一个一直往下分,最后再合并的过程。如下图:
在这里插入图片描述

4.2、代码实现

有了以上分析,那我们写起代码来虽说不是水到渠成,但也有方法理论了:
我们首先写一个递归返回众数的函数:

// lo指向一个区间的起始地址,hi区间的结尾地址
int get_mode(int* lo, int* hi) {
	if ( hi - lo == 0 ) {
		return *lo; // 说明只有一个元素,直接返回lo[0]
	}
	// 拆分成更小的区间

	int mid = (hi - lo) / 2 ; // 获得区间中点
	int left = get_mode(lo, lo + mid); // 获得左区间的众数
	int right = get_mode(lo + mid + 1, hi); // 获得右区间的众数

	// 对两个区间的众数进行比较
	if (left == right) {
		return left;
	}
	else if (left == -1) {
		return right;
	}
	else if (right == -1) {
		return left;
	}

	// 不相等就要统计整个区间内左右众数出现的频数
	int left_count = get_count(left, lo, hi); // 统计区间内left出现的频数
	int right_count = get_count(right, lo, hi); // 统计区间内right出现的频数
	if (left_count > right_count) {
		return left;
	}
	else if (left_count < right_count) {
		return right;
	}
	else {
		return -1;
	}
}

我们现在来写,统计区间内众数频数的函数:

int get_count(int num, int* left, int* right) {
	assert(left && right);
	int count = 0; // 记录频数
	while (left <= right) {
		if (*left == num) {
			count++;
		}
		left++;
	}
	return count;
}

// 这样我们只需要在函数里调用一次get_mode即可

int majorityElement4(int* nums, int numsSize) {
	return get_mode(nums, nums + numsSize - 1);
}

补充: 有人可能对我那些-1会产生疑问,其实这时为了避免一些特殊情况的:比如下面这个例子:
在这里插入图片描述

比如这里的(1)组,其左区间的众数是9,右区间的众数是10,但这两个众数在整个区间中出现的频率相同啊,那到底该选哪个,若是选9,那万一(2)组的众数是10,那返回判断(3)组的时候就又将不好判断了,若是选10,那返回判断(3)组的时候也又不好判断。
那要怎么办呢?
其实当出现(1)组这种情况的时候,我们完全可以把(1)组的众数给抛弃掉,因为如果像(1)组这种情况,(3)组的众数完全是由(2)组的众数决定的,因为只有当(1)组的众数与(2)相同时,(3)组的众数才能被确定,反之(3)组将还是难以确定众数。

所以,我们可以直接把(1)组的众数设为-1,然后在判断众数的时候再加上个条件:如果左区间的众数是-1,则整个区间的众数就是右区间的众数,如果右区间的众数是-1,则整个区间的众数就是左区间的众数。
这样就能很好的解决问题了,例如下例:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林先生-1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值