Mon objectif est de supprimer tous les nombres négatifs d'un tableau en Java.
J'ai écrit le code ci-dessous. Comment améliorer la complexité du code ou existe-t-il un bon algorithme?
public static void main(String[] args) {
int[] array = { 1, -3, 4, -9, 3, 4, 70, -10, 0, 7 };
System.out.println(Arrays.toString(removeNegativeNumbers(array)));
}
public static int[] removeNegativeNumbers(int[] num) {
List<Integer> list = new ArrayList<>();
for (int n : num) {
if (n >= 0) {
list.add(n);
}
}
int[] result = new int[list.size()];
for (int i = 0; i < result.length; i++) {
result[i] = list.get(i).intValue();
}
return result;
}
Comment améliorer la complexité du code ou existe-t-il un bon algorithme?
Voici un algorithme linéaire O(n)
à espace constant (en négligeant l’espace dont nous avons besoin pour le tableau en sortie). Lorsqu'un élément est non négatif, copiez-le et avancez le tableau de sortie et le chercheur de tableau d'entrée. Et lorsque l'élément est négatif, avancez uniquement le chercheur de tableau en entrée (indx) et évitez de le copier dans le tableau en sortie.
public static int[] removeNegativeNumbers(int[] num) {
int[] output = new int[num.length];
int k = 0;
for(int i = 0; i < num.length; i++) {
if(num[i] >= 0) {
output[k++] = num[i];
}
}
return Arrays.copyOfRange(output, 0, k);
}
Vous pouvez créer l’algorithme in-place en transformant le tableau en entrée en une sortie telle que insertion sort afin d’éviter la surcharge d’espace du tableau en sortie.
public static int removeNegativeNumbers(int[] num) {
int k = 0;
for(int i = 0; i < num.length; i++) {
if(num[i] >= 0) {
num[k++] = num[i];
}
}
// Now input array is holding the output data
// Return the length of output array
return k;
}
// Usage: int newLen = removeNegativeNumbers(array);
C'est ce qu'on appelle technique à deux pointeurs}, très simple et pourtant utile pour résoudre de nombreux casse-tête classiques comme la déduplication de tableaux, la fusion de deux tableaux triés, l'intersection de deux tableaux triés, etc.
Qu'en est-il des flux Java 8?
Arrays.stream(num).filter(s -> s >= 0).toArray();
Je ne pense pas que nous puissions améliorer notre efficacité par rapport à la réponse @ kaidul-islam (et @ user6904265 en termes de concision).
J'ajoute simplement une solution qui devrait avoir une certaine valeur dans un scénario très spécifique, où les négatifs apparaissent rarement et où le tableau est très grand.
L'idée de base est de différer la copie réelle jusqu'à ce qu'une valeur négative soit trouvée, puis de la copier avec System.arraycopy. Si aucun résultat négatif n'est trouvé, le tableau source sera renvoyé.
import Java.util.*;
public class NotNegativeTest {
public static void main(String[] args) {
int[] array = { 1, -3, 4, -9, 3, 4, 70, -10, 0, 7 };
System.out.println(Arrays.toString(removeNegativeNumbers(array)));
}
public static int[] removeNegativeNumbers(int[] num) {
int[] output = new int[num.length];
int k = 0;
int i = 0;
int last=-1;
int howmany=0;
while(i < num.length) {
if(num[i] < 0) {
howmany=i-last-1;
switch(howmany) {
case 0: break;
case 1:
output[k]=num[last+1];
k++;
break;
default:
System.arraycopy(num, last+1, output, k, howmany);
k+=howmany;
}
last=i;
}
i++;
}
if (last>=0) {
if(last!=i-1) {
howmany=i-last-1;
System.arraycopy(num, last+1, output, k, howmany);
k+=howmany;
}
} else {
return num;
}
return Arrays.copyOfRange(output, 0, k);
}
}
J'ai trouvé le temps d'écrire un micro-benchmark JMH.
J'ai utilisé utilisé ces configurations:
Options opts = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
.mode(Mode.AverageTime)
.warmupIterations(1)
.warmupTime(TimeValue.seconds(5))
.measurementIterations(10)
.measurementTime(TimeValue.seconds(5))
.jvmArgs("-server")
.forks(1)
.build();
new Runner(opts).run();
(disclaimer: mon premier test JMH, donc si quelqu'un a des suggestions, je serai ravi de le modifier et de mettre à jour les résultats)
Et voici les résultats:
# Run complete. Total time: 00:02:54
Benchmark Mode Cnt Score Error Units
MyBenchmark.testMethodUser6904265 avgt 10 0,201 ± 0,040 s/op
MyBenchmark.testMethodInsac avgt 10 0,093 ± 0,022 s/op
MyBenchmark.testMethodKaidul avgt 10 0,124 ± 0,029 s/op
Maintenant, avant de m'acclamer, veuillez regarder à quel point le test était biaisé:
int[] array = new int[10000000];
array[0]=-5;
array[10000]=-3;
array[40000]=-3;
array[8000000]=-3;
int[] answer=NotNegativeTest.removeNegativeNumbers(array);
Donc, ici, la chose à remarquer n’est pas que ma méthode ait été gagnée (le test a été écrit pour que ma méthode gagne :-) mais à quel point la méthode générique kaidul-islam était-elle mienne même dans ce scénario extrême.
MISE À JOUR: voici le lien vers le point de repère jmh que j'ai écrit afin que vous puissiez vérifier un scénario plus réaliste (si quelqu'un découvre des problèmes lors de la configuration du test, faites-le moi savoir). Il existe une dépendance à l'égard de Trove pour un test que j'ai effectué sur une autre réponse.
Cela le fera sur place avec une seule itération:
Conservez 2 index: src
et dst
sur le tableau d'origine. les deux sont initialisés à 0.
quand un nombre est positif, copiez-le de num[src]
à num[dst]
et avancez les deux index
quand un nombre est négatif, avancez simplement src
.
renvoie le tableau d'origine avec dst
comme nouvelle taille.
public static int removeNegativeNumbers(int[] num) {
int dst=0;
for (int src=0 ; src<num.length ; ++src)
if (num[src]>=0)
num[dst++] = num[src];
return dst;
}
Suivez le code ci-dessous (Java 8)
int test[] = new int[] { 15, -40, -35, 45, -15 };
// here we can take the test array in to stream and filter with condition (>=0).
int[] positives = Arrays.stream(test).filter(x -> x >= 0).toArray();
System.out.println("Here is the positive array elements");
for (int i : positives) {
System.out.print(i + "\t");
}
Etant donné que vous aurez nécessairement besoin d'examiner chaque élément pour déterminer s'il est inférieur à 0, le temps d'exécution doit être au moins égal à O (n). Puisque l'espace nécessaire pour stocker l'entrée est O (n), l'espace est au moins O (n). Votre solution s'exécute à O(n) avec une complexité de O(n). Par conséquent, la solution no peut avoir un espace ou une complexité d'exécution meilleurs que votre solution
Nous pouvons cependant obtenir de meilleurs résultats si nous supposons que le tableau est trié. Ensuite, nous effectuons une recherche binaire pour 0. Si nous avons un moyen de retourner le sous-tableau avec un temps constant (par exemple, la magie du pointeur en C, ou en renvoyant simplement l'index de départ à lire), alors l'algorithme aura O ( log n) heure.
public static int[] removeNegativeNumbers(int[] num) {
List<Integer> list = new ArrayList<>();
for (int n : num) {
if (n >= 0) {
list.add(n);
}
}
return list.toArray(new int[list.size()]);
}