您要求提供O(n log n)
,我在这里给你一个技术上的O(n)
解决方案。
正如@SayonjiNakate所暗示的那样,使用分段树的解决方案(我在我的实现中使用了Fenwick树)在O(n log M)
时间运行,其中M
是数组中最大的可能值。假设M
是恒定的(嘿它是以int的大小为界!),算法运行在O(n)
。对不起,如果你觉得我在报道复杂性时作弊,但嘿,从技术上讲这是真的! = D
首先,请注意,通过反转和取反数组,问题“左侧的较小元素的数量”等同于“右侧的较大元素的数量”问题。所以,在我的下面的解释中,我只描述了“左侧较小元素的数量”,我称其为“lesser_left_count”。
算法为lesser_left_count:
的想法是能够找到总比一个具体的数字更小的数字。
定义的阵列tree
与尺寸高达MAX_VALUE
,将存储值1
为看到号码和0
否则。
然后,当我们遍历阵列中,当我们看到了许多num
,只是分配值1
到tree[num]
(更新操作)。然后,数字num
的lesser_left_count是从1
到num-1
(总和操作)的总和,因为当前位置左侧的所有较小数字将被设置为1
。
简单吧?如果我们使用Fenwick tree,则可以在O(log M)
时间内完成更新和求和操作,其中M
是数组中的最大可能值。由于我们遍历数组结束,总时间为O(n log M)
,或者干脆O(n)
如果我们把M
为常数(在我的代码,我将它设置为2^20-1 = 1048575
,所以它的O(20n)
,这是O(n)
)
的唯一的缺点天真的解决方案是,它使用了大量的内存,因为M
变得更大(我在我的代码中设置了M=2^20-1
,这占用了大约4MB的内存)。这可以通过将数组中的不同整数映射为更小的整数(以保持顺序的方式)来改进。通过对数组进行排序,可以简单地在O(n log n)
中完成映射(好吧,这使得复杂度为O(n log n)
,但是因为我们知道n < M
,您可以将其视为O(n)
)。因此M
的数字可以重新解释为“阵列中不同元素的数量”。
所以内存也不会有任何问题了,因为如果这种改进后,你确实需要大量的内存,这意味着有数组中许多个不同的数字,和O(n)
的时间复杂度将已经过无论如何都是在普通机器中计算的高。
为了简单起见,我没有在代码中加入这种改进。
哦,既然Fenwick树只适用于正数,我将数组中的数字转换为最小值1.请注意,这不会改变结果。
Python代码:
MAX_VALUE = 2**20-1
f_arr = [0]*MAX_VALUE
def reset():
global f_arr, MAX_VALUE
f_arr[:] = [0]*MAX_VALUE
def update(idx,val):
global f_arr
while idx<MAX_VALUE:
f_arr[idx]+=val
idx += (idx & -idx)
def cnt_sum(idx):
global f_arr
result = 0
while idx > 0:
result += f_arr[idx]
idx -= (idx & -idx)
return result
def count_left_less(arr):
reset()
result = [0]*len(arr)
for idx,num in enumerate(arr):
cnt_prev = cnt_sum(num-1)
if cnt_sum(num) == cnt_prev: # If we haven't seen num before
update(num,1)
result[idx] = cnt_prev
return result
def count_left_right(arr):
arr = [x for x in arr]
min_num = min(arr)
if min_num<=0: # Got nonpositive numbers!
arr = [min_num+1+x for x in arr] # Convert to minimum 1
left = count_left_less(arr)
arr.reverse() # Reverse for greater_right_count
max_num = max(arr)
arr = [max_num+1-x for x in arr] # Negate the entries, keep minimum 1
right = count_left_less(arr)
right.reverse() # Reverse the result, to align with original array
return (left, right)
def main():
arr = [1,1,3,2,4,5,6]
(left, right) = count_left_right(arr)
print 'Array: ' + str(arr)
print 'Lesser left count: ' + str(left)
print 'Greater right cnt: ' + str(right)
if __name__=='__main__':
main()
会产生:
Original array: [1, 1, 3, 2, 4, 5, 6]
Lesser left count: [0, 0, 1, 1, 3, 4, 5]
Greater right cnt: [5, 5, 3, 3, 2, 1, 0]
,或者如果你想Java代码:
import java.util.Arrays;
class Main{
static int MAX_VALUE = 1048575;
static int[] fArr = new int[MAX_VALUE];
public static void main(String[] args){
int[] arr = new int[]{1,1,3,2,4,5,6};
System.out.println("Original array: "+toString(arr));
int[][] leftRight = lesserLeftRight(arr);
System.out.println("Lesser left count: "+toString(leftRight[0]));
System.out.println("Greater right cnt: "+toString(leftRight[1]));
}
public static String toString(int[] arr){
String result = "[";
for(int num: arr){
if(result.length()!=1){
result+=", ";
}
result+=num;
}
result+="]";
return result;
}
public static void reset(){
Arrays.fill(fArr,0);
}
public static void update(int idx, int val){
while(idx < MAX_VALUE){
fArr[idx]+=val;
idx += (idx & -idx);
}
}
public static int cntSum(int idx){
int result = 0;
while(idx > 0){
result += fArr[idx];
idx -= (idx & -idx);
}
return result;
}
public static int[] lesserLeftCount(int[] arr){
reset();
int[] result = new int[arr.length];
for(int i=0; i<arr.length; i++){
result[i] = cntSum(arr[i]-1);
if(cntSum(arr[i])==result[i]) update(arr[i],1);
}
return result;
}
public static int[][] lesserLeftRight(int[] arr){
int[] left = new int[arr.length];
int min = Integer.MAX_VALUE;
for(int i=0; i<arr.length; i++){
left[i] = arr[i];
if(min>arr[i]) min=arr[i];
}
for(int i=0; i<arr.length; i++) left[i]+=min+1;
left = lesserLeftCount(left);
int[] right = new int[arr.length];
int max = Integer.MIN_VALUE;
for(int i=0; i<arr.length; i++){
right[i] = arr[arr.length-1-i];
if(max<right[i]) max=right[i];
}
for(int i=0; i<arr.length; i++) right[i] = max+1-right[i];
right = lesserLeftCount(right);
int[] rightFinal = new int[right.length];
for(int i=0; i<right.length; i++) rightFinal[i] = right[right.length-1-i];
return new int[][]{left, rightFinal};
}
}
这将产生相同的结果。
问题在哪里? –
(1)请发布您正在使用的实际代码;明确地理解比叙述更容易理解。 (2)这可能比Stack Overflow更适合Code Review。 (3)尽管你可能能够做出重大改进,但从N^2到N^2/2是一个线性因素,所以你仍然是O(N^2)。 – chrylis
这个问题在我看来很清楚,他要求的是一个比蛮力更好的算法 – arynaq