最近、ソフトウェアエンジニア向けのMicrosoftインタビューの質問に出会いました。
正および負の整数の配列が与えられた場合、一方の端に正の整数、もう一方の端に負の整数があるように再配置しますが、元の配列での出現順序を保持します。
たとえば、[1, 7, -5, 9, -12, 15]
答えは次のとおりです。[-5, -12, 1, 7, 9, 15]
これはO(n)で行う必要があります。
O(n)で簡単にできますが、元の配列のように要素の順序をどのように維持できるかを考えることはできません。 O(n)複雑さを忘れたら、空間と時間の複雑さを考慮せずに要素の順序を保持する方法を教えてください。
[〜#〜] edit [〜#〜]:実際の問題では、O(1)スペースの複雑さも必要です。
一定のスペース(ただし2次時間)でこの結果を達成するには、配列の両端に1つのキューを配置する2キューアプローチを使用できます(オランダ国旗アルゴリズムと同様)。アイテムを左から右に読む:アイテムを左のキューに追加することは、そのままにすることを意味し、アイテムを右のキューに追加することは、キューにないすべての要素を左に1つシフトし、追加したアイテムを最後に配置することを意味します。次に、キューを連結するには、2番目のキューの要素の順序を単に逆にします。
これは、O(n)操作(要素を左にシフト))を最大O(n)回まで実行し、O(n²)の実行時間を生成します。
マージソートに似た方法を使用すると、O(n log n)の複雑さを軽減できます。配列を2つの半分にスライスし、[N P] [N P]
の形式で再帰的にソートしてから、最初のP
2番目のN
in O(n) time(正確に同じサイズではないが、まだ線形である場合は少し複雑になります).
これをO(n) time。
[〜#〜] edit [〜#〜]:実際、リンクリストの洞察は正しいです。データが二重にリンクされたリストとして提供されている場合、O(n) time、O(1) space:
sort(list):
negative = empty
positive = empty
while (list != empty)
first = pop(list)
if (first > 0)
append(positive,first)
else
append(negative,first)
return concatenate(negative,positive)
最初と最後の要素へのポインタを保持するリンクリスト実装では、ポップ、アペンド、および連結はすべてO(1)操作であるため、合計の複雑さはO(n)です。スペース、どの操作もメモリを割り当てないため(appendは単にpopによって解放されたメモリを使用するだけです)、全体としてO(1)です。
O(n) time O(1) space solution)の定数バージョンです。maxValue*(maxValue + 1)がIntegerより小さいと仮定しています。 MAX_VALUE、ここでmaxValueは、配列内の最大値から最小値を引いた結果で、元の配列を一時配列として使用して結果を保存します。
public static void specialSort(int[] A){
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for(int i=0; i<A.length; i++){
if(A[i] > max)
max = A[i];
if(A[i] < min)
min = A[i];
}
//Change all values to Positive
for(int i=0; i<A.length; i++)
A[i]-= min;
int newMax = max-min+1;
//Save original negative values into new positions
int currNegativeIndex = 0;
for(int i=0; i<A.length; i++)
if(A[i]%newMax < (-min))
A[currNegativeIndex++] += (A[i]%newMax)*newMax;
//Save original positive values into new positions
int currPositiveIndex = currNegativeIndex;
for(int i=0; i<A.length; i++)
if(A[i]%newMax > (-min))
A[currPositiveIndex++] += (A[i]%newMax)*newMax;
//Recover to original value
for(int i=0; i<A.length; i++){
A[i] = A[i]/newMax + min;
}
}
答えが単純すぎるように見えるので、質問を正しく理解しているかどうかはわかりません。
Pythonで簡単に行う方法を次に示します。最初に負の配列を作成し、次に正を追加する点で、上記とわずかに異なります。したがって、それほど効率的ではありませんが、それでもO(n)です。
>>> a = [1,7,-5,9,-12,15]
>>> print [x for x in a if x < 0] + [y for y in a if y >= 0]
[-5, -12, 1, 7, 9, 15]
編集:OK、今O(1)スペースの互換性ははるかに難しくなります。O(n)時間でそれを達成する方法に興味がありますそれが役立つ場合、O(1)スペースの複雑さを維持する方法ですが、O(n ^ 2)時間の複雑さを必要とします:
O(n)およびスペースO(1)で実行できます。
配列を3回スキャンし、一部の値を慎重に変更する必要があります。
仮定:サイズNの配列の最大値は_(N+1) * Integer.MAX_VALUE
_よりも小さくなければなりません。
配列内のいくつかの正の値を適切に変更するため、この仮定が必要です。
配列の先頭から開始し、「swap "最初に見つかった正の数(たとえば、インデックスi
)を最初に見つかった負の数(たとえば、j
)。場所に関して負の数が考慮されているため、スワップは問題ありません。
i
とj
の間に他の正の数が存在する可能性があるため、問題は正の数です。この問題を処理するには、スワップする前に、その値に正数のインデックスを何らかの方法でエンコードする必要があります。そのため、最初の時点でそれがどこにあったかがわかります。 a[i]=(i+1)*(max)+a[i]
でこれを行うことができます。
コードは次のとおりです。
_import Java.util.Arrays;
public class LinearShifting {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] a = {-1,7,3,-5,4,-3,1,2};
sort(a);
System.out.println(Arrays.toString(a)); //output: [-1, -5, -3, 7, 3, 4, 1, 2]
}
public static void sort(int[] a){
int pos = 0;
int neg = 0;
int i,j;
int max = Integer.MIN_VALUE;
for(i=0; i<a.length; i++){
if(a[i]<0) neg++;
else pos++;
if(a[i]>max) max = a[i];
}
max++;
if(neg==0 || pos == 0) return;//already sorted
i=0;
j=1;
while(true){
while(i<=neg && a[i]<0) i++;
while(j<a.length && a[j]>=0) j++;
if(i>neg || j>=a.length) break;
a[i]+= max*(i+1);
swap(a,i,j);
}
i = a.length-1;
while(i>=neg){
int div = a[i]/max;
if(div == 0) i--;
else{
a[i]%=max;
swap(a,i,neg+div-2);// minus 2 since a[i]+= max*(i+1);
}
}
}
private static void swap(int[] a, int i , int j){
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
_
編集(2015年5月29日):表示順序を維持するための要件を見落としていたため、以下の回答では質問のすべての要件を満たしていません。ただし、元の応答は一般的な関心のために残しておきます。
これは、「パーティション」として知られるクイックソートの非常に重要なサブルーチンの特別なバージョンです。定義:N個の数値エントリを持つ配列Aは、0 <= i <pのA [i] <Kおよびp <= j <NのA [j]> = Kの場合、インデックスpの値Kについて分割されます。エントリがK未満(p = Nを意味する)またはK以上(p = 0を意味する)です。問題の問題については、K = 0の周りで配列を分割します。
O(n) timeで任意の値Kについてソートされていない配列を分割し、O(1) additionalメモリ:配列の両端から値を移動し、配列の両端から順番にステップ実行します。配列の各辺で1つの誤った値が見つかったときにスワップを実行し、内側にステップ実行を続けます。
// Assume array A[] has size N
int K = 0; // For particular example partitioning positive and negative numbers
int i = 0, j = N-1; // position markers, start at first and last entries
while(1) { // Break condition inside loop
while(i < N && A[i] < K) i++; // Increase i until A[i] >= K
while(j >= 0 && A[j] >= K) j--; // Decrease j until A[j] < K
if(i < j)
swap(A[i],A[j]);
else
break;
}
// A[] is now partitioned, A[0]...A[j] < K, unless i==0 (meaning all entries >= K).
すべての要素がK(この場合はゼロ)に等しい場合、iは増分されず、最後にj = 0になることに注意してください。問題文は、これが決して起こらないと仮定しています。パーティションは非常に高速で効率的であり、この効率がクイックソートが大規模な配列の最も一般的なソートルーチンである理由です。スワップ関数は、C++のstd :: swapにすることも、独自に簡単に作成することもできます。
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
または、楽しみのために、一時的なメモリなしで数字を所定の位置に交換できますが、オーバーフローに注意してください:
// This code swaps a and b with no extra space. Watch out for overflow!
a -= b;
b += a;
a = b - a;
[elements <K] [elements == K] [elements> K]の3ウェイパーティションなど、特殊なケースのパーティションには多くのバリエーションがあります。クイックソートアルゴリズムはパーティションを再帰的に呼び出し、パーティション値Kは通常、現在のサブ配列の最初のエントリであるか、いくつかのエントリ(3の中央値など)から計算されます。教科書:Sedgewick and Wayneによるアルゴリズム(4th ed。、p。288)またはThe Art of Computer Programming Vol。 3 by Knuth(2nd ed。、p。113)。
2つのキューを使用して、それらをマージできます。この方法では、最初の配列と各サブキューで1回だけ反復します。
negatives = []
positives = []
for elem in array:
if elem >= 0:
positives.Push(elem)
else
negatives.Push(elem)
result = array(negatives, positives)
反復が2回しかないソリューションを次に示します。
長さをnとしましょう。
そして、Cのようなコードを使用し、構文エラーを無視します。
solution[n];
for (i= 0,j=0 ; i < n ; i++ ) {
if (array[i] < 0) solution[j++] = array[i];
}
for (i = n-1,j=n-1 ; ; i > 0 ; i--) {
if (array[i] >= 0) solution[j--] = array[i];
}
一度考えてみて、遭遇したすべてのネガを書きます。
次に、最後から2回目を読み、最後から最初に向かってポジティブを書きます。
このソリューションには、O(n)時間の複雑さおよびO(1)スペースの複雑さ
アイデアは:
最後に見られたネガティブ要素のインデックス(lastNegIndex)を追跡します。
配列をループして、正の要素が先行する負の要素を見つけます。
そのような要素が見つかった場合、lastNegIndexと現在のインデックスの間で要素を右に1回転します。次に、lastNegIndexを更新します(次のインデックスになります)。
コードは次のとおりです。
public void rightRotate(int[] a, int n, int currentIndex, int lastNegIndex){
int temp = a[currentIndex];
for(int i = currentIndex; i > lastNegIndex+ 1; i--){
a[i] = a[i-1];
}
a[lastNegIndex+1] = temp;
}
public void ReArrange(int[] a, int n){
int lastNegIndex= -1;
int index;
if(a[0] < 0)
lastNegIndex = 0;
for(index = 1; index < n; index++){
if (a[index] < 0 && a[index - 1] >= 0) {
rightRotate(a, n, index, lastNegIndex);
lastNegIndex = lastNegIndex + 1;
}
}
}
qiwangcsのソリューション のJavaScript実装は次のとおりです。
function specialSort(A){
let min = Number.MAX_SAFE_INTEGER, max = -Number.MAX_SAFE_INTEGER;
for(let i=0; i<A.length; i++){
if(A[i] > max)
max = A[i];
if(A[i] < min)
min = A[i];
}
//Change all values to Positive
for(let i=0; i<A.length; i++)
A[i]-= min;
const newMax = max-min+1;
//Save original negative values into new positions
let currNegativeIndex = 0;
for(let i=0; i<A.length; i++)
if(A[i]%newMax < (-min))
A[currNegativeIndex++] += (A[i]%newMax)*newMax;
//Save original positive values into new positions
let currPositiveIndex = currNegativeIndex;
for(let i=0; i<A.length; i++)
if(A[i]%newMax > (-min))
A[currPositiveIndex++] += (A[i]%newMax)*newMax;
//Recover to original value
for(let i=0; i<A.length; i++){
A[i] = Math.floor(A[i]/newMax) + min;
}
}
// Demo
const A = [-3,-7,2,8,-5,-2,4];
specialSort(A);
console.log(A);
O(n)ソリューションJava
private static void rearrange(int[] arr) {
int pos=0,end_pos=-1;
for (int i=0;i<=arr.length-1;i++){
end_pos=i;
if (arr[i] <=0){
int temp_ptr=end_pos-1;
while(end_pos>pos){
int temp = arr[end_pos];
arr[end_pos]=arr[temp_ptr];
arr[temp_ptr]=temp;
end_pos--;
temp_ptr--;
}
pos++;
}
}
O(n) time and O(1)が array。リンクリストを提案する人もいますが、これを行うにはノードに直接アクセスできるカスタムリンクリストが必要です。つまり、言語に組み込まれたリンクリストが必要です。仕事。
customdoublyリンクリストを使用する私のアイデアは、[1、7、-5、9 、-12、15]例:
リストをループする、ネガが見られる場合は、それを切り取り、ネガの先頭に追加します。各操作はO(1)であるため、合計時間はO(n)です。リンクリスト操作はインプレースであるため、O(1)スペース。
詳細に:
last_negative_node = null;
at -5:
cut off -5 by setting 7.next = 9,
then add -5 to front by -5.next = 1,
then update last_negative_node = 5 // O(1), the linked list is now [-5, 1, 7, 9, -12, 15]
at -12:
cut off -12 by setting 9.next = 15,
then add -12 to front by -12.next = last_negative_node.next,
then update last_negative_node.next = -12,
then update last_negative_node = -12 //O(1), the linked list is now [-5, -12, 1, 7, 9, 15]
no more negatives so done.
最初の構造が配列である必要がない場合は、さらに単純です。
リンクリストに元の番号がある場合、それは簡単です。
リンクされたリストを並べ替えることができます。そのたびに、ネガティブが次のネガティブの隣に、ポジティブが次のネガティブの隣になります。
再びCのようなコード、構文を無視します。 (あちこちでnullチェックが必要になる場合がありますが、これはアイデアです)
Cell firstPositive;
Cell* lastPoisitive;
lastPoisitive = &firstPositive;
Cell firstNegative;
Cell* lastNegative;
lastNegative = &firstNegative;
Cell* iterator;
for(Iterator = list.first ; Iterator != null ; Iterator = Iterator->next) {
if (Iterator->value > 0 ) lastPoisitive->next = Iterator;
else lastPoisitive->next = Iterator;
}
list.first = firstNegative->next;
list.last.next = firstPositive->next;
目標がO(1)スペース(自由に変更可能であると想定される要素自体に加えて)およびO(NlgN)時間、 pとPが0以上の正の数を表し、nとNが0以上の負の数を表すpnPNの形式であることが知られている配列を、pPnNの形式の配列に配置する問題。その形式の2つの配列が与えられた場合、最初の負の数、次の正の数、および最後の正の数を見つけ、配列の中央の2つのセクションを「スピン」します(一定の空間で、時間に比例して結果は、pPnNという形式の配列になります。このような連続した2つの配列は、pnPNという形式のより大きな配列を形成します。
一定の空間で物事を行うには、すべての要素をペアリングしてPN形式にすることから始めます。次に、要素のすべての四重奏、次に配列の合計サイズまでのすべてのオクテットなどを実行します。
単なるアイデア..単純な問題を考えてみましょう。
配列が与えられ、最初の部分(Np
要素)には正の数のみが含まれ、最後の部分(Nn
要素):負の数のみが含まれます。相対的な順序を維持しながらこれらの部品を交換する方法は?
最も簡単な解決策は、反転を使用することです:
inverse(array, Np + Nn); // whole array
inverse(array, Nn); // first part
inverse(array+Nn, Np); // second part
O(n)時間の複雑さおよびO(1)スペースの複雑さ。
非常に簡単な解決策は以下にありますが、O(n)にはありません。挿入ソートアルゴリズムを少し変更しました。数値が大きいか小さいかをチェックする代わりに、それらがゼロより大きいか小さいかをチェックします。
int main() {
int arr[] = {1,-2,3,4,-5,1,-9,2};
int j,temp,size;
size = 8;
for (int i = 0; i <size ; i++){
j = i;
//Positive left, negative right
//To get opposite, change it to: (arr[j] < 0) && (arr[j-1] > 0)
while ((j > 0) && (arr[j] >0) && (arr[j-1] < 0)){
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
j--;
}
}
//Printing
for(int i=0;i<size;i++){
cout<<arr[i]<<" ";
}
return 0;
}
#include <iostream>
using namespace std;
void negativeFirst_advanced (int arr[ ], int size)
{
int count1 =0, count2 =0;
while(count2<size && count1<size)
{
if(arr[count1]>0 && arr[count2]<0)
{
int temp = arr[count1];
arr[count1] = arr[count2];
arr[count2] = temp;
}
if (arr[count1]<0)
count1++;
if (arr [count2]>0)
count2++;
}
}
int main()
{
int arr[6] = {1,7,-5,9,-12,15};
negativeFirst_advanced (arr, 6);
cout<<"[";
for (int i =0; i<6;i++)
cout<<arr[i]<<" , ";
cout<<"]";
system("pause");
return 0;
}
このコードは、O(n) complexityおよびO(1) space。別の配列を宣言する必要はありません。
#include <stdio.h>
int* sort(int arr[], int size)
{
int i;
int countNeg = 0;
int pos = 0;
int neg = 0;
for (i = 0; i < size; i++)
{
if (arr[i] < 0)
pos++;
}
while ((pos < (size-1)) || (neg < size-(pos-1)))
{
if ((arr[pos] < 0) && (arr[neg] > 0))
{
arr[pos] = arr[pos] + arr[neg];
arr[neg] = arr[pos] - arr[neg];
arr[pos] = arr[pos] - arr[neg];
pos++;
neg++;
continue;
}
if ((arr[pos] < 0) && (arr[neg] < 0))
{
neg++;
continue;
}
if ((arr[pos] > 0) && (arr[neg] > 0))
{
pos++;
continue;
}
if ((arr[pos] > 0) && (arr[neg] < 0))
{
pos++;
neg++;
continue;
}
}
return arr;
}
void main()
{
int arr[] = { 1, 7, -5, 9, -12, 15 };
int size = sizeof(arr) / sizeof(arr[0]);
sort(arr, size);
int i;
for (i = 0; i < size; i++)
{
printf("%d ,", arr[i]);
}
printf(" \n\n");
}
まず、負の要素の数k
をカウントします。次に、配列の最初のk
番号(配列の最初の部分)が負であることを知っています。以下 N - k
要素は、配列をソートした後、正でなければなりません。
配列の両方の部分でこれらの条件を尊重する要素の数の2つのカウンターを維持し、1つの部分がOKであることがわかるまで各ステップで増分します(カウンターはその部分のサイズに等しくなります)。その後、他の部分もOKです。
これにはO(1)ストレージが必要で、O(N)時間かかります。
C++での実装:
#include <iostream>
#include <vector>
using namespace std;
void swap(vector<int>& L, int i, int j) {
int tmp = L[i];
L[i] = L[j];
L[j] = tmp;
}
void signSort(vector<int>& L) {
int cntNeg = 0, i = 0, j = 0;
for (vector<int>::iterator it = L.begin(); it < L.end(); ++it) {
if (*it < 0) ++cntNeg;
}
while (i < cntNeg && cntNeg + j < L.size()) {
if (L[i] >= 0) {
swap(L, i, cntNeg + j);
++j;
} else {
++i;
}
}
}
int main(int argc, char **argv) {
vector<int> L;
L.Push_back(-1);
L.Push_back(1);
L.Push_back(3);
L.Push_back(-2);
L.Push_back(2);
signSort(L);
for (vector<int>::iterator it = L.begin(); it != L.end(); ++it) {
cout << *it << endl;
}
return 0;
}
int [] input = {1, 7, -5, 9, -12, 15};
int [] output = new int [input.length];
int negativeIdx = 0;
int positiveIdx = input.length - 1;
for(int i = 0; i < input.length ; i++) {
if(input[i] < 0) {
output [negativeIdx++] = input[i];
} else {
output [positiveIdx--] = input[i];
}
}
System.out.println
(Arrays.toString(output));
出力:
[-5, -12, 15, 9, 7, 1]
これはComplexity O(1)ではなくですが、よりシンプルなアプローチです。コメントする
void divide (int *arr, int len) {
int positive_entry_seen = 0;
for (int j = 0; j < len ; j++) {
if (arr[j] >= 0 ) {
positive_entry_seen = 1;
} else if ((arr[j] < 0 ) && positive_entry_seen) {
int t = arr[j];
int c = j;
while ((c >= 1) && (arr[c-1] >= 0)) {
arr[c] = arr[c-1];
c--;
}
arr[c] = t;
}
}
}
これは、QuickSortのパーティションプロセスを使用します。このソリューションの時間の複雑さはO(n2)であり、補助スペースはO(1)です。このアプローチは、外観の順序を維持し、他のデータ構造を使用していません。
def sortSeg(intArr)
intArr.each_with_index do |el, idx|
# if current element is pos do nothing if negative,
# shift positive elements of arr[0..i-1],
# to one position to their right */
if el < 0
jdx = idx - 1;
while (jdx >= 0 and intArr[jdx] > 0)
intArr[jdx + 1] = intArr[jdx];
jdx = jdx - 1;
end
# Insert negative element at its right position
intArr[jdx + 1] = el;
end
end
end
これはうまくいくと思います:ここに、一定の空間(ただし2次時間)である簡単な方法があります。配列の長さがNであるとしましょう。i= 0からi = N-2の配列に沿って、要素iとi + 1をチェックします。要素iが正で要素i + 1が負の場合、それらを交換します。その後、そのプロセスを繰り返します。
アレイを通過する各パスは、(十分なパス数の後)それらがすべて正しい場所に来るまで、バブルソートのように、ネガを左に(およびポジティブを右に)ドリフトさせます。
また、これはうまくいくと思います:それは一定の空間でもあります(しかし、二次時間)。 Pが陽性の数だとしましょう。正のxが見つかったら左から右にスキャンし、スキャンを停止し、左以降のすべてのアイテムを1つずつシフトして「削除」します。次に、xを配列の最後に置きます。手順スキャンをP回繰り返して、すべてのポジティブを移動します。
O(n)時間の複雑さで動作するシンプルで簡単なソリューション。
int left = 0;
int right = arr.length - 1;
while (left < right) {
while (arr[left] >= 0) {
left++;
}
while (arr[right] < 0) {
right--;
}
if (left < right) {
ArrayUtility.swapInArray(arr, left, right);
}
ArrayUtility.printArray(arr);
ここに、再帰を使用したPythonでの私の解決策があります(このような割り当てがあり、配列は番号Kに対して相対的にソートされる必要があります。 K = 0、あなたはあなたの解決策を持っている、出現の順序を保持せずに):
def kPart(s, k):
if len(s) == 1:
return s
else:
if s[0] > k:
return kPart(s[1:], k) + [s[0]]
else:
return [s[0]] + kPart(s[1:], k)