目录
数学
1 数组中出现次数超过一半的数字
本题五种解法:
排序、hashmap统计次数、随机化、分治法以及摩尔投票法: 核心理念为 票数正负抵消 。此方法时间和空间复杂度分别为 O(N) 和 O(1) ,为本题的最佳解法。
1. 数组排序
数组排序法: 将数组 nums 排序,数组中点的元素 一定为众数。
1.1 函数排序
public int majorityElement(int[] nums) {
return Arrays.stream(nums).sorted().toArray()[nums.length / 2];
}
//或者
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
Arrays.sort比利用stream流再sorted,然后再toArray()更快
复杂度分析
-
时间复杂度:O(nlogn)。将数组排序的时间复杂度为 O(nlogn)。
-
空间复杂度:O(logn)。如果使用语言自带的排序算法,需要使用 O(logn) 的栈空间。如果自己编写堆排序,则只需要使用 O(1) 的额外空间。
1.2 快排
用了一下快排结果。。。。
class Solution {
public int majorityElement(int[] nums) {
qiuckSort(nums,0, nums.length-1);
return nums[nums.length / 2];
}
public void qiuckSort(int[] nums, int low, int high){
if(low > high) return;
int l = low,r = high;
int temp = nums[l];
while(l<r){
while (l<r && temp <= nums[r]) r--;
nums[l] = nums[r];
while (l<r && temp >= nums[l]) l++;
nums[r] = nums[l];
}
nums[l] = temp;
qiuckSort(nums, low, l-1);
qiuckSort(nums, l+1, high);
}
}
2. 哈希表统计次数
遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。此方法时间和空间复杂度均为 O(N)。
3. 随机化
思路
因为超过 一半以上 的数组下标被众数占据了,这样随机挑选一个下标对应的元素并验证,有很大的概率能找到众数。
算法
由于一个给定的下标对应的数字很有可能是众数,随机挑选一个下标,检查它是否是众数,如果是就返回,否则继续随机挑选。
class Solution {
private int randRange(Random rand, int min, int max) {
return rand.nextInt(max - min) + min;
}
private int countOccurences(int[] nums, int num) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] == num) {
count++;
}
}
return count;
}
public int majorityElement(int[] nums) {
Random rand = new Random();
int majorityCount = nums.length / 2;
while (true) {
int candidate = nums[randRange(rand, 0, nums.length)];
if (countOccurences(nums, candidate) > majorityCount) {
return candidate;
}
}
}
}
3.2 简化代码
public int majorityElement(int[] nums) {
while(true){
int candidate = nums[new Random().nextInt(nums.length)];
int count = 0;
for (int i = 0; i < nums.length; i++) {
if(nums[i] == candidate){
count++;
}
}
if(count > nums.length / 2){
return candidate;
}
}
}
官方题解的写的为了易读性,较为繁琐,改完舒服多了~
4. 分治法
如果数 a
是数组 nums
的众数,如果将 nums
分成两部分,那么 a
必定是至少一部分的众数。
使用反证法来证明这个结论。假设 a 既不是左半部分的众数,也不是右半部分的众数,那么 a 出现的次数少于 l / 2 + r / 2 次,其中 l 和 r 分别是左半部分和右半部分的长度。由于 l / 2 + r / 2 <= (l + r) / 2,说明 a 也不是数组 nums 的众数,因此出现了矛盾。所以这个结论是正确的。
这样以来,就可以使用分治法解决这个问题:将数组分成左右两部分,分别求出左半部分的众数 a1 以及右半部分的众数 a2,随后在 a1 和 a2 中选出正确的众数。
算法
使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。长度为 1 的子数组中唯一的数显然是众数,直接返回即可。如果回溯后某区间的长度大于 1,必须将左右子区间的值合并。
- 如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。
- 否则,需要比较两个众数在整个区间内出现的次数来决定该区间的众数。
class Solution {
private int countInRange(int[] nums, int num, int low, int high) {
int count = 0;
for (int i = low; i <= high; i++) {
if (nums[i] == num) {
count++;
}
}
return count;
}
private int majorityElementRec(int[] nums, int low, int high) {
if (low == high) {
return nums[low];
}
int mid = (high - low) / 2 + low;
int left = majorityElementRec(nums, low, mid);
int right = majorityElementRec(nums, mid + 1, high);
if (left == right) {
return left;
}
int leftCount = countInRange(nums, left, low, high);
int rightCount = countInRange(nums, right, low, high);
return leftCount > rightCount ? left : right;
}
public int majorityElement(int[] nums) {
return majorityElementRec(nums, 0, nums.length - 1);
}
}
5. 摩尔(Boyer-Moore)投票法
class Solution {
public int majorityElement(int[] nums) {
int count = 0;
Integer candidate = null;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
}
复杂度分析
时间复杂度:O(n)O(n)。Boyer-Moore 算法只对数组进行了一次遍历。
空间复杂度:O(1)O(1)。Boyer-Moore 算法只需要常数级别的额外空间
拓展: 由于题目说明 给定的数组总是存在多数元素 ,因此本题不用考虑 数组不存在众数 的情况。若考虑,需要加入一个 “验证环节” ,遍历数组 nums 统计 x 的数量。
若 x 的数量超过数组长度一半,则返回 x ;
否则,返回未找到众数;时间和空间复杂度不变,仍为 O(N) 和 O(1) 。
class Solution {
public int majorityElement(int[] nums) {
int x = 0, votes = 0, count = 0;
for(int num : nums){
if(votes == 0) x = num;
votes += num == x ? 1 : -1;
}
// 验证 x 是否为众数
for(int num : nums)
if(num == x) count++;
return count > nums.length / 2 ? x : 0; // 当无众数时返回 0
}
}
2 构建乘积数组
剑指 Offer 66. 构建乘积数组https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
class Solution {
public int[] constructArr(int[] a) {
int len = a.length;
if(len == 0) return new int[0];
int[] b = new int[a.length];
b[0] = 1;
int t = 1 ;
for(int i = 1; i < len; i++) {
b[i] = b[i - 1] * a[i - 1];
}
// 下三角
// System.out.println(Arrays.toString(b));
for (int i = a.length - 1; i > 0; i--) {
t *= a[i];
b[i-1] *= t;
}
return b;
}
}
我也想说我是个傻逼,做题都不知道拆解一下!