462. 最少移动次数使数组元素相等 II
思路:改变数组元素使所有的数组元素都相等:
Input:
[1,2,3]
Output:
2
Explanation:
Only two moves are needed (remember each move increments or decrements one element):
[1,2,3] => [2,2,3] => [2,2,2]
首先这一题我想到的就是找到众数,但其实这个逻辑是有问题的,将所有的数改变成众数是可以减少改变的元素个数,但是并不能减少改变的次数(移动次数),因此我们需要找到的是中位数,因为所有数离中位数的距离之和是最近的。
之前写过这个题目的题解,解法比较简单,使用的双指针,数组排序后两端做差相加即可,解题链接戳这里(●ˇ∀ˇ●)
但是今天介绍另一种方法,上一种方法的时间复杂度为O(nlogn),这一种方法我们的时间复杂读可以达到O(n),也就是使用快速排序法,只排好中位数及中位数之前的元素即可,因为我们的目的就是找到中位数,从而避免直接调用Arrays.sort() 全数组排序的O(nlogn)时间复杂度,剩下的操作是一样的,这里只重点介绍如何找到中位数。
public int minMoves2(int[] nums) {
int move = 0;
int median = findKthSmallest(nums, nums.length / 2);
for (int num : nums) {
move += Math.abs(num - median);
}
return move;
}
(这里还需要注意的是,这里实际上不一定是中位数,如果数组长度是奇数,那么找到的数就是中位数,如数组长度是偶数,那么找到的这个数可能是位于最中间的两个数的其中一个,这个并不会影响结果的,中间的两个中间数任选一个即可,不用严格的要求是这两个中间数的平均数。)
快速排序的核心思想就是,先找到一个基准数,然后以这个基准数为标准,将大于或者小于该数的放在数组的两边,这样一来,在该数左边的就是小于该数的所有数,该数右边的数就是大于该数的所有数,我们可以通过判断基准数的索引来知道是否此时的基准数就是中位数,如果索引大于 nums.length / 2 则再往基准数左边找,如果索引小于 nums.length / 2 则再往基准数右边找,如果刚好相等那么直接返回这个数,这个数就是中位数,找中位数的代码如下:
public int findKthSmallest(int[] nums, int k) {
int l = 0, h = nums.length - 1;
while (l < h) {//不能加=
int j = partition(nums, l, h);
if (j == k) {
break;
}
if (j < k) {
l = j + 1;
} else {
h = j - 1;
}
}
return nums[k];
}
然后采用快速排序、交换数组元素:
public int partition(int[] nums, int l, int h) {
int i = l, j = h + 1;
while (true) {
while (nums[++i] < nums[l] && i < h) ;
while (nums[--j] > nums[l] && j > l) ;
if (i >= j) {
break;
}
swap(nums, i, j);
}
swap(nums, l, j);
return j;
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}