-
문자열자료구조와 알고리즘/문제풀기 2024. 1. 10. 18:41
- 팰린드롬을 검사하는 간단한 문제이다.
- 단 문자열과 숫자만 비교해야한다.
> Leetcode (125) - 유효한 팰린드롬
나의 풀이 (13ms)
public boolean isPalindrome(String s) { s = s.toLowerCase(); s = s.replaceAll("[^a-z0-9]",""); int left = 0; int right = s.length()-1; for(int i=0; i<s.length();i++ ){ if(left > right){ return true; } if(s.charAt(left) == s.charAt(right)){ left++; right--; }else{ return false; } } return true; }
1. 문자 단위로 추출해서 처리 (2ms)
public boolean isPalindrome(String s) { int start =0; int end = s.length() - 1; while(start < end){ if(!Character.isLetterOrDigit(s.charAt(start))){ start++; }else if(!Character.isLetterOrDigit(s.charAt(end))){ end--; }else{ if(Character.toLowerCase(s.charAt(start))!=Character.toLowerCase(s.charAt(end))){ return false; } start++; end--; } } return true; }
2. 문자바로비교 (16ms)
public boolean isPAlindrome(String s){ String s_filtered = s.replaceAll("[^A-Za-z0-9]","").toLowerCase(); String s_reversed = new StringBuilder(s_filtered).reverse().toString(); return s_filtered.equals(s_reversed); }
- 배운 것
Chracter.isLetterOrDigit으로 숫자와 문자만 검사할 수 있다.
[^A-Za-z0-9] 정규식을 통한 제거
left,right로 하는 것은 좋았으나, 검사를 다르게 하는게 더 빨랐을 것 같다.
> 문자열 뒤집기 LeetCode(344)
1. 나의 풀이 (0ms)
public void reverseString(char[] s) { int len = s.length; for(int i=0; i<len/2;i++){ char temp = s[i]; s[i] = s[len-1-i]; s[len-1-i] = temp; } }
2. 풀이 스타일이 비슷하다!
int start = 0; int end = s.length - 1; while(start < end){ char temp = s[start]; s[start] = s[end]; s[end] = temp; start++; end--; }
- 배열 반만 도는 것 while문으로 표현하면 start<end로 start++, end-- 생각할 수 있다. (시작과 끝에서 한칸씩 오는경우)
> 가장 흔한 단어 (819)
1. 나의 풀이 (26)
public String mostCommonWord(String paragraph, String[] banned) { //a-z말고 다른 단어 모두 제거 paragraph = paragraph.toLowerCase().replaceAll("[^a-z]"," "); //띄어쓰기 두개이상인것 하나로 만들고 trim paragraph = paragraph.replaceAll("\\s+"," ").trim(); //split로 쪼개기 String[] arr = paragraph.split(" "); List<Integer> idxarr = new ArrayList<>(); for(int i=0; i<arr.length;i++){ int cnt =0; if(bannedString(arr[i],banned)){ idxarr.add(0); continue; }else{ for(int j=i+1; j<arr.length; j++){ //공백이 아니고 같은 단어일 경우 cnt++ if(!arr[i].isBlank()&&arr[i].compareTo(arr[j]) ==0){ cnt++; } } idxarr.add(++cnt); } } int MaxIdx = maxIdx(idxarr); return arr[MaxIdx]; } //금지단어일 경우 저장안함 private static boolean bannedString(String ar,String[] bad) { for(int i =0; i<bad.length;i++){ if(ar.equals(bad[i])){ return true; } } return false; } //배열에서 가장 큰 인덱스 찾기 public static int maxIdx(List<Integer> arr){ int maxIdx=0; for(int i=0; i<arr.size();i++){ if(arr.get(maxIdx) < arr.get(i)){ maxIdx=i; } } return maxIdx; }
2. HashMap 이용한 풀이
public String mostCommonWord(String paragraph, String[] banned) { Set<String> ban = new HashSet<>(Arrays.asList(banned)); Map<String,Integer> counts = new HashMap<>(); String[] words = paragraph.replaceAll("\\W+"," ").toLowerCase().split(" "); for(String w : words){ if(!ban.contains(w)){ counts.put(w,counts.getOrDefault(w,0)+1); } } return Collections.max(counts.entrySet(),Map.Entry.comparingByValue()).getKey(); }
- \W는 문자가 아닌 것을 의미 \W같은 것 사용하려면 replaceAll에선 \\으로 처리해야함
- set에 담은 이유는 contains사용하려고 Array를 List로 만들어서 Set에 담기 (Collection끼리 가능 생성자에 넣기 가능!)
- Map에 getOrDefault가 재밌다 value를 추출했을 때 있으면 값나오고 없으면 default 나옴!
- Collections.max는 가장 큰 값을 찾는다. (entrySet > [키=값, 키=값,키=값] -> map을 약간 배열화), 뒤에는 comp기준 담는다. Map.Entry.comparingByValue()로하면, Entry에 Value를 기준으로 값을 비교한다!
-> 해당메서드의 파라미터값이 객체면 가장 큰 객체 반환해준다!
-> Collection인 List와 Set에서만 사용 가능한듯!
*Collections.max 결과 Map.Entry<String,Integer>로 map에 저장된 엔트리 중 value제일 큰 것 하나 나옴!
=> 주말에 람다쓴 것 적어두기
- 그룹 애너그램 (리트코드 49)
1. 내풀이 (644ms)
public List<List<String>> groupAnagrams(String[] strs) { //애너그램이란 단어를 다시 조합했을 때 만들 수 있는 단어 의미 List<List<String>> stringList = new ArrayList<>(); for(int i=0; i<strs.length; i++){ if(!"-".equals(strs[i])) { List<String> ana = new ArrayList<>(); ana.add(strs[i]); for (int j = i + 1; j < strs.length; j++) { if (strs[i].length() == strs[j].length()) { if (isAna(strs[i], strs[j])) { ana.add(strs[j]); strs[j] = "-"; } } } stringList.add(ana); } } return stringList; } public static boolean isAna(String a, String b){ StringBuilder sb = new StringBuilder(b); for(char c : a.toCharArray()){ if(b.indexOf(c) == -1){ return false; }else{ char[] barr = b.toCharArray(); barr[b.indexOf(c)]='?'; b=new String(barr); } } return true; }
-> 진짜 구리다..무슨생각으로 푼건지 모르겠음 이러지말자!
public List<List<String>> groupAnagrams(String[] strs) { Map<String,List<String>> results = new HashMap<>(); for(String s : strs){ char[] chars = s.toCharArray(); Arrays.sort(chars); String key = String.valueOf(chars); if(!results.containsKey(key)) { results.put(key,new ArrayList<>()); } results.get(key).add(s); } return new ArrayList<>(results.values()); }
- String을 CharArray로 만든 후 -> sorting해서 키로 사용 -> 해당 키를 이용해서 문자열 값을 담아두기
- 같은 값임을 비교해서 저장해두어야 할 때 위와 같은 생각 떠올려보자. map과 List를 사용해서 저장하는 방법
- 가장 긴 팰린드롬 부분 문자열 (리트코드 5번)
- 내풀이 (283ms)
class Solution { public String longestPalindrome(String s) { char[] charArr = s.toCharArray(); TreeMap<Integer,String> paMap = new TreeMap(new Comparator<Integer>(){ @Override public int compare(Integer o1, Integer o2) { return o2-o1; } }); int left = 0; int right; String target=""; int len = charArr.length; for(; left<len;left++){ right = len-1; while(left < right){ if(charArr[left] == charArr[right]){ target = s.substring(left,right+1); if(isPalindro(target)){ if(target.length() == len || len - target.length() < target.length()){ return target; } paMap.put(target.length(),target); } } right--; } } if(paMap.isEmpty()){ return String.valueOf(charArr[0]); } return paMap.get(paMap.firstKey()); } public static boolean isPalindro(String target){ int end = target.length()-1; for(int i=0; i<(end+1)/2;i++){ if(target.charAt(i) != target.charAt(end-i)){ return false; } } return true; } }
- 팰린드롬이 무조건 처음과 끝 글자가 같아야하고, 배열은 반만 돌아도 된다는 점
- 맨 왼쪽 단어와 맨 오른쪽 단어 계속 비교해가는 과정으로 작성했음 (left,right)
- 만약 맨 오른쪽 단어와 왼쪽단어가 팰린드롬일 때 두 길이의 합이 전체 길이이거나, 전체 길이에서 팰린드롬을 뺀 길이가
팰린드롬 문자열 길이보다 작다면, 더 긴 필랜드롬이 나올 수 없음으로 return했다.
2. 투포인터 사용하기 (13ms..뮈친)
-> 최장 공통 부분 문자열 (DP)로 푸는 전형적인 문제이지만, 속도가 안나온다! 따라서 투포인터를 사용하셨다한다
int left; int maxLen; public String longestPalindrome(String s){ int len = s.length(); if(len < 2) return s; for(int i = 0; i < len -1; i++){ //0,1-> 1,2 2칸씩 확장하는 투포인터 extendPalindrome(s,i,i+1); //3칸씩 확장하는 투포인터 extendPalindrome(s,i,i+2); } return s.substring(left,left + maxLen); } public void extendPalindrome(String s, int j, int k){ while(j >= 0 && k <s.length() && s.charAt(j) == s.charAt(k)){ j--; k++; } if(maxLen < k-j-1){ left = j +1; maxLen = k - j -1; } }
> 팰린드롬을 발견하면, 확장하는 형태의 풀이
> 처음에 주어진 값에 따라 팰린드롬으로 판별된다면, 왼쪽, 오른쪽으로 퍼지면서 중앙에서부터 판별!
> 두개의 포인터가 2칸 3칸으로 (2>4>6),(1>3>5)형태로 확장된다.
ex) dcbabcdd
> 2개의 투 포인터가 우측으로 이동하다 3칸짜리 투포인터는 bab에서 팰린드롬으로 매칭되어, 양옆으로 확장하면서
가장 긴값을 가진다. 두개짜리 투포인터는 dd가 매칭되지만 더 확장을 못함!
> 마지막에 k-j-1을 해주는 이유는 조건식에 따라 마지막으로 검사하면서 k는 팰린도룸보다 1커질 수 있음!
scbabkl 길이 3,start는 2,end 4 > 이때 k는 5,j는 1 길이는 5로 측정됨
따라서 left는 j+1, maxlen은 k-j-1해주어야함! (위치뺀거랑 길이 다르다~)
- 투포인터 알고리즘
> 두개의 포인터를 사용해서 리스트나 1차원 배열을 탐색하는 알고리즘을 의미한다.
> 순차적으로 접근하며, 위치를 기록해두어야할 때 좋다. 또한 정렬되어있을 때 성능이 좋다.
> 연속된 배열 혹은 리스트에서 부분을 찾아야할 때 많이 사용되는 것 같다!
> 보통 left,right 혹은 start, end를 사용한다.
> 보통 left(start) <= right(end) 조건을 많이 사용한다.
> 시작과 끝을 가리키는 경우가 많다. (위의 팰린드롬 문제는 그렇게 하지않긴함)
'자료구조와 알고리즘 > 문제풀기' 카테고리의 다른 글
연결리스트 (0) 2024.01.15 배열 (0) 2024.01.12 SQL 고득점 킷 JOIN (0) 2023.09.01 백준 15990 - 1,2,3 더하기 5 (0) 2023.04.14 백준 11052 - 카드 구매하기 (0) 2023.04.14