1. 程式人生 > 其它 >程式設計題:單詞接龍遊戲(暴力遞迴)

程式設計題:單詞接龍遊戲(暴力遞迴)

技術標籤:刷題日記

題目 單詞接龍

參考了牛客網演算法課助教的程式碼

單詞接龍是一個與我們經常玩的成語接龍相類似的遊戲,現在我們已知一組單詞,且給定一個開頭的字母,要求出以這個字母開頭的最長的“龍”(每個單詞都最多在“龍”中出現兩次),在兩個單詞相連時,其重合部分合為一部分,例如beast和astonish,如果接成一條龍則變為beastonish,另外相鄰的兩部分不能存在包含關係,例如at和atide間不能相連。
輸入描述:

輸入的第一行為一個單獨的整數n(n ≤ 20)表示單詞數,以下n行每行有一個單詞,輸入的最後一行為一個單個字元,表示“龍”開頭的字母。你可以假定以此字母開頭的“龍”一定存在.

輸出描述:

只需輸出以此字母開頭的最長的“龍”的長度

示例:
輸入:

5
at
touch
cheat
choose
tact
a

輸出:

23

說明:

連成的“龍”為atoucheatactactouchoose

解析:
有幾個比較坑的地方需要注意

  • 每個單詞可以出現2次,也就是說備選列表裡需要填進2次單詞
  • 如果兩個單詞準備a+b這樣接龍但後者覆蓋前者,就不能算作接龍

解決辦法:

  • 在讀輸入的時候每次往備選列表list里加入兩邊該單詞
  • 在嘗試接龍匹配時,我們設定匹配內容為start,這是一個字串,從尾word.length()-1向前擴充套件,當然這個擴充套件並匹配的過程就是一個遍歷,而這個start
    遍歷到下標為1就停止了,防止出現匹配後者覆蓋前者的現象。比如aaabbb匹配aaabbb,那麼匹配到aabbb就結束了,因為如果覆蓋前者就不算是接龍
  • 遞迴過程中需要的引數有:最新可選的單詞列表list,用於匹配的前者子串start,目前已經接龍的字串pathStr
  • 對於每次遞迴過程,base case為 用光所有單詞,單詞列表長度為0,則跳出遞迴。
  • 對於其他情況需要我們用for迴圈來控制,如果遍歷結束則結束
  • for迴圈中就是選擇的一個過程,選擇之後會得到新的pathStr,儲存最大值
  • 如果要加上一個單詞接龍,則直接通過process函式獲取其作為起始單詞以及剩餘單詞列表所能組成的最長接龍結果

拆解:

  1. 匹配接龍,對於前者單詞word,進行start
    字串選取並遞迴匹配(比如aaabbb,start就是b bb bbb abbb…
for(int i=word.length()-1;i>0;i--){
	//直接進入遞迴,遞迴是從單詞列表裡選擇,遍歷過程,並選取最大值
	//這裡的start就是word.substring(i)
	res = Math.max(res,process(list,word.substring(i),path);
	//注意合起來寫的時候list是newList,這樣在每次遍歷時新建list,就不用在遞迴之後還原list
}
  1. 因為每個單詞有兩次選取機會,但是我們不能在一輪迴圈中(選擇某單詞的下一個單詞)重複選,所以建立HashSet來避免重複
HashSet<String> set = new HashSet<>();
//開始遍歷list裡的每個單詞選項啦,這裡的單詞就是下一個選擇
for(String word:list){
	//可被選擇的條件,首先當然要大於start不然path就沒有增加,然後是要匹配上start
	if(word.length()>start.length()&&word.substring(0,start.length()).equals(start)){
		//還沒有被重複選過
		if(!set.contains(word)){
			//進行我們的新建工作
			ArrayList<String> newList = new ArrayList<>(list);
			newList.remove(word);//將這個單詞從可選列表裡移除
			set.add(word);//將這個單詞加入不可重複set
			String newPath = "";
			if(path.length()==0){
				//這是第一個單詞
				newPath = word;
			}else{
				newPath = path + word.substring(start.length());//從start下一個開始,即下標直接是start的長度
			}
			//接1中,從word裡選取start並遞迴
		}
	}
}

最後整理一下思路:

  • 建立單詞選擇列表list
  • 選擇當前單詞wordword遍歷選取匹配字尾start
  • 在新的單詞選擇列表裡選擇新單詞匹配start
  • 對於list中的每個不重複選擇,返回匹配字尾的接龍結果最大值(相當於以該單詞為起始)
  • 注意base case是單詞被選完,以及用for迴圈控制遍歷list

完整程式碼:

import java.util.*;

public class Main{
    public static void main(String[] args){
        //System.out.println(connect("at","touch"));
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        ArrayList<String> list = new ArrayList<>();
        
        for(int i=0;i<n;i++){
            String s = sc.next();
            list.add(s);
            list.add(s);
        }
        String startStr = sc.next();
        System.out.println(process(list,startStr,""));
        sc.close();
    }
    
    /**
     * 
     * @param list 目前可選單詞列表
     * @param start 目前作為開頭(前一個單詞末尾)的字串
     * @param path 目前已經接龍了的字串結果
     * @return
     */
    public static int process(ArrayList<String> list, String start, String path){
        if(list.size()==0){
            return path.length();
        }
        int res = path.length();
        HashSet<String> set = new HashSet<>();
        //開始選擇
        for(String word:list){
            if(word.length()>start.length()&&word.substring(0,start.length()).equals(start)){
                //如果大於start的長度且0到start已經跟start匹配上,則可以選擇(這裡將start每次擴充套件1位)
                //如果還沒有選取過
                if(!set.contains(word)){
                    set.add(word);
                    //直接傳遞新的list和str,之後就不用恢復了
                    ArrayList<String> newList = new ArrayList<>(list);//將list填充進去
                    newList.remove(word);//暫時移除,表示已經選取
                    String newStr = "";//表示暫時的接龍單片語
                    if(path.length()==0){
                        newStr = word;//起始單詞
                    } else {
                        newStr = path+word.substring(start.length());//在末尾接上本單詞
                    }
                    for(int i=word.length()-1;i>0;i--){//從最末尾開始,到1結束,作為新的start看能不能匹配上
                        res = Math.max(res,process(newList,word.substring(i),newStr));//對目前選取的word進行擷取成為遞迴時的start
                    }
                }
            }
        }
        return res;
    }
    
    
}