Sword se refiere a offer-45-el último número restante en el círculo-java

Preguntas y pruebas

package sword045;
/* 题目:0,1,...,n-1 这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。

测试样例:

输入: 0,1 , 2, 3, 4

输出: 3
*/

import java.util.List;

public class main {
	
	public static void main(String[] args) {
		int [] testTable = {5,10,15};
		int [] testTable2 = {1,2,3};
		for(int i=0;i<testTable.length;i++){
			test(testTable[i],testTable2[i]);
		}
	}
		 
	private static void test(int ito,int ito2) {
		Solution solution = new Solution();
		int rtn;
		long begin = System.currentTimeMillis();
		System.out.print(ito+"  ");
		System.out.print(ito2);
		System.out.println();
		//开始时打印数组
		
		rtn= solution.lastRemaining(ito,ito2);//执行程序
		long end = System.currentTimeMillis();	
		
		System.out.println("rtn=" );
		System.out.print(rtn);	
		System.out.println();
		System.out.println("耗时:" + (end - begin) + "ms");
		System.out.println("-------------------");
	}

}

Solución 1 (éxito)

Dado que hay un círculo digital en el título, la idea natural es utilizar una estructura de datos para simular este círculo. Entre las estructuras de datos de uso común, podemos pensar fácilmente en listas enlazadas circulares. Podemos crear una lista enlazada circular con un total de n nodos, y luego eliminar el nodo m-ésimo de esta lista enlazada cada vez

package sword045;


public class Solution {
	public int lastRemaining(int n, int m) {
		if(n == 0 || n==1) {
			return 0;
		}
		ListNode first = new ListNode(0);
		ListNode now = first;
		for(int i=1;i<n;i++) {
			now.next = new ListNode(i);
			now = now.next;
		}
		now.next = first;
		now = first;
		for(int i=0;i<n-1;i++) {
			for(int j = 1;j<m;j++) {
				now = now.next;
			}
			removeNode(now);
		}
		return now.val;
	}
	
	private void removeNode(ListNode node) {
		ListNode next = node.next;
		node.val = next.val;		
		node.next = next.next;
		next.next = null;
	}
	
	
}

Solución 2 (otras personas)

Defina la función f (n, m), que significa eliminar el último número restante del m-ésimo número de los n números 0,1, ..., n-1 cada vez.
Entre los n números, el primer número que se eliminará es (m% n) -1, y lo denotamos como K. Después de eliminar el primer elemento K, los n-1 restantes son 0, 1,2, .. ., k-1, k + 1, ..., n-1, y la siguiente eliminación comienza a contar desde K + 1. Entonces, la siguiente cuenta es realmente equivalente a atravesar en tal secuencia: K + 1, ..., n-1, 0, 1, ..., K-1. Esta secuencia es en realidad la misma que la anterior, la diferencia es que hemos modificado su orden, pero el orden de recorrido es el mismo al eliminar elementos. Por lo tanto, los números restantes después de varias eliminaciones deben ser los mismos que en la secuencia anterior. Eliminamos el m-ésimo dígito en la última secuencia y registramos los dígitos restantes como f '(n-1, m). En cuanto a por qué se registra como f' (n-1, m), lo entenderás más adelante. Así que ahora podemos al menos estar seguros de que f (n, m) = f '(n-1, m).

Veamos esta secuencia nuevamente: k + 1, ..., n-1,0,1, ..., k-1. Mapeamos esta secuencia, y el resultado del mapeo es una secuencia de 0 a n-2:
       k + 1 -> 0
       k + 2 -> 1
              ......
       n-1 -> nk-2
       0-> nk-1
       1 -> nk
              ......
       k-1 -> n-2
    f '(n-1, m) f (n-1, m)
definimos el mapeo como p, luego p (x) = (xk-1)% n. Significa que si el número antes del mapeo es x, entonces el número después del mapeo es (xk-1)% n. La inversa de este mapeo es p-1 (x) = (x + k + 1)% n. Dado que desea dominar este método, debe comprenderlo a fondo. Permítame probarlo conmigo a continuación:

Prueba:
Sea y = p (x), es decir, y = (xk-1)% n,
luego y = (xk-1) + t1n, t1 es un número entero y 0 <= y <n  
<--- > x = y-t1n + k + 1
<----> x = (y + k + 1) + t2n, es decir, y = (x + k + 1) + tn, entonces p-1 (x) = (x + k +1)% n
prueba completa.

Ahora, encontramos que los n-1 números después del mapeo son los mismos que los n números originales en forma. Solo mire un número n-1 menos. Luego, para los n-1 números 0, 1, ..., n-2, colóquelos en un círculo y elimine el número m-ésimo cada vez del número 0. ¿Pueden los números restantes expresarse como f (n -1, m) ?! Ahora, ¿has descubierto por qué definimos la secuencia como f '(n-1, m) antes? ¡Esto es para establecer una conexión entre dos eliminaciones! Es decir, los n elementos originales, después de eliminar el primer elemento k, se podría decir que la secuencia inicial se interrumpe y no hay reglas; pero reorganizamos la secuencia en la forma de la secuencia inicial a través de una relación de mapeo. De esta manera, siempre que encontremos tal relación de mapeo y encontremos la relación funcional (ley de iteración) entre las dos operaciones, el problema se transformará en un problema recursivo. La salida del problema recursivo es muy fácil de determinar, cuando n = 1, la secuencia tiene un solo elemento: 0, f (1, m) es este 0!
Ahora que existe una relación de mapeo, encontremos la relación entre dos operaciones iterativas, es decir, cómo encontrar f (n, m) a partir de f (n-1, m).

Solución:
Debido a que f (n, m) = f '(n-1, m) y f' (n-1, m) = (f (n-1, m) + k + 1)% n, entonces f (norte, metro) = (f (norte-1, metro) + k + 1)% norte. Y como k = (m% n) -1, trayendo f (n, m) = (f (n-1, m) + k + 1)% n, obtenemos: f (n, m) = (f (n-1, m) + m)% n.
Por lo tanto, cuando n = 1, f (n, m) = 0
Cuando n> 1, f (n, m) = [f (n-1, m) + m]% n

Con esta relación de recurrencia, ¿es posible escribir código? Puede usar la recursividad de arriba hacia abajo o iterar de abajo hacia arriba. La recursividad obviamente no tiene el problema de resolver subproblemas aquí, pero habrá muchas operaciones de pila, por lo que es mejor usar métodos iterativos directamente. En cuanto al código fuente del método iterativo, se ha dado anteriormente. int último = 0; es el valor de f (1, m) cuando n = 1; el siguiente ciclo for es resolver el valor de f (n, m) de abajo hacia arriba.
Esta idea es muy complicada, pero el código es particularmente conciso y la mayor parte del tiempo se dedica a analizar y derivar fórmulas. La complejidad temporal de este método es O (n) y la complejidad espacial es O (1). Tanto la complejidad temporal como la complejidad espacial son mejores que el primer método.

public static int lastRemaining_2(int n,int m){
        if(n<1||m<1) return -1;
        int last = 0;
        for(int i=2;i<=n;i++){
            last = (last+m)%i;
        }
        return last;
    }

 

Supongo que te gusta

Origin blog.csdn.net/xushiyu1996818/article/details/112461340
Recomendado
Clasificación