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 * 10^4
-109 <= nums[i] <= 109
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
解题思路:
首先方法需要int类型的返回值,返回数组中的多数元素(指在数组中出现次数大于nums.length/2的元素),可以假设数组非空,并且总是存在多数元素,也可以理解为众数。
方法①:
第一眼看到后的最简单的想法就是暴力破解,双重for循环,外层枚举数组中每一个元素,内层再遍历一遍数组统计其出现次数。只要超过数组元素个数的一半,那么就可以返回该元素。
具体的Java实现代码如下:
class Solution {
public int majorityElement(int[] nums) {
int number=0;
if(nums==null || nums.length==0){
return 0;
}
else if(nums.length==1){
number=nums[0];
return number;
}
for(int i=0;i<=nums.length-1;i++){
int count=1;
for(int j=i+1;j<=nums.length-1;j++){
if(nums[i]==nums[j]){
count++;
}
}
if(count>nums.length/2){
number=nums[i];
}
}
return number;
}
}
但是这个算法的时间复杂度为 O(n^2),其中 n 是数组的长度。外层循环需要遍历数组中的每一个元素,内层循环需要遍历数组中剩余的元素,因此总共需要进行 n(n-1)/2 次比较。算法的空间复杂度为 O(1),因为只需要常数级别的额外空间用于存储变量,没有使用额外的数组或集合来存储元素。
虽然提交成功了,但是花费的时间太长了,所以这种暴力破解的方式不可取,需要优化。
方法②:
重新审视一下题意,多数元素即数组中的众数,出现次数超过数组元素的一半,但是因为数组内的元素是乱序的,所以没办法判断。但是如果是有序数组的话,那么数组中间位置的元素那么必定就是多数元素。因为出现次数会超过数组元素的一半!可行。因此接收到数组后先进行排序!
具体的Java实现代码如下:
class Solution {
public static int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length >> 1];
}
}
return nums[nums.length >> 1];
返回排序后的数组 nums 中索引为 nums.length >> 1 的元素,也就是中间位置的元素,即为该数组的众数。
PS:nums.length >> 1
是一个位运算操作,它等价于将数组 nums
的长度除以二(即右移一位),得到中间位置的索引。
在这段代码中,我们已经将数组 nums
排好序了,所以中间位置的数就是众数。因为众数最多,所以无论是数组长度为偶数还是奇数,中间位置的数都会是众数或其一个相邻的数,因此返回 nums[nums.length >> 1]
可以有效地找到众数。
进行用例测试,看看能否解决需求:
可以看到这次的速度快了不止一点,成功满足需求!
进行题后反思:这段代码的时间复杂度是 $O(n\log n)$,其中 $n$ 是输入数组的长度。这是因为 Arrays.sort(nums)
的时间复杂度是 $O(n\log n)$,所以整个算法的时间复杂度也是 $O(n\log n)$。空间复杂度是 $O(\log n)$,因为 Arrays.sort(nums)
使用了快速排序算法,其空间复杂度是 $O(\log n)$。所以整个算法的空间复杂度与快速排序算法的空间复杂度相同,都是 $O(\log n)$。
方法③:
再进阶考虑一下,尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
思路如下:
要设计时间复杂度为 $O(n)$、空间复杂度为 $O(1)$ 的算法解决此问题,可以使用摩尔投票算法(Boyer-Moore Voting Algorithm)。算法基于一个简单的事实:出现次数大于 $\lfloor n/2\rfloor$ 的数字最多只能有一个,因此只需要遍历一遍数组,记录当前数字出现的次数,同时记录候选数字,如果遍历到下一个数字和候选数字相同则次数加一,否则次数减一,当次数为零时更换候选数字。最后剩下的候选数字就是出现次数大于$\lfloor n/2\rfloor$ 的数字。
具体的Java实现代码如下:
class Solution {
public int majorityElement(int[] nums) {
int count = 0, candidate = 0; // 定义计数器和候选数字
for (int num : nums) { // 遍历数组
if (count == 0) { // 若计数器为 0
candidate = num; // 则当前数字作为候选数字
}
count += (num == candidate) ? 1 : -1; //使用三元运算符? 如果当前数字和候选数字相同,则计数器加 1,否则减 1
}
return candidate; // 返回候选数字,即出现次数大于 n/2 的数字
}
}
进行用例测试,看看能否解决需求:
可以看到成功满足需求!
题后反思:
方法③的代码时间复杂度是 $O(n)$,因为只遍历了一遍数组,并且没有额外的循环或递归操作。而空间复杂度是 $O(1)$,因为它只使用了常量级别的额外空间来存储计数器和候选数字。满足我们进阶的需求!
时间:2023/05/06-23:13分 以上仅为个人对力扣刷题的学习记录,有错误欢迎各位指出,交流学习!感谢观看