Je suis récemment tombé sur une question d'entrevue Microsoft pour un ingénieur logiciel.
Pour un tableau d'entiers positifs et négatifs, réorganisez-le de manière à avoir des entiers positifs à une extrémité et des entiers négatifs à l'autre, tout en conservant l'ordre d'apparition dans le tableau d'origine.
Par exemple, donné [1, 7, -5, 9, -12, 15]
La réponse serait: [-5, -12, 1, 7, 9, 15]
Cela devrait être fait en O (n).
Nous pourrions facilement le faire dans O (n), mais je ne suis pas en mesure de penser à la façon de conserver l'ordre des éléments comme dans le tableau d'origine. Si nous oublions la complexité de O(n), quelqu'un pourrait-il me dire comment préserver l'ordre des éléments sans prendre en compte la complexité de l'espace et du temps.
EDIT: En question, nous devons également avoir la complexité d'espace O(1).
Pour obtenir ce résultat en espace constant (mais en temps quadratique), vous pouvez utiliser l'approche à deux files d'attente en plaçant une file d'attente à chaque extrémité du tableau (similaire à l'algorithme du drapeau national néerlandais). Lire les éléments de gauche à droite: ajouter un élément à la file de gauche signifie le laisser seul, ajouter un élément à la file de droite signifie déplacer tous les éléments qui ne sont pas dans la file à gauche et placer l'élément ajouté à la fin. Ensuite, pour concaténer les files d'attente, inversez simplement l'ordre des éléments dans la seconde file d'attente.
Ceci effectue une opération O(n) (éléments décalés à gauche) jusqu'à O(n) fois, ce qui donne un temps d'exécution O (n²).
En utilisant une méthode similaire à la fusion, vous pouvez obtenir une complexité plus faible de O (n log n): découpez le tableau en deux moitiés, triez-les récursivement sous la forme [N P] [N P]
puis échangez la première P
avec la seconde N
dans O(n) time (ça devient un peu délicat quand ils n'ont pas exactement la même taille, mais c'est quand même linéaire).
Je n'ai absolument aucune idée de la façon de réduire ce temps à O(n).
EDIT: en fait, votre aperçu de la liste chaînée est exact. Si les données sont fournies sous forme de liste à double liaison, vous pouvez implémenter la stratégie de double file d'attente dans le temps O(n), O(1):
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)
Avec une implémentation de liste chaînée qui garde les pointeurs sur les premier et dernier éléments, puis pop, ajout et concaténation sont toutes des opérations O(1), la complexité totale est donc O (n). En ce qui concerne l'espace, aucune des opérations n'alloue de mémoire (append utilise simplement la mémoire libérée par pop), il s'agit donc de O(1) dans son ensemble.
Voici une version constante de O(n) time O(1) solution d'espace, supposons que maxValue * (maxValue + 1) soit inférieur à Integer.MAX_VALUE, où maxValue est la valeur résultat de la valeur maximale moins la valeur minimale dans le tableau. Il utilise le tableau d'origine en tant que tableau temporaire pour stocker le résultat.
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;
}
}
Je ne suis pas sûr de bien comprendre la question, car la réponse semble trop simple:
Voici un moyen rapide de le faire en Python. Il diffère légèrement de ce qui précède en créant d’abord un tableau pour les négatifs, puis en ajoutant les positifs. Donc, ce n'est pas aussi efficace, mais toujours 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]
Edit: Ok, maintenant avec O(1) la compexité spatiale, cela devient beaucoup plus difficile. Je suis également intéressé par la façon de le réaliser dans O(n) temps complexe. Si cela vous aide, voici un moyen de conserver la complexité d'espace O(1), mais requiert la complexité temporelle de O (n ^ 2):
Cela peut être fait dans O(n) et dans l'espace O (1).
Nous devons parcourir 3 fois le tableau et modifier certaines valeurs avec soin.
Hypothèse: la valeur maximale dans le tableau de taille N doit être inférieure à (N+1) * Integer.MAX_VALUE
.
Nous avons besoin de cette hypothèse car nous changeons certaines valeurs positives dans le tableau.
Nous partons du début du tableau et nous " permutons " le premier nombre positif trouvé (par exemple, à l'indice i
) avec le premier nombre négatif trouvé (par exemple, j
). Étant donné que les nombres négatifs sont pris en compte par rapport à leur emplacement, l’échange se fera sans problème.
Le problème réside dans les nombres positifs, car il peut exister d'autres nombres positifs entre i
et j
. Pour gérer ce problème, nous devons en quelque sorte encoder l'index du nombre positif dans cette valeur avant la permutation. Nous pouvons alors réaliser où il en était au premier point. Nous pouvons le faire par a[i]=(i+1)*(max)+a[i]
.
Voici le code:
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;
}
}
Edit (29/05/2015): J'ai négligé l'exigence de maintien de l'ordre d'affichage, la réponse ci-dessous ne satisfait donc pas à toutes les exigences de la question. Cependant, je laisse la réponse initiale au sujet de l'intérêt général.
Ceci est une version spéciale d'un très important sous-programme de tri rapide appelé "partition". Définition: un tableau A comportant N entrées numériques est partitionné autour de la valeur K à l’indice p si A [i] <K pour 0 <= i <p et A [j]> = K pour p <= j <N, sauf si tous les les entrées sont inférieures à K (signifiant p = N) ou non inférieures à K (signifiant p = 0). Pour le problème en question, nous allons partitionner le tableau autour de K = 0.
Vous pouvez partitionner un tableau non trié autour de toute valeur K pendant le temps O(n), en accédant une seule fois à chaque entrée du tableau, en utilisant O(1) mémoire supplémentaire. De manière informelle, vous parcourez le tableau des deux côtés, en déplaçant des valeurs du mauvais côté. Effectuez un échange lorsqu'une valeur mal placée se trouve de chaque côté du tableau, puis continuez votre progression vers l'intérieur. Maintenant le code C++:
// 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).
Notez que si tous les éléments sont égaux à K (zéro dans ce cas), i n'est jamais incrémenté et j = 0 à la fin. L'énoncé du problème suppose que cela n'arrivera jamais. La partition est très rapide et efficace, et cette efficacité est la raison pour laquelle quicksort est la routine de tri la plus populaire pour les baies de grande taille. La fonction swap peut être std :: swap en C++ ou vous pouvez facilement écrire votre propre:
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
Ou juste pour le plaisir, les chiffres peuvent être échangés à la place sans mémoire temporaire, mais soyez conscient du débordement:
// This code swaps a and b with no extra space. Watch out for overflow!
a -= b;
b += a;
a = b - a;
Il existe de nombreuses variantes à partitionner pour des cas particuliers, telles qu'une partition à trois voies pour [éléments <K] [éléments == K] [éléments> K]. L'algorithme quicksort appelle la partition de manière récursive, et la valeur de la partition K est généralement la première entrée du sous-tableau en cours ou est calculée à partir de quelques entrées (telles que la médiane sur trois). Voir manuels: Algorithms de Sedgewick et Wayne (4 e éd., P. 288) ou The Art of Computer Programming Vol. 3 de Knuth (2e éd., P. 113).
Vous pouvez utiliser 2 files d'attente et les fusionner. De cette façon, vous n'itérez qu'une fois sur le premier tableau et une fois chaque sous-file d'attente.
negatives = []
positives = []
for elem in array:
if elem >= 0:
positives.Push(elem)
else
negatives.Push(elem)
result = array(negatives, positives)
Voici une solution avec seulement 2 itérations:
Disons que la longueur est n.
Et je vais utiliser C comme code, ignorer les erreurs de syntaxe.
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];
}
L'idée est de l'examiner une fois et d'écrire tous les négatifs que nous rencontrons.
Ensuite, relisez-le une deuxième fois à partir de la fin et écrivez les éléments positifs de la fin au début.
Cette solution a O(n) complexité temporelle et O(1) complexité spatiale
L'idée est:
garder trace de l'index du dernier élément négatif vu (lastNegIndex).
parcourez le tableau pour rechercher les éléments négatifs précédés d’éléments positifs.
Si un tel élément est trouvé, faites une rotation à droite des éléments entre lastNegIndex et Index en cours. Puis mettez à jour lastNegIndex (ce sera le prochain index).
Voici le code:
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;
}
}
}
O (n) solution 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++;
}
}
Je doute fort que O(n) temps et O(1) soient réalisables avec untableau. Certains suggèrent une liste chaînée, mais vous aurez besoin d’une liste chaînée personnalisée dans laquelle vous aurez un accès direct aux nœuds. les listes chaînées intégrées aux langues ne fonctionneront pas.
Voici mon idée en utilisant une liste chaînée custom doublyqui satisfont les complexités contraintes, en utilisant [1, 7, -5, 9, -12, 15]:
Parcourez la liste , si vous voyez un négatif, coupez-le et ajoutez-le à la fin des négatifs à l'avant. Chaque opération correspond à O(1) et le temps total correspond donc à O (n). Les opérations de liste chaînée sont en place, donc O(1) d'espace.
En détail:
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.
Si la structure au début ne doit pas nécessairement être un tableau, c'est encore plus simple.
Si vous avez les numéros originaux dans une liste chaînée, c'est facile.
Vous pouvez réorganiser la liste chaînée, chaque fois que vous pointez le négatif au prochain négatif et le positif au suivant.
Encore une fois C comme code, ignorez la syntaxe. (peut avoir besoin d'un chèque nul ici et là, mais c'est l'idée)
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;
Si le but est O(1) l'espace (à part les éléments eux-mêmes, supposés être librement modifiables) et le temps O(NlgN), divisez le problème en organisant des tableaux connus pour être de la forme pnPN, où p et P représentent zéro ou plusieurs nombres positifs et n et N représente 0 ou plusieurs nombres négatifs, dans des tableaux de la forme pPnN. Tout tableau à deux éléments sera automatiquement de cette forme. Avec deux tableaux de cette forme, localisez le premier nombre négatif, le prochain nombre positif et le dernier nombre positif, et "faites tourner" les deux sections du milieu du tableau (facile à faire dans un espace constant et dans le temps proportionnel à la taille du tableau être 'filé'). Le résultat sera un tableau de la forme pPnN. Deux tableaux consécutifs de ce type formeront un tableau plus grand de la forme pnPN.
Pour faire des choses dans un espace constant, commencez par associer tous les éléments et en les mettant sous forme PN. Ensuite, faites tous les quartets d’éléments, puis tous les octets, etc. jusqu’à la taille totale du tableau.
Juste une idée .. Considérons un problème plus simple:
Étant donné un tableau, où la première partie (Np
éléments) contient uniquement des nombres positifs et la dernière partie (Nn
éléments): uniquement des nombres négatifs . Comment échanger ces parties tout en maintenant l'ordre relatif?
La solution la plus simple consiste à utiliser l'inversion:
inverse(array, Np + Nn); // whole array
inverse(array, Nn); // first part
inverse(array+Nn, Np); // second part
Il a O(n) complexité temporelle et O(1) complexité spatiale.
Une solution extrêmement simple est ci-dessous mais pas dans O (n). J'ai un peu modifié l'algorithme de tri par insertion. Au lieu de vérifier si un nombre est supérieur ou inférieur, il vérifie s'il est supérieur ou inférieur à zéro.
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;
}
Voici une implémentation JavaScript de la solution de qiwangcs :
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);
#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;
}
Ce code fonctionne avec la complexité O(n) et l'espace O(1) . Inutile de déclarer un autre tableau.
#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");
}
Commencez par compter le nombre k
d'éléments négatifs . Ensuite, vous saurez que les premiers nombres k
du tableau (première partie du tableau) doivent être négatifs . Les éléments N - k
suivants doivent être positifs après le tri du tableau. .
Vous gérez deux compteurs du nombre d'éléments respectant ces conditions dans les deux parties du tableau et l'incrémentez à chaque étape jusqu'à ce que vous sachiez qu'une partie est correcte (le compteur est égal à la taille de cette partie). Ensuite, l'autre partie est OK aussi.
Cela nécessite O(1) de la mémoire et prend O(N) temps.
Implémentation en 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;
}
Ceci est not Complexity O(1) mais une approche plus simple. Faire un commentaire
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;
}
}
}
Ceci utilise le processus de partitionnement de QuickSort. La complexité temporelle de cette solution est O(n2) et l'espace auxiliaire est O (1). Cette approche maintient l'ordre d'apparition et n'a utilisé aucune autre structure de données. C'est en rubis
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
Je pense que cela fonctionnerait: Voici un moyen simple qui est un espace constant (mais un temps quadratique). Disons que la matrice est de longueur N. Parcourez la matrice de i = 0 à i = N-2 en vérifiant les éléments i et i + 1. Si l'élément i est positif et que l'élément i + 1 est négatif, échangez-les. Ensuite, répétez ce processus.
Chaque passage dans la matrice fera dériver les négatifs vers la gauche (et les positifs vers la droite), un peu comme une sorte de bulle, jusqu'à ce que (après un nombre suffisant de passages) ils soient tous à la bonne place.
De plus, je pense que cela fonctionnerait: c'est aussi un espace constant (mais un temps quadratique). Dites P est le nombre de points positifs. Numérisez de gauche à droite, lorsque vous trouvez un x positif, arrêtez la numérisation et "supprimez-la" en décalant tous les éléments suivants. Puis mettez x à la fin du tableau. Répétez la procédure, scannez P fois pour déplacer chaque positif.
Une solution simple et facile qui fonctionne dans la complexité temporelle 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);
Voici ma solution dans Python, en utilisant la récursivité (j'avais une affectation comme celle-ci, où le tableau devait être trié en fonction d'un nombre K. Si vous mettez K = 0, vous avez votre solution, sans conserver l'ordre de comparution ):
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)