2 D配列で指定したシーケンスが存在するかどうかを検索


タイトルの説明:
URL : https://oj.leetcode.com/problems/word-search/
Given a 2D board and a word, find if the word exists in the grid.
The word can be constructed from letters of sequentially adjacent cell, where "adjacent"cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once.
For example,
Given board =
[
  ["ABCE"],
  ["SFCS"],
  ["ADEE"]
]
word = "ABCCED", -> returns true,
word = "SEE", -> returns true,
word = "ABCB", -> returns false.
このテーマの入力は2次元の文字配列で、m*nと仮定し、文字列(文字列)を与えて、この2次元配列の中で1つの経路(横に歩くか縦に歩くしかない)がこの文字配列と同じであるかどうかを判断させます.そして、各ポイントが最大1回しか通過できないことを保証します.
この問題を手に入れると、私はまずこの2次元配列を巡り、各文字とこの文字が現れるすべての座標のlistのマッピング関係を保存し、入力された文字シーケンスから各文字を順次検索し、検索対象の文字ごとに、そのすべての座標を調べ、各座標の隣接座標(x同じy差1またはx同じy差1)について文字列の次の文字と同じかどうか、同じ場合は続行し、異なる場合は見つからないことを示します.
しかし、このような考え方は間違っており、2つの条件を無視している:1、2次元配列の中で1文字ごとに1回しか現れない.2、判断される文字列シーケンスがABCDEFGであると仮定すると、前のABCDは対応するマッチングパスを見つけたが、DはEへのパスを見つけられず、前のABCのためにDの位置が固定されている.次の例を示します.
ABCD
ABCD
ABCD
HGFE
このとき与えられたシーケンスがABCDEFGHであると仮定し,まず最初の行のAを見つけ,次に隣接するBを順に見つけ,B隣接するCを見つけ,C隣接するDを見つけたが,D隣接するEはなかった?!このようにfalseに戻ると、結果は間違いに違いない.以下にABCDDEFGHというシーケンスが確かに存在するからだ.これは遡及がないからだ.つまり、DからEが見つからない場合は、Cに戻り、Cに隣接する他のDノードを見て、Eを見つけてみる必要がある.もしなければ、Bに戻り、すべてのAを試してみるまで順番に類推する必要がある.また,各試行ごとに各ノードが最大で順次しか通過しないことを保証する必要がある.
ここで解法を見ると明らかであり、再帰的な検索が必要であり、遍歴する文字シーケンス上の各ノードXについて、そのノードに隣接するすべてのノードが文字シーケンスの次の文字と同じノードを持っているかどうかを確認し、ノードKが存在する場合、文字Kは文字シーケンス列のX+1番目に対応し、このとき再帰的なルックアップノードKの隣接ノードは、文字列のX+2番目の文字と同じノードがあるかどうか、ある場合は文字列の最後の文字まで続くとtrueを返す.そうでなければfalseを返し、呼び出し元がtrueを得た場合、パスが見つかったことを示す場合はtrueを直接返し、falseを得た場合は次の隣接ノードを試し続けます.考え方は以下の通りである.
まず位置情報を定義します.
class Point {
private int x;
private int y;
private boolean used;    //       。
}

1、二次元配列の各文字を遍歴し、各文字とそれが現れるのはすべての位置情報を1つのmapに保存し、keyは文字の値であり、valueはListであり、この時すべてのpointのusedはfalseである.
2、呼び出し関数checkExist(map,word,0);ここでmapは第1ステップで確立されたmapであり、wordは入力された文字シーケンスであり、0は開始位置を表す.
2.1.checkExist関数では、まず最後のパラメータがwordの最後の位置であるか否かを判断し、もしそうであれば説明を最後に戻し、trueを返す.
2.2.最後の位置でない場合は、その位置にある文字列と次の位置にある文字に基づいて、これらの文字のすべての出現ポイントを得る
2.3.前の文字の各位置について、後の文字の各位置が隣接しているかどうかを順次確認し、隣接説明が現在の位置で次のノードを見つけた場合、この関数を再帰的に呼び出し、最後のパラメータを1とする.
2.4、呼び出された関数がtrueを返し、最後のノードに遍歴したことを示す場合は、trueを直接返します.そうでなければ、次の遍歴を続けます(もちろん、表示する前に位置が通過したかどうかを判断する必要があります).
	//     word            
	private boolean checkExist(Map<Character, Unit> map, String word, int index) {
		if(index >= word.length() - 1)
			return true;
		
		char ch = word.charAt(index);
		Unit unit = map.get(ch);
		char nextCh = word.charAt(index + 1);
		Unit nextUnit = map.get(nextCh);
		
		//         map ,             ,    false
		if(unit == null || nextUnit == null)
			return false;
  
		//            
		for(Point p : unit.getPoints()) {
			if(p.isUsed())
				continue;
			//        ,              ,        
			p.setUsed(true);
			for(Point next : nextUnit.getPoints()) {
				//       ,     
				if(next.isUsed())
					continue;
				if(!p.adjacent(next))
					continue;
				//           ,  word         ,       
				boolean ret = checkExist(map, word, index + 1);
				//   true。     word       ,      			
				if(ret) 
					return true;
			}
			p.setUsed(false);
		}
		
		//                       ,  false,             
		return false;
	}

