<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wodnr_09.log</title>
        <link>https://velog.io/</link>
        <description>발전하는 꿈나무 개발자 / 취준생 </description>
        <lastBuildDate>Mon, 16 Oct 2023 12:23:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>wodnr_09.log</title>
            <url>https://velog.velcdn.com/images/wodnr_09/profile/adccb39e-611f-4014-bbb1-6fa805f78b5c/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. wodnr_09.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wodnr_09" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[LeetCode - Word Pattern]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Word-Pattern</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Word-Pattern</guid>
            <pubDate>Mon, 16 Oct 2023 12:23:41 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-word-pattern"># Word Pattern</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/word-pattern/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 290. Word Pattern
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>주어진 pattern과 문자열의 패턴이 같으면 True 아니면 False</li>
</ul>
</blockquote>
<ul>
<li>ex) pattern = abba, s = dog cat cat dog</li>
</ul>
<p>가장 먼저 문자열 구분하기 위해서는 공백을 기준으로 split()하고 pattern을 key 값, value를 문자열로 하는 HashMap의 방법으로 접근했다.</p>
<p>HashMap에서 이미 key가 있으면 반복되는 패턴임을 확인할 수 있고, key의 value와 문자열을 비교하면 패턴이 맞는지 확인하는 조건문으로 구현을 했다.</p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def wordPattern(self, pattern: str, s: str) -&gt; bool:
        word = s.split(&#39; &#39;)
        w_dict = {}
        # 패턴의 길이와 문자열의 길이 비교
        if len(pattern) != len(word):
            return False

        for i, d in enumerate(pattern):
            # pattern에 해당하는 key가 있을 경우 key와 분리된 문자열 비교  
            if d in w_dict and w_dict[d] != word[i]:
                return False

            # key가 없는데 문자열이 HashMap에 있는 경우      
            elif d not in w_dict and word[i] in w_dict.values():
                return False

            # pattern을 key, 분리된 문자열을 value로 하는 HashMap 값 생성
            elif d not in w_dict:
                w_dict[d] = word[i]
        return True 
</code></pre>
<p><strong>Runtime</strong>: 26ms | <strong>Memory</strong>: 16.2MB
LeetCode 코드 중 Runtime 99%, Memory 85% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>문자열 s를 공백 기준으로 분리</li>
<li>enumerate()로 인덱스와 값을 동시에 활용 가능 </li>
<li>pattern을 key로 하고 분리된 문자열을 value로 하는 HashMap</li>
<li>False의 경우를 분리 </li>
<li>반복문 내의 조건이 모두 아닌 경우 True </li>
</ul>
<hr>
<h3 id="📝-word-pattern-회고">📝 Word Pattern 회고</h3>
<ul>
<li><p>문자열을 분리하는 과정에서 O(N)의 시간복잡도가 발생했고, 모든 문자열이 독립적일 경우 딕셔너리에 문자열 전부를 저장해야하므로 최악의 경우 O(N)의 공간복잡도를 가진다.</p>
</li>
<li><p>HashMap의 key:value로 바로 접근할 수 있다는 특성을 활용한 간단한 문제였다.</p>
</li>
<li><p>문제를 해결하고 솔루션을 참고했을 때 dict를 2개 사용한 코드도 있었고, dict를 사용하지 않은 코드들도 있었다. 그리고 거의 같은 방법으로 접근하여 해결한 코드도 있었다.</p>
</li>
<li><p>크게 어려운 문제는 아니었지만 응용되는 문제가 나왔을 때 다른 자료구조와 함께 HashMap의 방법을 잘 사용하는 것이 중요할 것 같다고 생각했다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Same Tree]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Same-Tree</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Same-Tree</guid>
            <pubDate>Sun, 15 Oct 2023 13:05:52 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-same-tree"># Same Tree</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/same-tree/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 100. Same Tree
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>두 Binary Tree <code>p</code>와 <code>q</code>가 같은지 확인</li>
</ul>
</blockquote>
<p>두 binary tree가 같은 형태, 같은 값을 가지는지 확인하기 위해서는 모든 노드를 순회해야한다고 생각했고, DFS와 BFS 중 BFS의 방법으로 접근했다.</p>
<p><code>p</code>와 <code>q</code>를 순회하며 노드의 값을 저장하고, 만약 트리의 형태가 다르면 (ex: p는 왼쪽 q는 오른쪽에 하위 트리가 구성 되어있는 경우) 순회 결과를 다르게 하기 위해 0을 추가로 삽입했다. </p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -&gt; bool:
        # p와 q가 모두 None이거나 한쪽만 None일 경우
        if p is None and q is None:
            return True
        if p is None or q is None:
            return False

        p_tree,q_tree=[],[]
        self.level_search(p, p_tree)  
        self.level_search(q, q_tree)
        return p_tree == q_tree

    # BFS
    def level_search(self, node, l):
        queue = [(node, 0)]
        while queue:
            node, level = queue.pop(0)
            if node is not None:
                l.append(node.val)
                if node.left:
                    queue.append((node.left, level+1))
                if node.right:
                    queue.append((node.right, level+1))
                else:
                    l.append(0)</code></pre>
<p><strong>Runtime</strong>: 32ms | <strong>Memory</strong>: 16.1MB
LeetCode 코드 중 Runtime 90%, Memory 94% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li><code>p</code>와<code>q</code> 트리를 각각 BFS로 순회</li>
<li>node.left가 있고, node.right가 있는 조건과 더불어 0값을 추가하여 하위 트리 형태가 다르면 순회 결과를 다르게 하여 T or F 판단  </li>
</ul>
<hr>
<h3 id="📝-same-tree-회고">📝 Same Tree 회고</h3>
<ul>
<li><p>모든 트리를 순회하고 마지막으로 두 리스트를 비교하기에 최악의 경우 O(N) 시간복잡도를 가지고, 방문한 노드의 값을 모두 저장해야하므로 O(N)의 공간복잡도를 가진다.</p>
</li>
<li><p>시간복잡도는 BFS, DFS 모두 모든 노드를 순회하기 때문에 O(N)으로서, 이미 최적화가 되었다고 생각하고 공간복잡도는 p_tree와 q_tree 리스트를 제거하는 방향으로 개선할 수 있을 것 같다.</p>
<pre><code class="language-python"># 개선 후
class Solution:
  def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -&gt; bool:
      if p is None and q is None:
          return True
      if p is None or q is None:
          return False

      queue = [(p, q)]
      while queue:
          np, nq = queue.pop(0)
          if np is None and nq is None:
              continue

          elif np is None or nq is None or np.val != nq.val:
              return False

          queue.append((np.left, nq.left))
          queue.append((np.right, nq.right))
      return True</code></pre>
</li>
</ul>
<p>시간복잡도와 공간복잡도는 여전히 O(N)으로 변함이 없지만 두 리스트를 제거함으로써 메모리에 할당되는 것을 줄였다.</p>
<p>코드도 더 간단해졌지만 while문 내부의 조건문이 길어져서 가독성은 떨어질 수 있겠다고 생각했다. </p>
<p>메모리적으로 더 유리할 수 있지만 개인적으로 개선한 코드보다는 함수로 작성한 이전 로직이 보기에도 편하고, 비즈니스 로직이라고 생각했을 때, 유지보수성이 더 높을 것 같다고 판단된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Merge k Sorted Lists]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Merge-k-Sorted-Lists</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Merge-k-Sorted-Lists</guid>
            <pubDate>Thu, 12 Oct 2023 12:45:14 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-merge-k-sorted-lists"># Merge k Sorted Lists</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/merge-k-sorted-lists/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 23. Merge k Sorted Lists
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>이중 리스트 구조로 된 연결 리스트를 모두 합치고, 오름차순 정렬된 연결 리스트로 반환 </li>
</ul>
</blockquote>
<p>[[1,4,5],[1,3,4],[2,6]]로 이루어진 연결리스트를 1-&gt;1-&gt;2-&gt;3-&gt;4-&gt;4-&gt;5-&gt;6로 반환 해야하는 문제이다.
가장 먼저 떠올린 접근방법은 다음과 같다.</p>
<ul>
<li>포인터를 이동하며 이중 리스트 구조에 접근 </li>
<li>연결 리스트의 값을 배열에 저장</li>
<li>값이 저장된 배열을 sorted()로 정렬</li>
<li>정렬된 배열을 기준으로 연결 리스트 생성</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -&gt; Optional[ListNode]:
        # 연결 리스트의 값 저장
        link=[]
        # 이중 리스트에 접근할 포인터
        i=0
        while len(lists)-1 &gt;= i:
            if lists[i]:
                link.append(lists[i].val)
            else:
                i+=1
                continue
            lists[i]=lists[i].next

        # O(N log N) 정렬
        link=sorted(link)

        # 연결 리스트 생성
        result=ListNode()
        curr=result
        for i in link:
            node=ListNode(i)
            curr.next=node
            curr=node
        return result.next</code></pre>
