これがO(n log n)
解決策です。
@SayonjiNakate で示唆されているように、セグメント ツリー (実装では Fenwick ツリーを使用) を使用したソリューションはO(n log M)
時間内に実行されます。ここM
で、 は配列内で可能な最大値です。
まず、「左側の小さい要素の数」の問題は、配列を反転して否定することにより、「右側の大きい要素の数」の問題と等価であることに注意してください。したがって、以下の説明では、「左側の小さい要素の数」のみを説明します。これを「lesser_left_count」と呼びます。
lesser_left_count のアルゴリズム:
アイデアは、特定の数よりも小さい数の合計を見つけることができるようにすることです。
tree
サイズが uptoの配列を定義します。これは、表示された数値などMAX_VALUE
の値を格納します。1
0
次に、配列をトラバースし、数値が表示されたら、その値を(更新操作num
)に代入します。次に、数値の lesser_left_count は、現在の位置の左側にあるすべての小さい数値が に設定されているため、これまでの から (合計演算)までの合計です。1
tree[num]
num
1
num-1
1
シンプルですよね?フェンウィック ツリーを使用すると、更新と合計の操作をそれぞれO(log M)
時間内に実行できます。ここM
で、 は配列内で可能な最大値です。配列を繰り返し処理しているため、合計時間はO(n log M)
です。
単純なソリューションの唯一の欠点は、M
大きくなるにつれて大量のメモリを使用することです (コードで設定M=2^20-1
すると、約 4MB のメモリが必要になります)。これは、配列内の個別の整数をより小さな整数に (順序を維持する方法で) マッピングすることで改善できます。O(n log n)
マッピングは、配列をソートするだけで実行できます。したがって、数値は「配列内の個別の要素の数」M
として再解釈できます。
したがって、メモリはもう問題になりません。なぜなら、この改善の後、実際に巨大なメモリが必要な場合、それは配列に非常に多くの異なる数値がありO(n)
、通常のマシンで計算するには時間の複雑さがすでに高すぎることを意味するからです。とりあえず。
簡単にするために、コードにはその改善を含めませんでした。
ああ、フェンウィック ツリーは正の数に対してのみ機能するため、配列内の数値を最小の 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()
生成されます:
元の配列: [1, 1, 3, 2, 4, 5, 6]
少ない方の左の数: [0, 0, 1, 1, 3, 4, 5]
大きい右 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};
}
}
同じ結果が得られます。