これにより、正しい結果が得られますが、再帰的な考え方は簡単であることはよく知られていますが、入力が次のようになります.
aaa
aaa
aaa
wordがaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
では、再帰解法がある以上、非再帰的な解法があるに違いないが、私たちは自分でスタックを構築し、スタックフレームごとにどのようなものを記録するのだろうか.現在の文字(すなわち、文字シーケンスの各文字)、2 Dテーブルの現在の文字の位置(listの下付き文字を記録するだけ)、次の文字の下付き文字(これにより、次の位置から遍歴を続けることができる)を含めるべきだと思います.
class Element {
private char ch;
private int index;
private int nextIndex;
}

1、同様にmapを算出する
2、wordの最初の文字のすべての位置(つまりmapの最初の文字に対応するpointのlistを巡回する必要がある)について、次の操作を実行する必要があります.
     2.1、現在の文字(つまりwordの最初の文字)をスタックに入れ、indexは遍歴の下付き文字に等しく、この下付き文字に対応するPointのusedをtrueに設定し、nextIndexは-1に等しい.私たちはまだ次の文字の位置を判断していないからだ.
     2.2.スタックが空になるまで循環する.毎回の循環について:
     2.3、まずスタックトップ要素を見て、この要素は現在wordに沿って前に遍歴している現在の要素であり、wordの最後の要素に着いた場合(スタック内の現在の要素数で判断できる)、説明は経路を見つけた.
     2.4、そうでなければ現在の要素の情報に基づいて次の要素が現れることができる下付き文字を探し出して、もし探し当てることができるならばそれをスタックに入れて、そうでなければ現在の要素はスタックを出して、usedはfalseに設定します.
	public boolean exist(char[][] board, String word) {
		//    map
        int length = word.length();
        Stack stack = new Stack(length);
        List<Point> head = indexMap.get(word.charAt(0));
        if(head == null)
        	return false;
        for(int i = 0 ; i < head.size() ; ++ i) {
        	stack.push(new Element(word.charAt(0), i));
        	head.get(i).setUsed(true);
        	while(stack.getTop() > 0) {
        		int index = stack.getTop() - 1;
        		if(index == length - 1)
        			return true;
        		Element top = stack.top();
        		Element next = getNextElement(indexMap, word, index, top);
        		if(next == null) {
        			Element e = stack.pop();
        			indexMap.get(e.getChar()).get(e.getIndex()).setUsed(false);
        		}
        		else {
        			stack.push(next);
        		}
        	}
        }
        return false;
	}
	
	//                   
	private Element getNextElement(Map<Character, List<Point>> map, String word, int index, Element prev) {
		if(index >= word.length() - 1)
			return null;
		
		char first = word.charAt(index);
		char second = word.charAt(index + 1);
		
		List<Point> cur = map.get(first);
		List<Point> next = map.get(second);
		if(cur == null || next == null)
			return null;
		
		Point point = cur.get(prev.getIndex());
		int size = next.size();
		//              
		for(int i = prev.getNextIndex() + 1 ; i < size ; ++ i) {
			Point nextPoint = next.get(i);
			if(nextPoint.isUsed())
				continue;
			if(point.adjacent(nextPoint)) {
				nextPoint.setUsed(true);
				prev.setNextIndex(i);
				return new Element(second, i);
			}
		}
		return null;
	}
	

この解法はOJの検査に合格したが、やはり複雑で、比較的簡単な案があるに違いない.OJシステムにも答えが出ていないので、もっと良い解法があるかどうかを考えているだろう.
この問題から、再帰的に解決できる問題については、必ず非再帰的に解決できることを学びましたが、再帰的なスタックをどのようにシミュレートするかは考えなければならない問題です.まず、再帰的に次の呼び出しに入るときに現在のスタックに保存する必要があるものを考えてみましょう.この例では、次の再帰に入るとき、現在のスタックにはwordがどの位置に行ったか(wordの下付き)、現在の文字で使用されているPointと次の文字で使用されているPointが保存されています.また、次の再帰に入る前に現在のPointが使用されていることを忘れずに、フラグが必要です.
この問題は迷宮に似ていて、迷宮は入り口点の座標を与えていますが、ここでは探す必要があり、複数の入り口点が存在する可能性があります.迷宮も遡及する必要があります.ある座標が通過したことを記録する必要があります.
間違ったところやもっと良い解法があれば、もっと指摘してほしいです.
コードアドレス:https://github.com/terry-chelsea/Algorithm/tree/master/src/word_search