<p><strong>Runtime</strong>: 75ms | <strong>Memory</strong>: 19.8MB
LeetCode 코드 중 Runtime 99%, Memory 81% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li><code>lists = [[1,4,5],[1,3,4],[2,6]]</code>일때, i 포인터로 lists[i]에 접근 ex)<code>(lists[0]=[1,4,5])</code></li>
<li>lists[i].next로 1-&gt;4-&gt;5 접근, 만약 lists[i]가 None일 경우, i 포인터의 값 증가 = 다음 리스트를 가리키고 다시 다음 리스트의 첫 값을 가리키도록 continue로 반복문 제어</li>
<li>병합 정렬 기반 sorted()함수로 오름차순 정렬 </li>
<li>정렬된 리스트로 새로운 연결 리스트 생성</li>
</ul>
<hr>
<h3 id="📝-merge-k-sorted-lists-회고">📝 Merge k Sorted Lists 회고</h3>
<ul>
<li><p>시간복잡도는 이중 연결리스트의 모든 값을 조회하므로 O(N)이 되고, 처음 연결 리스트의 값 개수 만큼 다시 새로운 연결 리스트를 생성하므로 O(N)의 공간복잡도를 가진다.</p>
</li>
<li><p>이중 리스트 구조에서 첫번째 리스트를 첫번째 하위 연결 리스트로 생각한다면, 재귀와 분할정복의 개념으로도 문제를 해결할 수 있을 것 같다. 하지만 성능적으로 봤을 때는 비슷하게 나올거라 예상된다.</p>
</li>
<li><p>문제 해결 후 솔루션을 확인해보니 heap 자료구조를 활용해서 문제를 해결할 수도 있었고, 코드는 더 간결하지만 현재 사용한 접근법과 유사한 방법으로 해결한 문제도 꽤 많았다.</p>
</li>
<li><p>추가적으로 연결 리스트를 생성하고, 서로 연결해주는 과정을 조금 더 간단하게 개선해보았다.</p>
<pre><code class="language-python">class Solution:
  def mergeKLists(self, lists: List[Optional[ListNode]]) -&gt; Optional[ListNode]:
      link=[]
      i=0
      while len(lists)-1 &gt;= i:
          if lists[i]:
              link.append(lists[i].val)
          else:
              i+=1
              continue
          lists[i]=lists[i].next

      link=sorted(link, reverse=True)
      result=None
      for i in link:
          result=ListNode(i, result)
      return result</code></pre>
</li>
</ul>
<p>성능은 비슷한 것으로 확인했고, 정렬된 값으로 새로운 노드를 생성할 때 next 값을 result로 해줌으로써 역순으로 연결이 된다.예를들어 [1,2,3,4,5,6]의 값으로 정렬되어 있으면 연결 리스트는 6-&gt;5-&gt;4-&gt;3-&gt;2-&gt;1 로 생성된다. </p>
<p>따라서 기존 sorted() 함수로 오름차순 정렬 해주었던 연결 리스트의 값을 내림차순 정렬로 변경하여 1-&gt;2-&gt;3-&gt;4-&gt;5-&gt;6이 될 수 있도록 구현했다.</p>
<p>난이도가 Hard로 표기 되어있지만 연결 리스트 문제들을 경험하고 응용함으로써 비교적 수월하게 문제를 해결한 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Insert Delete GetRandom O(1)]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Insert-Delete-GetRandom-O1</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Insert-Delete-GetRandom-O1</guid>
            <pubDate>Sun, 08 Oct 2023 13:37:55 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-insert-delete-getrandom-o1"># Insert Delete GetRandom O(1)</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/insert-delete-getrandom-o1/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 380. Insert Delete GetRandom O(1)
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>문자열 삽입, 삭제, 랜덤 조회 함수 만들기</li>
</ul>
</blockquote>
<ul>
<li>평균 O(1)의 시간복잡도</li>
<li>이미 문자열이 있을 때 삽입시 False</li>
<li>문자열이 없을 때 삭제시 False</li>
</ul>
<p>O(1)의 시간복잡도로 접근하기 위해서는 Hashmap을 활용해야 겠다고 생각했다. </p>
<p>문자열이 없을 때 삭제시 False를 반환하는 것은 stack을 구현할 때의 stack underflow가 떠올랐다.</p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class RandomizedSet:
    def __init__(self):
        self.ran_set=[]
        self.ran_hash={}

    def insert(self, val: int) -&gt; bool:
        if val in self.ran_hash.keys():
            return False
        else:
            self.ran_hash[val]=None
            self.ran_set.append(val)
            return True

    def remove(self, val: int) -&gt; bool:
        try:
            del self.ran_hash[val]
            self.ran_set.remove(val)
            return True
        except KeyError:
            return False

    def getRandom(self) -&gt; int:
        return random.choice(self.ran_set)</code></pre>
<p><strong>Runtime</strong>: 349ms | <strong>Memory</strong>: 63.5MB
LeetCode 코드 중 Runtime 53%, Memory 95% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>ran_hash 딕셔너리에 데이터를 key 값으로 저장</li>
<li>in 연산자로 이미 key가 있는지 확인</li>
<li>try except 구문으로 딕셔너리에 key 값이 없는 경우 처리</li>
<li>random.choice() 라이브러리 및 함수로 동일 확률 랜덤하게 원소 조회</li>
</ul>
<hr>
<h3 id="📝-insert-delete-getrandom-o1-회고">📝 Insert Delete GetRandom O(1) 회고</h3>
<ul>
<li><p>시간복잡도는 O(1), 공간복잡도는 O(N)이 나왔다.</p>
</li>
<li><p>dictionary.get(&#39;key&#39;)로 key값이 없으면 None으로 확인가능 했지만 key가 있음에도 None이 출력되는 상황을 발견해서 try except 구문으로 KeyError를 응용하여 구현했다.</p>
</li>
<li><p>리스트와 딕셔너리를 모두 사용하며 삽입, 삭제하기 때문에 조금 비효율적이라고 생각해서 set()의 특징을 활용하여 개선해보았다.</p>
<pre><code class="language-python">class RandomizedSet:
  def __init__(self):
      # ran_hash를 중복이 없는 집합으로 선언
      self.ran_hash=set()

  def insert(self, val: int) -&gt; bool:
      if val in self.ran_hash:
          return False
      self.ran_hash.add(val)
      return True

  def remove(self, val: int) -&gt; bool:
      if val in self.ran_hash:
          self.ran_hash.remove(val)
          return True
      return False

  def getRandom(self) -&gt; int:
      return random.choice(list(self.ran_hash))</code></pre>
</li>
<li><p><em>Runtime*</em>: 349ms ---&gt; <strong>493ms</strong></p>
</li>
<li><p><em>Memory*</em>: 63.5MB ---&gt; <strong>63.7MB</strong></p>
</li>
</ul>
<p>코드는 간결해졌지만 list, hashmap 두 가지를 모두 활용한 방법보다 오히려 성능적으로 떨어지게 되었다.</p>
<p><code>list(self.ran_hash)</code>로 집합을 list로 변환하는 과정에서 O(N)의 시간복잡도가 되었고, 평균적으로 시간복잡도가 증가하여 더 좋지 못한 성능이 나온 것 같다.</p>
<p>그래서 Hashmap만 활용하여 구현을 해보았지만 set()과 마찬가지로 getRandom()함수에서 O(N)의 시간복잡도가 걸렸다.</p>
<p>결과적으로 처음 작성한 코드가 성능적으로는 가장 우수했다는 것을 알았고, 대부분의 솔루션 코드도 같은 방법으로 접근한 것을 확인했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Length of Last Word]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Length-of-Last-Word</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Length-of-Last-Word</guid>
            <pubDate>Sat, 07 Oct 2023 13:31:33 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-single-number"># Single Number</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/length-of-last-word/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 58. Length of Last Word
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>문자열 s의 하위 문자열에서 마지막 하위 문자열(단어)의 길이 </li>
</ul>
</blockquote>
<ul>
<li>hello world --&gt; world : 5 출력</li>
</ul>
<p>문자열 s에서 공백이 있는 경우도 있을 것이기 때문에 strip()함수를 활용해서 해결해야겠다고 생각했다.</p>
<p>간단한 문제이기 때문에 2가지의 접근 방법으로 접근 해보았다. </p>
<ul>
<li>첫번째는 split()으로 s를 공백으로 분리하여 해결</li>
<li>두번째는 문자열을 역순으로 반복하여 판단하는 것으로 해결</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python"># 첫번째 split() 활용
class Solution:
    def lengthOfLastWord(self, s: str) -&gt; int:
        s=s.rstrip().split(&#39; &#39;)
        return len(s[-1])</code></pre>
<p><strong>Runtime</strong>: 26ms | <strong>Memory</strong>: 16.3MB
LeetCode 코드 중 Runtime 99%, Memory 72% 해당하는 결과</p>
<pre><code class="language-python"># 두번째 반복문 활용
class Solution:
    def lengthOfLastWord(self, s: str) -&gt; int:
        s=s.strip()
        i=1
        while len(s)&gt;i:
            if s[-i] == &#39; &#39;:
               return i-1
            i+=1  
        return i</code></pre>
<p><strong>Runtime</strong>: 28ms | <strong>Memory</strong>: 16.1MB
LeetCode 코드 중 Runtime 97%, Memory 99% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>strip(), rstrip() 함수의 활용으로 문자열에서 공백 제거</li>
<li>첫번째는 공백으로 분리하여 마지막 단어를 쉽게 찾음</li>
<li>두번째는 마지막 단어와 그 이전 단어와의 공백을 활용하여 찾음</li>
<li>두번째에서 i는 마지막과 그 이전 단어와의 공백을 가리키므로 -1을 해줌으로써 마지막 단어의 처음을 가리키게 됨</li>
</ul>
<hr>
<h3 id="📝-length-of-last-word-회고">📝 Length of Last Word 회고</h3>
<ul>
<li><p>두 방법 모두 시간복잡도는 O(N), 공간복잡도는 O(1)에 해당하는 결과이다.</p>
</li>
<li><p>성능적으로는 두 코드 모두 유사하다고 할 수 있다. 하지만 python 함수를 아는 경우, 코드 가독성과 간결성은 첫번째 코드가 더 좋다고 판단된다.</p>
</li>
<li><p>문제를 해결하고 다른 솔루션들을 참고해보니 대부분 첫번째 코드의 방법으로 접근하여 해결했지만, 두번째 접근법과 유사한 코드도 확인했다.</p>
</li>
<li><p>문자열을 다루는 문제는 다른 복잡한 자료구조 문제와는 달리 접근 방법이 더 다양한 것 같다고 생각된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Single Number]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Single-Number</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Single-Number</guid>
            <pubDate>Thu, 05 Oct 2023 13:49:06 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-single-number"># Single Number</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/single-number/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 136. Single Number
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>nums 리스트에서 중복되지 않는 값 1개 찾기</li>
</ul>
</blockquote>
<p>큰 주제가 비트를 조작하여 문제를 풀어보라는 의도 같았지만, 다른 접근법으로도 좋은 성능으로 해결할 방법이  떠올라서 비트연산이 아닌 다른 방법으로 풀어보았다.</p>
<p>값들끼리 중복이 되는지 원할하게 판단하기 위해서는 중복되는 값끼리 붙어 있으면 편하다고 생각했다.
그래서 오름차순 정렬을 하고 두개의 포인터로 창문(sliding window)을 만들어서 창문을 이동시키는 방법으로 접근했다.</p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def singleNumber(self, nums: List[int]) -&gt; int:
        # 오름차순 정렬
        nums = sorted(nums)
        i,j=0,1

        # 두 포인터를 이동
        while len(nums) &gt; j:
            if nums[i] != nums[j]:
                return nums[i]
            i+=2
            j=i+1
        return nums[i]</code></pre>
