剣はオファーを指します-45-サークル内の最後の残りの数-java

質問とテスト

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("-------------------");
	}

}

解決策1(成功)

タイトルにはデジタルサークルがあるため、データ構造を使用してこのサークルをシミュレートするのが自然なアイデアです。一般的に使用されるデータ構造の中で、循環リンクリストを簡単に考えることができます。合計n個のノードを持つ循環リンクリストを作成し、そのたびにこのリンクリストからm番目のノードを削除できます。

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;
	}
	
	
}

解決策2(他の人の)

関数f(n、m)を定義します。これは、m番目の数の最後の残りの数をn個の数0,1、...、n-1から毎回削除することを意味します。
n個の数のうち、最初に削除される数は(m%n)-1であり、この数をKと表記します。最初の要素Kを削除した後、残りのn-1個の数は0、1、2、...です。 。、k-1、k + 1、...、n-1、そして次の削除はK +1からカウントを開始します。次に、次のカウントは、実際には、K + 1、...、n-1、0、1、...、K-1のような順序でトラバースすることと同じです。このシーケンスは実際には前のシーケンスと同じです。違いは、順序を変更したことですが、要素を削除するときの走査順序は同じです。したがって、いくつかの削除後の残りの数は、前のシーケンスと同じである必要があります。最後のシーケンスのm番目の桁を削除し、残りの桁をf '(n-1、m)として記録します。なぜf'(n-1、m)として記録されるのかについては、後で理解します。したがって、少なくともf(n、m)= f '(n-1、m)であることを確認できます。

このシーケンスをもう一度見てみましょう:k + 1、...、n-1,0,1、...、k-1。このシーケンスをマッピングすると、マッピングの結果は0から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)
マッピングをpとして定義し、次にp(x) =(xk-1)%n。これは、マッピング前の数値がxの場合、マッピング後の数値は(xk-1)%nであることを意味します。このマッピングの逆は、p-1(x)=(x + k + 1)%nです。この方法をマスターしたいので、それを完全に理解する必要があります。以下で私と一緒にそれを証明させてください。

証明:
y = p(x)、つまりy =(xk-1)%nとし
、y =(xk-1)+ t1n、t1は整数、0 <= y <n  
<--- > x = y-t1n + k + 1
<----> x =(y + k + 1)+ t2n、つまりy =(x + k + 1)+ tnなので、p-1(x) =(x + k +1)%n
証明が完了しました。

さて、マッピング後のn-1の数は、フォームの元のnの数と同じであることがわかりますか?1つ少ない数n-1を見てください。次に、n-1個の数0、1、...、n-2について、それらを円に配置し、番号0から毎回m番目の数を削除します。残りの数はf(n -1、m)?!さて、以前にシーケンスをf '(n-1、m)として定義した理由を発見しましたか?これは、2つの削除間の接続を確立するためです!つまり、元のn個の要素は、最初の要素kを削除した後、おそらく最初のシーケンスが中断され、ルールはありませんが、マッピング関係を介してシーケンスを最初のシーケンスの形式に再配置します。このように、このようなマッピング関係を見つけ、2つの操作間の関数関係(反復法則)を見つける限り、問題は再帰的な問題に変換されます。再帰的問題の終了は非常に簡単に判別できます。n= 1の場合、シーケンスには1つの要素しかありません。0、f(1、m)はこの0です。
マッピング関係ができたので、2つの反復操作間の関係、つまりf(n-1、m)からf(n、m)を見つける方法を見つけましょう。

解決策:
f(n、m)= f '(n-1、m)、およびf'(n-1、m)=(f(n-1、m)+ k + 1)%nであるため、f (n、m)=(f(n-1、m)+ k + 1)%n。また、k =(m%n)-1であるため、f(n、m)=(f(n-1、m)+ k + 1)%nを取得すると、次のようになります。f(n、m)=(f (n-1、m)+ m)%n。
したがって、n = 1の場合、f(n、m)= 0
n> 1の場合、f(n、m)= [f(n-1、m)+ m]%n

この漸化式で、コードを書くことは可能ですか?上から下に再帰を使用することも、下から上に繰り返すこともできます。再帰には明らかにここでサブ問題を解決する問題はありませんが、スタック操作がたくさんあるので、反復法を直接使用することをお勧めします。反復法のソースコードについては、上記のとおりです。int last = 0;はn = 1のときのf(1、m)の値です。次のforループは、f(n、m)の値を下から上に解くことです。
このアイデアは非常に複雑ですが、コードは特に簡潔であり、主な時間は数式の分析と導出に費やされます。この方法の時間計算量はO(n)であり、空間計算量はO(1)です。時間計算量と空間計算量の両方が最初の方法よりも優れています。

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;
    }

 

おすすめ

転載: blog.csdn.net/xushiyu1996818/article/details/112461340