方法1: 树状数组动态维护前缀和 (关于什么是树状数组,网上介绍的有很多,这儿不作赘述)
/**
* 使用树状数组
* @param Integer[] $nums
* @return Integer[]
*/
function countSmaller($nums) {
$len = count($nums);
if ($len == 0) {
return [];
}
// 将nums中的元素去重再排序
$uniqueArr = [];
foreach ($nums as $num) {
$uniqueArr[$num] = 1;
}
$uniqueArr = array_keys($uniqueArr);
sort($uniqueArr);
$uniqueArr = array_flip($uniqueArr);
$size = count($uniqueArr);
// 初始化树状数组C
$c = array_fill(1, $size, 0);
// 函数用于返回nums中的元素对应在C中的索引(从1开始)
$findIdx = function($num) use ($uniqueArr) {
return $uniqueArr[$num] + 1;
};
// 获取某数的低位码数,如6(110) 返回 2(10)
$getLowBit = function($x) {
return $x & (-$x);
};
// 更新C中的数据
$updateC = function($idx) use (&$c, $size, $getLowBit) {
while ($idx <= $size) {
$c[$idx]++;
$idx += $getLowBit($idx);
}
};
// 结果数组
$updateRlt = function($i, $c, &$rlt, $idx) use ($getLowBit) {
$rlt[$i] = 0;
while ($idx > 0) {
$rlt[$i] += $c[$idx];
$idx -= $getLowBit($idx);
}
};
$rlt = new SplFixedArray($len);
// 倒序遍历nums
for ($i=$len-1; $i>=0; $i--) {
// 获取nums[i]对应的c中的索引
$idx = $findIdx($nums[$i]);
// 更新c中的次数
$updateC($idx);
// 更新结果数组
$updateRlt($i, $c, $rlt, $idx-1);
}
return $rlt->toArray();
}
方法二: 归并排序法
<?php
class Solution {
private $rlt = null;
/**
* 方法二,归并排序法
* @param $nums
*/
function countSmallerV2($nums) {
$len = count($nums);
if ($len == 0) {
return [];
}
// 为了维持排序的元素索引,此处将索引和原数组同步进行归并排序
$idxArr = array_keys($nums);
$arrForMerge = new SplFixedArray($len);
$arrForIdx = new SplFixedArray($len);
$this->rlt = array_fill(0, $len, 0);
$this->sort($nums, $idxArr, 0, $len-1, $arrForMerge, $arrForIdx);
return $this->rlt;
}
function sort(&$nums, &$idxArr, $begin, $end, &$arrForMerge, &$arrForIdx) {
if ($begin >= $end) {
return;
}
$mid = $begin + intdiv($end-$begin, 2);
$this->sort($nums, $idxArr, $begin, $mid, $arrForMerge, $arrForIdx);
$this->sort($nums, $idxArr,$mid+1, $end, $arrForMerge, $arrForIdx);
$this->merge($nums, $idxArr, $begin, $mid, $end, $arrForMerge, $arrForIdx);
}
function merge(&$nums, &$idxArr, $begin, $mid, $end, &$arrForMerge, &$arrForIdx) {
$i = $begin;
$j = $mid+1;
$k = $begin;
while ($i <= $mid && $j <= $end) {
if ($nums[$i] <= $nums[$j]) {
$arrForMerge[$k] = $nums[$i];
$arrForIdx[$k] = $idxArr[$i];
// 关键处在此,当左边的数小于等于右边的数时,已经排序好了的在临时数组中的右边的数(这些数小于当前左边的数)的个数就是原数组中在左边数右侧且小于左边数的贡献值,将其累加
// 这儿的idexArr[i]体现了索引数组与原数组同步排序的好处,可以拿到元素在原数组中的索引。
$this->rlt[$idxArr[$i]] += $j - $mid - 1;
$k++;
$i++;
} else {
$arrForMerge[$k] = $nums[$j];
$arrForIdx[$k] = $idxArr[$j];
$j++;
$k++;
}
}
while ($i <= $mid) {
$arrForMerge[$k] = $nums[$i];
$arrForIdx[$k] = $idxArr[$i];
$this->rlt[$idxArr[$i]] += $j - $mid - 1;
$k++;
$i++;
}
while ($j <= $end) {
$arrForMerge[$k] = $nums[$j];
$arrForIdx[$k] = $idxArr[$j];
$j++;
$k++;
}
for ($i=$begin; $i<$k; $i++) {
$nums[$i] = $arrForMerge[$i];
$idxArr[$i] = $arrForIdx[$i];
}
}
}
$s = new Solution();
var_dump($s->countSmallerV2([5,2,6,1]));