<p><strong>Runtime</strong>: 120ms | <strong>Memory</strong>: 18.8MB
LeetCode 코드 중 Runtime 77%, Memory 86.2% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>sorted()로 오름차순 정렬하여 중복된 수는 함께 붙어있도록 설계</li>
<li>포인터 2개로 창문을 만들어서 옮겨줌으로써 중복 판단 ex) [0,1], [2,3], [4,5] 등</li>
<li>창문 속 수가 다르면 중복이 되지 않는다고 판단</li>
<li>[1,1,2,2,3]일 경우 창문은 항상 [2,2]까지만 이동, 나머지 수는 중복되지 않는다고 판단</li>
</ul>
<hr>
<h3 id="📝-single-number-회고">📝 Single Number 회고</h3>
<ul>
<li><p>시간복잡도는 sorted() 과정에서 가장 많은 시간이 걸려서 O(N log N)이고, 공간복잡도는 O(1)으로 상수의 공간복잡도를 가진다.</p>
</li>
<li><p>문제를 해결하고 다른 솔루션들을 찾아보니 대부분 XOR 연산을 활용하여 매우 간단하게 문제를 해결했고, 코드는 다음과 같다.</p>
<pre><code class="language-python">class Solution:
  def singleNumber(self, nums: List[int]) -&gt; int:
      num = 0
      for i in nums:
          num ^= i
      return num</code></pre>
<p>num에 XOR 연산을 수행한 결과를 저장하고 갱신하는 방법이다. 
예를 들어 [4,1,2,1,2]의 nums를 for문을 실행하면 num은 4,5,7,6,4의 결과로 최종 4가 출력된다.</p>
</li>
</ul>
<p>하지만 해당 방법의 시간복잡도는 nums를 모두 순회해야 하기 때문에 O(N)의 시간복잡도를 가진다.
따라서 코드 간결성은 좋지만 성능상으로는 원래 작성한 코드가 더 좋을 수도 있기에 따로 변경하지는 않았다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Binary Tree Level Order Traversal]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Binary-Tree-Level-Order-Traversal</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Binary-Tree-Level-Order-Traversal</guid>
            <pubDate>Sun, 01 Oct 2023 08:36:13 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-binary-tree-level-order-traversal"># Binary Tree Level Order Traversal</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/binary-tree-level-order-traversal/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 102. Binary Tree Level Order Traversal
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>Binary Tree를 레벨 별로 노드 값을 리스트에 담아 반환</li>
</ul>
</blockquote>
<p>레벨 별로 노드 값을 알아야하기 때문에 Binary Tree BFS(너비 우선 탐색)를 활용하여 접근했다.
BFS 방법으로 순회하며 결과 리스트에 레벨 별로 리스트를 생성하고, 노드의 값을 추가해주는 법으로 구현했다. </p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def levelOrder(self, root: Optional[TreeNode]) -&gt; List[List[int]]:
        l=[]
        self.level_search(root, l)
        return l

    # BFS
    def level_search(self, node, l):
        queue = [(node, 0)]
        while queue:
            node, level = queue.pop(0)
            if node is not None:
                if len(l) &lt; level+1:
                    l.append([])   
                l[level].append(node.val)
                if node.left:
                    queue.append((node.left, level+1))
                if node.right:
                    queue.append((node.right, level+1))</code></pre>
