JavaRush /Blog Java /Random-ES /Análisis de preguntas y respuestas de entrevistas para de...

Análisis de preguntas y respuestas de entrevistas para desarrollador Java. parte 13

Publicado en el grupo Random-ES
¡Hola!
El movimiento hacia una meta es, ante todo, movimiento.
Por tanto, no basta con pensar que se quiere conseguir algo. Necesitas hacer algo, incluso los pasos más pequeños, pero hazlos todos los días y solo así lograrás el objetivo final. Y dado que está aquí para convertirse en desarrollador de Java, debe dar al menos un paso mínimo para profundizar su conocimiento de Java todos los días. Para el paso de Java de hoy, le sugiero que se familiarice con la nueva parte del análisis de las preguntas de entrevistas más populares para desarrolladores. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 1Hoy repasaremos la parte práctica de las preguntas para especialistas Junior. Una tarea práctica en una entrevista no es infrecuente. Es importante no perderse en tal situación, tratar de mantener la cabeza fría y ofrecer la solución óptima, o incluso varias. También recomendaría no quedarte callado a la hora de resolver un problema, sino comentar tu línea de pensamiento y escribir la solución, o después de escribir, explicar con palabras qué hiciste y por qué. Esto le hará ganarse el cariño del entrevistador mucho más que una decisión silenciosa. ¡Entonces empecemos!

111. ¿Cómo intercambiar datos entre hilos?

Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 2Para intercambiar datos entre subprocesos, puede utilizar muchos enfoques y medios diferentes: por ejemplo, utilizar variables atómicas, colecciones sincronizadas y un semáforo. Pero para solucionar este problema, daré un ejemplo con Exchanger . Exchanger es una clase de sincronización del paquete concurrente que facilita el intercambio de elementos entre un par de subprocesos mediante la creación de un punto de sincronización común. Su uso simplifica el intercambio de datos entre dos hilos. La forma en que funciona es bastante simple: espera a que dos subprocesos separados llamen a su método exchange() . Entre ellos se crea algo así como un punto de intercambio: el primer hilo pone su objeto y recibe a cambio el objeto del otro, y éste, a su vez, recibe el objeto del primero y pone el suyo. Es decir, el primer hilo usa el método exchange() y está inactivo hasta que otro hilo llama al método exchange() en el mismo objeto y se intercambian datos entre ellos. Como ejemplo, considere la siguiente implementación de la clase Thread :
public class CustomThread extends Thread {
 private String threadName;
 private String message;
 private Exchanger<String> exchanger;

 public CustomThread(String threadName, Exchanger<String> exchanger) {
   this.threadName = threadName;
   this.exchanger = exchanger;
 }

 public void setMessage(final String message) {
   this.message = message;
 }

