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
-10^9 <= nums[i] <= 10^9
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
哈希表解法
使用哈希表来记录数组中元素出现的次数。
哈希表的基本用法
首先,我们需要了解一下哈希表的基本用法。
import java.util.HashMap;
import java.util.Map;
public class HashTableDemo {
public static void main(String[] args) {
// 创建一个哈希表
Map<String, Integer> map = new HashMap<>();
// 添加一个键值对
map.put("apple", 1);
System.out.println("HashMap: " + map); //正确输出: HashMap: {apple=1}
// 检查一下这个键是否在哈希表中
boolean exists = map.containsKey("apple");
System.out.println("Contains 'apple' key: " + exists); //正确输出: Contains 'apple' key: true
// 获取这个键对应的值
Integer value = map.get("apple");
System.out.println("Value for 'apple': " + value); //正确输出: Value for 'apple': 1
// 在哈希表中删掉这个键
map.remove("apple");
System.out.println("HashMap after remove: " + map); //正确输出: HashMap after remove: {}
// 添加一些键值对
map.put("banana", 2);
map.put("cherry", 3);
System.out.println("HashMap: " + map); //正确输出: HashMap: {banana=2, cherry=3}
// 输出哈希表中的键值对
for(Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
//正确输出: Key = banana, Value = 2
// Key = cherry, Value = 3
// 输出哈希表的键值对个数
int size = map.size();
System.out.println("Size of HashMap: " + size); //正确输出: Size of HashMap: 2
}
}
用到的方法:
创建
Map<,> map = new Hash<>();
检查是否包含
boolean con = map.containsKey(key);
插入更新键值对
map.put(key, value);
哈希解法代码
class Solution {
private Map<Integer, Integer> count(int[] nums) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for(int i = 0; i < nums.length; i++) {
// 如果哈希表中未包含这个数字
if(! map.containsKey(nums[i])) {
// 则添加这个数字,对应的次数为1
map.put(nums[i], 1);
} else {
// 否则更新这个数字的次数加一
map.put(nums[i], map.get(nums[i]) + 1);
}
}
return map;
}
public int majorityElement(int[] nums) {
Map<Integer, Integer> map = count(nums);
for(Integer key : map.keySet()) {
// 遍历哈希表,若次数大于n/2,就返回对应的数字
if(map.get(key) > nums.length/2) return key;
}
// 找不到多数元素返回0
return 0;
}
}
排序
思路
排序解法的难点在于理解算法思路,而不是代码过程。
因为题目说多数元素的个数必大于n/2
。
那么将代码升序(或降序)排序之后,多数元素必定会在数组的中间点。
可以这样想象,如果你把数组按照升序或者降序排列,那么多数元素由于数量超过半数,就一定会占据数组中间的位置。无论它在前半部分的数量还是在后半部分的数量,由于它的数量超过了总数的一半,因此它肯定会延伸到数组的中间,也就是中位数的位置。
比如考虑一个由 7 个元素组成的数组 [2, 2, 2, 2, 5, 5, 5],其中出现最多的元素就是 2,数量为 4 > 7/2,排序后得到的数组是 [2, 2, 2, 2, 5, 5, 5],你可以看到元素 “2” 在数组的中间。
这就是为什么在排序后,多数元素会出现在数组的中间。因此,只需要返回排序后数组的中位数就好。在编程实现上,这通常意味着返回索引为 n/2 的元素(在 Java 中,数组的索引从 0 开始)。
代码
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length/2];
}
}
这里面用到了Arrays类的一个方法,即排序方法Arrays.sort()
,参数是数组,默认升序排序。
分治法
思路
分治法的过程 : 先分解,后合并。
也就是将数组分成两半,两个子问题来处理,然后再将两个子问题得到的结果合并,得到大问题的答案。
对应在这个问题当中,可以得到
大问题:求整个数组的多数元素
子问题:将数组进行划分,左右两个子数组的多数元素
然后再依次进行划分,直到子数组的长度为一时,则唯一的元素就是它的多数元素,进行返回,也就是递归条件。
代码
基于这里,可以得到我们的递归函数中划分部分代码。
private int Rec(int[] nums, int start, int end) {
// 当数组只有一个元素时,结束递归
if(start == end) return nums[start];
// 算出中间点,进行划分
int mid = (start + end) /2;
// 得到左子数组的多数元素
int left = Rec(nums, start, mid);
// 得到右子数组的多数元素
int right = Rec(nums, mid, end);
// 合并过程
}
加上合并即全部代码:
class Solution {
// 计算子数组中多数元素出现的个数
private int count(int[] nums, int start, int end, int num){
int count = 0;
for(int i = start; i <= end; i++) {
if(nums[i] == num) count++;
}
return count;
}
private int Rec(int[] nums, int start, int end) {
// 当数组只有一个元素时,结束递归
if(start == end) return nums[start];
// 算出中间点,进行划分
int mid = (start + end) /2;
// int mid = (end - start) / 2 + start;
// 得到左子数组的多数元素
int left = Rec(nums, start, mid);
// 得到右子数组的多数元素
// 注意这里起点时mid+1
int right = Rec(nums, mid+1, end);
// 合并过程
// 若左右子数组的多数元素相同,则直接返回
if(left == right) return left;
// 否则算出左右多数元素在整个数组出现的次数,次数多的则是大数组的多数元素
int leftcount = count(nums, start, end, left);
int rightcount = count(nums, start, end, right);
// if(leftcount > rightcount) return left;
// else return right;
return leftcount > rightcount ? left : right;
}
public int majorityElement(int[] nums) {
return Rec(nums, 0, nums.length-1);
}
}