<p><strong>Runtime</strong>: 40ms | <strong>Memory</strong>: 16.8MB
LeetCode 코드 중 Runtime 84%, Memory 99.7% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>BFS 함수 생성, 재귀 활용하여 Left, Right Tree 탐색</li>
<li>Queue 자료구조를 활용하여 방문처리</li>
<li>level 변수를 활용하여 레벨 별 리스트에 바로 접근하여 노드의 값 추가</li>
</ul>
<hr>
<h3 id="📝-binary-tree-level-order-traversal-회고">📝 Binary Tree Level Order Traversal 회고</h3>
<ul>
<li><p>모든 트리를 순회하고, 결과 리스트에 레벨 별로 노드 값을 모두 저장해야하므로 시간복잡도와 공간복잡도 모두 O(N)이다.</p>
</li>
<li><p>BFS 방법을 활용하여 레벨 별로 노드 값을 저장하는 간단한 응용 문제여서 비교적 쉽게 해결했다.</p>
</li>
<li><p>BFS를 활용한 <a href="https://leetcode.com/problems/average-of-levels-in-binary-tree/?envType=study-plan-v2&amp;envId=top-interview-150">637. Average of Levels in Binary Tree</a>를 먼저 해결해서 비교적 더 쉬웠던 것 같다.</p>
</li>
<li><p>문제를 해결하고 다른 솔루션을 보니 대부분 현재 코드와 비슷한 방식으로 해결해 나간 것을 확인했다. 그리고 특별한 추가적인 개선은 떠오르지 않아서 다른 수정은 진행하지 않았다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Reverse Words in a String]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Reverse-Words-in-a-String</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Reverse-Words-in-a-String</guid>
            <pubDate>Sat, 30 Sep 2023 12:38:38 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-reverse-words-in-a-string"># Reverse Words in a String</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/reverse-words-in-a-string/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 151. Reverse Words in a String
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>문자열을 단어 단위로 거꾸로 뒤집기</li>
</ul>
</blockquote>
<ul>
<li>ex) &quot;  hello world  &quot; ---&gt; &quot;world hello&quot;</li>
<li>단어는 공백으로 분리, 문자열들의 맨 앞, 맨 뒤의 공백은 제거</li>
</ul>
<p>단어가 공백을 기준으로 분리되어 있으므로 split()함수를 활용하여 공백 기준으로 분리하고, 스택의 원리를 활용하여 문자열을 뒤집는 접근법으로 생각했다. </p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def reverseWords(self, s: str) -&gt; str:
        s=s.split(&#39; &#39;)
        r=&#39;&#39;
        while s:
            if s[-1] != &#39;&#39;:
                r=r+s.pop()+&#39; &#39;
            else:
                s.pop()
        # 양쪽 공백 제거
        r=r.strip()
        return r</code></pre>
<p><strong>Runtime</strong>: 28ms | <strong>Memory</strong>: 16.2MB
LeetCode 코드 중 Runtime 98%, Memory 99% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>split() 함수로 공백 기준 분리</li>
<li>분리한 문자열이 담긴 리스트를 POP 하여 결과 문자열에 공백과 함께 더해가기 </li>
<li>빈 값 (공백)일 경우 pass</li>
<li>strip()함수로 제거되지 않은 문자열 양 끝단의 공백 제거</li>
</ul>
<hr>
<h3 id="📝-reverse-words-in-a-string-회고">📝 Reverse Words in a String 회고</h3>
<ul>
<li><p>시간복잡도는 O(N)이지만 공간복잡도는 r에 더해가는게 전부이기 때문에 O(1)을 가진다.</p>
</li>
<li><p>split()으로 분리된 문자열을 다른 리스트에 추가하는 방법으로도 쉽게 해결이 가능한 문제지만 그렇게 할 경우, 공간복잡도는 O(N)이 되므로 현재 방법을 선택했다.</p>
</li>
<li><p>while문을 탈출하게 되면 &#39;  abc def  &#39; 에서 &#39;def abc &#39;가 되어 문자열 끝 부분에는 공백이 남게된다. 이를 해결하기 위해 양 끝단의 공백을 제거해주는 strip() 함수를 사용하므로써 간편하게 해결했다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Simplify Path]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Simplify-Path</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Simplify-Path</guid>
            <pubDate>Fri, 29 Sep 2023 13:13:06 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-simplify-path"># Simplify Path</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/simplify-path/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 71. Simplify Path
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>unix 스타일 절대경로를 표준경로로 변환</li>
</ul>
</blockquote>
<ul>
<li>경로는 <code>/</code> 로 시작</li>
<li><code>/</code> 으로 끝나지 않아야 함</li>
<li>디렉토리는 <code>/</code> 로 구분되어야 함</li>
<li><code>.</code> or <code>..</code>는 경로에 미포함</li>
</ul>
<p>stack의 LIFO 특성을 활용하여 디렉토리의 문자열만 Push하고 <code>/</code>를 배치해주는 방법으로 접근했다.</p>
<ul>
<li>path 경로를 <code>/</code> 기준으로 분리 (split 함수 사용)</li>
<li>분리한 경로 만큼 반복</li>
<li>빈 값 or <code>.</code>이면 pass</li>
<li><code>..</code>인 경우 POP</li>
<li>이외(디렉토리 문자열)에는 Push</li>
<li>스택에 있는 문자열 앞과 문자열 사이에는 <code>/</code> 삽입</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def simplifyPath(self, path: str) -&gt; str:
        # stack
        result=[]

        # /로 경로 문자열 분리
        path = path.split(&#39;/&#39;)
        for i in path:
            # 빈 값 pass
            if i == &#39;&#39;:
                continue

            # ..일 경우 POP, stack != None
            elif i == &#39;..&#39;:
                if result != []:
                    result.pop()

            # . pass, 문자열 PUSH
            elif i != &#39;.&#39;:
                result.append(i)

        # &#39;/&#39; 삽입 
        return &#39;/&#39;+&#39;/&#39;.join(result)</code></pre>
<p><strong>Runtime</strong>: 38ms | <strong>Memory</strong>: 16.1MB
LeetCode 코드 중 Runtime 76%, Memory 97% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li><code>/</code>로 경로 문자열 분리함으로써 <code>//</code>같은 문자열 PUSH 상황 삭제</li>
<li><code>..</code>일 경우 <code>Stack Underflow</code> 방지를 위해 result가 None이 아닌 상황에만 POP</li>
<li><code>&#39;/&#39;.join</code> 연산으로 디렉토리 문자열 사이에 <code>/</code>추가, <code>&#39;/&#39;+&#39;/&#39;</code>로 경로 <code>a/b</code> 앞에 <code>/</code>추가 </li>
</ul>
<hr>
<h3 id="📝-simplify-path-회고">📝 Simplify Path 회고</h3>
<ul>
<li><p>시간복잡도와 공간복잡도는 모두 O(N)이다.</p>
</li>
<li><p>스택의 LIFO 특성을 응용하는 간단한 문제였다. 처음에는 path를 그대로 반복하며 if문과 함께 push와 pop을 진행했다. 하지만 코드가 복잡했고 <code>//</code>이 섞이면서 뜻대로 되지않았다. </p>
</li>
<li><p>그래서 <code>/</code>기준으로 문자열을 분리해보았고, if문의 경우의 수도 줄어들며 join 연산을 통해 간편하게 <code>/</code>을 추가 할 수 있어서 문제를 해결할 수 있었다.</p>
</li>
<li><p>문제를 해결하고 다른 솔루션들을 보아도 같은 방법으로 접근한 코드들이 많았다는 것을 확인하며 간단한 문제일수록 생각이 비슷하다는 것을 느낄 수 있었다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Find First and Last Position of Element in Sorted Array]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Find-First-and-Last-Position-of-Element-in-Sorted-Array</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Find-First-and-Last-Position-of-Element-in-Sorted-Array</guid>
            <pubDate>Wed, 27 Sep 2023 12:33:10 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-find-first-and-last-position-of-element-in-sorted-array"># Find First and Last Position of Element in Sorted Array</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 34. Find First and Last Position of Element in Sorted Array
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>오름차순으로 정렬된 배열에서 target값의 시작점과 끝나는 지점을 반환</li>
</ul>
</blockquote>
<ul>
<li>target 값이 없는 경우 [-1, -1] 반환</li>
<li>O(log N)의 시간복잡도</li>
</ul>
<p>O(log N)의 시간복잡도이기 때문에 이진탐색의 방법으로 접근해야한다.
처음 지점을 가리키는 l 포인터, 끝 지점을 가리키는 r 포인터를 활용하여 다음과 같이 생각했다.</p>
<ul>
<li>l &lt;= r 만큼 반복</li>
<li>mid = (l+r)//2 일때 nums[mid]가 target보다 크면 r 포인터를 mid - 1로 옮긴다.</li>
<li>반대면 l 포인터를 mid + 1로 옮긴다.</li>
<li>만약 nums[l]이 target과 같지 않으면 l포인터를 1씩 증가시킨다.</li>
<li>nums[r]이 target과 같지 않으면 r 포인터를 1씩 증가시킨다.</li>
<li>nums[l]이 가리키는 값과 nums[r]이 가리키는 값이 target과 일치하면 l, r을 반환</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def searchRange(self, nums: List[int], target: int) -&gt; List[int]:
        l,r = 0, len(nums)-1
        while l &lt;= r:
            mid = (l+r)//2 
            if nums[l] == nums[r] == target:
                return [l, r]
            # 중앙값이 target 보다 크고 작은 경우 l, r 옮김
            if nums[mid] &gt; target:
                r = mid - 1 
            if nums[mid] &lt; target:
                l = mid + 1
            # l과 r 포인터 1칸씩 이동
            else:
                if nums[l] != target:
                    l += 1
                if nums[r] != target:
                    r -= 1
        return [-1, -1]</code></pre>
<p><strong>Runtime</strong>: 78ms | <strong>Memory</strong>: 17.7MB
LeetCode 코드 중 Runtime 87%, Memory 90% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>중앙값과 target값 비교상황 이외에도 l과 r 포인터를 1칸씩 이동</li>
<li>l과 r이 가리키는 값이 target 값과 같으면 l, r이 target의 시작과 끝점을 가리키는 것</li>
</ul>
<hr>
<h3 id="📝-find-first-and-last-position-of-element-in-sorted-array-회고">📝 Find First and Last Position of Element in Sorted Array 회고</h3>
<ul>
<li><p>시간복잡도는 이진검색을 활용하여 O(log N)이고, 공간복잡도는 O(1)을 가진다.</p>
</li>
<li><p>처음에는 일반적인 이진검색 알고리즘을 활용하여 중앙값에 따라 l과 r 포인터만 움직였지만 틀렸었다.</p>
</li>
<li><p>그래서 중앙값과 target의 비교 이외에도 l과 r이 가리키는 값을 각각 target과 비교함으로써 l, r의 포인터를 한 칸씩 움직이는 것을 추가했다.</p>
</li>
<li><p>더 이상 어떻게 성능적으로 개선해 볼 수 있을지는 떠오르지 않아서 코드 수정은 하지 않았다.</p>
</li>
<li><p>문제를 해결하고 다른 솔루션을 보니 재귀를 활용한 코드가 간단해서 인상 깊었다. 그리고 현재 코드와 동일한 방법으로 해결한 코드들이 많았던 것을 확인할 수 있었다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Remove Duplicates from Sorted List II]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Remove-Duplicates-from-Sorted-List-II</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Remove-Duplicates-from-Sorted-List-II</guid>
            <pubDate>Tue, 26 Sep 2023 13:13:09 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-remove-duplicates-from-sorted-list-ii"># Remove Duplicates from Sorted List II</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/">LeetCode 82. Remove Duplicates from Sorted List II
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>주어진 링크드 리스트에서 중복되는 노드는 전부 삭제한 후 링크드 리스트 반환</li>
</ul>
</blockquote>
<ul>
<li>ex) [1-2-3-3-4] ---&gt; [1-2-4]</li>
</ul>
<p>조금 복잡하지만 내가 생각한 구현 흐름은 다음과 같다.</p>
<ul>
<li>Linked List의 데이터인 val을 리스트에 모두 담는다.</li>
<li>리스트에 담긴 값을 key, 중복되는 개수를 value로 하는 HashTable 생성</li>
<li>HashTable에서 value가 1 초과인 key는 모두 삭제</li>
<li>남아있는 key를 다시 리스트에 담고, 리스트 값을 기준으로 링크드 리스트로 변환</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -&gt; Optional[ListNode]:
        # num에 Linked List 데이터 담기
        num=[]
        while head:
            num.append(head.val)
            head = head.next

        # num 값을 key, 중복 개수를 value로 하는 dict 
        num_dict={}
        for i in num:
            num_dict[i] = num_dict.get(i, 0) + 1

        # value &gt; 1인 key 삭제
        duplicate = [key for key, value in num_dict.items() if value &gt; 1]
        for i in duplicate:
            del num_dict[i]

        # key 기준 다시 리스트로 변경, 링크드 리스트로 재생성
        num = [i for i in num_dict.keys()]
        result = ListNode()
        curr = result
        i=0
        while len(num) &gt; i:
            node = ListNode(num[i])
            curr.next = node
            curr = node
            i+=1
        return result.next</code></pre>