 @Override
 public void run() {
   while (true) {
     try {
       message = exchanger.exchange(message);
       System.out.println(threadName + " поток получил сообщение: " + message);
       Thread.sleep(1000);
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
 }
}
En el constructor del hilo, definimos un objeto Exchanger que acepta objetos de tipo String , y al inicio (en el método run ) usamos su exchange() para intercambiar un mensaje con otro hilo que usa este método en el mismo Exchanger . Ejecutémoslo en main :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
La consola mostrará:
El primer hilo recibió el mensaje: Mensaje del segundo hilo El segundo hilo recibió el mensaje: Mensaje del primer hilo El segundo hilo recibió el mensaje: Mensaje del segundo hilo El primer hilo recibió el mensaje: Mensaje del primer hilo El segundo hilo recibió el mensaje: Mensaje del primer hilo El primer hilo recibió el mensaje: Mensaje del segundo hilo... .
Esto significa que el intercambio de datos entre subprocesos es exitoso.

112. ¿Cuál es la diferencia entre la clase Thread y la interfaz Runnable?

Lo primero que notaré es que Thread es una clase, Runnable es una interfaz, lo cual es una diferencia muy obvia =D Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 3También diré que Thread usa Runnable (composición). Es decir, tenemos dos formas:
  1. Hereda de Thread , anula el método de ejecución, luego crea este objeto e inicia el hilo a través del método start() .

  2. Implemente Runnable en una determinada clase, implemente su método run() y luego cree un objeto Thread , asignando la implementación de este objeto de la interfaz Runnable a su constructor . Bueno, al final, lanza el objeto Thread usando el método start() .

¿Qué es preferible? Pensemos un poco:
  • Cuando implementas la interfaz Runnable , no cambias el comportamiento del hilo. Básicamente, simplemente le estás dando al hilo algo para ejecutar. Y esta es nuestra composición, que a su vez se considera un buen enfoque.

  • La implementación de Runnable le da más flexibilidad a su clase. Si heredas de Thread , entonces la acción que realices siempre estará en el hilo. Pero si implementas Runnable, no tiene por qué ser solo un hilo. Después de todo, puedes ejecutarlo en un hilo o pasarlo a algún servicio ejecutor. Bueno, o simplemente páselo a algún lugar como una tarea en una aplicación de un solo subproceso.

  • El uso de Runnable le permite separar lógicamente la ejecución de tareas de la lógica de control de subprocesos.

  • En Java, sólo es posible la herencia única, por lo que sólo se puede ampliar una clase. Al mismo tiempo, la cantidad de interfaces expandibles es ilimitada (bueno, no del todo ilimitada, pero 65535 , pero es poco probable que alguna vez alcance este límite).

Bueno, depende de ti decidir qué es exactamente preferible usar ^^

113. Hay hilos T1, T2 y T3. ¿Cómo implementarlos secuencialmente?Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 4

Lo primero y más sencillo que me viene a la mente es utilizar el método join() . Suspende la ejecución del hilo actual (que llamó al método) hasta que el hilo en el que se llamó al método termine de ejecutarse. Creemos nuestra propia implementación de hilo:
public class CustomThread extends Thread {
private String threadName;

 public CustomThread(final String  threadName){
   this.threadName = threadName;
 }

 @Override
 public void run() {
   System.out.println(threadName + " - начал свою работу");
   try {
     // происходит некая логика
     Thread.sleep(1000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }

   System.out.println(threadName + " - закончил свою работу");
 }
}
Comencemos tres de estos hilos uno por uno usando join() :
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
Salida de consola:
El primer hilo - comenzó su trabajo El primer hilo - terminó su trabajo El segundo hilo - comenzó su trabajo El segundo hilo - terminó su trabajo El tercer hilo - comenzó su trabajo El tercer hilo - terminó su trabajo
Esto significa que hemos completado nuestra tarea. A continuación, pasamos directamente a las tareas prácticas del nivel Junior .

Tareas practicas

114. Suma diagonal de matriz (problema de Leetcode)

Condición: Calcula la suma de todos los elementos de la diagonal principal y todos los elementos de la diagonal adicional que no forman parte de la diagonal principal. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 51. Con una matriz de la forma: mat = [[1,2,3], [4,5,6], [7,8,9]] La salida debe ser - 25 2. Con una matriz - mat = [[1,1,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] La salida debe ser - 8 3. Con una matriz - mat = [[ 5]] La conclusión debe ser - 5 Haga una pausa en la lectura e implemente su decisión. Mi solución sería la siguiente:
public static int countDiagonalSum(int[][] matrix) {
 int sum = 0;
 for (int i = 0, j = matrix.length - 1; i < matrix.length; i++, j--) {
   sum += matrix[i][i];
   if (j != i) {
     sum += matrix[i][j];
   }
 }
 return sum;
}
Todo sucede con una pasada por la matriz, durante la cual tenemos dos índices para el informe: i - para informar las filas de la matriz y las columnas de la diagonal principal, j - para informar las columnas de la diagonal adicional. Si la celda de la diagonal principal y la adicional coinciden, entonces uno de los valores se ignora al calcular la suma. Comprobemos usando las matrices de la condición:
int[][] arr1 = {
   {1, 2, 3},
   {4, 5, 6},
   {7, 8, 9}};
System.out.println(countDiagonalSum(arr1));

int[][] arr2 = {
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1}};
System.out.println(countDiagonalSum(arr2));

int[][] arr3 = {{5}};
System.out.println(countDiagonalSum(arr3));
Salida de consola:
25 8 5

115. Mover ceros (desafío Leetcode)

Condición: en una matriz de números enteros, mueva todos los ceros hasta el final, manteniendo el orden relativo de los elementos distintos de cero. 1. Con una matriz: [0,1,0,3,12] La salida debe ser: [1,3,12,0,0] 2. Con una matriz: [0] La salida debe ser: [0] Haz una pausa y escribe mi decisión... Mi decisión:
public static void moveZeroes(int[] nums) {
 int counterWithoutNulls = 0;
 int counterWithNulls = 0;
 int length = nums.length;
 while (counterWithNulls < length) {
   if (nums[counterWithNulls] == 0) {// находим нулевые элементы и увеличиваем счётчик
     counterWithNulls++;
   } else { // сдвигаем элементы на количество найденных нулевых элементов слева
     nums[counterWithoutNulls++] = nums[counterWithNulls++];
   }
 }
 while (counterWithoutNulls < length) {
   nums[counterWithoutNulls++] = 0;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Examen:
int[] arr1 = {1, 2, 0, 0, 12, 9};
moveZeroes(arr1);
System.out.println(Arrays.toString(arr1));

int[] arr2 = {0};
moveZeroes(arr2);
System.out.println(Arrays.toString(arr2));
Salida de consola:
[1, 2, 12, 9, 0, 0] [0]

116. Lista dada <Cadena> nombres. Elimine la primera letra de cada nombre y rote la lista ordenada

1. Lo primero que me viene a la mente son los métodos de la clase Colecciones , que contiene muchos métodos auxiliares para las colecciones:
public static List<String> processTheList(List<String> nameList) {
 for (int i = 0; i < nameList.size(); i++) {
   nameList.set(i, nameList.get(i).substring(1));
 }
 Collections.sort(nameList);
 return nameList;
}
2. Además, si utilizamos Java versión 8 y superior, simplemente debemos mostrar la solución mediante streams:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Independientemente de la solución elegida, la comprobación puede ser la siguiente:
List<String> nameList = new ArrayList();
nameList.add("John");
nameList.add("Bob");
nameList.add("Anna");
nameList.add("Dmitriy");
nameList.add("Peter");
nameList.add("David");
nameList.add("Igor");

System.out.println(processTheList(nameList));
Salida de consola:
[ávido, eter, gor, mitriy, nna, ob, ohn]

117. Voltear la matriz

Solución 1 De nuevo, lo primero que me viene a la mente es utilizar los métodos de la clase de utilidad auxiliar Collections . Pero como tenemos una matriz, primero debemos convertirla en una colección (lista):
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Solución 2 Dado que la pregunta era sobre una matriz, creo que es necesario mostrar la solución sin utilizar la funcionalidad lista para usar, por así decirlo, según los clásicos:
public static Integer[] reverse(Integer[] arr) {
 for (int i = 0; i < arr.length / 2; i++) {
   int temp = arr[i];
   arr[i] = arr[arr.length - 1 - i];
   arr[arr.length - 1 - i] = temp;
 }
 return arr;
}
Examen:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Salida de consola:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Comprobar si una cuerda es un palíndromo

Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 6Solución 1 Vale la pena recordar inmediatamente StringBuilder : es más flexible y rico en varios métodos en comparación con el String normal . Estamos especialmente interesados ​​en el método inverso :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Solución: El siguiente enfoque será sin utilizar las “lagunas” existentes. Comparamos los caracteres de la parte posterior de la cadena con los caracteres correspondientes del frente:
public static boolean isPalindrome(String string) {
  string = string.toLowerCase();
 int length = string.length();
 int fromBeginning = 0;
 int fromEnd = length - 1;
 while (fromEnd > fromBeginning) {
   char forwardChar = string.charAt(fromBeginning++);
   char backwardChar = string.charAt(fromEnd--);
   if (forwardChar != backwardChar)
     return false;
 }
 return true;
}
Y comprobamos ambos enfoques:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Salida de consola:
verdadero

119. Escriba un algoritmo de clasificación simple (Burbuja, Selección o Lanzadera). ¿Cómo puede ser mejorado?

Como algoritmo simple de implementación, elegí el ordenamiento por selección: Ordenamiento por selección:
public static void selectionSorting(int[] arr) {
 for (int i = 0; i < arr.length - 1; i++) {
   int min = i;
   for (int j = i + 1; j < arr.length; j++) {
     if (arr[j] < arr[min]) {
       min = j; // выбираем минимальный элемент в текущем числовом отрезке
     }
   }
   int temp = arr[min]; // меняем местами минимальный элемент с элементом под индексом i
   arr[min] = arr[i]; // так Cómo отрезок постоянно уменьшается
   arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
 } // и Cómo итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
La versión mejorada quedaría así:
public static void improvedSelectionSorting(int[] arr) {
 for (int i = 0, j = arr.length - 1; i < j; i++, j--) { // рассматриваемый отрезок с каждой итерацией
   // будет уменьшаться с ДВУХ сторон по одному элементу
   int min = arr[i];
   int max = arr[i];
   int minIndex = i;
   int maxIndex = i;
   for (int n = i; n <= j; n++) { // выбираем min и max на текущем отрезке
     if (arr[n] > max) {
       max = arr[n];
       maxIndex = n;
     } else if (arr[n] < min) {
       min = arr[n];
       minIndex = n;
     }
   }
   // меняем найденный минимальный элемент с позиции с индексом min на позицию с индексом i
   swap(arr, i, minIndex);

   if (arr[minIndex] == max) {// срабатывает, если элемент max оказался смещен предыдущей перестановкой -
     swap(arr, j, minIndex); // на старое место min, поэтому с позиции с индексом min смещаем его на позицию j
   } else {
     swap(arr, j, maxIndex); // простое обмен местами элементов с индексами max и j
   }
 }
}

static int[] swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 return arr;
}
Bueno, ahora tenemos que asegurarnos de si la clasificación realmente ha mejorado. Comparemos el rendimiento:
long firstDifference = 0;
long secondDifference = 0;
long primaryTime;
int countOfApplying = 10000;
for (int i = 0; i < countOfApplying; i++) {
 int[] arr1 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 selectionSorting(arr1);
 firstDifference += System.nanoTime() - primaryTime;

 int[] arr2 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 improvedSelectionSorting(arr2);
 secondDifference += System.nanoTime() - primaryTime;
}

System.out.println(((double) firstDifference / (double) secondDifference - 1) * 100 + "%");
Ambos tipos comenzaron en el mismo ciclo, porque Si hubiera bucles separados, ordenar desde el código anterior mostraría un resultado peor que si se colocara en segundo lugar. Esto se debe al hecho de que el programa se "calienta" y luego funciona un poco más rápido. Pero me estoy saliendo un poco del tema. Después de cinco ejecuciones de esta verificación en la consola, vi un aumento en el rendimiento de: 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% En cuanto a mí, esto es bastante bueno. resultado. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 7

120. Escriba un algoritmo (secuencia de acciones) para componer un literal de tipo int con un literal de tipo byte. Explica qué sucede con la memoria.

  1. El valor del byte se convierte a int. No se le asignará 1 byte de memoria, pero como todos los valores int, 4, si este valor aún no está en la pila int. Si lo hay, simplemente se recibirá un enlace.

  2. Se sumarán dos valores int y se obtendrá el tercero. Se le asignará una nueva sección de memoria: 4 bytes (o se recibirá una referencia de la pila int al valor existente).

    En este caso, la memoria de dos int todavía estará ocupada y sus valores se almacenarán en la pila de int, respectivamente.

En realidad, aquí es donde terminan las preguntas de nivel Junior de nuestra lista. A partir del próximo artículo, comprenderemos los problemas de nivel medio. Me gustaría señalar que las preguntas de nivel medio también se hacen activamente a los desarrolladores principiantes: Junior. Así que estad atentos. Bueno, eso es todo por hoy: ¡nos vemos!Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 13 - 8
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION