본문 바로가기
Study Log/Algorithm

카카오 2020 블라인드 채용 1차 코딩테스트 4번 문제 - 가사 검색

by HZie 2020. 9. 11.

문제 링크: programmers.co.kr/learn/courses/30/lessons/60060

 

코딩테스트 연습 - 가사 검색

 

programmers.co.kr

!! 내가 푼 코드는 효율성 테스트를 통과하지 못했다 !!



// 카카오 테크 블로그에서 설명해준 바에 따르면 Trie 자료 구조를 사용하면 되는 듯 하다
// trie를 썼다. 효율성 테스트 12는 이제 통과 하는데 3,4,5가 통과되지 않는다
// 아무래도 arraylist 때문인 것 같다.
import java.util.*;

class Solution {
    public int[] solution(String[] words, String[] queries) {
        int[] answer = new int[queries.length];
        Trie trie = new Trie();
        for(int i = 0; i < words.length; i++) {
        	trie.add(words[i]);
        }
        
        for(int i = 0; i < queries.length; i++) {
        	if(queries[i].startsWith("?")) {
        		answer[i] = trie.contains(queries[i], true);
        	}
        	else {
        		answer[i] = trie.contains(queries[i], false);
        	}
        	
        }
        
        return answer;
    }
    
    
}

class TrieNode{
    private HashMap<Character, TrieNode> children = new HashMap<>();
    private boolean isLastChar;
    private ArrayList<Integer> restLength = new ArrayList<>();
    
    public HashMap<Character,TrieNode> getChildren(){ return this.children; }
    public ArrayList<Integer> getRestLength(){ return this.restLength; }
    
    public boolean isLastChar(){ return this.isLastChar; }
    public void setIsLastChar(boolean isLastChar){ this.isLastChar = isLastChar; }
}

class Trie{
    private TrieNode root;
    private TrieNode revRoot;
    Trie(){ 
        root = new TrieNode();
        revRoot = new TrieNode();
    }
    
    public void add(String word){
        TrieNode curr = root;
        HashMap<Character, TrieNode> children = curr.getChildren();
        
        for(int i = 0; i < word.length() ; i++){
            char c = word.charAt(i);
            if(!children.containsKey(c)){
                curr = new TrieNode();
                children.put(c, curr);
            }
            else{
                curr = children.get(c);
            }
            curr.getRestLength().add(word.substring(i+1).length());
            children = curr.getChildren();
        }
        curr.setIsLastChar(true);
        
        curr = revRoot;
        children = curr.getChildren();
        for(int i = word.length()-1; i >= 0 ; i--){
            char c = word.charAt(i);
            if(!children.containsKey(c)){
                curr = new TrieNode();
                children.put(c, curr);
            }
            else{
                curr = children.get(c);
            }
            curr.getRestLength().add(word.substring(0,i).length());
            children = curr.getChildren();
        }
        curr.setIsLastChar(true);
        
        
    }
    
    public int contains(String word, boolean reverse){
    	TrieNode curr;
        if(reverse) {
        	curr = revRoot;
            int count = 0;
            
            for(int i = word.length()-1; i >= 0; i--){
                char c = word.charAt(i);
                if(c == '?'){
                    count = 0;
                    int rest = word.substring(0,i+1).length();
                    for(int len : curr.getRestLength()){
                        if(len == rest)
                            count++;
                    }
                    return count;
                }
                TrieNode node = curr.getChildren().get(c);
                
                if(node == null)
                    return count;
                
                curr = node;
            }
            return count;
        }
        
        else {
        	curr = root;
            int count = 0;
            
            for(int i = 0; i < word.length(); i++){
                char c = word.charAt(i);
                if(c == '?'){
                    count = 0;
                    int rest = word.substring(i).length();
                    for(int len : curr.getRestLength()){
                        if(len == rest)
                            count++;
                    }
                    return count;
                }
                TrieNode node = curr.getChildren().get(c);
                
                if(node == null)
                    return count;
                
                curr = node;
            }
            return count;
        }
    }
        	

    
    
}



/*
// 정규 표현식을 사용하였다
// 정답은 모두 나왔는데 여전히 효율성 테스트 1~3을 통과하지 못했다.
// 아무래도 O(n^2) 외에 더 좋은 방법이 있는 것 같다.
class Solution {
    public int[] solution(String[] words, String[] queries) {
        int[] answer = new int[queries.length];
        String qRegx;
        for(int i = 0; i < queries.length; i++){
            qRegx = getRegx(queries[i]);
            for(int j = 0; j < words.length; j++){
                if(words[j].matches(qRegx))
                    answer[i]++;
            }
        }
        
        return answer;
    }
    
    public String getRegx(String q){
        String regx="*";
        if(q.startsWith("?")){
            int len = q.lastIndexOf("?") + 1;
            String subQ = q.substring(len);
            regx = "^.{"+len+"}"+subQ+"$";
        }
        else if(q.endsWith("?")){
            int len = q.length() - q.indexOf("?");
            String subQ = q.substring(0, q.indexOf("?"));
            regx = "^"+subQ+".{"+len+"}$";
        }
        return regx;
    }
    
}

*/

/*
// 정답은 나오나 효율성 테스트를 통과하지 못함
class Solution {
    public int[] solution(String[] words, String[] queries) {
        int[] answer = new int[queries.length];
        for(int i = 0; i < queries.length; i++){
            int[] position = getWCPosition(queries[i]);
            answer[i] = isMatch(words, queries[i], position);
        }
        
        return answer;
    }
    
    public int[] getWCPosition(String q){
        int[] position = {-1,-1};   // head일 경우 1, tail일 경우 2
        char[] strCharArray = q.toCharArray();
        for(int i = 0; i < strCharArray.length; i++){
            if(strCharArray[i] == '?'){
                if(position[0] == -1){
                    if(i == 0)
                        position[0] = 1;
                    else{
                        position[0] = 2;
                        position[1] = i;
                    }
                }
                else if(position[0] == 1){
                    if(i == strCharArray.length - 1 || strCharArray[i+1] != '?')
                        position[1] = i+1;
                }
            }
        }
        return position;
    }
    
    public int isMatch(String[] words, String query, int[] p){
        int count = 0;
        String subQuery;
        int wcLength = 0;
        if(p[0] == 1) {
            subQuery = query.substring(p[1]);
            wcLength = query.substring(0,p[1]).length();
        }
        else {
            subQuery = query.substring(0, p[1]);
            wcLength = query.substring(p[1]).length();
        }
        for(int i = 0; i < words.length; i++){
            String subWord;
            int wordWCLength;
            try{
            	
                if(p[0] == 1) {
                    subWord = words[i].substring(p[1]);
                    wordWCLength = words[i].substring(0,p[1]).length();
                }
                else {
                    subWord = words[i].substring(0, p[1]);
                    wordWCLength = words[i].substring(p[1]).length();
                }
                
                if(wcLength != wordWCLength)
                	continue;
                
                if(subWord.equals(subQuery))
                    count++;
            }
            catch(Exception e){}

        }
        return count;
    }
}
*/

효율성 테스트도 통과한 코드들은 아래의 블로그에서 확인할 수 있다.

leveloper.tistory.com/99

 

[프로그래머스] 2020 KAKAO BLIND RECRUITMENT 가사 검색 (Java)

프로그래머스 2020 KAKAO BLIND RECRUITMENT 가사 검색 : https://programmers.co.kr/learn/courses/30/lessons/60060 코딩테스트 연습 - 가사 검색 | 프로그래머스 programmers.co.kr 예전에 못 풀었다가 다시 한..

leveloper.tistory.com

velog.io/@ptm0304/2020%EC%B9%B4%EC%B9%B4%EC%98%A4%EA%B3%B5%EC%B1%84-%EA%B0%80%EC%82%AC-%EA%B2%80%EC%83%89

 

[2020카카오공채] 가사 검색

문제출처: https://programmers.co.kr/learn/courses/30/lessons/60060 문제 친구들로부터 천재 프로그래머로 불리는 프로도는 음악을 하는 친구로부터 자신이 좋아하는 노래 가사에 사용된 단어들 중에 특정 키

velog.io

 

 

이 문제를 풀면서 Trie에 대해 알게 되었다.

Trie는 특히 문자 검색을 할 때 많이 쓰이는데 자동 완성도 trie를 이용한 것이라고 한다.

String의 한 character씩을 모두 노드로 만들고, 나중에 나오는 character가 먼저 나오는 character의 자식 노드가 되는 트리의 한 종류이다. 이렇게 하면 front, frodo 와 같이 공통으로 시작하는 문자의 경우 탐색 시간이 훨씬 줄어든다.

 

다음은 Trie를 공부하면서 짠 자바 코드이다. 

 

class TrieNode{
	char val;
	private HashMap<Character, TrieNode> child = new HashMap<>();
	private boolean isLastChar;
	
	TrieNode(){}
	TrieNode(char val){
		this.val = val;
	}
	
	HashMap<Character, TrieNode> getChild(){
		return this.child;
	}
	
	boolean isLastChar() {
		return this.isLastChar;
	}
	
	void setIsLastChar(boolean isLastChar) {
		this.isLastChar = isLastChar;
	}
	
}

class Trie{
	private TrieNode root;
	
	public Trie(){
		root = new TrieNode();
	}
	
	void insert(String word) {
		TrieNode curr = this.root;
		HashMap<Character, TrieNode> child = curr.getChild();
		
		for(char ch: word.toCharArray()) {
			
			if(!child.containsKey(ch)) {
				curr = new TrieNode(ch);
				child.put(ch, curr);
			}
			else {
				curr = child.get(ch);
			}
			
			child = curr.getChild();
		}
		curr.setIsLastChar(true);
	}
	
	boolean contains(String word) {
		TrieNode curr = root;
		
		for(char ch: word.toCharArray()) {
			TrieNode child = curr.getChild().get(ch);
			
			if(child == null)
				return false;
			
			curr = child;
		}
		
		return curr.isLastChar();
	}
	
	void delete(String word) {
		delete(root, word, 0);
	}
	
	private boolean delete(TrieNode curr, String word, int index) {
		if(index == word.length()) {
			if(!curr.isLastChar())
				return false;
			curr.setIsLastChar(false);
			return curr.getChild().isEmpty();
		}
		char ch = word.charAt(index);
		TrieNode node = curr.getChild().get(ch);
		if(node == null)
			return false;
		
		boolean deleteCurrNode = delete(node, word, index+1) && !node.isLastChar();
		
		if(deleteCurrNode) {
			curr.getChild().remove(ch);
			return curr.getChild().isEmpty();
		}
		
		return false;
		
		
	}
}

어떤 사람들은 람다를 사용했는데 확실히 람다를 사용하면 코드가 훨씬 간단해진다.

람다를 사용하고자 한다면 insert 부분을 다음과 같이 바꾸면 된다.

	void insert(String word) {
		TrieNode curr = this.root;
		HashMap<Character, TrieNode> child = curr.getChild();
		
		for(char ch: word.toCharArray()) {
			curr = curr.getChild().computeIfAbsent(ch, c -> new TrieNode());
		}
		curr.setIsLastChar(true);
	}

 

댓글