<p><strong>Runtime</strong>: 42ms | <strong>Memory</strong>: 16.2MB
LeetCode 코드 중 Runtime 80%, Memory 78% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>Linked List 데이터를 추출</li>
<li>HashTable 활용 중복 데이터 제거</li>
<li>다시 Linked List로 재생성</li>
</ul>
<hr>
<h3 id="📝-remove-duplicates-from-sorted-list-ii-회고">📝 Remove Duplicates from Sorted List II 회고</h3>
<ul>
<li><p>시간복잡도와 공간복잡도는 모두 O(N)이다. 반복문이 많이 사용되긴 했지만, 최대 Linked List의 Node 개수 만큼만 반복하므로 O(N)만큼의 시간복잡도와 공간복잡도가 나오게 된 것이다.</p>
</li>
<li><p>리스트에 데이터를 담는 과정과 dict를 생성하는 과정을 합칠 수 있고, 중복된 데이터를 삭제하고 다시 리스트로 바꾸지 않고도 Linked List로 변경할 수 있어서 코드를 조금 변경해보았다.</p>
<pre><code class="language-python"># 수정된 코드
class Solution:
  def deleteDuplicates(self, head: Optional[ListNode]) -&gt; Optional[ListNode]:
      num_dict = {}
      while head:
          num_dict[head.val] = num_dict.get(head.val, 0) + 1
          head = head.next

      duplicate = [key for key, value in num_dict.items() if value &gt; 1]
      for i in duplicate:
          del num_dict[i]

      result = ListNode()
      curr = result
      for i in num_dict:
          node = ListNode(i)
          curr.next = node
          curr = node
      return result.next</code></pre>
<p>성능상 큰 차이는 없지만 리스트에 Linked List의 데이터를 담는 과정을 삭제하고, key값을 리스트에 담는 과정을 줄임으로써 반복문 2개를 제거했다.</p>
</li>
</ul>
<p>문제를 해결하고, 다른 솔루션을 찾아보니 재귀적으로 해결한 솔루션이 가장 신기하게 느껴졌고, HashTable 방법을 활용한 코드도 많다는 것을 알게 되었다. </p>
<p>아직 Linked List 자체를 조작하는 것에 익숙하지 않아서 더 어렵게 느껴지는 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode -  Median of Two Sorted Arrays
]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Median-of-Two-Sorted-Arrays</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Median-of-Two-Sorted-Arrays</guid>
            <pubDate>Mon, 25 Sep 2023 11:45:15 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-median-of-two-sorted-arrays"># Median of Two Sorted Arrays</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/median-of-two-sorted-arrays/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 4. Median of Two Sorted Arrays
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>두 리스트를 합쳤을 때 오름차순 정렬한 상태에서의 중앙값 반환</li>
</ul>
</blockquote>
<ul>
<li>O(log m+n)의 시간복잡도 제한</li>
</ul>
<p>문제에서 요구한 대로 우선 두 리스트를 더하고, 병합정렬(O nlogn)기반의 sorted() 함수로 정렬하는 방법을 생각했다.</p>
<p>정렬한 리스트의 길이가 홀수일 경우 중앙값을 그대로 반환하고, 짝수이면 중앙값 2개의 평균을 계산하도록 했다.</p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -&gt; float:
        nums=sorted(nums1+nums2)
        mid=len(nums)//2
        if len(nums)%2!=0:
            return nums[mid]
        else:
            return (nums[mid] + nums[mid-1])/2</code></pre>
<p><strong>Runtime</strong>: 81ms | <strong>Memory</strong>: 16.4MB
LeetCode 코드 중 Runtime 93%, Memory 84% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>병합정렬 기반의 sorted()함수로 오름차순 정렬</li>
</ul>
<hr>
<h3 id="📝-median-of-two-sorted-arrays-회고">📝 Median of Two Sorted Arrays 회고</h3>
<ul>
<li>시간복잡도는 정렬과정에서 발생한 O(n log n)이고 공간복잡도는 O(1)이다.</li>
<li>난이도 Hard 문제였지만 내장 함수의 시간복잡도를 잘 알고 활용한다면 쉽게 접근이 가능한 문제였다.</li>
<li>two pointer 방법과 분할정복의 개념으로 접근할 수도 있으나, 성능 차이는 거의 없다고 판단했고, 오히려 포인터 변수를 많이 할당하게 되어 메모리를 더 사용하며 코드 복잡도도 증가할 거라 생각했다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode -  Maximum Depth of Binary Tree
]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Maximum-Depth-of-Binary-Tree</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Maximum-Depth-of-Binary-Tree</guid>
            <pubDate>Sun, 24 Sep 2023 13:22:38 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-maximum-depth-of-binary-tree"># Maximum Depth of Binary Tree</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/maximum-depth-of-binary-tree/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 104.  Maximum Depth of Binary Tree
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>이진 트리의 깊이를 구하는 문제</li>
</ul>
</blockquote>
<p>깊이를 구하기 위해서는 깊이 우선 탐색(DFS) 방법으로 접근이 필요하다.
DFS는 전위, 중위, 후위 방식이 있는데 이번에는 전위순회 방식을 활용했다.</p>
<ul>
<li>현재 노드를 방문 처리하고 깊이를 저장</li>
<li>왼쪽 서브 트리를 방문 후 오른쪽 서브 트리를 방문하는 재귀호출</li>
<li>재귀 호출 전 깊이를 증가</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def maxDepth(self, root: Optional[TreeNode]) -&gt; int:
        depth=0
        result=[]
        self.preorder(root, depth, result)

        # depth가 담긴 리스트의 중복 제거와 정렬
        result=sorted(set(result))
        return result[-1]

    # 전위순회
    def preorder(self, root, depth, result):
        if root is None:
            result.append(depth)
            pass
        else:
            depth+=1
            self.preorder(root.left, depth, result)
            self.preorder(root.right, depth, result)</code></pre>
<p><strong>Runtime</strong>: 37ms | <strong>Memory</strong>: 18.6MB
LeetCode 코드 중 Runtime 96%, Memory 74% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>왼쪽과 오른쪽 서브트리를 순회하며 result 리스트에 깊이를 저장</li>
<li>저장된 깊이 중 중복을 제거하고 오름차순 정렬</li>
<li>값이 가장 큰 result의 마지막 값 반환</li>
</ul>
<hr>
<h3 id="📝-maximum-depth-of-binary-tree-회고">📝 Maximum Depth of Binary Tree 회고</h3>
<ul>
<li>시간복잡도는 <code>N=트리 노드의 수</code> 라고 할 때 전위순회 과정에서 O(N), 중복제거와 정렬과정에서 O(NlogN)이므로 O(N + NlogN) = O(NlogN)을 가진다.</li>
<li>공간복잡도는 최악의 경우 트리의 모든 리프노드의 깊이를 저장할 수 있으므로 O(N)이 된다.</li>
<li>다른 솔루션들을 참고해보니 단순 재귀의 방법으로 깊이를 구하고 max()함수로 최대 깊이를 구한 코드가 인상 깊었다. </li>
<li>솔루션을 참고하여 코드를 다시 변경해보았다.<pre><code class="language-python"># 수정된 코드
class Solution:
  def maxDepth(self, root: Optional[TreeNode]) -&gt; int:
      if root is None:
          return 0
      return max(self.maxDepth(root.left), self.maxDepth(root.right))+1</code></pre>
</li>
<li><em>Runtime*</em>: 37ms ---&gt; <strong>43ms</strong></li>
<li><em>Memory*</em>: 18.6MB ---&gt; <strong>18.5MB</strong></li>
</ul>
<p>성능상으로는 기존 코드와 크게 차이는 없는 코드이다. 하지만 코드가 훨씬 간결해졌고 Memory를 조금 더 개선할 수 있었다.</p>
<p>이번 문제를 해결하면서 느낀점은 아직 재귀가 익숙하지 않아서 그런지 탐색을 구현하는데 있어서 어려움이 있는 것 같다. 문제를 풀며 익숙해질 수 있도록 더 노력해야겠다 :D</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Longest Consecutive Sequence
]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Longest-Consecutive-Sequence</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Longest-Consecutive-Sequence</guid>
            <pubDate>Sat, 23 Sep 2023 11:44:22 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-longest-consecutive-sequence"># Longest Consecutive Sequence</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/longest-consecutive-sequence/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 128. Longest Consecutive Sequence
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>리스트 내에서 연속된 수의 최대 개수 반환</li>
</ul>
</blockquote>
<p>Hashmap을 활용하는 문제이지만 우선 바로 생각나는대로 풀어보았다.
생각의 흐름은 다음과 같다.</p>
<ul>
<li>nums 리스트의 중복을 제거</li>
<li>중복 제거한 nums 리스트를 정렬</li>
<li>리스트의 첫 값을 변수에 저장, 카운트 변수와 카운트 리스트 선언</li>
<li>리스트의 길이만큼 반복</li>
<li>저장한 변수 + 1이 다음 순서의 값과 같으면 카운트 + 1</li>
<li>아니면 카운트 리스트에 카운트 추가, 카운트 = 0</li>
<li>변수는 비교한 리스트 값으로 재정의</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def longestConsecutive(self, nums):
        # nums가 없으면 0
        if not nums:
            return 0

        # 중복제거 &amp; 정렬
        nums = sorted(set(nums))
        n = nums[0]

        # 연속된 수의 개수 저장
        count = 0

        # 연속이 끊겼을 때, 연속되었던 수 저장
        max_c = []

        for i in range(1,len(nums)):
            if n+1 == nums[i]:
                count+=1
            else:
                max_c.append(count+1)
                count=0
            n = nums[i]

        max_c.append(count+1)
        max_c.sort()
        return max_c[-1]</code></pre>
<p><strong>Runtime</strong>: 335ms | <strong>Memory</strong>: 30.9MB
LeetCode 코드 중 Runtime 99%, Memory 87% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>주어진 리스트의 중복을 제거하고 정렬</li>
<li>연속되는지 확인 후 연속된 개수 세기</li>
<li>연속이 끊기면 현재까지 연속된 개수를 리스트에 저장</li>
<li>모두 연속된 경우를 대비하여 반복 후, 연속된 개수를 다시 추가</li>
<li>연속된 개수가 담긴 리스트를 정렬하면 가장 큰수는 마지막 값 </li>
</ul>
<hr>
<h3 id="📝-longest-consecutive-sequence-회고">📝 Longest Consecutive Sequence 회고</h3>
<ul>
<li><p>해당 코드의 시간복잡도는 nums의 중복을 제거하여 O(nlogn)이고 최악은 중복이 없는 경우의 O(N)이다. 공간복잡도는 중복을 제거후, 정렬된 새로운 리스트를 생성하는 과정에서 O(N)이다.</p>
</li>
<li><p>현재 코드에서는 max_c 리스트에 연속되는 개수를 저장하는 방법으로 최대 연속 개수를 판단하지만, max()함수를 활용해서 하나의 변수를 최대값으로 갱신하는 것으로도 결과를 도출할 수 있을 것이라 생각했다.</p>
</li>
<li><p>다른 솔루션들도 중복을 제거, 정렬하는 방법으로 접근을 했고, 굳이 dictionary 자료구조를 활용한 솔루션은 거의 없던 것 같다. 현재 코드가 성능도 어느정도 좋게 나왔고, 코드 흐름도 파악하기 쉽다고 생각하여 추가 변경은 하지 않았다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Remove Nth Node From End of List
]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Remove-Nth-Node-From-End-of-List</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Remove-Nth-Node-From-End-of-List</guid>
            <pubDate>Fri, 22 Sep 2023 12:47:30 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-remove-nth-node-from-end-of-list"># Remove Nth Node From End of List</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 19. Remove Nth Node From End of List
</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>단일 Linked List의 끝에서 n번째 노드를 삭제한 Linked List 반환</li>
</ul>
</blockquote>
<p>끝에서 n번째 노드를 삭제하기 위해서는 마지막 노드를 알아야한다.</p>
<p>문제에서 주어진 Linked List는 데이터와 다음 노드를 가리키는 next 포인터밖에 없는 구조이기 때문에 마지막 노드를 찾기 위해서는 O(n)의 시간복잡도를 가지게 되므로 최대 O(n)을 기준으로 잡고 고민을 해보았다.</p>
<p>다음과 같은 생각의 흐름대로 구현을 시도했다.</p>
<ul>
<li>Linked List의 데이터들을 리스트에 담는다</li>
<li>끝에서 n번째에 해당하는 데이터를 삭제한다 [O(1)]</li>
<li>리스트에 담긴 데이터를 다시 노드로 만든다</li>
<li>queue 자료구조를 활용하여 노드들을 다시 Linked List가 될 수 있게 연결해준다</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -&gt; Optional[ListNode]:
        # node 데이터를 담을 list
        dummy = []
        while head:
            dummy.append(head.val)
            head = head.next

        # 끝에서 n번째 데이터 삭제
        dummy.pop(-n)

        # 데이터가 없다면 빈 값을 리턴
        if not dummy:
            return 

        # list 데이터로 노드 생성
        for i in range(len(dummy)):
            dummy[i] = ListNode(dummy[i])

        # 새로운 노드를 만들고, 노드들을 다시 연결
        from collections import deque
        queue = deque(dummy)
        result = queue.popleft()
        curr = result

        while queue:
            node = queue.popleft()
            curr.next = node
            curr = node 
        return result</code></pre>
<p><strong>Runtime</strong>: 35ms | <strong>Memory</strong>: 16.3MB
LeetCode 코드 중 Runtime 89%, Memory 64% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li><p>tail이 있는 구조의 Linked List라면 마지막 노드를 찾기 까지 O(1)의 시간복잡도를 가지지만 그렇지 않기 때문에, 끝에서 n번째의 노드를 쉽게 삭제하기 위해 노드의 데이터들을 리스트에 담고 원하는 값을 삭제했다.</p>
</li>
<li><p>리스트에 담은 데이터를 다시 Linked List로 만들어 줌.</p>
</li>
<li><p>로직의 이해를 돕기 위한 그림은 다음과 같다.<img src="https://velog.velcdn.com/images/wodnr_09/post/c5358749-2f57-444a-a71e-99e775d4f4af/image.jpeg" alt=""></p>
</li>
</ul>
<hr>
<h3 id="📝-remove-nth-node-from-end-of-list-회고">📝 Remove Nth Node From End of List 회고</h3>
<ul>
<li><p>해당 코드의 시간복잡도는 O(N), 공간 복잡도는 O(N)이다.</p>
</li>
<li><p>루프를 순회하며 Linked List의 데이터를 담고, 데이터를 다시 Node로 만들면서 O(N)의 시간이 소요된 것이다.</p>
</li>
<li><p>list에는 Linked List의 순서대로 데이터가 담겨 있기 때문에, 리스트의 앞에서 부터 데이터를 가져와야 했으므로 deque의 popleft 연산을 활용했다. insert(0)도 있지만 insert(0)은 배열의 특성상 O(n)의 시간이 소요되므로 배제했다.</p>
</li>
<li><p>다른 솔루션도 확인해보니 포인터 두개를 사용하는 토끼와 거북이 알고리즘을 활용하여 해결한 코드들이 대부분이었다.  </p>
</li>
</ul>
<pre><code class="language-python">class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -&gt; Optional[ListNode]:
        slow, fast = head, head

        while n &gt; 0:
            fast = fast.next
            n -= 1

        while fast and fast.next:
            slow = slow.next
            fast = fast.next

        if fast:
            slow.next = slow.next.next
        else:
            head = head.next
        return head</code></pre>
<p><strong>Runtime</strong>: 35ms ---&gt; 35ms
<strong>Memory</strong>: 16.3MB ---&gt; 16.1MB</p>
<p>솔루션을 참고하고, 토끼와 거북이 알고리즘을 활용하여 개선해보았다. </p>
<p>결과적으로 시간복잡도는 같지만 공간복잡도는 O(1)로 개선되었다.
로직의 흐름은 다음과 같다.<img src="https://velog.velcdn.com/images/wodnr_09/post/a1fae81d-b1ec-4a85-9cdc-d686bbd11443/image.jpeg" alt=""></p>
<p>빠른 포인터가 마지막 노드에 도착하면 느린 포인터는 끝에서 n번째에 위치하는 것이 핵심이다.
삭제하려는 노드의 이전 노드와 다음노드를 이어줌으로써 삭제가 되는 것이다.</p>
<p>문제를 풀 때는 이런 접근 방법을 생각하지도 못해서 로직을 이해하면서 신기했고, 이런 접근법을 떠올리고자 더 노력해야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode - Container With Most Water]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-Container-With-Most-Water</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-Container-With-Most-Water</guid>
            <pubDate>Thu, 21 Sep 2023 11:32:16 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-container-with-most-water"># Container With Most Water</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/container-with-most-water/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 11. Container With Most Water</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>배열의 길이를 x축, 배열의 값을 기둥의 높이라고 한다.</li>
</ul>
</blockquote>
<ul>
<li>두 기둥 사이에 물을 담을 수 있다고 할 때, 물을 담을 수 있는 최대 용량 구하기</li>
</ul>
<p>두 기둥을 가리킬 수 있는 포인터 2개가 필요하므로 투 포인터 접근법을 생각했다.
그래서 생각한 코드의 흐름은 다음과 같다.</p>
<ul>
<li>배열의 가장 첫번째 값과 마지막 값을 가리키는 포인터 2개 생성</li>
<li>두 포인터가 가리키는 값 중 작은 값을 고른다.</li>
<li>물의 양 = 고른 값 * 두 기둥 사이의 거리 </li>
<li>작은 값을 가진 포인터를 왼쪽 혹은 오른쪽으로 1칸씩 이동</li>
</ul>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def maxArea(self, height: List[int]) -&gt; int:
        l,r = 0,len(height)-1
        # 물의 양
        amount = 0
        while l != r:
            # 두 포인터가 가리키는 값 중 더 작은 값
            x = min(height[l], height[r])

            # 계산한 물의 양이 기존 보다 크면 갱신
            amount = max(amount, (x * (r-l)))

            # 더 작은 값을 가리키는 포인터를 이동
            if height[l] &lt;= height[r]:
                l+=1
            else:
                r-=1
        return amount</code></pre>
<p><strong>Runtime</strong>: 594ms | <strong>Memory</strong>: 29.1MB
LeetCode 코드 중 Runtime 69%, Memory 96% 해당하는 결과</p>
<h4 id="📌-로직-핵심">📌 로직 핵심</h4>
<ul>
<li>두 기둥 중 짧은 기둥만큼 물을 채울 수 있으므로 x 변수에 더 짧은 기둥의 높이를 담았다.</li>
<li>그리고 x와 두 기둥 사이의 거리인 r-l을 곱하여 물의 양을 구하고, 기존에 계산된 물의 양보다 크면 갱신한다.</li>
<li>다음 기둥으로 이동하면서 물을 더 많이 채울 수 도 있기 때문에 더 짧은 기둥을 다른 기둥으로 교체한다.</li>
</ul>
<hr>
<h3 id="📝-container-with-most-water-회고">📝 Container With Most Water 회고</h3>
<ul>
<li><p>해당 코드의 시간복잡도는 O(N), 공간 복잡도는 O(1)이다.</p>
</li>
<li><p>코드를 제출하고 다른 대부분의 솔루션들도 max(), min() 함수를 활용하여 투포인터 접근 법으로 문제를 해결한 것을 확인했다.</p>
</li>
<li><p>abs() 함수로 두 기둥간의 거리를 얻는 코드도 있었다. 똑같은 생각을 했었지만 오른쪽 포인터가 무조건 더 크기때문에 (오른쪽 - 왼쪽)으로 해결할 수 있어서 abs() 활용은 필요하지 않다고 판단했다.</p>
</li>
<li><p>x변수에 할당한 것을 amount 연산 과정에 합치는 것으로 코드를 더 간결하게 개선할 수 있지만, 코드의 가로 길이가 그만큼 길어지기 때문에 가독성을 낮춘다고 생각하여 따로 개선을 진행하지는 않았다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[📝 원티드 프리온보딩 인턴십 회고]]></title>
            <link>https://velog.io/@wodnr_09/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@wodnr_09/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%EC%9D%B8%ED%84%B4%EC%8B%AD-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 16 Sep 2023 08:20:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>2023.08.21 OT를 시작으로 원티드에서 진행하는 프리온보딩 인턴십 - 백엔드 과정이 시작되었다.</p>
<blockquote>
</blockquote>
<p>처음 확인했던 커리큘럼과는 조금 변경된 점이 있었지만, 오직 알고리즘, 자료구조에 집중하는 방향으로 바뀐 것이라서 오히려 좋았다! 덕분에 자료구조를 공부하는데 큰 도움이 되었다.</p>
<blockquote>
</blockquote>
<p>매주 2회에 걸쳐 LeetCode에서 알고리즘 문제를 푸는 과제가 있었는데, 처음에는 문제도 영어로 되어있고, 자료구조에도 익숙하지 않아서 많이 어려웠고 상당한 시간이 걸렸다.</p>
<blockquote>
</blockquote>
<p>하지만 Linked List를 시작으로 Stack, Queue, Tree, BFS, DFS 등 멘토님의 강의를 듣고 스스로 알고리즘을 구현 해보고, 복습하는 과정을 겪으면서 <strong>알고리즘 문제를 풀 때 접근하는 방법을 떠올리기가 더 쉬워졌다는 것을 스스로 느꼈다.</strong></p>
<blockquote>
</blockquote>
<p>혼자서 자료구조, 알고리즘에 대해 공부할 때에는 막막했지만 자료구조를 공부하는 방법에 대해 알 수 있어서 굉장히 유익한 교육이었다고 생각한다. 이외에도 생각나는 것 중 가장 유익하다고 생각한 것은 git 활용법이 있다.</p>
<hr>
<h2 id="⭐️-배우게-된-것들">⭐️ 배우게 된 것들</h2>
<h3 id="📌-자료구조--알고리즘">📌 자료구조 &amp; 알고리즘</h3>
<ul>
<li>Linked List</li>
<li>Stack, Queue</li>
<li>HashTable</li>
<li>Recursion</li>
<li>Sorting</li>
<li>Tree ,Trie, Heap, Graph</li>
</ul>
<p>많아 보이지만 기본적인 자료구조이고, 이렇게 배운 자료구조를 바탕으로 Leetcode 문제들을 풀며 알고리즘을 적용해보았다. 아직 재귀(Recursion)와 Tree 관련 자료구조는 이해는 했지만 코드 구현에 어려움이 있는 것 같아서 더욱 공부 해야겠다고 생각했다.</p>
<h3 id="📌-git-활용">📌 Git 활용</h3>
<p>자료구조 &amp; 알고리즘 이외에도 팀 과제, 팀 프로젝트를 진행했다.</p>
<p>학교를 졸업하고 혼자 개발하는 시간이 길어져서 함께 개발을 진행해 볼 기회가 잘 없었는데, 팀 과제를 계기로 팀 프로젝트도 진행하며 <strong>협업시 git 활용이 얼마나 중요한 일인지 배우게 되었다.</strong></p>
<ul>
<li><p>혼자 개발 할 때에는 아래 명령어만을 대충 활용했던 것 같다.</p>
<pre><code>git add . 
git commit -m&quot;&quot;
git push origin branch</code></pre></li>
<li><p>하지만 Readme.md의 중요성, git commit &amp; PR message의 중요성을 알게 되었고, 현재는 더 꼼꼼히 사용할 수 있도록 습관화를 하고 있다.</p>
<pre><code>git status 
git diff
git commit &quot;[type] title  body  footer&quot;</code></pre></li>
<li><p><strong>git status</strong> 
: 명령어로 commit 전 파일들을 확인하고 불 필요한 파일들이 포함되지 않도록 주의 한다. </p>
</li>
<li><p><strong>git diff</strong> 
: 명령어로 commit 전 수정 내용이 정확한지 다시 한번 확인한다.</p>
</li>
<li><p><strong>git commit</strong> 
: commit별 타입을 구분하고, 1 commit에 1개의 내용만 포함되도록 commit을 세분화하고 body를 활용하여 상세하게 기입한다.</p>
</li>
<li><p><strong>PR</strong> (Pull request)
: commit처럼 title에 타입을 작성해주고, 무엇을, 어떻게, 왜, 테스트 결과, 중요하게 볼 부분 처럼 상세한 내용들을 PR 메시지에 녹여내서 팀원들이 코드리뷰를 하기 쉽도록 작성해준다.</p>
</li>
</ul>
<p>현재 팀 프로젝트인 <a href="https://github.com/wodnrP/realtime_auction">실시간 경매 서비스 (github)</a>에서는 개발도 중요하지만 이러한 git commit, PR을 신경 쓰며 진행하고 있고, 팀원들끼리 코드리뷰도 진행하고 있다. 이후 어느정도 개발이 진행되었을 때 Readme 파일도 상세히 정리할 예정이다.</p>
<p>자료구조, 알고리즘도 매우 중요하고 유익했지만! git 활용법 또한 실무에서 굉장히 유용하게 사용할 것 같아서 큰 도움이 되었고, 여태까지 혼자 개발할 때는 크게 신경쓰지 않았지만 <strong>이제 부터라도 모든 git 활용을 꼼꼼히 할 수 있도록 꾸준히 규칙들을 지켜나가야겠다고 생각했다.</strong></p>
<h5 id="참고-이-링크는-현재-진행하는-팀-프로젝트에서-실제로-직접-pr을-한-내용이다-실시간-경매-서비스-pr"><strong>[참고]</strong> 이 링크는 현재 진행하는 팀 프로젝트에서 실제로 직접 PR을 한 내용이다. <a href="https://github.com/wodnrP/realtime_auction/pull/25">실시간 경매 서비스 PR</a></h5>
<hr>
<blockquote>
<p>매일 과제, 강의, 공부를 하며 1달이 정말 빠르게 지나갔고, 프리온보딩 인턴십 과정은 어제로 끝이 났다.</p>
</blockquote>
<p>그래도 팀원들과의 팀 프로젝트는 꾸준히 이어갈 예정이고 탄탄한 프로젝트로 완성하고 싶다, 알고리즘 역시 배운 자료구조를 바탕으로 꾸준히 풀고 공부해봐야겠다!</p>
<blockquote>
</blockquote>
<p>그리고 아직 2주 간의 취업 집중 과정도 남아 있기에 늘어지지 않고 열심히 달려야겠다 :D</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚨 Git 협업시 겪은 문제]]></title>
            <link>https://velog.io/@wodnr_09/Git-%ED%98%91%EC%97%85%EC%8B%9C-%EA%B2%AA%EC%9D%80-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@wodnr_09/Git-%ED%98%91%EC%97%85%EC%8B%9C-%EA%B2%AA%EC%9D%80-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Wed, 13 Sep 2023 14:22:57 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@wodnr_09/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91">실시간 경매 서비스</a> 팀 프로젝트를 진행 중 merge 과정에서 오류가 발생했다!</p>
</blockquote>
<ul>
<li>이후에도 같은 상황이 발생했을 때 대처를 잘 하기 위해</li>
<li>같은 상황의 오류를 맞이하신 분들을 위해 이 글을 남깁니다.</li>
</ul>
<h2 id="🚨-문제-상황">🚨 문제 상황</h2>
<ul>
<li>각자의 역할을 Django app을 기준으로 나눈 상황</li>
<li>각자 DB Model을 작성하여 develop branch에 merge</li>
<li>repo 책임자가 develop branch에 DB migrations을 진행하여 올림</li>
<li>팀원 분 중 한분이 pull을 진행하지 않고, DB Model을 수정 하고 migrations 후 PR</li>
</ul>
<p>결국 이미 migrations을 진행한 develop branch의 migration 폴더와 PR 요청을 하신 팀원의 migration 폴더 내부의 파일과 충돌 발생</p>
<hr>
<h2 id="⭐️-원인">⭐️ 원인</h2>
<p>migration이 가장 처음 진행되면 0001_intial.py가 생성되는데, 협업시 이를 pull 받지 않고 다시 migration을 진행하면 0002_intial.py 파일이 생성되는게 아니라 0001_intial.py이 생성되고 merge 과정에서 충돌이 발생하는 것</p>
<hr>
<h2 id="💡-해결">💡 해결</h2>
<blockquote>
<ul>
<li>로컬에서 migration한 파일들을 지우고, develop branch를 다시 pull을 받은 후 merge</li>
</ul>
</blockquote>
<p>하지만 3명 이상이 git을 통해 협업을 하는 과정에서 다시 DB migration 충돌 문제가 발생할 것을 우려했다.
그래서 다음과 같은 방법을 사용하여 해결했다.</p>
<blockquote>
<ul>
<li><strong>.gitignore 파일에 migration 폴더 자체를 추가</strong></li>
</ul>
</blockquote>
<p>이렇게 하면 git에 올라가지 않게 관리하여 충돌 문제를 해결 할 수 있다. <strong>단, 배포 과정에서는 다시 git에 올라가도록 조치를 취해야하며</strong>, 개발 과정에서만 충돌 문제를 해결하기 위함이다.  </p>
<p>또한 이 방법은 DB 관련 코드에 변경사항이 발생했을 때, pull을 받고 개인이 migration을 진행해야 한다는 번거로움이 있다.</p>
<h3 id="✋🏻-추가적인-고려사항">✋🏻 [추가적인 고려사항]</h3>
<p>이렇게 .gitignore 파일에 항목을 추가했을 때 적용이 안되는 문제가 있는데, 
이는 git에 .gitignore 항목들이 캐시되어서 발생하는 문제이다.</p>
<pre><code>git rm -r --cached</code></pre><p>따라서 위 명령어를 적용한 후 git add ., git push ~ 를 진행하면 문제없이 .gitignore가 작동한다.</p>
<hr>
<h2 id="회고">회고</h2>
<p>혼자 프로젝트를 진행했을 때 보다 팀 프로젝트시 경험하는 범위가 더 크기 때문에, 에러사항이나 문제상황이 발생했을 때 배우거나, 다양한 문제를 대처할 수 있어서 오히려 더 좋은 것 같다. </p>
<p>merge 과정은 어떤 새로운 문제가 발생할 지 모르기에 항상 긴장이 되는 것 같다 :D </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[원티드 프리온보딩 4-1 알고리즘 (1) ]]></title>
            <link>https://velog.io/@wodnr_09/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-4-1-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1</link>
            <guid>https://velog.io/@wodnr_09/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-4-1-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-1</guid>
            <pubDate>Wed, 13 Sep 2023 13:44:13 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-clone-graph"># Clone Graph</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/clone-graph/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 133. Clone Graph</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<p>Node로 구성된 Graph를 그대로 반환</p>
</blockquote>
<p>말 그대로 그래프를 복사하여 새로 반환하는 문제이다.</p>
<p>그러기 위해서는 노드를 탐색해야 했고, 순회 알고리즘 중 BFS 방법을 사용했다.
코드의 흐름은 다음 그림 같이 구성했다.<img src="https://velog.velcdn.com/images/wodnr_09/post/13f41120-56b2-4e68-b414-763056504972/image.jpeg" alt=""></p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">from typing import Optional
class Solution:
    def cloneGraph(self, node: Optional[&#39;Node&#39;]) -&gt; Optional[&#39;Node&#39;]:
        from collections import deque

        if node is None:
            return None

        visited = {}
        queue = deque([node])
        visited[node] = Node(node.val)

        while queue:
            curr = queue.popleft()
            for i in curr.neighbors:
                if i not in visited:
                    queue.appendleft(i)
                    visited[i] = Node(i.val)
                visited[curr].neighbors.append(visited[i])
        return visited[node]</code></pre>
<p><strong>Runtime</strong>: 40ms | <strong>Memory</strong>: 16.65MB
LeetCode 코드 중 Runtime 85%, Memory 89% 해당하는 결과</p>
<p>queue에 값이 존재한다면 queue에서 pop한 값을 현재 노드로 지정하고, 현재 노드의 인접한 값으로 반복하는데 visited에 인접 값이 없다면 queue에 push하고 visited에도 값을 키로 하여 노드를 저장한다.</p>
<p>그리고 visited에서 현재 노드를 키 값으로 하는 인접 값의 리스트에 방문한 노드를 추가하고 이를 queue가 빌 때 까지 반복하게 된다. </p>
<h3 id="📝-clone-graph-회고">📝 Clone Graph 회고</h3>
<ul>
<li><p>deque() 모듈을 활용하여 queue를 사용했고, 탐색한 노드를 저장하기 위해 visited라는 해쉬테이블을 활용했다.</p>
</li>
<li><p>시간복잡도는 정점의 개수인 V, 간선의 개수 E라 했을 때, O(V + E)를 가진다.</p>
</li>
<li><p>다른 솔루션에서도 같은 방법으로 많이 푼 것 같고, 현재 더 이상 개선할 것은 없다고 생각된다.</p>
</li>
<li><p>한 가지 간과했던 점은 node가 없을 경우 None을 return해줘야 visited[node] 부분에서 오류가 발생하지 않을거라는 점이다. 그 점을 빼먹고 다시 한번 고민해보다가 실수한 것을 깨닫게 되었다. </p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LeetCode 알고리즘 ]]></title>
            <link>https://velog.io/@wodnr_09/LeetCode-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@wodnr_09/LeetCode-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Tue, 12 Sep 2023 14:13:32 GMT</pubDate>
            <description><![CDATA[<h1 id="⭐️-leetcode-알고리즘-풀이-with-python">⭐️ LeetCode 알고리즘 풀이 (with Python)</h1>
<h2 id="-is-subsequence"># Is Subsequence</h2>
<p><strong>[Quetion]</strong></p>
<blockquote>
<p><a href="https://leetcode.com/problems/is-subsequence/description/?envType=study-plan-v2&amp;envId=top-interview-150">LeetCode 392. Is Subsequence</a></p>
</blockquote>
<h3 id="📌-접근법">📌 접근법</h3>
<h4 id="문제-이해">[문제 이해]</h4>
<blockquote>
<ul>
<li>t 문자열에서 특정 문자를 제거한 하위 문자열 s가 맞으면 True</li>
</ul>
</blockquote>
<p>처음에는 s문자열이 단순히 t문자열에 있으면 True가 된다고 판단하여 코드를 구현했다.</p>
<pre><code class="language-python"># 실패한 시도
class Solution:
    def isSubsequence(self, s: str, t: str) -&gt; bool:
        # t 안에 s가 있는지 확인
        for i in s:
            if i not in t:
                return False
        return True </code></pre>
<p>하지만 단순히 있는지 확인하는 것이 아니라 s문자열의 순서에 맞게 t문자열에 포함되어야 했다.
그래서 실패한 시도에 더하여 s문자열이 t문자열에 있으면 t에서 그 문자열의 인덱스까지 슬라이싱하는 방법으로 접근했다.</p>
<h3 id="💻-구현">💻 구현</h3>
<pre><code class="language-python">class Solution:
    def isSubsequence(self, s: str, t: str) -&gt; bool:
        for i in s:
            if i not in t:
                return False
            if i in t:
                t = t[t.index(i)+1:]
        return True
</code></pre>
<p><strong>Runtime</strong>: 42ms | <strong>Memory</strong>: 16.3MB
LeetCode 코드 중 Runtime 53%, Memory 88% 해당하는 결과</p>
<h3 id="📝-is-subsequence-회고">📝 Is Subsequence 회고</h3>
<ul>
<li><p>구현한 코드의 시간 복잡도는 O(s * t)이므로 O(n)의 시간복잡도 보다 느리다.</p>
</li>
<li><p>in 연산자도 O(n)의 시간복잡도를 가지고, index() 함수도 O(n)의 시간복잡도가 걸리기 때문이다.</p>
</li>
<li><p>그래서 포인터를 for문 내부의 포인터와 s를 가르키는 포인터 2개를 활용하는 투포인터 접근법으로 다시 개선했다.</p>
<pre><code class="language-python">class Solution:
  def isSubsequence(self, s: str, t: str) -&gt; bool:
      pointer = 0
      if len(s) == 0:
          return True

      for i in t:
          if len(s) &lt;= pointer:
              break
          if s[pointer] == i:
              pointer += 1

      if len(s) == pointer:
          return True
      else:
          return False</code></pre>
</li>
<li><p><em>Runtime*</em>: 42ms ---&gt; <strong>28ms</strong></p>
</li>
<li><p><em>Memory*</em>: 16.3MB ---&gt; <strong>16.4MB</strong></p>
</li>
<li><p>가장 처음 s가 &quot;&quot;일 경우, 모든 t에 포함되므로 True 반환</p>
</li>
<li><p>s의 길이보다 포인터의 길이가 크면 반복문을 탈출 (index out of error 방지)</p>
</li>
<li><p>포인터가 가르키는 s문자열이 t문자열과 같으면 포인터가 s의 다음 문자열을 가르킴</p>
</li>
<li><p>만약 포인터의 길이와 s 문자열의 길이가 같다면 모두 포함된다는 의미라서 True</p>
</li>
</ul>
<p>코드는 조금 더 복잡해졌지만, 투포인터 접근법을 활용하여 시간복잡도는 O(t)즉 O(n)으로 개선되었다.
그래서 leetcode 기준 98%의 Runtime으로 속도를 2배가량 개선하게 되었다.</p>
]]></description>
        </item>
    </channel>
</rss>