<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>매일 작성하는 개발 일기</title>
    <link>https://divheer.tistory.com/</link>
    <description>Small Steps make a Big Difference.</description>
    <language>ko</language>
    <pubDate>Thu, 20 Aug 2026 11:49:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Horang_</managingEditor>
    <image>
      <title>매일 작성하는 개발 일기</title>
      <url>https://tistory1.daumcdn.net/tistory/5728747/attach/5d20f8ecd9684b2ca27e7a84d51c4056</url>
      <link>https://divheer.tistory.com</link>
    </image>
    <item>
      <title>[My Top 9 Books] 이용자 수 3000% 증가한 개인 프로젝트에 대응했던 일주일</title>
      <link>https://divheer.tistory.com/602</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;발단&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 3월, &lt;a href=&quot;https://www.mytop9books.com/discover&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;My Top 9 Books&lt;/b&gt;&lt;/a&gt;라는 개인 프로젝트를 출시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'내가 좋아하는 책 9권'을 선택해 이미지를 만들고 공유하는 서비스였는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트위터에서 바이럴 되면서 &lt;b&gt;출시 한 달 만에 이용자 수 2만 명&lt;/b&gt;을 달성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 개발기&lt;/p&gt;
&lt;figure id=&quot;og_1784727654827&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[My Top 9 Books] 4시간만에 만든 프로젝트가 X 트렌드에 오르기까지&quot; data-og-description=&quot;https://www.mytop9books.com/ 개발 계기어느 날 인터넷을 보다가 요즘 '나를 구성하는 9가지의 게임'을 골라서 공유하는 사이트가 유행이라는 글을 봤다.  https://my9games.com/ 이미 유행이 시작된 지 몇 &quot; data-og-host=&quot;divheer.tistory.com&quot; data-og-source-url=&quot;https://divheer.tistory.com/601&quot; data-og-url=&quot;https://divheer.tistory.com/601&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/S5gkf/dJMb8XSi7kd/fAZk26VMBsooAUMypPsy1K/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/SdCOq/dJMb8TCm5vy/nTTlfTZjg6nnoXEWWK6knK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/BwH0u/dJMb8UH2tQ2/eR3aeguu0hzJpKF5nVUmP1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://divheer.tistory.com/601&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://divheer.tistory.com/601&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/S5gkf/dJMb8XSi7kd/fAZk26VMBsooAUMypPsy1K/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/SdCOq/dJMb8TCm5vy/nTTlfTZjg6nnoXEWWK6knK/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/BwH0u/dJMb8UH2tQ2/eR3aeguu0hzJpKF5nVUmP1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[My Top 9 Books] 4시간만에 만든 프로젝트가 X 트렌드에 오르기까지&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;https://www.mytop9books.com/ 개발 계기어느 날 인터넷을 보다가 요즘 '나를 구성하는 9가지의 게임'을 골라서 공유하는 사이트가 유행이라는 글을 봤다.  https://my9games.com/ 이미 유행이 시작된 지 몇&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;divheer.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 유행이 그렇듯이 약 일주일이 지나자 이용자 수는 빠르게 줄어들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 당시 내가 선택한 책 9권을 웹페이지로 공유할 수 있는 기능을 추가했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성도를 높이고 싶다는 욕심 때문에 업데이트까지 일주일이 걸렸고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 공개했을 때는 이미 유행이 끝난 뒤라 기대한 만큼 기능이 이용되지 않아 아쉬웠었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;1176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nmx2Q/dJMcafU019O/eGgRGRyRf7aW03mzTqL4S1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nmx2Q/dJMcafU019O/eGgRGRyRf7aW03mzTqL4S1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nmx2Q/dJMcafU019O/eGgRGRyRf7aW03mzTqL4S1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNmx2Q%2FdJMcafU019O%2FeGgRGRyRf7aW03mzTqL4S1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;1176&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;1176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이후 몇 달 동안은 하루 이용자 수가 20명 정도를 유지했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'블라인드북' 기능도 추가했지만 두 달 가까이 이 기능의 사용자는 한 명도 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;두 번째 바이럴&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;트위터 인증.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2583&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GMFYi/dJMcajiM7IX/U3AC3HieEzAirRMJsFDWcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GMFYi/dJMcajiM7IX/U3AC3HieEzAirRMJsFDWcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GMFYi/dJMcajiM7IX/U3AC3HieEzAirRMJsFDWcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGMFYi%2FdJMcajiM7IX%2FU3AC3HieEzAirRMJsFDWcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;2583&quot; data-filename=&quot;트위터 인증.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2583&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7월 9일부터 이유는 모르겠지만 내가 3월에 올렸던 서비스 홍보 트윗이 갑자기 다시 리트윗 되기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbAcyN/dJMcaaGbvFX/nJDs3HW7ePeWdedaat6hJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbAcyN/dJMcaaGbvFX/nJDs3HW7ePeWdedaat6hJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbAcyN/dJMcaaGbvFX/nJDs3HW7ePeWdedaat6hJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbAcyN%2FdJMcaaGbvFX%2FnJDs3HW7ePeWdedaat6hJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;631&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;기존에 20만 조회수였던 트윗은 약 열흘 만에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;125만 조회수&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 넘겼고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;서비스 이용자 수도 하루에&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;1~2천 명까지 증가했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기회에는 지난 바이럴 때 아쉬웠던 점을 보완해서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작은 기능이라도 빠르게 출시하면서 사용자 반응을 확인하는 방식&lt;/b&gt;으로 개발하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능 업데이트를 통해 9권의 도서 이미지만 단발적으로 저장하고 끝나는 것이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트를 계속 방문하고 싶게 만드는 서비스를 제공하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7월 10일부터 14일까지 5일 동안의 업데이트 과정을 기록으로 남겨본다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;랜덤 서재 추천 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차 바이럴 이후 서재 공유 링크가 많이 생성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존에 서재 공유 링크가 185개였는데, 일주일 만에 1,000여 개로 증가했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지네이션으로 모든 서재를 확인하기에는 한계가 있으니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 서재 발견을 활성화하기 위해 랜덤 서재 추천 기능을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때로는 내 취향과 관계 없이 새로운 책을 발견하고 싶은 분들에게 재미있는 기능이 될 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;랜덤 셔플.gif&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;797&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9zJF/dJMcadJwwr1/mdSdNYk4ZglvuDbE4FimTK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9zJF/dJMcadJwwr1/mdSdNYk4ZglvuDbE4FimTK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9zJF/dJMcadJwwr1/mdSdNYk4ZglvuDbE4FimTK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/n9zJF/dJMcadJwwr1/mdSdNYk4ZglvuDbE4FimTK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;797&quot; data-filename=&quot;랜덤 셔플.gif&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;797&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측 상단의 랜덤 버튼을 클릭하면 아이콘이 회전하면서 (로딩 중 표시) 랜덤으로 선정된 서재 페이지로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도서 검색 기능&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brHz8D/dJMb991yexB/2bVklHFPLiIKPgmSIeIyO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brHz8D/dJMb991yexB/2bVklHFPLiIKPgmSIeIyO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brHz8D/dJMb991yexB/2bVklHFPLiIKPgmSIeIyO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrHz8D%2FdJMb991yexB%2F2bVklHFPLiIKPgmSIeIyO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;588&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 '나와 취향이 비슷한 다른 사람의 서재'를 추천받는 기능만 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이번에 서재 데이터도 충분히 쌓인 김에, 3월부터 추가하고 싶었던 기능을 추가하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 기능은 바로 내가 좋아하는 책이 포함된 사람들의 서재를 모두 확인할 수 있는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 책에 접근하기 위해 일단 도서 검색 기능을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvZsBs/dJMcaae5Pjy/NdfS1oo9uIgdabkQRSqkO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvZsBs/dJMcaae5Pjy/NdfS1oo9uIgdabkQRSqkO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvZsBs/dJMcaae5Pjy/NdfS1oo9uIgdabkQRSqkO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvZsBs%2FdJMcaae5Pjy%2FNdfS1oo9uIgdabkQRSqkO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;585&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색창은 접근성을 고려해서 서재 리스트의 상단에 배치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1074&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm6OAM/dJMcajbUPao/KbJsEKOZuE37BR7KDGCBQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm6OAM/dJMcajbUPao/KbJsEKOZuE37BR7KDGCBQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm6OAM/dJMcajbUPao/KbJsEKOZuE37BR7KDGCBQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm6OAM%2FdJMcajbUPao%2FKbJsEKOZuE37BR7KDGCBQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;713&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;1074&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책 제목 또는 작가명으로 검색하면, 공개 서재 데이터에 등록된 책들의 리스트가 제공된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트는 서재 선택 횟수가 많은 순으로 정렬되며, 몇 개의 서재에 포함되었는지도 함께 표시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;특정 도서가 포함된 서재 모아보기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;1231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/smLQQ/dJMcabrvp2w/gxYJSkrwp3LztEV9Q4kRTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/smLQQ/dJMcabrvp2w/gxYJSkrwp3LztEV9Q4kRTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/smLQQ/dJMcabrvp2w/gxYJSkrwp3LztEV9Q4kRTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsmLQQ%2FdJMcabrvp2w%2FgxYJSkrwp3LztEV9Q4kRTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1231&quot; data-origin-width=&quot;754&quot; data-origin-height=&quot;1231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 결과에서 책을 선택하면 해당 책이 포함된 서재만 모아볼 수 있는 페이지로 이동한다. (이하 '도서 페이지')&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 페이지에서는 최신 등록 순으로 해당 책이 포함된 모든 서재를 한눈에 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/etnyeT/dJMcaa7giE9/tglWLSBEvBVS3zgmkzqZ9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/etnyeT/dJMcaa7giE9/tglWLSBEvBVS3zgmkzqZ9k/img.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;372&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; style=&quot;width: 58.7143%; margin-right: 10px;&quot; data-widthpercent=&quot;59.41&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/etnyeT/dJMcaa7giE9/tglWLSBEvBVS3zgmkzqZ9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FetnyeT%2FdJMcaa7giE9%2FtglWLSBEvBVS3zgmkzqZ9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;769&quot; height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OGPPC/dJMcacD04uP/bYfArR7KKGbqnsi8TEgTkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OGPPC/dJMcacD04uP/bYfArR7KKGbqnsi8TEgTkk/img.png&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;538&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; style=&quot;width: 40.1229%;&quot; data-widthpercent=&quot;40.59&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OGPPC/dJMcacD04uP/bYfArR7KKGbqnsi8TEgTkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOGPPC%2FdJMcacD04uP%2FbYfArR7KKGbqnsi8TEgTkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;760&quot; height=&quot;538&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서재 페이지 이용률을 높이기 위해 서재 페이지에 접근할 수 있는 여러 경로를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 서재 페이지와 랭킹 페이지에 기존의 '상세' 버튼 외에 '서재' 버튼을 추가해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제든 같은 책을 선택한 사람들의 서재를 둘러볼 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함께 선택된 책 추천&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도서 페이지를 만들다 보니 또 하나의 아이디어가 떠올랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자들이 내가 좋아하는 책을 통해 비슷한 책을 추천받고 싶어 한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 서재에서 가만 많이 함께 선택된 책의 데이터를 제공하면 좋겠다고 생각을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3K6Bo/dJMcaalKXld/4ImT85kaX8XTKgaAKgaha1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3K6Bo/dJMcaalKXld/4ImT85kaX8XTKgaAKgaha1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3K6Bo/dJMcaalKXld/4ImT85kaX8XTKgaAKgaha1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3K6Bo%2FdJMcaalKXld%2F4ImT85kaX8XTKgaAKgaha1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1168&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;1168&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 도서 페이지 상단에 가로스크롤 형태로 '이 책과 함께 선택된 도서' 섹션을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 서재에서 함께 선택된 책 중에 가장 많이 함께 선택된 순서로 도서 목록을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 3월 처음 My Top 9 Books가 바이럴 되었을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스를 장기적으로는 '도서 취향 공유 커뮤니티'로 발전시키고 싶다는 생각을 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 두 번째 바이럴이라는 계기를 통해 이 방향성에 다가갈 수 있는 기능을 많이 추가할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2608&quot; data-start=&quot;2581&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무엇보다 지난 경험에서 얻은 교훈 덕분에 개발 방식도 달라졌다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2637&quot; data-start=&quot;2610&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예전에는 기간보다는 완성도에 집착했다면,&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2685&quot; data-start=&quot;2639&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번에는 기능을 나눠서 빠르게 공개하고 사용자 반응을 보며 개선하는 방식을 선택했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2685&quot; data-start=&quot;2639&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2685&quot; data-start=&quot;2639&quot; data-ke-size=&quot;size16&quot;&gt;이번에 만들어 놓긴 했지만 반응이 아쉬웠던 서재 공유 링크 생성,&lt;/p&gt;
&lt;p data-end=&quot;2685&quot; data-start=&quot;2639&quot; data-ke-size=&quot;size16&quot;&gt;서재 공유 링크 내 작품 추천, 블라인드북 등 다양한 기능의 사용이 활성화되었던 점도 너무 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 사랑하는 독자분들이 9권의 책을 고르면서 나는 어떤 취향을 가지고 있었나 고민하는 시간을 가지거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람들의 서재를 구경하면서 내 취향에 맞을 것 같은 새로운 책을 찾아가신다면 너무 뿌듯할 것 같다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>1인개발자</category>
      <category>MyTop9Books</category>
      <category>사이드프로젝트</category>
      <category>웹개발</category>
      <category>프로젝트회고</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/602</guid>
      <comments>https://divheer.tistory.com/602#entry602comment</comments>
      <pubDate>Fri, 24 Jul 2026 15:59:39 +0900</pubDate>
    </item>
    <item>
      <title>[My Top 9 Books] 4시간만에 만든 프로젝트가 X 트렌드에 오르기까지</title>
      <link>https://divheer.tistory.com/601</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9books og_image2.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZE7cc/dJMcagLZbYI/XxTPLoUXS3eiGs01dSMXbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZE7cc/dJMcagLZbYI/XxTPLoUXS3eiGs01dSMXbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZE7cc/dJMcagLZbYI/XxTPLoUXS3eiGs01dSMXbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZE7cc%2FdJMcagLZbYI%2FXxTPLoUXS3eiGs01dSMXbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;630&quot; data-filename=&quot;9books og_image2.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mytop9books.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.mytop9books.com/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 날 인터넷을 보다가 요즘 '나를 구성하는 9가지의 게임'을 골라서 공유하는 사이트가 유행이라는 글을 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://my9games.com/&quot;&gt;https://my9games.com/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 유행이 시작된 지 몇 주가 지난 시점이었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이트를 보자마자 다양한 장르로 변주가 가능하겠다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS에 검색해 보니 역시나 영화, 노래 버전 등 다양한 파생 서비스가 이미 운영 중이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 사랑하는 사람으로서, 그리고 이미 '웹소설 캘린더'라는 사이트도 운영 중이었기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국 사용자들을 위한 도서 버전의 사이트 만들어보면 어떨까 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB도 필요 없이 검색 API만 연동하면 되겠다는 판단에 가벼운 마음으로 시작했고, 실제로 4시간 만에 완성했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UnlWF/dJMcabwXQi0/6j7RVS4rPMgwO1M7sDkuu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UnlWF/dJMcabwXQi0/6j7RVS4rPMgwO1M7sDkuu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UnlWF/dJMcabwXQi0/6j7RVS4rPMgwO1M7sDkuu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUnlWF%2FdJMcabwXQi0%2F6j7RVS4rPMgwO1M7sDkuu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;1228&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만들자마자 X에 바로 홍보 트윗을 올렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SNS 바이럴로 X 트렌드까지&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트윗을 올린 당일에는 큰 반응이 없었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음날인 3월 8일부터 갑자기 SNS와 각종 커뮤니티 상에서 바이럴이 되기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;1313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vMlVh/dJMcabcLZoo/OVhVcPS9jcAzuavzrV2SL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vMlVh/dJMcabcLZoo/OVhVcPS9jcAzuavzrV2SL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vMlVh/dJMcabcLZoo/OVhVcPS9jcAzuavzrV2SL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvMlVh%2FdJMcabcLZoo%2FOVhVcPS9jcAzuavzrV2SL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;1313&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;1313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이날 마침 친구와 약속이 있어서 밥을 먹고 카페에 앉아 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이용자 수가 오후 내내 수백 명 단위로 늘어나더니 오후 5시쯤엔 X 실시간 트렌드에 올라와 있는 걸 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M7ZLs/dJMcafGf3DQ/01i95BFMby6qxDdSWSfcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M7ZLs/dJMcafGf3DQ/01i95BFMby6qxDdSWSfcM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M7ZLs/dJMcafGf3DQ/01i95BFMby6qxDdSWSfcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM7ZLs%2FdJMcafGf3DQ%2F01i95BFMby6qxDdSWSfcM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;457&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS 바이럴 덕분에 서비스 출시 5일만에 누적 사용자 1.5만 명을 달성했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용자가 급증하면서 가장 힘들었던 부분&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 트래픽은 이미 Vercel Pro를 사용하고 있기도 했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 연동 없이 거의 로컬에서만 작업이 이루어지는 구조라 사용자가 급증해도 큰 부담은 없었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 문제는 검색 API였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbBDJ6/dJMcagkJWM8/NU9LVzjnyZqKLJ2bEIA2rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbBDJ6/dJMcagkJWM8/NU9LVzjnyZqKLJ2bEIA2rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbBDJ6/dJMcagkJWM8/NU9LVzjnyZqKLJ2bEIA2rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbBDJ6%2FdJMcagkJWM8%2FNU9LVzjnyZqKLJ2bEIA2rK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;235&quot; data-origin-width=&quot;691&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MT9 (My Top 9 Books) 는 일반 도서와 전자책 검색 기능을 나눠서 제공하는데, 검색 API 제공처도 분리돼 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;일반 도서&lt;/b&gt;: 네이버 검색 API &amp;mdash; 일일 25,000회 제한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전자책 (주로 웹소설)&lt;/b&gt;: 알라딘 검색 API &amp;mdash; 일일 5,000회 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;온라인 도서 판매를 전문으로 하는 알라딘으로 검색 API를 통일하고 싶었지만&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일일 검색 횟수가 너무 적었기 때문에 부득이하게 둘로 나누게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초반에 일일 이용자가 4,000~5,000명에 달하던 시기에는 빠르면 오후 5시부터 검색이 제한되기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자들에게 원활한 서비스 이용을 제공할 수 없다는 점에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루에도 몇 번씩 검색 이용 횟수를 확인하면서 스트레스를 많이 받았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 한도 문제를 해결하기 위해 예스24, 교보문고, 알라딘 세 곳에 검색 API 제휴 문의를 보내기도 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 두 곳에서 긍정적인 답장을 받았고, 한 곳과는 대면 미팅까지 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 사정으로 인해 실제로 성사되지는 않았지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 대기업에서 나의 서비스를 이미 알고 있었다는 얘기를 들을 수 있어서 신기하고 뿌듯한 경험이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 과정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4시간만에 개발했던 초기 버전&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;1387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Duy95/dJMcaiiy3A2/hhHdNqUqI25o5QYwHQCvYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Duy95/dJMcaiiy3A2/hhHdNqUqI25o5QYwHQCvYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Duy95/dJMcaiiy3A2/hhHdNqUqI25o5QYwHQCvYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDuy95%2FdJMcaiiy3A2%2FhhHdNqUqI25o5QYwHQCvYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;1069&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;741&quot; data-origin-height=&quot;1387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 정말 기본적인 기능들만 제공했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도서 검색 후 표지 이미지 가져오기&lt;/li&gt;
&lt;li&gt;이미지 저장하기&lt;/li&gt;
&lt;li&gt;X에서 공유하기&lt;/li&gt;
&lt;li&gt;GA 연동, 메타데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 전혀 사용하지 않고 로컬에서만 작동하는 버전이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 완벽한 사이트를 만들려고 하지 말고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 MVP 버전을 만들어서 배포한 후 사용자들의 반응을 보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 도메인도 구입하지 않아서 Vercel 무료 도메인을 그대로 사용했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(지금은 도메인 구입 후 리다이렉트를 적용해 놓았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자 급증에 따른 조치&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;사용자들이 X에 해시태그로 공유해 주시는 트윗을 열심히 모니터링하면서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 이용에 문제가 없는지, 어떤 부분을 개선해야 할지를 탐색했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인하면서 이미지 첨부 없이 글만 올라온다거나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 저장이 되지 않아서 그냥 화면을 캡처해서 올리시는 분들이 많아서 이 부분에도 대응을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGzGiU/dJMcafMXr8X/o6esdwiEK4yZKHJd5VWBd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGzGiU/dJMcafMXr8X/o6esdwiEK4yZKHJd5VWBd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGzGiU/dJMcafMXr8X/o6esdwiEK4yZKHJd5VWBd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGzGiU%2FdJMcafMXr8X%2Fo6esdwiEK4yZKHJd5VWBd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;369&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트위터에서 이미지 없이 글만 올라오는 경우가 자주 발생해서 'X에서 공유하기' 버튼 클릭 시 안내 팝업을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 직접 첨부해 달라는 강조 문구와 함께, 안내 팝업에서 바로 이미지를 저장할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cI7SLQ/dJMcahRuPWL/k7830lfxzpTQyTgqCoktbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cI7SLQ/dJMcahRuPWL/k7830lfxzpTQyTgqCoktbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cI7SLQ/dJMcahRuPWL/k7830lfxzpTQyTgqCoktbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcI7SLQ%2FdJMcahRuPWL%2Fk7830lfxzpTQyTgqCoktbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;215&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 저장이 되지 않는다고 화면을 그대로 캡처해서 올리시는 분들도 있었는데&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에 표지 우측 상단에 X 버튼으로 인해 이미지가 지저분해 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 추가 기능 개발하느라 정신이 없었기 때문에 이 닫기 버튼을 아예 없애 버렸었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Error: l.roundRect is not a function. (In 'l.roundRect(a,n,384,s,24)', 'l.roundRect' is undefined)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 에러 로그를 확인해서 브라우저에 따라 이미지 크롭 함수가 작동하지 않는 것이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제라는 것을 알아내서 해당 에러를 수정했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색 API의 일일 한도량을 모두 소진해서 검색이 불가능해지는 상황이 대비하는 작업도 진행했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;검색 결과에 캐싱을 적용해서, 동일한 검색어 중복 요청 시 API 호출을 절약했다&lt;/li&gt;
&lt;li&gt;자정이 지나도 전날 캐시가 남아 검색이 되지 않는 이슈를 경험하고, 캐싱 태그에 날짜를 추가해서 자정 이후엔 항상 갱신되도록 했다&lt;/li&gt;
&lt;li&gt;검색 한도 초과 시 검색 모달에 안내 메시지를 노출했다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0eM1d/dJMcahqt6ql/gP89qk3jvjRe4OFhN9tYK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0eM1d/dJMcahqt6ql/gP89qk3jvjRe4OFhN9tYK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0eM1d/dJMcahqt6ql/gP89qk3jvjRe4OFhN9tYK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0eM1d%2FdJMcahqt6ql%2FgP89qk3jvjRe4OFhN9tYK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;416&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색 한도가 초과된 경우 상황을 자세히 설명하고, 언제부터 검색 가능한지를 안내해서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자들이 기능을 이용하는 데 혼란이 없도록 노력했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IzO7a/dJMb990j8QT/TQvENEgy7bOIK6ANKjb4Mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IzO7a/dJMb990j8QT/TQvENEgy7bOIK6ANKjb4Mk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IzO7a/dJMb990j8QT/TQvENEgy7bOIK6ANKjb4Mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIzO7a%2FdJMb990j8QT%2FTQvENEgy7bOIK6ANKjb4Mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;137&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한국이 아닌 다른 국가에서도 꾸준히 유입이 있는 것을 보고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;헤더에 한국어 또는 영어 중에 언어를 선택할 수 있는 버튼도 추가했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 추후 랭킹 정보를 제공하기 위해 DB에 작품별 선택된 횟수를 저장하기 시작했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 무료로 사용할 수 있고 빠른 구현이 가능한 Friebase를 이용했었는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;데이터 확인이나 정렬에 어려움이 있어서&lt;span&gt;&amp;nbsp;&lt;/span&gt;나중에 Supabase로 마이그레이션을 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기까지가 이 서비스가 SNS에서 바이럴 되고 초기 이틀 동안 업데이트했던 내용이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;공유 링크 생성 기능 추가&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자들의 이용 방식을 보면서 새로운 기능 아이디어가 떠올랐다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단순하게 top9 이미지만 저장하는 게 아니라 공유 링크를 만들어서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 고른 책의 제목이나 상세 정보를 더 편리하게 공유하고, 영구적으로 저장할 수 있으면 좋겠다는 생각이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공유 링크 생성 기능은 비슷한 &quot;9가지&quot; 컨셉의 사이트에서도 일반적으로 제공되는 부분이었고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기에 두 가지 기능을 추가하고 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bspJKZ/dJMcagd3Klr/PNiYAeFhZh7hKSJK0c8Kck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bspJKZ/dJMcagd3Klr/PNiYAeFhZh7hKSJK0c8Kck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bspJKZ/dJMcagd3Klr/PNiYAeFhZh7hKSJK0c8Kck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbspJKZ%2FdJMcagd3Klr%2FPNiYAeFhZh7hKSJK0c8Kck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;331&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 작품 추천 기능&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;top9 이미지를 올리면서 내 취향에 맞을 거 같은 책을 추천해 달라는 사용자들이 많았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 기능을 사이트 내에서 직접 제공하면 어떨까 싶었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아쉽게도 결과적으로는 현재 거의 이용되지 않는 기능이 되었지만...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKc8Xn/dJMcagLT4e5/AbKFJvzkprFhHTadAUToBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKc8Xn/dJMcagLT4e5/AbKFJvzkprFhHTadAUToBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKc8Xn/dJMcagLT4e5/AbKFJvzkprFhHTadAUToBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKc8Xn%2FdJMcagLT4e5%2FAbKFJvzkprFhHTadAUToBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;396&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 나와 비슷한 서재 추천&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;top9 이미지를 올리면서 나와 취향이 비슷한 영혼의 단짝을 찾는다는 글이 많았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제 데이터를 바탕으로 내가 선택한 책 중에서 2권 이상이 일치하는 다른 사람의 서재를 추천해 주는 기능이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;1164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sQ5Uc/dJMcabjBFae/88f37dy08YbZmfZvJBWJp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sQ5Uc/dJMcabjBFae/88f37dy08YbZmfZvJBWJp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sQ5Uc/dJMcabjBFae/88f37dy08YbZmfZvJBWJp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsQ5Uc%2FdJMcabjBFae%2F88f37dy08YbZmfZvJBWJp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;1164&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;1164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공유 링크 생성 기능을 개발하면서 다음 작업들을 진행했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유 링크 생성 시 닉네임과 서재명, 도서별 코멘트 작성 기능&lt;/li&gt;
&lt;li&gt;검색 결과에 없는 책을 직접 이미지로 첨부해서 등록하는 경우 DB에 저장 -&amp;gt; 이후 다른 사용자들도 검색 가능&lt;/li&gt;
&lt;li&gt;링크 생성 전까지 작성 내용을 로컬에 저장&lt;/li&gt;
&lt;li&gt;공유 링크 생성 시 선택한 책 9권으로 og_image 생성 후 Cloudflare R2에 저장&lt;/li&gt;
&lt;li&gt;공유 링크 생성 실패 시 DB 업데이트, og_image 등 전부 롤백&lt;/li&gt;
&lt;li&gt;공유 페이지에서 작품 추천 및&amp;nbsp;비슷한 취향의 서재 추천 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;1052&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zGSl8/dJMcadayTBv/WQJc6cPxwicKPk5ZLfytMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zGSl8/dJMcadayTBv/WQJc6cPxwicKPk5ZLfytMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zGSl8/dJMcadayTBv/WQJc6cPxwicKPk5ZLfytMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzGSl8%2FdJMcadayTBv%2FWQJc6cPxwicKPk5ZLfytMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1052&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;619&quot; data-origin-height=&quot;1052&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 추가된 기능의 사용률을 높이고 싶어서 '공유 링크 생성 안내' 모달도 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 호출 횟수를 최적화하기 위해서&amp;nbsp;공유 페이지의 렌더링 전략도 섹션마다 다르게 적용했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;screencapture-mytop9books-share-woXnuv2GEiUtLz8mWfK2w-2026-04-14-17_09_44.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;2466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blESok/dJMcadhlWWO/KVjXRZunSmcdddyQ3VoT90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blESok/dJMcadhlWWO/KVjXRZunSmcdddyQ3VoT90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blESok/dJMcadhlWWO/KVjXRZunSmcdddyQ3VoT90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblESok%2FdJMcadhlWWO%2FKVjXRZunSmcdddyQ3VoT90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;2466&quot; data-filename=&quot;screencapture-mytop9books-share-woXnuv2GEiUtLz8mWfK2w-2026-04-14-17_09_44.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;2466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 책 정보 + 표지 이미지 그리드 섹션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초 생성 후에는 변하지 않는 정적인 데이터이기 때문에&amp;nbsp;SSG로 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 잘못된 경우에는 관리자 페이지에서 캐시 태그로 갱신할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjEaEi/dJMcaiJG2Wo/pzCZprUZhAyxAQiqRZTaC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjEaEi/dJMcaiJG2Wo/pzCZprUZhAyxAQiqRZTaC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjEaEi/dJMcaiJG2Wo/pzCZprUZhAyxAQiqRZTaC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjEaEi%2FdJMcaiJG2Wo%2FpzCZprUZhAyxAQiqRZTaC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;320&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 작품 추천 섹션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작품 추천이 새로 등록될 때만 캐시를 갱신하는 On-demand ISR로 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;1121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/V2a79/dJMcaf0wjxj/gCQKTNtRQLLP5tkK19MElk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/V2a79/dJMcaf0wjxj/gCQKTNtRQLLP5tkK19MElk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/V2a79/dJMcaf0wjxj/gCQKTNtRQLLP5tkK19MElk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FV2a79%2FdJMcaf0wjxj%2FgCQKTNtRQLLP5tkK19MElk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;1121&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;1121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 비슷한 서재 추천 섹션&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 사람들의 서재 데이터에 의존하기 때문에&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 실시간성을 판단하기 어려워서&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;SSR로 항상 새로운 데이터를 불러오도록 했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한 가지 아쉬운 점이 있다면 접근성을 높이기 위해 로그인 기능을 넣지 않았기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서재를 한 번 생성하면 수정이나 삭제가 불가능하다는 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(생성 시 비밀번호를 받아서 인증하는 방식도 생각해 봤지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단은 간단하게 구현하고 이용률이 많아지면 그때 수정하기로...)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vDqcp/dJMcajaLHUM/3hrQThhpEHDsqjMOUmMMak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vDqcp/dJMcajaLHUM/3hrQThhpEHDsqjMOUmMMak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vDqcp/dJMcajaLHUM/3hrQThhpEHDsqjMOUmMMak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvDqcp%2FdJMcajaLHUM%2F3hrQThhpEHDsqjMOUmMMak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;368&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용자들에게도 이러한 사항을 미리 고지하기 위해 '공유 링크 생성하기' 버튼을 누르면 안내용 팝업을 띄운다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 간혹 동일한 사용자가 서재를 생성한 뒤 1~2권만 바꿔서 새 서재를 다시 만드는 경우가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 경우 '서재 구경하기' 페이지에서 먼저 생성된 서재를 숨김 처리하는 방식으로 관리하고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공유 링크 생성 기능을 개발할 때 디테일한 부분까지 완벽하게 마무리하고 싶기도 했고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 기술도 사용해 보느라 생각보다 개발 기간이 길어졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;배포를 마쳤을 때는 이미 유행이 약간 식은 분위기였기 때문에&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;단계적으로 쪼개서 일부 기능이라도 빠르게 추가했다면 어땠을까 하는 아쉬움이 남는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;공유 링크 생성 기능 출시 후 UX 디테일 개선&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공유 링크 생성 기능 출시 후 초반에는 이용률이 생각보다 저조했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 이용률을 높이기 위해 디테일한 부분들을 여러 차례 수정했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7CW8/dJMcabRm0Sc/tvrxcSl22JFfXJO4CuTRH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7CW8/dJMcabRm0Sc/tvrxcSl22JFfXJO4CuTRH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7CW8/dJMcabRm0Sc/tvrxcSl22JFfXJO4CuTRH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7CW8%2FdJMcabRm0Sc%2FtvrxcSl22JFfXJO4CuTRH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;929&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;929&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. '공유 링크 생성하기' 버튼 위치 변경 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 '공유 링크 생성하기' 버튼을 페이지 최하단에 위치했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;책 9권을 고르고 &amp;rarr; 정보를 작성하고 &amp;rarr; 링크를 생성하는 순서로 자연스럽게 내려올 거라 생각했기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 막상 배포하고 나니까 사용자들이 굳이 페이지 최하단까지 스크롤하지 않는 것 같았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;598&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oMDdG/dJMcahcZh9K/5jHVJmbR69NEsf1yMMb49K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oMDdG/dJMcahcZh9K/5jHVJmbR69NEsf1yMMb49K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oMDdG/dJMcahcZh9K/5jHVJmbR69NEsf1yMMb49K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoMDdG%2FdJMcahcZh9K%2F5jHVJmbR69NEsf1yMMb49K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;390&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;598&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 공유 링크 생성 버튼을 사용자들이 가장 많이 이용하는 '이미지 저장하기' 버튼 하단으로 옮기고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;가장 눈에 띄는 색상으로 바꿔서 접근성을 높였다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;'공유 링크 생성하기'&lt;span&gt;&amp;nbsp;&lt;/span&gt;버튼 텍스트 변경&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;텍스트를 어떻게 해야 사용자들이 이 기능을 사용하고 싶어 할까 고민하면서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'공유 링크 생성하기' &amp;rarr; '페이지 저장하기' &amp;rarr; '내 서재 공유 링크 만들기'로 버튼의 표현을 여러 차례 수정했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;465&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NrGwn/dJMcaaLInIL/HhLpuJw4J3f76nAcC9aWGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NrGwn/dJMcaaLInIL/HhLpuJw4J3f76nAcC9aWGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NrGwn/dJMcaaLInIL/HhLpuJw4J3f76nAcC9aWGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNrGwn%2FdJMcaaLInIL%2FHhLpuJw4J3f76nAcC9aWGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;465&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;465&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 생성자명 필수 입력 해제 &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 공유 링크 생성 시 생성자명을 필수적으로 입력하도록 했었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 사용자들이 필수 입력 누락으로 공유 링크 생성을 포기하는 경우가 생길 거 같아서 (물론 안내 메시지를 보여준다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;9권의 책을 선택하기만 하면 즉시 공유 링크를 생성할 수 있도록 절차를 간소화했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;서재 구경하기 페이지 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 링크가 생성될 때마다 하나하나 확인하면서 검수를 하다 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타인의 개성 넘치는 서재를 구경하는 게 생각보다 너무 재밌었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이걸 나만 보는 게 아니라 다 같이 보고 싶다는 생각이 들어서 바로 개발에 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM9uMg/dJMcaiQsPoH/YIOBTKg6ugALz54K8J3HQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM9uMg/dJMcaiQsPoH/YIOBTKg6ugALz54K8J3HQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM9uMg/dJMcaiQsPoH/YIOBTKg6ugALz54K8J3HQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM9uMg%2FdJMcaiQsPoH%2FYIOBTKg6ugALz54K8J3HQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;195&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;'서재 구경하기' 페이지는 일반 도서와 장르소설/만화로 나뉘는데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;검색 API에서 세부 장르 정보를 제공하지 않기 때문에 어느 검색 API만으로 검색했는지에 따라 단순하게 구분된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(네이버 &amp;rarr; 일반 도서, 알라딘 &amp;rarr; 장르소설)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ8O2J/dJMcagFbe1t/YjnlG4PStOYwDSCwapVQ90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ8O2J/dJMcagFbe1t/YjnlG4PStOYwDSCwapVQ90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ8O2J/dJMcagFbe1t/YjnlG4PStOYwDSCwapVQ90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ8O2J%2FdJMcagFbe1t%2FYjnlG4PStOYwDSCwapVQ90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;960&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 때문에 가끔 잘못 분류되는 경우가 생기는데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새 서재가 생성될 때마다 슬랙 알림이 오도록 연동해서 관리자 페이지에서 직접 수정해주고 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;장르뿐만 아니라 서재 데이터에 잘못된 부분은 없는지 한 번씩 검수하는 용도로도 좋은 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음 구경하기 페이지를 구상할 때 내가 생성한 공유 링크가 모두에게 공개된다는 점이&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오히려 이 기능의 사용률을 낮추지 않을까 걱정했었는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 꾸준히 이용되고 있는 점을 보면 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;크게 영향은 없는 것 같다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래도 언젠가 공개/비공개 옵션을 추가하고 싶은 마음은 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;랭킹 페이지 추가&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;799&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmcN5K/dJMcadn5ZBC/7I28MC1NFSFSWaPqWLp7E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmcN5K/dJMcadn5ZBC/7I28MC1NFSFSWaPqWLp7E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmcN5K/dJMcadn5ZBC/7I28MC1NFSFSWaPqWLp7E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmcN5K%2FdJMcadn5ZBC%2F7I28MC1NFSFSWaPqWLp7E0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;799&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;799&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Firebase와 Supabase에 분산되어 있던 도서별 카운팅 데이터를 통합하느라 공개가 좀 늦어졌다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;랭킹 데이터를 정기적으로 업데이트하고 최신 업데이트 시각을 표기하는 등 더 친절하게 만들 수도 있었지만,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 빨리 배포하자는 생각으로 최대한 심플하게 만들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;비정기적으로 업데이트하긴 하는데, 업데이트해도 기존 데이터에서 큰 변화는 없는 편이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;주제 페이지 추가&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uTeKp/dJMcac3Nx7H/FVv8UkKMe1MUXzlYngaCpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uTeKp/dJMcac3Nx7H/FVv8UkKMe1MUXzlYngaCpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uTeKp/dJMcac3Nx7H/FVv8UkKMe1MUXzlYngaCpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuTeKp%2FdJMcac3Nx7H%2FFVv8UkKMe1MUXzlYngaCpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;870&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공유 링크 생성 기능을 추가하면서 이 서비스가 독서인들의 다양한 취향을 공유할 수 있는&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;커뮤니티로서의 성격을 가질 수 있기를 바랐다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 방향성에 맞게 단순한 '인생책 9권'뿐만 아니라&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다양한 주제로 서재를 만들고 모아볼 수 있으면 좋겠다는 생각에 주제 생성 기능을 추가했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDMJLc/dJMcagFbnJ6/zXu4VPjpuumsDg9CkWA8e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDMJLc/dJMcagFbnJ6/zXu4VPjpuumsDg9CkWA8e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDMJLc/dJMcagFbnJ6/zXu4VPjpuumsDg9CkWA8e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDMJLc%2FdJMcagFbnJ6%2FzXu4VPjpuumsDg9CkWA8e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;737&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;737&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;특정 주제 페이지에서 공유 링크를 생성하면 해당 주제만의 서재 데이터를 모아볼 수 있고,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;1251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdSchR/dJMcajaLQQ5/wPgqpsGzUqF1r24hBkjmKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdSchR/dJMcajaLQQ5/wPgqpsGzUqF1r24hBkjmKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdSchR/dJMcajaLQQ5/wPgqpsGzUqF1r24hBkjmKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdSchR%2FdJMcajaLQQ5%2FwPgqpsGzUqF1r24hBkjmKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;1251&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;1251&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;해당 공유 링크 페이지에는 어떤 주제로 만들어진 것인지 표기된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;그 외 신경 쓴 것들&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgTTww/dJMcagSHnEo/IFCi8ZG5PTmXtpYiMjgK3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgTTww/dJMcagSHnEo/IFCi8ZG5PTmXtpYiMjgK3K/img.png&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;1301&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.1087%; margin-right: 10px;&quot; data-widthpercent=&quot;50.7&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgTTww/dJMcagSHnEo/IFCi8ZG5PTmXtpYiMjgK3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgTTww%2FdJMcagSHnEo%2FIFCi8ZG5PTmXtpYiMjgK3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1542&quot; height=&quot;1301&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7FW1W/dJMcajaLQS2/8EQ4k1U1eIgpmYmHD8zjZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7FW1W/dJMcajaLQS2/8EQ4k1U1eIgpmYmHD8zjZK/img.png&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;924&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.7285%;&quot; data-widthpercent=&quot;49.3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7FW1W/dJMcajaLQS2/8EQ4k1U1eIgpmYmHD8zjZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7FW1W%2FdJMcajaLQS2%2F8EQ4k1U1eIgpmYmHD8zjZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1065&quot; height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. SEO &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEO를 위해 페이지별 메타데이터, 사이트맵, 렌더링 전략 등을 신경 썼는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글이나 네이버 검색 결과에 사이트가 잘 뜨는 것을 보면 효과가 있었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(SNS 공유가 활발했던 것도 영향이 있었을 거라 생각한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Honeycam 2026-04-14 23-13-54.gif&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kBBBw/dJMcac3Nyn2/WKXiCoaMFASrVoSzAEHQH1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kBBBw/dJMcac3Nyn2/WKXiCoaMFASrVoSzAEHQH1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kBBBw/dJMcac3Nyn2/WKXiCoaMFASrVoSzAEHQH1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/kBBBw/dJMcac3Nyn2/WKXiCoaMFASrVoSzAEHQH1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;499&quot; height=&quot;1108&quot; data-filename=&quot;Honeycam 2026-04-14 23-13-54.gif&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;1108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 드래그 앤 드롭 순서 변경 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택한 책의 순서를 바꾸고 싶은 경우가 있을 거 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 드로그 앤 드롭으로 책의 순서를 변경할 수 있는 기능을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 MT9를 만들고 운영하면서 느낀 점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;할까 말까 고민할 시간에 일단 만들어 보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;처음부터 너무 완벽한 걸 만들려고 하지 말자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 빠르게 만들고, 사용자들의 반응을 보면서 고쳐나가면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;MT9는 특히 해시태그를 통한 SNS 공유가 활발하게 이루어졌기 때문에,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내가 만든 서비스가 실제로 누군가에게 이용되고 있다는 것을,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 어떻게 이용되고 있는지를 직접 볼 수 있어서 정말 행복했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;책 9권을 고르기 너무 힘들었어요&quot;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;고르면서 나도 몰랐던 내 취향을 알게 됐어요&quot; 같은 반응들을 볼 때마다 너무 재밌고 뿌듯했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자는 관심을 먹고사는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 만든 서비스를 누군가가 시간을 내서 이용해 준다는 점이 너무 매력적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로도 사람들에게 재미와 가치를 줄 수 있는 서비스를 계속 만들어나가고 싶다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>1인개발자</category>
      <category>MyTop9Books</category>
      <category>웹개발</category>
      <category>프로젝트회고</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/601</guid>
      <comments>https://divheer.tistory.com/601#entry601comment</comments>
      <pubDate>Fri, 17 Apr 2026 17:48:30 +0900</pubDate>
    </item>
    <item>
      <title>[교정일기] 앱 출시 1년 만에 대규모 업데이트</title>
      <link>https://divheer.tistory.com/600</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;교정일기 앱 소개&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;앱 화면.png&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsp9f4/dJMcaiCrJM0/QwiQl6ug1r8Go54VN97I20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsp9f4/dJMcaiCrJM0/QwiQl6ug1r8Go54VN97I20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsp9f4/dJMcaiCrJM0/QwiQl6ug1r8Go54VN97I20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsp9f4%2FdJMcaiCrJM0%2FQwiQl6ug1r8Go54VN97I20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4320&quot; height=&quot;2142&quot; data-filename=&quot;앱 화면.png&quot; data-origin-width=&quot;4320&quot; data-origin-height=&quot;2142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교정일기는 치아교정 중인 사용자들이 &lt;b&gt;자신의 치아교정 과정을 한 곳에서 통합적으로 관리할 수 있도록 만든 앱&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 제공하는 기능은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;교정 시작일로부터의 D-Day 확인&lt;/li&gt;
&lt;li&gt;타임라인 탭에서 날짜별로 치료 내용 또는 치아 상태를 기록&lt;/li&gt;
&lt;li&gt;앨범 탭에서 치아 사진을 업로드해서 갤러리 형태로 확인&lt;/li&gt;
&lt;li&gt;치아 사진 두 장을 선택해서 콜라주 형태의 비교 이미지 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앱 출시 과정 및 성과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 앱은 내가 실제로 치아교정을 시작하면서 느낀 불편함에서 출발했다. 교정을 하면서 치료 내용은 메모 앱에, 치아 사진은 갤리리앱에서 따로 관리하는 게 불편하게 느껴져서 치아교정을 하나의 앱에서 관리할 수 있다면 좋겠다는 생각으로 개발을 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 만들기 위해 노마드 코더에서 Dart, Flutter 강의를 수강했고, 도서관에서 Flutter 개념서를 빌려서 기본 개념을 공부하기도 했다. 스토어 출시 목적으로 앱을 만드는 것은 처음이었지만, 내가 실제로 사용하고 싶은 앱이었기 때문에 사용자 입장에서 기능을 고민하며 즐겁게 개발했던 기억이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발은 2024년 5월에 시작했고, 생각날 때마다 기능을 조금씩 추가하다 보니 2024년 11월에 개발을 마무리했다. 비공개 테스트를 거친 후에 2024년 12월 2일에 프로덕션으로 정식 출시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출시 후 약 1년 3개월이 지난 현재 다음과 같은 성과를 달성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청나게 많은 사용자 수는 아니지만, 내가 만든 앱을 지속적으로 사용해 주시는 분들이 있다는 사실이 너무 신기하고 감사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;990&quot; data-start=&quot;972&quot; data-section-id=&quot;ojz5x2&quot;&gt;&lt;b&gt;전체 설치 수: 380&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1011&quot; data-start=&quot;991&quot; data-section-id=&quot;8txci1&quot;&gt;&lt;b&gt;회원가입 사용자: 약 200명&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1027&quot; data-start=&quot;1012&quot; data-section-id=&quot;kvmh1e&quot;&gt;&lt;b&gt;DAU: 약 10명&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;v1.1.0 대규모 업데이트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이스토어의 정책 업데이트(16KB 대응 등)를 제외하면 마지막 기능 업데이트는 2025년 6월이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 개선하고 싶은 부분이 정말 많았지만 취업 준비와 다른 프로젝트 작업 때문에 계속 우선순위가 뒤로 밀렸다. 그러다가 바쁜 일이 어느 정도 마무리되면서, 그동안 앱을 이용하면서 가장 불편하다고 생각했던 앨범 탭을 중심으로 대규모 업데이트를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앨범 탭 갤러리 뷰로 개편 및 로드 개선&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 앨범 탭 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qaWPs/dJMcaa5vzMq/KD4w57WRbW7I4vbY5yQkw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qaWPs/dJMcaa5vzMq/KD4w57WRbW7I4vbY5yQkw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qaWPs/dJMcaa5vzMq/KD4w57WRbW7I4vbY5yQkw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqaWPs%2FdJMcaa5vzMq%2FKD4w57WRbW7I4vbY5yQkw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 앨범 탭 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 앨범 탭에서는 월별로 사진을 묶어 보여주고, 각 월 안에서 사진을 가로 스크롤로 확인해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 구조는 원하는 사진에 바로 접근하기 어렵고, 사진 수가 많아질수록 사용성이 떨어지는 문제가 있었다. 그래서 &lt;b&gt;기본 갤러리 앱과 유사한 그리드 레이아웃으로 앨범 탭을 전면 개편&lt;/b&gt;했다. 이제 사용자의 기기 화면 너비에 따라 한 줄에 최소 3장의 사진이 표시되는 갤러리 형태로 사진을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1772783571312&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  final ScrollController _scrollController = ScrollController();

  void _onScroll() {
    if (_scrollController.position.pixels &amp;gt;=
        _scrollController.position.maxScrollExtent - 300) {
      teethPhotoController.fetchMoreTeethPhotoData();
    }
  }

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    teethPhotoController.initAlbumTabData();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 사용자의 사진 데이터가 많아질 경우 로딩 시간이 길어질 수 있다는 점을 고려해서 &lt;b&gt;무한 스크롤 방식의 지연 로딩 구조를 적용&lt;/b&gt;했다. 사진 데이터를 15개 단위로 나누어서 순차적으로 로드하기 때문에, 사용자가 앨범 탭에 진입했을 때 초기 로딩 부담을 줄이고 보다 빠르게 화면을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;치아사진 전체 화면 조회 및 다운로드 기능 추가&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 치아사진 조회 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pdsdN/dJMcaaqUmDE/5BcmnMk5l8zm95muKerBv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pdsdN/dJMcaaqUmDE/5BcmnMk5l8zm95muKerBv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pdsdN/dJMcaaqUmDE/5BcmnMk5l8zm95muKerBv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpdsdN%2FdJMcaaqUmDE%2F5BcmnMk5l8zm95muKerBv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 치아사진 조회 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 특정 사진을 클릭하면 하단에서 Bottom Sheet가 올라오는 방식으로 사진을 확인할 수 있었다. 하지만 실제로 사용하다 보니 영역이 제한되어 있어 답답하게 느껴졌고, 사진을 자세히 확인하기도 어려웠다. 그래서 &lt;b&gt;사진 조회 UI를 Full Screen 형태로 전면 개편&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Honeycam 2026-03-06 16-57-21.gif&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;1216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ1RDD/dJMcacbcgVL/VLiFBdvL883tb9ONHRKkI0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ1RDD/dJMcacbcgVL/VLiFBdvL883tb9ONHRKkI0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ1RDD/dJMcacbcgVL/VLiFBdvL883tb9ONHRKkI0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cJ1RDD/dJMcacbcgVL/VLiFBdvL883tb9ONHRKkI0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;879&quot; data-filename=&quot;Honeycam 2026-03-06 16-57-21.gif&quot; data-origin-width=&quot;609&quot; data-origin-height=&quot;1216&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;전체 화면에서 사진을 확대/축소하거나, 좌우 스와이프로 다음 사진으로 넘어가는 등 인터랙션을 통해 더욱 편리하게 사진을 조회&lt;/b&gt;할 수 있다. 또한 앨범 탭을 개선하는 과정에서 &lt;b&gt;사진을 기기에 다운로드할 수 있는 기능&lt;/b&gt;까지 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;타임라인, 앨범 탭 데이터 정렬 기능 및 로딩 구조 개선&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpwxDl/dJMcabJ9vS9/2l5pkBZ0F7pwNQ7K6cMYt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpwxDl/dJMcabJ9vS9/2l5pkBZ0F7pwNQ7K6cMYt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpwxDl/dJMcabJ9vS9/2l5pkBZ0F7pwNQ7K6cMYt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpwxDl%2FdJMcabJ9vS9%2F2l5pkBZ0F7pwNQ7K6cMYt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;967&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 타임라인과 앨범 데이터가 항상 최신순으로만 정렬되어 제공됐는데, 교정 과정을 처음부터 순서대로 보고 싶은 사용자도 있을 수 있다고 판단해 &lt;b&gt;정렬 옵션 기능&lt;/b&gt;을 추가했다. 앱 바에 정렬 버튼을 누르면 하단에서 Bottom Sheet가 올라와서 최신순 혹은 오래된순 정렬을 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;데이터 로딩 구조까지 최적화&lt;/b&gt;했다. 기존에는 앱 실행 시 타임라인, 치아사진 데이터를 모두 불러와서 이를 홈 탭의 최신 데이터를 보여주는 데에도 사용했다. 그런데 정렬 기능을 추가하면서 홈 탭의 데이터와 각 탭의 데이터 로딩을 분리했기 때문에 앱 실행 시 불필요하게 모든 데이터를 불러올 필요가 없어졌다. 그래서 각 탭의 데이터는 그 탭에 들어갔을 때 불러오도록 Provider 로직을 개선해서 불필요한 데이터 로딩을 줄였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;애드몹 네이티브 광고 배너 추가&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 광고 배너 추가.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b37FvF/dJMcagklFMZ/HFKvsPRh7jScK6OXWwt1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b37FvF/dJMcagklFMZ/HFKvsPRh7jScK6OXWwt1j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b37FvF/dJMcagklFMZ/HFKvsPRh7jScK6OXWwt1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb37FvF%2FdJMcagklFMZ%2FHFKvsPRh7jScK6OXWwt1j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2140&quot; data-filename=&quot;교정일기 광고 배너 추가.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교정일기는 이미지 업로드를 위해 AWS S3, API Gateway, Lambda를 사용하는데, 사용자 데이터도 점점 쌓여가면서 AWS에서 적게나마 비용이 발생하기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 앱의 지속적인 유지를 위해 이번 업데이트에 광고 배너를 추가했다. 광고를 추가하면서 사용자의 앱 이용 경험을 해치지 않는 것에 집중했고, 이를 위해 커스텀이 가능한 네이티브 배너 광고를 이용해서 전체적인 UI에서 이질감을 느끼지 않도록 노력했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 광고를 추가하고 나서도 큰 돈은 되지 않지만, 내가 만든 앱에서 무언가 물질적인 보상이 발생하고 있다는 점에서 이 앱을 계속 유지보수하고 발전해 나가고 싶다는 동기부여가 되어주는 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GA 이벤트 태그 연동 추가&lt;/h2&gt;
&lt;pre id=&quot;code_1772782688865&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AnalyticsEvents {
  /// 로그아웃
  static const String logout = 'logout';

  // ===== Navigation Events =====
  /// 홈 탭 이동
  static const String tabHomeNavigated = 'tab_home_navigated';

  /// 타임라인 탭 이동
  static const String tabTimelineNavigated = 'tab_timeline_navigated';

  /// 앨범 탭 이동
  static const String tabAlbumNavigated = 'tab_album_navigated';

  /// 프로필 탭 이동
  static const String tabProfileNavigated = 'tab_profile_navigated';

  // ===== Timeline Events =====
  /// 타임라인 상세 보기
  static const String timelineDetailViewed = 'timeline_detail_viewed';

  /// 타임라인 목록 새로고침
  static const String timelineListRefreshed = 'timeline_list_refreshed';

  // ===== Teeth Photo Events =====
  /// 사진 선택 성공
  static const String photoSelected = 'photo_selected';

  /// 사진 삭제 성공
  static const String photoDeleteSuccess = 'photo_delete_success';

  /// 사진 목록 새로고침
  static const String photoListRefreshed = 'photo_list_refreshed';

  /// 사진 비교 화면 열기
  static const String photoComparisonOpened = 'photo_comparison_opened';

  /// 비교이미지 생성 버튼 탭 (앨범 탭에서 비교 화면으로 이동)
  static const String photoComparisonCreateTapped =
      'photo_comparison_create_tapped';

  /// 비교 이미지 기기 저장(다운로드) 성공
  static const String photoComparisonImageSaved =
      'photo_comparison_image_saved';

  /// 비교 이미지 기기 저장(다운로드) 실패
  static const String photoComparisonImageSaveFailure =
      'photo_comparison_image_save_failure';

  // ===== Album Events =====
  /// 앨범 화면 조회
  static const String albumScreenViewed = 'album_screen_viewed';

  // ===== Settings Events =====
  /// 다크모드 설정 변경
  static const String darkModeSettingChanged = 'dark_mode_setting_changed';
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 앱을 처음 출시했을 때는 GA를 연동만 하고, 세부적인 이벤트 태그는 추가하지 않았었다. 근데 이번에는 사용자들이 어떤 기능을 많이 사용하는지, 개선할 점은 없는지를 파악하기 위해 다양한 이벤트 태그를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;전체적인 UI 개선&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 로그인 화면 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSjb8q/dJMcaaqUlcM/QhAv3dXkyRZWiSQg8g9tQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSjb8q/dJMcaaqUlcM/QhAv3dXkyRZWiSQg8g9tQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSjb8q/dJMcaaqUlcM/QhAv3dXkyRZWiSQg8g9tQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSjb8q%2FdJMcaaqUlcM%2FQhAv3dXkyRZWiSQg8g9tQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 로그인 화면 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교정일기는 로그인을 해야만 홈 탭에 진입할 수 있는데, 기존 로그인 화면은 너무 흰 배경에 텍스트만 있는 느낌이라 좀 더 완성도 있는 첫인상을 주기 위해 로그인 화면을 개선했다. 로그인 버튼도 화면 하단에 배치하면서 화면이 좀 더 꽉 찬 느낌을 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 홈 화면 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwtfrU/dJMcabcfRvn/zV9dCLOCEbEFybTJmKqr70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwtfrU/dJMcabcfRvn/zV9dCLOCEbEFybTJmKqr70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwtfrU/dJMcabcfRvn/zV9dCLOCEbEFybTJmKqr70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwtfrU%2FdJMcabcfRvn%2FzV9dCLOCEbEFybTJmKqr70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 홈 화면 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 앱에서는 전체적으로 폰트 크기가 너무 컸던 거 같아서 16px을 기준으로 폰트 사이즈를 좀 줄였다. 교정 시작일 D-Day 카드에 그래디언트 배경을 넣어서 홈 화면의 단조로움을 좀 줄이고 싶었다. 그리고 홈 탭의 앨범 섹션에서 기존에는 최대 3장의 이미지를 가로 스크롤로 보여주었는데, 이걸 갤러리 느낌으로 최대 3장 그리드 레이아웃으로 수정해서 최신 사진이 한눈에 들어오도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 설정 탭 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sTrjb/dJMcabXD75h/yIYRZxfr1Bf4ndxxV5qkq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sTrjb/dJMcabXD75h/yIYRZxfr1Bf4ndxxV5qkq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sTrjb/dJMcabXD75h/yIYRZxfr1Bf4ndxxV5qkq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsTrjb%2FdJMcabXD75h%2FyIYRZxfr1Bf4ndxxV5qkq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 설정 탭 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MY 탭에서는 각 버튼에 아이콘을 추가하고, 버튼 항목을 추가했다. 이렇게 모두 정리해서 모아보니 전체적으로 디테일들을 추가해서 앱이 더 완성도 있는 느낌을 주고싶었던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 에러 스크린 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6KaPz/dJMcadOEa2f/pzgY7NkPLcew6oX9KmAhSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6KaPz/dJMcadOEa2f/pzgY7NkPLcew6oX9KmAhSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6KaPz/dJMcadOEa2f/pzgY7NkPLcew6oX9KmAhSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6KaPz%2FdJMcadOEa2f%2FpzgY7NkPLcew6oX9KmAhSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2160&quot; height=&quot;2253&quot; data-filename=&quot;교정일기 에러 스크린 수정전후.png&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;2253&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 에러 스크린은 단순 정보만 제공했다면, 업데이트를 하면서 홈 탭 버튼을 추가해서 사용자에게 자연스러운 다음 행동을 유도했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;교정일기 EmptyState.png&quot; data-origin-width=&quot;3240&quot; data-origin-height=&quot;2140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1zWRS/dJMcaaqU7UJ/Ul2pEq1hnwnY6DDCsrs6a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1zWRS/dJMcaaqU7UJ/Ul2pEq1hnwnY6DDCsrs6a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1zWRS/dJMcaaqU7UJ/Ul2pEq1hnwnY6DDCsrs6a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1zWRS%2FdJMcaaqU7UJ%2FUl2pEq1hnwnY6DDCsrs6a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3240&quot; height=&quot;2140&quot; data-filename=&quot;교정일기 EmptyState.png&quot; data-origin-width=&quot;3240&quot; data-origin-height=&quot;2140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이번에 업데이트하면서 원형 안에 아이콘과 텍스트를 제공하는 `EmptyState`라는 공용 위젯을 만들었는데 데이터가 없거나, 에러가 발생한 경우 등에 일관성 있는 UI를 제공할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추후 업데이트 예정 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 업데이트하고 싶은 부분이 더 많았는데 우선순위가 높은 부분만 선별해서 우선 업데이트를 진행했다. 추후에 또 업데이트를 진행하고 싶은 부분이 있는데, 우선은 영어 지원 기능을 추가해서 사용자층을 더욱 확대해보고 싶다. 또 유료 구독 모델도 붙여보고 싶다. 이번에 업데이트하면서 몇몇 기능은 유료 구독을 위해 빼놓았는데, 올해 안에 이 두 가지 목표를 달성하고 싶다. 둘 다 아직 구현해본 적 없는 기능이기 때문에 많이 배울 수 있는 기회가 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>1인개발자</category>
      <category>교정일기</category>
      <category>앱개발</category>
      <category>프로젝트회고</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/600</guid>
      <comments>https://divheer.tistory.com/600#entry600comment</comments>
      <pubDate>Fri, 20 Mar 2026 23:03:23 +0900</pubDate>
    </item>
    <item>
      <title>슬랙과 Vercel 연동해서 배포 성공/실패 결과 알림받기</title>
      <link>https://divheer.tistory.com/599</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slack-Vercel 연동&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1579&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nQXer/dJMcaivdpdB/ifFsSVJA6kJNkhs1QKKVA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nQXer/dJMcaivdpdB/ifFsSVJA6kJNkhs1QKKVA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nQXer/dJMcaivdpdB/ifFsSVJA6kJNkhs1QKKVA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnQXer%2FdJMcaivdpdB%2FifFsSVJA6kJNkhs1QKKVA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1579&quot; height=&quot;775&quot; data-origin-width=&quot;1579&quot; data-origin-height=&quot;775&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vercel Marketplace에 Slack이 제공된다. 아래 링크에서 [Connect Account] 버튼을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://vercel.com/marketplace/slack&quot;&gt;https://vercel.com/marketplace/slack&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJ9LHU/dJMcachvxI4/iti7Kn5DOAMyD1ewJvDRd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJ9LHU/dJMcachvxI4/iti7Kn5DOAMyD1ewJvDRd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJ9LHU/dJMcachvxI4/iti7Kn5DOAMyD1ewJvDRd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJ9LHU%2FdJMcachvxI4%2Fiti7Kn5DOAMyD1ewJvDRd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;626&quot; height=&quot;716&quot; data-origin-width=&quot;626&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 팀을 선택한 후 모든 프로젝트 혹은 특정 프로젝트와 연동되도록 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ALtTy/dJMcaiot5CM/VJA4fBanHvocBFy3ktLa31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ALtTy/dJMcaiot5CM/VJA4fBanHvocBFy3ktLa31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ALtTy/dJMcaiot5CM/VJA4fBanHvocBFy3ktLa31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FALtTy%2FdJMcaiot5CM%2FVJA4fBanHvocBFy3ktLa31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;749&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정을 연결 후, 슬랙에서 액세스를 허용할 워크스페이스를 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAiry0/dJMb99LQkgi/tPr3CpGkFGpcQKz1aG2JUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAiry0/dJMb99LQkgi/tPr3CpGkFGpcQKz1aG2JUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAiry0/dJMb99LQkgi/tPr3CpGkFGpcQKz1aG2JUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAiry0%2FdJMb99LQkgi%2FtPr3CpGkFGpcQKz1aG2JUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;882&quot; height=&quot;482&quot; data-origin-width=&quot;882&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬랙의 해당 워크스페이스에 Vercel 앱이 추가되었다면, 버튼을 클릭해서 슬랙에서도 다시 한번 Vercel와 연동을 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MqUeF/dJMcaajG8fd/ltqEVct7TpKUXPZ4wolVt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MqUeF/dJMcaajG8fd/ltqEVct7TpKUXPZ4wolVt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MqUeF/dJMcaajG8fd/ltqEVct7TpKUXPZ4wolVt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMqUeF%2FdJMcaajG8fd%2FltqEVct7TpKUXPZ4wolVt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1357&quot; height=&quot;127&quot; data-origin-width=&quot;1357&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬랙과 Vercel 연동이 성공적으로 완료되면 다음과 같은 메시지를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9JuEj/dJMb99ZoSBj/NC0TWs1Z1kEyHzPJbDbPcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9JuEj/dJMb99ZoSBj/NC0TWs1Z1kEyHzPJbDbPcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9JuEj/dJMb99ZoSBj/NC0TWs1Z1kEyHzPJbDbPcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9JuEj%2FdJMb99ZoSBj%2FNC0TWs1Z1kEyHzPJbDbPcK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1178&quot; height=&quot;711&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Vercel &amp;gt; Integrations 탭에서도 Slack이 추가된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vercel 배포 성공 여부 알림 받기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qJtJs/dJMcaaqsy1Y/AMusozxftVv6hoskTk5p5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qJtJs/dJMcaaqsy1Y/AMusozxftVv6hoskTk5p5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qJtJs/dJMcaaqsy1Y/AMusozxftVv6hoskTk5p5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqJtJs%2FdJMcaaqsy1Y%2FAMusozxftVv6hoskTk5p5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;156&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림을 받을 채널을 하나 새로 생성하고, `/invite @vercel`을 입력해서 해당 채널에 Vercel 앱을 멤버로 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r3tb1/dJMcab3Yool/3rRzrvL2cXrSmggT9HkXJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r3tb1/dJMcab3Yool/3rRzrvL2cXrSmggT9HkXJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r3tb1/dJMcab3Yool/3rRzrvL2cXrSmggT9HkXJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr3tb1%2FdJMcab3Yool%2F3rRzrvL2cXrSmggT9HkXJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;732&quot; height=&quot;466&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`/vercel help`를 입력하면 프로젝트를 구독할 수 있는 메시지가 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 [Subscriba in this channel] 버튼을 클릭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIYUb/dJMcac2Qxrg/fuettcRoFNkWDv9NOLivvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIYUb/dJMcac2Qxrg/fuettcRoFNkWDv9NOLivvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIYUb/dJMcac2Qxrg/fuettcRoFNkWDv9NOLivvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIYUb%2FdJMcac2Qxrg%2FfuettcRoFNkWDv9NOLivvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;853&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀과 프로젝트를 선택하고, 만약 배포가 성공 또는 실패되는 경우에만 알림을 받기를 원한다면 `Deployment Ready`와 `Deployment Errored`를 이벤트로 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;91&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coXHvo/dJMcagc9bl7/kF95TgHlkTgvQaoNpD2sjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coXHvo/dJMcagc9bl7/kF95TgHlkTgvQaoNpD2sjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coXHvo/dJMcagc9bl7/kF95TgHlkTgvQaoNpD2sjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoXHvo%2FdJMcagc9bl7%2FkF95TgHlkTgvQaoNpD2sjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;91&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;91&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독이 완료되었는지 확인하고 싶다면 `/vercel subscribe list`를 입력하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cM8ufh/dJMcachvxWc/0XSlnIVKOrES3wg2MKWk9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cM8ufh/dJMcachvxWc/0XSlnIVKOrES3wg2MKWk9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cM8ufh/dJMcachvxWc/0XSlnIVKOrES3wg2MKWk9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcM8ufh%2FdJMcachvxWc%2F0XSlnIVKOrES3wg2MKWk9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;190&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독이 성공적으로 완료되었다면 배포에 성공했을 경우 슬랙에서 다음과 같은 알림을 받을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vercel 구독 명령어&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Configure subscriptions&lt;/b&gt;&lt;br /&gt;You can customize your notifications by subscribing to events that are relevant to your Slack channel.&lt;br /&gt;`/vercel subscribe team/project [events]`&lt;br /&gt;`/vercel unsubscribe team/project [events]`&lt;br /&gt;&lt;br /&gt;You can also subscribe to an entire team: `/vercel subscribe team [events]`&lt;br /&gt;&lt;br /&gt;Default events that get subscribed to if no options are passed in: &lt;br /&gt;`comments`, `deployment_ready`, `deployment_promoted`, `deployment_error`, `projects`&lt;br /&gt;&lt;br /&gt;Available events for both team and project subscriptions:&lt;br /&gt;- `comments`: Comments and replies on deployments &lt;br /&gt;- `deployment_created`: A deployment was created&lt;br /&gt;- `deployment_error`: A deployment failed &lt;br /&gt;- `deployment_ready`: A deployment was successful &lt;br /&gt;- `deployment_promoted`: A production deployment was promoted&lt;br /&gt;- `deployments`: All deployment events (deployment created/errored/ready/promoted) &lt;br /&gt;- `firewall_attack`: An attack was detected &lt;br /&gt;- `firewall`: All Firewall events &lt;br /&gt;&lt;br /&gt;Events only available for team subscriptions:&lt;br /&gt;- `project_created`: A project was created &lt;br /&gt;- `project_removed`: A project was removed &lt;br /&gt;- `projects`: All project events (project created/removed) &lt;br /&gt;&lt;br /&gt;To remove an event that is already subscribed to, you can use the `unsubscribe` command:&lt;br /&gt;`/vercel unsubscribe team/project [events]`&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Filters for comment events&lt;/b&gt;&lt;br /&gt;Comment filters allow filtering comment notifications. By default, when you susbscribe, you will receive notifications for comments left on any branch and page. However, you can choose to filter on a specific branch, page and target.&lt;br /&gt;&lt;br /&gt;To filter on a specific target (preview/production):&lt;br /&gt;`/vercel subscribe team/project comments +target:preview`&lt;br /&gt;&lt;br /&gt;To filter on specific branches:&lt;br /&gt;`/vercel subscribe team/project comments +branch:my-feature +branch:my-other-feature`&lt;br /&gt;&lt;br /&gt;To filter on specific pages you can use exact paths or glob patterns:&lt;br /&gt;`/vercel subscribe team/project comments +page:/docs* +page:/help/docs`&lt;br /&gt;&lt;br /&gt;You can also remove one or more filters by passing the same options to the unsusbscribe command:&lt;br /&gt;`/vercel unsubscribe team/project comments +page:/docs* +branch:my-feature`&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Alert rules&lt;/b&gt;&lt;br /&gt;You can also subscribe to alert rules by passing the `rule` option:&lt;br /&gt;`/vercel subscribe team/project [alert-events] +rule:rule-id`&lt;br /&gt;&lt;br /&gt;To unsubscribe from an alert rule, you can use the `unsubscribe` command:&lt;br /&gt;`/vercel unsubscribe team/project [alert-events] +rule:rule-id`&lt;br /&gt;&lt;br /&gt;&lt;b&gt;List active subscription&lt;/b&gt;&lt;br /&gt;List all active subscriptions in a channel:&lt;br /&gt;`/vercel subscribe list`&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어를 통해서도 구독 세부 사항을 설정할 수 있다.&lt;/p&gt;</description>
      <category>개발 기록장</category>
      <category>Slack</category>
      <category>vercel</category>
      <category>Vercel배포알림</category>
      <category>슬랙연동</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/599</guid>
      <comments>https://divheer.tistory.com/599#entry599comment</comments>
      <pubDate>Fri, 9 Jan 2026 21:45:27 +0900</pubDate>
    </item>
    <item>
      <title>[웹소설 캘린더] MAU 64명 &amp;rarr; 2.5만 명, 사이드 프로젝트 성장기</title>
      <link>https://divheer.tistory.com/598</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(비자발적) 퇴사 후 약 2년 간을 백수로 지내면서, 사실 취업보다는 나만의 서비스 만들기에 몰두했었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjjUyN/dJMb9955RBZ/sG3GKdVmNDG0C1JLkKI6hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjjUyN/dJMb9955RBZ/sG3GKdVmNDG0C1JLkKI6hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjjUyN/dJMb9955RBZ/sG3GKdVmNDG0C1JLkKI6hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjjUyN%2FdJMb9955RBZ%2FsG3GKdVmNDG0C1JLkKI6hk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;307&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에 올해 3월에 출시했던 '웹소설 캘린더'는 9개월간 전체 이용자 수가 655명, 11월 이용자 수는 64명이었을 정도로 이용률이 저조했던 서비스였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F33ieh%2FdJMcacBKrbh%2F96t2Pm1xQ8G4Q7gx8ZQrck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1210&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더욱 많은 사람들에게 이용되기를 바라면서 신작 캘린더 데이터를 지속적으로 업데이트 해왔는데, 12월에 리디 마크다운 라인업 페이지를 추가하고 트위터에 올린 홍보글이 38만의 조회수를 달성하면서 많은 반응을 얻게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBeB6/dJMcaajDm2F/RvhQPINnlICp0KNktfa8y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBeB6/dJMcaajDm2F/RvhQPINnlICp0KNktfa8y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBeB6/dJMcaajDm2F/RvhQPINnlICp0KNktfa8y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBeB6%2FdJMcaajDm2F%2FRvhQPINnlICp0KNktfa8y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;922&quot; height=&quot;425&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 DAU 최대 6.6천 명, 12월 MAU 2.5만 명을 달성하면서, 내가 만든 서비스가 많은 사람들에게 이용이 되고 실시간으로 피드백을 받을 수 있는, 1인 개발자로서는 황금 같은 기회가 주어졌다. 이런 소중한 기회를 놓치고 싶지 않아서 한 달 동안 오로지 '웹소설 캘린더' 개발에만 매달리면서 몇 차례 업데이트를 진행했는데, 그 과정이 기억에서 사라지기 전에 하나하나 기록으로 남기려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;웹소설 캘린더란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 원래 웹소설 읽는 것을 좋아하는데, 플랫폼은 크게 따지지 않고 상황에 따라 혜택이 가장 많은 곳에서 구매한다. 그런데 매달 말 플랫폼마다 각각 링크를 찾아가며 출간 일정을 확인하는 것이 너무 번거롭게 느껴져서, 이걸 '하나의 사이트에서 편하게 조회할 수 있으면 좋겠다'는 생각에 '웹소설 캘린더'를 개발하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.novel-calendar.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.novel-calendar.com/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPYrn/dJMcabixvLx/3CWN1wCkIsvgDEf86EHb01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPYrn/dJMcabixvLx/3CWN1wCkIsvgDEf86EHb01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPYrn/dJMcabixvLx/3CWN1wCkIsvgDEf86EHb01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPYrn%2FdJMcabixvLx%2F3CWN1wCkIsvgDEf86EHb01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1890&quot; height=&quot;861&quot; data-origin-width=&quot;1890&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년에 앱 개발을 하면서 느낀 점이 있었기 때문에 최소한의 기능만 담아서 일단 출시부터 하는 방식으로 진행해서, 3월에 플랫폼별 라인업 조회 기능만 담은 '웹소설 캘린더'를 출시했고, 한 달 후에 검색 기능을 추가했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4월에 검색 기능을 추가한 이후로도 이용자가 거의 없었기 때문에, 서비스를 개선할 동기부여를 받지 못해서 '웹소설 캘린더'는 뒷전으로 미뤄두고 취준에 집중하면서 다른 서비스 개발을 시작했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리디 마크다운 라인업 사이트 개발 계기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7tqJU/dJMcagEae9j/4cgM0EBkpPlwA0SQkYtSS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7tqJU/dJMcagEae9j/4cgM0EBkpPlwA0SQkYtSS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7tqJU/dJMcagEae9j/4cgM0EBkpPlwA0SQkYtSS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7tqJU%2FdJMcagEae9j%2F4cgM0EBkpPlwA0SQkYtSS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;445&quot; height=&quot;1297&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 1일에 리디 마크다운 라인업이 공개되면서 어떤 작품이 올라왔을까 구경하러 리디에 들어갔는데 리스트를 확인하는 게 너무 불편했다. 일단 1,000개가 넘는 라인업을 단순 텍스트 형태로 일렬로 늘어놓았다는 점에서 가독성이 너무 떨어진다고 생각했고, 가장 불편했던 점은 작품을 클릭해서 해당 작품의 상세페이지로 이동할 수 없다는 점이었다. 관심 가는 작품을 발견할 때마다 페이지 최상단의 검색창으로 이동해서 검색 후 다시 이전 페이지로 돌아오거나, 다른 창을 열어서 검색하는 등 불편함이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 사용자 입장에서 직접 느낀 불편함을 해소하기 위해 라인업 데이터를 스크래핑해서 내가 원하는 형태로 가공하면 어떨까? 하는 생각이 들었다. 이미 '웹소설 캘린더'라는 사이트가 존재했기 때문에, 여기에 웹소설이라는 연관성이 있는 '리디 마크다운' 페이지를 추가만 하면 되는 것이어서 큰 부담은 없었다. 그래서 라인업이 공개된 바로 다음날부터 개발을 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1차 업데이트: 맠다 리스트 조회, 검색 기능 추가 (12월 4일)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맠다 라인업이 공개된 다음날인 2일부터 개발을 시작해서 사흘간 개발 후 12월 4일에 '리디 마크다운' 페이지가 추가된 서비스를 배포했다. 처음에는 장르별 단순 라인업 조회, 제목 클릭 시 해당 작품 검색 페이지로 이동, 장르별 제목/작가 검색 등의 핵심 기능들만 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ruq2L/dJMcaf6iSGj/DmE9UxmfcWrl4RbamAZ3xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ruq2L/dJMcaf6iSGj/DmE9UxmfcWrl4RbamAZ3xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ruq2L/dJMcaf6iSGj/DmE9UxmfcWrl4RbamAZ3xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fruq2L%2FdJMcaf6iSGj%2FDmE9UxmfcWrl4RbamAZ3xK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;495&quot; height=&quot;475&quot; data-origin-width=&quot;501&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소설은 장르별 특성이 뚜렷하다고 생각했기 때문에 장르를 기준으로 페이지를 나누고, 제공되는 장르를 한눈에 확인할 수 있는 마크다운 메인 페이지를 만들었다. 리디 맠다 라인업에서는 GL 장르가 로맨스나 로판 장르에 포함되어서 구분 없이 공개되었는데, 장르를 세분화해서 제공하고 싶어서 GL 장르를 따로 추출해서 페이지를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1043&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2eSMg/dJMcaiBUyak/7aco7E80my5f7jW46uwEpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2eSMg/dJMcaiBUyak/7aco7E80my5f7jW46uwEpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2eSMg/dJMcaiBUyak/7aco7E80my5f7jW46uwEpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2eSMg%2FdJMcaiBUyak%2F7aco7E80my5f7jW46uwEpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;480&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1043&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장르별로 페이지를 나누긴 했지만 하나의 페이지 안에서 타장르까지 자유롭게 탐색할 수 있기를 원했다. 그런데 장르의 수가 9개로 한 줄에 담기에는 너무&amp;nbsp; 많았기 때문에 각 장르 이동 버튼을 칩 형태로 구현하고 수평 스크롤을 적용해 편의성을 높였다. 검색 기능도 장르별로 제공되기 때문에 아예 검색창을 해당 장르 라인업 테이블 바로 위에 위치했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhEIjz/dJMcah37ms4/IEmrn8BOAFqfpGFnY3nwSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhEIjz/dJMcah37ms4/IEmrn8BOAFqfpGFnY3nwSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhEIjz/dJMcah37ms4/IEmrn8BOAFqfpGFnY3nwSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhEIjz%2FdJMcah37ms4%2FIEmrn8BOAFqfpGFnY3nwSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;307&quot; height=&quot;71&quot; data-origin-width=&quot;307&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 1차 라인업에서만 장르별 데이터가 1,000건을 넘었기 때문에 작품 탐색의 편의성과 데이터를 불러올 때 효율성을 생각해 페이지네이션을 적용했고, 우측 하단에 TOP 버튼을 추가해서 스크롤을 내리면서 리스트를 확인하다가도 바로 페이지 최상단으로 이동할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdX1on/dJMcagxmPul/W8ICPAUt1Oej6MxzR9Ujak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdX1on/dJMcagxmPul/W8ICPAUt1Oej6MxzR9Ujak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdX1on/dJMcagxmPul/W8ICPAUt1Oej6MxzR9Ujak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdX1on%2FdJMcagxmPul%2FW8ICPAUt1Oej6MxzR9Ujak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;478&quot; height=&quot;300&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 정말 가볍고 즐거운 마음으로 시작했는데, 개발을 할수록 추가하고 싶은 기능이 점점 많아지고 욕심이 생겼다. 내가 실제로 리디의 사용자 입장에 있었기 때문에 다양한 아이디어가 떠오를 수 있었던 거 같다. 그래서 나만의 목표를 세웠다. 라인업이 새로 발표되는 날마다 기능을 추가해서 배포하기로! 이렇게 해서 나는 12월 한 달을 리디의 맠다 라인업 공개 일정에 따라 보내게 되었다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2차 업데이트: 관심 작품 찜하기 기능 추가 (12월 7일)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라인업 페이지와 검색 기능을 추가하면서 맠다 라인업을 더 편하게 확인할 수 있게 되었다. 근데 마음에 드는 작품이 생길 때마다 메모장에 작품을 따로 작성해야 된다는 점이 불편했다. 흠... 그냥 리스트를 구경하면서 마음에 드는 작품이 생기면 바로 찜을 해두고, 그 찜한 작품 리스트를 모아서 조회할 수 있다면 편하지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/utI81/dJMcafL0fQ9/KmwCtow3WLtMyIY0e8gRw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/utI81/dJMcafL0fQ9/KmwCtow3WLtMyIY0e8gRw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/utI81/dJMcafL0fQ9/KmwCtow3WLtMyIY0e8gRw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FutI81%2FdJMcafL0fQ9%2FKmwCtow3WLtMyIY0e8gRw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;496&quot; height=&quot;1119&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 작품 찜하기 기능을 추가하기로 했다! 기존의 '번호' 열을 삭제하고 찜 하기가 가능한 별 아이콘으로 대체했고, '찜한 작품' 페이지를 생성해서 내가 찜한 작품 리스트를 장르별로 확인할 수 있도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'웹소설 캘린더'에는 아직 로그인 기능이 없었고, 빠르고 간단하게 완성하고 싶었기 때문에 찜한 작품이 로컬 스토리지에만 저장되도록 했는데, 이용자들이 급속도로 늘어나면서 이 부분이 가장 아쉽긴 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 찜한 작품 리스트를 로컬에만 저장하면 다음과 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 찜한 작품 리스트가 안전하게 보존되지 않는다. (기기 간에 연동이 되지 않고, 리스트가 언제든 삭제될 위험성이 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사용자들이 작품을 많이 찜한 순서대로 '추천 작품'이 제공되는데, 이 순위를 조작할 가능성이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 또 마크다운 라인업을 제공하게 된다면 로그인 기능을 붙여서 찜한 작품 리스트를 더욱 안정적으로 관리하고 싶은 목표가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3차 업데이트: 추천 작품 탭 추가 (12월 11일)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 8일 자정에 찜하기 기능을 추가해서 배포하고 나서 문득 다른 사람들은 어떤 작품들을 찜하고 있을지 궁금해졌다. 다른 사람들도 비슷한 궁금증을 가지고 있을 거 같아서, 사람들이 많이 찜한 작품을 리스트업 해서 보여주는 페이지를 추가하면 어떨까 하는 생각이 들었다. 그래서 아침에 일어나자마자 사용자들이 작품을 찜할 때마다 DB에 작품별 카운트를 저장하는 기능을 급하게 구현해서 오후 5시에 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찜한 작품 데이터가 DB에 쌓이고 있는 동안 클라이언트에서는 추천 작품 페이지를 추가하는 작업에 들어 갔다. 일단 페이지를 어떻게 구성할지 고민했는데, 처음엔 메인 페이지에 '추천 작품' 버튼을 추가해서 라인업 페이지와 추천 작품 페이지를 별도로 구성하려고 했다. 근데 개발을 하다 보니 전체 라인업과 추천 작품 페이지를 바로 이동 가능한 방식이 더 접근성이 좋을 거 같아서 이 두 페이지를 탭 안에서 편하게 이동할 수 있도록 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sslzi/dJMcajt2SBt/LKk2iAjvEEGnfTKQ4zioWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sslzi/dJMcajt2SBt/LKk2iAjvEEGnfTKQ4zioWk/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1043&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.7949%; margin-right: 10px;&quot; data-widthpercent=&quot;49.37&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sslzi/dJMcajt2SBt/LKk2iAjvEEGnfTKQ4zioWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsslzi%2FdJMcajt2SBt%2FLKk2iAjvEEGnfTKQ4zioWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1043&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tqEdt/dJMcaiopNxZ/gL0zvXGSHxHKhs4RtgoII0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tqEdt/dJMcaiopNxZ/gL0zvXGSHxHKhs4RtgoII0/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1017&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 50.0423%;&quot; data-widthpercent=&quot;50.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tqEdt/dJMcaiopNxZ/gL0zvXGSHxHKhs4RtgoII0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtqEdt%2FdJMcaiopNxZ%2FgL0zvXGSHxHKhs4RtgoII0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1017&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;수정 전 / 수정 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라인업/추천작품 탭이 추가되면서 테이블 상단에 콘텐츠 요소가 너무 많아지는 거 같아서 텍스트 형태의 제목을 삭제하고 장르별 칩 버튼 크기를 키워서 현재 선택한 장르 정보를 더욱 강조하고, 총 데이터 건수는 탭 안으로 옮겼다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IDuDj/dJMcaacRaaK/QWLmZXgUtvagGbXmz5vAk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IDuDj/dJMcaacRaaK/QWLmZXgUtvagGbXmz5vAk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IDuDj/dJMcaacRaaK/QWLmZXgUtvagGbXmz5vAk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIDuDj%2FdJMcaacRaaK%2FQWLmZXgUtvagGbXmz5vAk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;393&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라인업이 공개될 때마다 해당 차수의 리스트만 따로 조회하고 싶은 사용자들을 위해 차수별 라인업 조회 기능까지 추가했는데, 총 5차의 라인업 중에 4차 라인업이 공개될 때에야 추가해서 아쉬웠다. 개발 초반부터 이 기능을 염두에 두고 데이터를 추출하긴 했었는데, 다른 기능들에 우선순위를 두다 보니 해당 기능 구현이 늦어지게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/33ieh/dJMcacBKrbh/96t2Pm1xQ8G4Q7gx8ZQrck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F33ieh%2FdJMcacBKrbh%2F96t2Pm1xQ8G4Q7gx8ZQrck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1210&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12일 자정에 추천 작품 탭 추가 + 4차 라인업까지 반영 후 트위터에 홍보글을 올렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cusBXa/dJMcagqBqGQ/hXNWLNPGmFHPpBf91zSa9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cusBXa/dJMcagqBqGQ/hXNWLNPGmFHPpBf91zSa9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cusBXa/dJMcagqBqGQ/hXNWLNPGmFHPpBf91zSa9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcusBXa%2FdJMcagqBqGQ%2FhXNWLNPGmFHPpBf91zSa9k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1428&quot; height=&quot;340&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12일에서 13일로 넘어가는 자정쯤부터 갑자기 사용자들이 엄청나게 늘어나는 동시에 트윗의 조회수, 알티수도 상승하기 시작했다. (전체 기간 동안 13일에 가장 많은 이용자 수를 기록했다.) 이전 업데이트 때에도 트위터에 계속 홍보글을 올렸었는데 이번에 유독 반응이 좋았던 것을 보면, 다들 '추천 작품' 기능을 좋아해 주셨던 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 당시에 내가 만든 서비스가 이렇게 많은 사람들에게 이용되어서 진심으로 기쁜 한편으로는, 한번도 겪어본 적 없는 상황에 서버 비용이 얼마나 나올지 몰라서 두려움에 떨고 있었다.   DB는 놀랍게도 잘 버티고 있는 상황이었고, 서비스를 배포했던 Vercel은 결국 14일에 20달러를 내고 프로 플랜으로 업그레이드를 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 최적화 및 유지보수&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5차 라인업 공개 때는 서버 비용을 줄이기 위해 사이트 최적화를 하고, 자잘하게 유지보수도 하다 보니 새로운 기능을 추가하지 못했다. 그리고 이때 실제 라인업 공개 시 기존 데이터와 연동할 계획까지 세우고 있었기 때문에 잠시 쉬어가면서 준비하는 기간으로 삼았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2193&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byUtDq/dJMcafZw43e/8RImQmTZkKLJGuydKMtKc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byUtDq/dJMcafZw43e/8RImQmTZkKLJGuydKMtKc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byUtDq/dJMcafZw43e/8RImQmTZkKLJGuydKMtKc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyUtDq%2FdJMcafZw43e%2F8RImQmTZkKLJGuydKMtKc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2193&quot; height=&quot;676&quot; data-origin-width=&quot;2193&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SNS에 올려주신 사이트 인증샷을 보다가 다크모드는 전혀 고려하지 않았다는 것을 깨달았다. 그래서 검색창 배경색이나 찜한 작품 페이지에 칩 버튼 배경색 등 디테일한 부분들을 빠르게 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;431&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIRZ7T/dJMcacBKrKT/PIHQH8aHFUxe8bFDfR5NVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIRZ7T/dJMcacBKrKT/PIHQH8aHFUxe8bFDfR5NVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIRZ7T/dJMcacBKrKT/PIHQH8aHFUxe8bFDfR5NVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIRZ7T%2FdJMcacBKrKT%2FPIHQH8aHFUxe8bFDfR5NVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1054&quot; height=&quot;431&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;431&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m6bhM/dJMcafSMgKe/HnMPzwDAExEFZBHU8AVkd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m6bhM/dJMcafSMgKe/HnMPzwDAExEFZBHU8AVkd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m6bhM/dJMcafSMgKe/HnMPzwDAExEFZBHU8AVkd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm6bhM%2FdJMcafSMgKe%2FHnMPzwDAExEFZBHU8AVkd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1386&quot; height=&quot;354&quot; data-origin-width=&quot;1386&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메타데이터를 추가해서 구글 검색 시에 더 잘 노출되도록 했다. SEO에 신경 쓰고 이용자 수도 많이 늘어난 덕분에 구글에서 '리디 마크다운'을 검색했을 때 8번째에 위치하는 결과를 얻기도 했고, 구글 검색 유입도 많이 상승했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vhv8r/dJMcai2YywH/LvqJpJgzx3qqpypauJk2x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vhv8r/dJMcai2YywH/LvqJpJgzx3qqpypauJk2x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vhv8r/dJMcai2YywH/LvqJpJgzx3qqpypauJk2x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvhv8r%2FdJMcai2YywH%2FLvqJpJgzx3qqpypauJk2x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;343&quot; height=&quot;346&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;399&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 서버 비용을 줄이기 위해 전체 라인업과 추천 작품 데이터에 캐싱(SSG)을 적용했다. 전체 라인업은 데이터가 추가/수정되지 않는 이상 동일하기 때문에 캐싱된 데이터를 계속해서 사용했고, 추천 작품 데이터는 사용자들이 찜한 작품을 추가하면서 순위가 계속 변동되기 때문에 10분에 한 번씩 캐싱된 데이터를 갱신해 새로운 데이터를 제공하도록 했다. 14일 오후 8시 35분에 캐싱을 적용한 웹페이지를 배포했는데 35분을 기점으로 DB 요청량이 감소한 것을 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czKvy8/dJMcahXniii/eC7QKzupb3Udrufn1Q9eaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czKvy8/dJMcahXniii/eC7QKzupb3Udrufn1Q9eaK/img.png&quot; data-alt=&quot;처음에 경악했던 모바일 광고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czKvy8/dJMcahXniii/eC7QKzupb3Udrufn1Q9eaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczKvy8%2FdJMcahXniii%2FeC7QKzupb3Udrufn1Q9eaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;498&quot; height=&quot;2140&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;처음에 경악했던 모바일 광고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 서버 비용으로 인한 손실을 보전하기 위해서 17일에 급하게 애드센스 광고 배너를 추가했다. 애드센스를 승인받는데 하루가 걸렸고, 웹 광고는 처음 넣어보는 거라 모바일에서 자꾸 화면의 반을 가릴 정도로 크고 못생긴 광고가 나와서 17일에는 일단 데스크탑 사이트에만 광고 배너를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmrbaq/dJMcab3T8e3/gDErhkeM9dkmuYBpZg6uDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmrbaq/dJMcab3T8e3/gDErhkeM9dkmuYBpZg6uDk/img.png&quot; data-alt=&quot;최종 정착한 모바일 배너 사이즈&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmrbaq/dJMcab3T8e3/gDErhkeM9dkmuYBpZg6uDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmrbaq%2FdJMcab3T8e3%2FgDErhkeM9dkmuYBpZg6uDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;725&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 정착한 모바일 배너 사이즈&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 환경에서는 실제 광고 배너를 확인할 수 없기 때문에, 실제 서비스에서 테스트를 하기 위해 사용자 수가 가장 적은 새벽 4시에 일어나서 테스트를 해보면서 모바일 광고 크기를 조절했었다. 모바일 이용자가 대다수이기 때문에 모바일 광고에 다양한 실험을 했었다. 광고 배너를 조금 키워보기도 하고, 페이지 하단에도 추가해보기도 했는데 사이트가 너무 지저분해지는 거 같아서 결국 상단에 제일 작은 배너 광고 하나만 남겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫날 광고 수익이 61원이 나와서 이게 맞나? 싶었다. 이땐 어차피 이렇게 돈도 안될 거 그냥 다시 빼버릴까 고민했었다. 다행히 시간이 지나면서 처음보다 광고 단가가 올라가긴 했지만, 돈은 거의 되지 않는다. (심지어 애드센스는 100달러 이상부터 출금이 가능하다 ^^)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 마크다운 라인업 연동 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 라인업이 공개되기 전에 미리 여러 작업을 해두었다. 제일 고민이 많았던 부분은 할인율, 가격, 리뷰 등 추가 정보를 어떻게 보여줄까 하는 것이었다. 웹소설 캘린더는 모바일 사용자가 85%일 정도로 모바일 위주의 사이트이기 때문에, 모바일에서도 모든 정보를 한눈에 확인할 수 있도록 하고 싶었다. 그래서 작품당 데이터를 두 줄로 구성해서 보여주는 방법을 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1481&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baqAeI/dJMcabCPvM8/0f3IKvdw1m07Y5ss8xgqd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baqAeI/dJMcabCPvM8/0f3IKvdw1m07Y5ss8xgqd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baqAeI/dJMcabCPvM8/0f3IKvdw1m07Y5ss8xgqd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaqAeI%2FdJMcabCPvM8%2F0f3IKvdw1m07Y5ss8xgqd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1481&quot; height=&quot;199&quot; data-origin-width=&quot;1481&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VtFQA/dJMcaaKHgFv/y9cRul3qAWzMQkp4pA0is0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VtFQA/dJMcaaKHgFv/y9cRul3qAWzMQkp4pA0is0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VtFQA/dJMcaaKHgFv/y9cRul3qAWzMQkp4pA0is0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVtFQA%2FdJMcaaKHgFv%2Fy9cRul3qAWzMQkp4pA0is0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;491&quot; height=&quot;858&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 시행착오를 거치면서 데스크탑과 모바일에서 다음과 같은 테이블 UI가 구성되었다. 데스크탑은 한 줄로, 모바일은 두 줄로 보여주도록 조건부 렌더링을 적용했다. 모바일에서는 제목/작가 행에 약간의 배경색을 넣어서 작품이 더 쉽게 구분되도록 했고, 두 번째 줄은 폰트 크기를 좀 줄였다. 걱정이 많았던 부분인데 개인적으로는 만족스러운 결과물이었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;185&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b8wV5h/dJMcaiu9qrt/GUBKIr9bZKKKZHnANENuq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b8wV5h/dJMcaiu9qrt/GUBKIr9bZKKKZHnANENuq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b8wV5h/dJMcaiu9qrt/GUBKIr9bZKKKZHnANENuq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb8wV5h%2FdJMcaiu9qrt%2FGUBKIr9bZKKKZHnANENuq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;185&quot; height=&quot;307&quot; data-origin-width=&quot;185&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 라인업 정렬 기준은 6개를 준비했다. 트위터에서 어떤 분이 '작가순' 정렬을 요청해주셨는데, 작가별로 작품을 모아볼 수 있으면 좋을 거 같아서 '작가순' 정렬을 추가했고, 나머지 옵션도 내가 독자 입장에서 어떤 기준으로 작품들을 정렬해서 보고 싶은지를 고민해서 추가했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osNBR/dJMcagjQbQa/kodvpejfJvmzO2kZRmgaB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osNBR/dJMcagjQbQa/kodvpejfJvmzO2kZRmgaB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osNBR/dJMcagjQbQa/kodvpejfJvmzO2kZRmgaB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FosNBR%2FdJMcagjQbQa%2FkodvpejfJvmzO2kZRmgaB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;64&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 6가지 정렬 옵션의 반대 기준(예를 들면 가격 높은순)으로 작품을 탐색하고 싶은 분들이 있을까 봐 페이지네이션에 맨앞, 맨뒤로 버튼을 추가했다. 데이터가 장르별로 최고 7,000개까지 늘어났기 때문에라도 필요한 기능이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oxH9M/dJMcacaF0pC/8VssTSQfyxk35MqQVFgOWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oxH9M/dJMcacaF0pC/8VssTSQfyxk35MqQVFgOWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oxH9M/dJMcacaF0pC/8VssTSQfyxk35MqQVFgOWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoxH9M%2FdJMcacaF0pC%2F8VssTSQfyxk35MqQVFgOWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;496&quot; height=&quot;498&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 기존에는 '찜한 작품' 페이지에 들어가려면 메인 페이지에서 들어가야 했는데, 어느 페이지에 있든 '찜한 작품' 페이지로 이동할 수 있으면 접근성이 더 좋아질 거 같아서 헤더 우측에 '찜한 작품' 페이지 이동 버튼을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;4차 업데이트: 실제 라인업 할인율, 가격 연동 (12월 19일)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 데이터만 들어오면 배포가 가능하도록 며칠 동안 미리 준비를 해놓고, 긴장되는 마음으로 실제 라인업이 공개되는 19일 자정을 기다렸다. 이때 고생했던 포인트가 3가지 있었는데, 첫 번째는 작년 맠다 페이지를 참고해서 미리 만들어놓은 스크래핑 로직을 올해 페이지에 맞게 수정해야 하는 것이었다. BL만화 장르가 BL만화, BL웹툰 두 개의 장르로 나뉘기도 했고, 만화 장르에서는 할인율이 제공되지 않는 등 예상하지 못했던 부분이 많았다. (특히 리뷰 수 가져오는데 엄청 고생했다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OTOLm/dJMcaaDVCx4/nLEvLbSvQcXWZKRly45NR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OTOLm/dJMcaaDVCx4/nLEvLbSvQcXWZKRly45NR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OTOLm/dJMcaaDVCx4/nLEvLbSvQcXWZKRly45NR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOTOLm%2FdJMcaaDVCx4%2FnLEvLbSvQcXWZKRly45NR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1209&quot; height=&quot;236&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 두 번째 문제는 자정이 되자마자 Cursor(AI 도구)의 유료 토큰을 모두 소진했다는 것이었다. 12월 13일에 프로 플랜이 정기결제 됐는데 1주일 만에 제공되는 토큰을 다 써버린 것이다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그래서 ChatGPT에 코드 복붙해가면서 구현하느라 시간이 생각보다 더 오래 걸렸다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째로 힘들었던 부분은 임시 라인업과 실제 라인업에서 동일한 작품을 매칭하는 것이었다. 실제 마크다운 라인업 데이터를 가져오는 것 자체는 어렵지 않은데, 미리 공개되었던 임시 라인업과 실제 라인업과의 작품이 연동되어야 내가 찜한 작품과 추천 작품 순위가 유지되기 때문에 나는 이 두 데이터를 연동하고 싶었다. (만약 연동하지 않았다면 추천 작품과 찜한 작품이 실제 라인업 공개 전후 두 개의 데이터로 분리되었을 것이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCgBvI/dJMcacIwZTZ/qKqTpcxfW8wEKFOAkykdR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCgBvI/dJMcacIwZTZ/qKqTpcxfW8wEKFOAkykdR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCgBvI/dJMcacIwZTZ/qKqTpcxfW8wEKFOAkykdR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCgBvI%2FdJMcacIwZTZ%2FqKqTpcxfW8wEKFOAkykdR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4000&quot; height=&quot;2569&quot; data-origin-width=&quot;4000&quot; data-origin-height=&quot;2569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임시 라인업에서는 작품별 고유번호가 제공되지 않았기 때문에 제목과 작가, 장르 텍스트가 일치하는 경우에 동일한 작품으로 판단하도록 했다. 근데 실제 라인업 공개 후 두 데이터를 비교해보니 매칭에 실패하는 작품들이 너무 많았다. 예를 들어 만화의 경우 작가가 여러 명인 경우가 많은데 임시 라인업에서는 &quot;A작가&quot;만 표기하고, 실제 라인업에서는 &quot;A작가, B작가&quot;로 표기되는 경우 두 작품이 일치되지 않는 것으로 판단된다. 또 BL만화가 BL만화와 BL웹툰으로 세분화되면서 동일한 작품을 각각 만화와 웹툰으로 나눠서 할당해주어야 했다. 그래서 이런 예외 경우의 데이터들을 직접 판별하느라 시간이 엄청 오래 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2951&quot; data-origin-height=&quot;3934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjEaNV/dJMcajnhCDi/zr2GocUYGS03kYEOyzCMN1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjEaNV/dJMcajnhCDi/zr2GocUYGS03kYEOyzCMN1/img.jpg&quot; data-alt=&quot;포스트잇 지옥...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjEaNV/dJMcajnhCDi/zr2GocUYGS03kYEOyzCMN1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjEaNV%2FdJMcajnhCDi%2Fzr2GocUYGS03kYEOyzCMN1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2951&quot; height=&quot;3934&quot; data-origin-width=&quot;2951&quot; data-origin-height=&quot;3934&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;포스트잇 지옥...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이날 자정부터 밤을 새고 17시간을 작업했기 때문에 라인업 공개 당일 오후 5시에 할인율 등 실제 정보를 추가한 사이트를 빠르게 배포할 수 있었다. 그야말로 내 체력을 갈아 넣어서 만든... 이렇게 오래 걸릴 줄 모르고 매칭 작업을 시작한 거였기 때문에 다음에 또 이렇게 할 수 있을지는 잘 모르겠다. 그래도 다 만들고 나서 누워서 맠다 라인업 확인하는데 솔직히 작품 탐색하기 너무 편해서 뿌듯하긴 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5차 업데이트: 찜한 작품 금액 계산 기능 추가 (12월 20일)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJqXUf/dJMcaiIGZRX/SJLrlAvDn0GhXUsE0nT7KK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJqXUf/dJMcaiIGZRX/SJLrlAvDn0GhXUsE0nT7KK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJqXUf/dJMcaiIGZRX/SJLrlAvDn0GhXUsE0nT7KK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bJqXUf/dJMcaiIGZRX/SJLrlAvDn0GhXUsE0nT7KK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;300&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 실제 라인업으로 업데이트하면서 '찜한 작품' 페이지에 상세 정보 온오프 기능을 추가했었다. 테이블에서 제목과 작가만 간단하게 표시함으로써 SNS에 구매 리스트를 공유하는 효과를 기대했는데, GA 결과상으로 이용이 거의 되지 않았고 실제 라인업 공개 후에는 리디 사이트를 통해 구매 리스트를 공유하는 사용자들이 더 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 좀 더 실용적인 기능으로 수정하자는 생각에 19일 업데이트 바로 다음 날인 20일에 '구매 금액 계산' 기능을 추가했다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'구매 금액 계산'은&lt;/span&gt; 리디에서 매일 nn만원 이상 구매 시 포인트 증정 이벤트를 진행하기도 했고, 구매 예산 내에서만 작품을 고르고 싶은 사용자들이 있을 거 같아서 내가 찜한 작품 리스트 중에 원하는 작품만 체크해서 총 구매 금액이 얼마인지를 계산할 수 있는 기능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl1TrA/dJMcajnhpAY/OmFudFVaiQjZo5jcFzk7dK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl1TrA/dJMcajnhpAY/OmFudFVaiQjZo5jcFzk7dK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl1TrA/dJMcajnhpAY/OmFudFVaiQjZo5jcFzk7dK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bl1TrA/dJMcajnhpAY/OmFudFVaiQjZo5jcFzk7dK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;440&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'구매 금액 계산' 체크박스를 선택하면 찜하기 아이콘이 체크박스 아이콘으로 변하고, 원하는 작품만 선택할 수 있게 된다. 특히 웹툰의 경우에는 보통 여러 회차를 구매하기 때문에 구매 회차에 따라 금액을 계산하는 기능도 넣었다. 이 기능은 GA에서 지속적으로 이용되고 있는 것을 확인할 수 있어서 마지막으로 추가하길 잘했다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 최종적으로 추가하고 싶은 기능이 하나 더 있었는데 너무 힘들어서 그 기능은 추가하지 않았다. 언젠가 기회가 된다면 추가해보고 싶긴 하다. 이로써 리디 마크다운 페이지 개발은 마무리되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6차 업데이트: 웹소설 신작 캘린더 페이지 개선 (12월 26일)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사람의 욕심은 끝이 없으니.. 올해 초에 개발해놓고 손 놓고 있었던 웹소설 신작 캘린더 페이지가 신경 쓰이기 시작했다. 이번에 사이트 홍보가 많이 되었으니 내년 1월 신작 캘린더 업데이트를 앞두고 지속적인 사용자들을 확보하고 싶어서 기회가 주어진 김에 신작 캘린더 부분을 개선하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;793&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlEea4/dJMcadtP2Bt/fgI8y3Fe7higcih3cxPssk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlEea4/dJMcadtP2Bt/fgI8y3Fe7higcih3cxPssk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlEea4/dJMcadtP2Bt/fgI8y3Fe7higcih3cxPssk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlEea4%2FdJMcadtP2Bt%2FfgI8y3Fe7higcih3cxPssk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1893&quot; height=&quot;793&quot; data-origin-width=&quot;1893&quot; data-origin-height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 캘린더의 문제점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 플랫폼, 장르 타입 필터 옵션을 하나하나 선택 후 '적용' 버튼까지 클릭해야 데이터가 업데이트된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 새로고침하면 내가 선택한 옵션 정보가 유지되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 어딘가 지저분한 듯한 디자인 (제목 앞에 저 파란 점은 왜 넣었을까..?)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bia0R9/dJMcagKVhjL/YpVUKWT7VS3dsLc7tz3h3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bia0R9/dJMcagKVhjL/YpVUKWT7VS3dsLc7tz3h3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bia0R9/dJMcagKVhjL/YpVUKWT7VS3dsLc7tz3h3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbia0R9%2FdJMcagKVhjL%2FYpVUKWT7VS3dsLc7tz3h3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;501&quot; height=&quot;129&quot; data-origin-width=&quot;629&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 어떤 플랫폼과 장르가 있는지 한눈에 확인할 수 있고, 원하는 옵션을 바로 선택할 수 있도록 플랫폼은 탭 형태로, 장르는 칩 형태로 수정했다. 사실 개인적으로 완벽하게 마음에 들지는 않는데 현재로서는 최선의 방법을 선택했다고 생각한다. 그리고 이제 플랫폼이든 장르든 클릭하기만 하면 즉시 선택값에 따른 새로운 데이터가 반영된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;https://www.novel-calendar.com/ridi/romance/2025-12&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 플랫폼, 장르, 현재 달을 url에 반영해서 새로고침을 해도 선택 옵션이 유지되도록 하고, SEO도 개선하기 위해 노력했다. 이렇게 하면 내가 원하는 플랫폼과 장르 캘린더를 북마크에 추가해서 더욱 편하게 접근할 수도 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nYOeV/dJMcac2MuxT/9I19z6miJN04FW5Qcgvkk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nYOeV/dJMcac2MuxT/9I19z6miJN04FW5Qcgvkk0/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1478&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.3851%; margin-right: 10px;&quot; data-widthpercent=&quot;49.97&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nYOeV/dJMcac2MuxT/9I19z6miJN04FW5Qcgvkk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnYOeV%2FdJMcac2MuxT%2F9I19z6miJN04FW5Qcgvkk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1478&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O1xsD/dJMcadABFcv/BKlTlNURHmWNFV4sMvJLl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O1xsD/dJMcadABFcv/BKlTlNURHmWNFV4sMvJLl1/img.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1476&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4521%;&quot; data-widthpercent=&quot;50.03&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O1xsD/dJMcadABFcv/BKlTlNURHmWNFV4sMvJLl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO1xsD%2FdJMcadABFcv%2FBKlTlNURHmWNFV4sMvJLl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;수정 전/후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트/아이콘 크기 축소, 테이블 레이아웃 변경, 요일 위치 변경, 제목순 정렬 등도 추가하면서 더욱 깔끔한 UI를 제공하기 위해 노력했다. 또한 캘린더 검색 기능도 개선했는데, 띄어쓰기나 영어 대소문자에 상관없이 더욱 정확한 작품 검색이 가능해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;간단 통계&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 작품을 통틀어 총 '61,896회' 북마크되었다.&lt;/li&gt;
&lt;li&gt;가장 많이 북마크 된 작품은 '497회'를 달성했다.&lt;/li&gt;
&lt;li&gt;가장 많이 조회된 장르는 'BL소설'이다.&lt;/li&gt;
&lt;li&gt;가장 많이 사용된 정렬 옵션은 '리뷰 많은 순'이다.&lt;/li&gt;
&lt;li&gt;12월 한 달 동안 '65개국'에서 서비스가 이용되었다.&lt;/li&gt;
&lt;li&gt;iOS와 Android 이용률은 약 '6:4 비율'을 기록했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리하며&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cODBgq/dJMcaiBUSdE/iOzWptEXjXjYFmVfnZxa4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cODBgq/dJMcaiBUSdE/iOzWptEXjXjYFmVfnZxa4k/img.png&quot; data-alt=&quot;비공개 테스트 중&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cODBgq/dJMcaiBUSdE/iOzWptEXjXjYFmVfnZxa4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcODBgq%2FdJMcaiBUSdE%2FiOzWptEXjXjYFmVfnZxa4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;395&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비공개 테스트 중&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;취준 기간이 길어지면서 점점 동기부여를 잃고 무기력해지던 시기였는데 마침 이렇게 좋은 기회가 주어져서 오랜만에 개발에 대한 열정을 불태우게 되었다. 일단 웹소설 캘린더 개발은 마무리하고 현재는 '비포알리미'라는 앱 출시를 위해 노력하고 있다. 만약 앱 심사에 차질이 없다면 내년 1월에 출시하는 것을 목표로 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 당분간 취준을 뒷전으로 미뤄 두고 서비스 개발만 하고 싶어 지는데... 취준과 개발 사이에 균형을 찾을 수 있도록 노력해야겠다. 연말에 찾아온 선물 같았던 웹소설 캘린더 개발 과정글 끝! 기술적 내용을 담은 후기도 언젠가 작성할 것... &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트</category>
      <category>개발</category>
      <category>사이드프로젝트</category>
      <category>웹소설캘린더</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/598</guid>
      <comments>https://divheer.tistory.com/598#entry598comment</comments>
      <pubDate>Tue, 30 Dec 2025 20:17:19 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 전력망을 둘로 나누기 | 파이썬</title>
      <link>https://divheer.tistory.com/597</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/86971&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/86971&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;n개의 송전탑이 전선을 통해 하나의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #0078ff;&quot; href=&quot;https://en.wikipedia.org/wiki/Tree_(data_structure)&quot;&gt;트리&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형태로 연결되어 있습니다. 당신은 이 전선들 중 하나를 끊어서 현재의 전력망 네트워크를 2개로 분할하려고 합니다. 이때, 두 전력망이 갖게 되는 송전탑의 개수를 최대한 비슷하게 맞추고자 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;송전탑의 개수 n, 그리고 전선 정보 wires가 매개변수로 주어집니다. 전선들 중 하나를 끊어서 송전탑 개수가 가능한 비슷하도록 두 전력망으로 나누었을 때, 두 전력망이 가지고 있는 송전탑 개수의 차이(절대값)를 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;n은 2 이상 100 이하인 자연수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;wires는 길이가&lt;span&gt;&amp;nbsp;&lt;/span&gt;n-1인 정수형 2차원 배열입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;wires의 각 원소는 [v1, v2] 2개의 자연수로 이루어져 있으며, 이는 전력망의 v1번 송전탑과 v2번 송전탑이 전선으로 연결되어 있다는 것을 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; v1 &amp;lt; v2 &amp;le; n 입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;전력망 네트워크가 하나의 트리 형태가 아닌 경우는 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9RqwN/dJMcahJEVQr/GWn641aNaapdZHmi98ACz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9RqwN/dJMcahJEVQr/GWn641aNaapdZHmi98ACz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9RqwN/dJMcahJEVQr/GWn641aNaapdZHmi98ACz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9RqwN%2FdJMcahJEVQr%2FGWn641aNaapdZHmi98ACz1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;710&quot; height=&quot;183&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;입출력 예 #1&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q5gH7/dJMcadtFDhc/LMoIy4Mnbm2lhKtC6q6YX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q5gH7/dJMcadtFDhc/LMoIy4Mnbm2lhKtC6q6YX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q5gH7/dJMcadtFDhc/LMoIy4Mnbm2lhKtC6q6YX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq5gH7%2FdJMcadtFDhc%2FLMoIy4Mnbm2lhKtC6q6YX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;549&quot; height=&quot;549&quot; data-origin-width=&quot;549&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;4번과 7번을 연결하는 전선을 끊으면 두 전력망은 각 6개와 3개의 송전탑을 가지며, 이보다 더 비슷한 개수로 전력망을 나눌 수 없습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;또 다른 방법으로는 3번과 4번을 연결하는 전선을 끊어도 최선의 정답을 도출할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yWJaF/dJMcaawZr2f/caaejO6VvyNeZIRfqIIg11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yWJaF/dJMcaawZr2f/caaejO6VvyNeZIRfqIIg11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yWJaF/dJMcaawZr2f/caaejO6VvyNeZIRfqIIg11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyWJaF%2FdJMcaawZr2f%2FcaaejO6VvyNeZIRfqIIg11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;148&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2번과 3번을 연결하는 전선을 끊으면 두 전력망이 모두 2개의 송전탑을 가지게 되며, 이 방법이 최선입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #3&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRVpKB/dJMcadAqzlk/3sz4nKNUObO3DlcTD77zsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRVpKB/dJMcadAqzlk/3sz4nKNUObO3DlcTD77zsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRVpKB/dJMcadAqzlk/3sz4nKNUObO3DlcTD77zsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRVpKB%2FdJMcadAqzlk%2F3sz4nKNUObO3DlcTD77zsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;547&quot; height=&quot;549&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3번과 7번을 연결하는 전선을 끊으면 두 전력망이 각각 4개와 3개의 송전탑을 가지게 되며, 이 방법이 최선입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from copy import deepcopy

def dfs(node, temp, visited):
    visited.append(node)
    
    for next in temp[node]:
        if next not in visited:
            dfs(next, temp, visited)
    
    return visited

def solution(n, wires):
    tree = [[] for _ in range(n + 1)]
    answer = 100
    
    for v1, v2 in wires:
        tree[v1].append(v2)
        tree[v2].append(v1)
    
    for v1, v2 in wires:
        temp = deepcopy(tree)
        temp[v1].remove(v2)
        temp[v2].remove(v1)
        
        A = dfs(v1, temp, [])
        B = dfs(v2, temp, [])
        
        answer = min(answer, abs(len(A) - len(B)))
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;179&quot; data-start=&quot;102&quot; data-ke-size=&quot;size16&quot;&gt;먼저 `wires`를 이용해 인접 리스트 형태의 트리 구조를 만든다.&lt;/p&gt;
&lt;p data-end=&quot;281&quot; data-start=&quot;181&quot; data-ke-size=&quot;size16&quot;&gt;이후 `wires`를 하나씩 순회하면서, 인접 리스트에서 연결된 노드를 제거하는 방식으로 해당 전선을 일시적으로 끊어준다.&lt;/p&gt;
&lt;p data-end=&quot;373&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;그 상태에서 `dfs()` 함수를 실행해, 하나의 네트워크에 속한 노드 개수를 계산한다.&lt;/p&gt;
&lt;p data-end=&quot;440&quot; data-start=&quot;375&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 구한 두 네트워크의 크기 차이를 비교하여, 최종적으로 최소값을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/597</guid>
      <comments>https://divheer.tistory.com/597#entry597comment</comments>
      <pubDate>Tue, 2 Dec 2025 09:07:27 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] AdMob 배너 광고, 네이티브 광고 추가 방법 (Android)</title>
      <link>https://divheer.tistory.com/596</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;AdMob 광고 종류 비교&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;적응형 배너 (Adaptive Banner)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;b&gt;고정 크기 배너 (Fixed Size Banner)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;디바이스 화면 크기에 맞춰 자동으로 크기가 조절된다 &lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;정해진 픽셀 값을 그대로 유지한다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XpGU4/dJMcaiBHWjP/HfYLG4buSyR6eYDQ6WQ6PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XpGU4/dJMcaiBHWjP/HfYLG4buSyR6eYDQ6WQ6PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XpGU4/dJMcaiBHWjP/HfYLG4buSyR6eYDQ6WQ6PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXpGU4%2FdJMcaiBHWjP%2FHfYLG4buSyR6eYDQ6WQ6PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;195&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 25%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dX22FX/dJMcahQnGZn/lZqmyQqXORSZEX9e7nhis1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dX22FX/dJMcahQnGZn/lZqmyQqXORSZEX9e7nhis1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dX22FX/dJMcahQnGZn/lZqmyQqXORSZEX9e7nhis1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdX22FX%2FdJMcahQnGZn%2FlZqmyQqXORSZEX9e7nhis1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;205&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;205&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;네이티브 템플릿 광고 (Native Templates)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt;네이티브 플랫폼 설정 광고 (Platform Setup)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;텍스트, 색상, 버튼 스타일 등을 커스텀할 수 있다&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;XML 레이아웃 기반으로 완전한 커스텀이 가능하다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lAezl/dJMcabo5Njt/0cxmZu4kfr4sMTpMM9pDZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lAezl/dJMcabo5Njt/0cxmZu4kfr4sMTpMM9pDZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lAezl/dJMcabo5Njt/0cxmZu4kfr4sMTpMM9pDZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlAezl%2FdJMcabo5Njt%2F0cxmZu4kfr4sMTpMM9pDZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;325&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t5N4i/dJMcadAosES/srWqeh3yKRDxwkcF5YvTXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t5N4i/dJMcadAosES/srWqeh3yKRDxwkcF5YvTXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t5N4i/dJMcadAosES/srWqeh3yKRDxwkcF5YvTXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft5N4i%2FdJMcadAosES%2FsrWqeh3yKRDxwkcF5YvTXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;266&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 간단하게 배너 광고를 추가했지만,&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;앱 전체의 UI에 자연스럽게 녹아들 수 있는 스타일을 원해서 네이티브 광고로 전환했다.&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;758&quot; data-start=&quot;622&quot; data-ke-size=&quot;size16&quot;&gt;플러터에서는 두 가지의 네이티브 광고 방식을 제공하는데&lt;/p&gt;
&lt;p data-end=&quot;817&quot; data-start=&quot;760&quot; data-ke-size=&quot;size16&quot;&gt;이 중에서도 가장 자유도가 높은 &lt;b&gt;플랫폼 네이티브 방식&lt;/b&gt;을 선택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;Flutter AdMob 연동 기초 설정&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 63px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.372%; height: 21px;&quot;&gt;적응형 배너 (Adaptive Banner)&lt;/td&gt;
&lt;td style=&quot;width: 66.628%; height: 21px;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;ca-app-pub-3940256099942544&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;9214589741&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.372%; height: 21px;&quot;&gt;고정 크기 배너 (Fixed Size Banner)&lt;/td&gt;
&lt;td style=&quot;width: 66.628%; height: 21px;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;ca-app-pub-3940256099942544&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;6300978111&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.372%; height: 21px;&quot;&gt;네이티브 광고&lt;/td&gt;
&lt;td style=&quot;width: 66.628%; height: 21px;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;ca-app-pub-3940256099942544&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;/&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;2247696110&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애드몹에서 광고 단위를 생성하면 앱 ID와 광고 단위 ID가 제공되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 단계에서는 반드시 테스트 광고 단위 ID를 사용해야 한다.&lt;br /&gt;개발 단계에서 실제 광고 단위 ID를 사용하면 계정이 정지될 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764059584040&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;manifest&amp;gt;
    &amp;lt;application&amp;gt;
        &amp;lt;!-- Sample AdMob app ID: ca-app-pub-3940256099942544~3347511713 --&amp;gt;
        &amp;lt;meta-data
            android:name=&quot;com.google.android.gms.ads.APPLICATION_ID&quot;
            android:value=&quot;ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy&quot;/&amp;gt;
    &amp;lt;application&amp;gt;
&amp;lt;manifest&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android/app/src/main/AndroidManifest.xml`에 자신의 &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;애드몹 앱 ID를 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764059839381&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter pub add google_mobile_ads&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;플러터 앱에 애드몹 광고를 추가하기 위해 &lt;a href=&quot;https://pub.dev/packages/google_mobile_ads&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;google_mobile_ads 패키지&lt;/a&gt;를 설치한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764060016440&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';

// 배너 광고 유닛 ID
String admobBannerUnitId = kReleaseMode
    ? 'YOUR_ADMOB_BANNER_UNID_ID'
    : 'ca-app-pub-3940256099942544/6300978111'; 

// 네이티브 광고 유닛 ID
String admobNativeUnitId = kReleaseMode
    ? 'YOUR_ADMOB_NATIVE_UNIT_ID'
    : 'ca-app-pub-3940256099942544/2247696110'; 
      
void main() async {
  // Flutter 초기화
  WidgetsFlutterBinding.ensureInitialized();

  // 구글 애드몹 초기화
  MobileAds.instance.initialize();

  runApp(const MyApp());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;`main.dart`에서 애드몹 SDK 초기화하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;디버그, 릴리즈 환경에 따라 광고 유닛 ID를 가져온다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;배너 광고 추가하기&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1764074884929&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';

/// 배너 광고 위젯
class BannerAdWidget extends StatefulWidget {
  const BannerAdWidget({super.key});

  @override
  State&amp;lt;BannerAdWidget&amp;gt; createState() =&amp;gt; _BannerAdWidgetState();
}

class _BannerAdWidgetState extends State&amp;lt;BannerAdWidget&amp;gt; {
  BannerAd? _bannerAd;
  AdSize? _adSize;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _loadAd();
    });
  }

  @override
  void dispose() {
    _bannerAd?.dispose();
    super.dispose();
  }

  // 광고 생성 및 로드
  Future&amp;lt;void&amp;gt; _loadAd() async {
    if (!mounted) return;

    final screenWidth = MediaQuery.of(context).size.width.truncate();

    // Padding 추가
    final availableWidth = screenWidth - 42;
    final size = await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
      availableWidth,
    );

    if (size == null) {
      return;
    }

    if (mounted) {
      setState(() {
        _adSize = size;
      });
    }

    BannerAd(
      adUnitId: admobBannerUnitId,
      request: const AdRequest(),
      size: size,
      listener: BannerAdListener(
        onAdLoaded: (ad) {
          if (mounted) {
            setState(() {
              _bannerAd = ad as BannerAd;
            });
          }
        },
        onAdFailedToLoad: (ad, err) {
          debugPrint(&quot;Ad failed to load with error: $err&quot;);
          ad.dispose();
        },
      ),
    ).load();
  }

  @override
  Widget build(BuildContext context) {
    if (_adSize == null) {
      return const SizedBox();
    }

    return SizedBox(
      width: _adSize!.width.toDouble(),
      height: _adSize!.height.toDouble(),
      child: _bannerAd != null ? AdWidget(ad: _bannerAd!) : const SizedBox(),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적응형 배너와 고정 크기 배너를 구현할 때는 동일한 코드에 유닛 ID만 교체해 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배너 광고는 구현이 가장 간단하지만, 사이즈를 제외한 요소들을 커스텀 할 수 없다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네이티브 템플릿 광고 추가하기&lt;/h2&gt;
&lt;pre id=&quot;code_1764075726783&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';

// 네이티브 템플릿 광고 위젯
class NativeTemplatesAdWidget extends StatefulWidget {
  const NativeTemplatesAdWidget({super.key});

  @override
  State&amp;lt;NativeTemplatesAdWidget&amp;gt; createState() =&amp;gt;
      _NativeTemplatesAdWidgetState();
}

class _NativeTemplatesAdWidgetState extends State&amp;lt;NativeTemplatesAdWidget&amp;gt; {
  NativeAd? _nativeAd;
  bool _nativeAdIsLoaded = false;
  final double _adAspectRatioMedium = (370 / 355);

  @override
  void initState() {
    super.initState();
    _loadAd();
  }

  @override
  void dispose() {
    _nativeAd?.dispose();
    super.dispose();
  }

  /// 네이티브 광고 로드
  void _loadAd() async {
    setState(() {
      _nativeAdIsLoaded = false;
    });

    _nativeAd = NativeAd(
      adUnitId: admobNativeUnitId,
      listener: NativeAdListener(
        onAdLoaded: (ad) {
          debugPrint('$NativeAd loaded.');
          if (mounted) {
            setState(() {
              _nativeAdIsLoaded = true;
            });
          }
        },
        onAdFailedToLoad: (ad, error) {
          debugPrint('$NativeAd failedToLoad: $error');
          ad.dispose();
        },
      ),
      request: const AdRequest(),
      nativeTemplateStyle: NativeTemplateStyle(
        templateType: TemplateType.small,
        callToActionTextStyle: NativeTemplateTextStyle(
          textColor: Colors.white,
          style: NativeTemplateFontStyle.monospace,
          size: 16.0,
        ),
        primaryTextStyle: NativeTemplateTextStyle(
          textColor: Colors.black,
          style: NativeTemplateFontStyle.bold,
          size: 16.0,
        ),
      ),
    )..load();
  }

  @override
  Widget build(BuildContext context) {
    if (_nativeAdIsLoaded &amp;amp;&amp;amp; _nativeAd != null) {
      return SizedBox(
        height: MediaQuery.of(context).size.width * _adAspectRatioMedium,
        width: double.infinity,
        child: AdWidget(ad: _nativeAd!),
      );
    }
    return const SizedBox.shrink();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이티브 템플릿 광고도 간단하게 구현할 수 있고, 기본적인 색상&amp;middot;텍스트 스타일 변경도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네이티브 플랫폼 설정 광고 추가하기&lt;/h2&gt;
&lt;pre id=&quot;code_1764078040015&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;com.google.android.gms.ads.nativead.NativeAdView
    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    xmlns:tools=&quot;http://schemas.android.com/tools&quot;
    android:layout_width=&quot;match_parent&quot;
    android:layout_height=&quot;match_parent&quot;&amp;gt;

    &amp;lt;FrameLayout
        android:layout_width=&quot;match_parent&quot;
        android:layout_height=&quot;match_parent&quot;&amp;gt;

        &amp;lt;TextView
            android:id=&quot;@+id/tv_list_tile_native_ad_attribution_small&quot;
            android:layout_width=&quot;wrap_content&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:background=&quot;#F19938&quot;
            android:text=&quot;Ad&quot;
            android:textColor=&quot;#FFFFFF&quot;
            android:textSize=&quot;12sp&quot; /&amp;gt;

        &amp;lt;ImageView
            android:id=&quot;@+id/iv_list_tile_native_ad_icon&quot;
            android:layout_width=&quot;48dp&quot;
            android:layout_height=&quot;48dp&quot;
            android:layout_gravity=&quot;center_vertical&quot;
            android:layout_marginStart=&quot;16dp&quot;
            android:layout_marginLeft=&quot;16dp&quot;
            android:scaleType=&quot;fitXY&quot;
            tools:background=&quot;#EDEDED&quot; /&amp;gt;

        &amp;lt;TextView
            android:id=&quot;@+id/tv_list_tile_native_ad_attribution_large&quot;
            android:layout_width=&quot;48dp&quot;
            android:layout_height=&quot;48dp&quot;
            android:layout_gravity=&quot;center_vertical&quot;
            android:layout_marginStart=&quot;16dp&quot;
            android:layout_marginLeft=&quot;16dp&quot;
            android:background=&quot;#F19938&quot;
            android:gravity=&quot;center&quot;
            android:text=&quot;Ad&quot;
            android:textColor=&quot;#FFFFFF&quot;
            android:visibility=&quot;invisible&quot; /&amp;gt;

        &amp;lt;LinearLayout
            android:layout_width=&quot;match_parent&quot;
            android:layout_height=&quot;wrap_content&quot;
            android:layout_gravity=&quot;center_vertical&quot;
            android:layout_marginStart=&quot;80dp&quot;
            android:layout_marginLeft=&quot;80dp&quot;
            android:layout_marginEnd=&quot;16dp&quot;
            android:layout_marginRight=&quot;16dp&quot;
            android:orientation=&quot;vertical&quot;&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/tv_list_tile_native_ad_headline&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:ellipsize=&quot;end&quot;
                android:lines=&quot;1&quot;
                android:maxLines=&quot;1&quot;
                android:textColor=&quot;#000000&quot;
                android:textSize=&quot;16sp&quot;
                tools:text=&quot;Headline&quot; /&amp;gt;

            &amp;lt;TextView
                android:id=&quot;@+id/tv_list_tile_native_ad_body&quot;
                android:layout_width=&quot;wrap_content&quot;
                android:layout_height=&quot;wrap_content&quot;
                android:ellipsize=&quot;end&quot;
                android:lines=&quot;1&quot;
                android:maxLines=&quot;1&quot;
                android:textColor=&quot;#828282&quot;
                android:textSize=&quot;14sp&quot;
                tools:text=&quot;body&quot; /&amp;gt;

        &amp;lt;/LinearLayout&amp;gt;

    &amp;lt;/FrameLayout&amp;gt;

&amp;lt;/com.google.android.gms.ads.nativead.NativeAdView&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android\app\src\main\res\layout` 디렉토리에 `native_ad_custom.xml` 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764078105212&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.name ✅패키지 이름 수정

import com.rainypoint.alarm.R
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin

class ListTileNativeAdFactory(val context: Context) : GoogleMobileAdsPlugin.NativeAdFactory {

    override fun createNativeAd(
        nativeAd: NativeAd,
        customOptions: MutableMap&amp;lt;String, Any&amp;gt;?
    ): NativeAdView {
        val nativeAdView = LayoutInflater.from(context)
            .inflate(R.layout.native_ad_custom, null) as NativeAdView

        with(nativeAdView) {
            val attributionViewSmall =
                findViewById&amp;lt;TextView&amp;gt;(R.id.tv_list_tile_native_ad_attribution_small)
            val attributionViewLarge =
                findViewById&amp;lt;TextView&amp;gt;(R.id.tv_list_tile_native_ad_attribution_large)

            val iconView = findViewById&amp;lt;ImageView&amp;gt;(R.id.iv_list_tile_native_ad_icon)
            val icon = nativeAd.icon
            if (icon != null) {
                attributionViewSmall.visibility = View.VISIBLE
                attributionViewLarge.visibility = View.INVISIBLE
                iconView.setImageDrawable(icon.drawable)
            } else {
                attributionViewSmall.visibility = View.INVISIBLE
                attributionViewLarge.visibility = View.VISIBLE
            }
            this.iconView = iconView

            val headlineView = findViewById&amp;lt;TextView&amp;gt;(R.id.tv_list_tile_native_ad_headline)
            headlineView.text = nativeAd.headline
            this.headlineView = headlineView

            val bodyView = findViewById&amp;lt;TextView&amp;gt;(R.id.tv_list_tile_native_ad_body)
            with(bodyView) {
                text = nativeAd.body
                visibility = if (nativeAd.body.isNullOrEmpty()) View.INVISIBLE else View.VISIBLE
            }
            this.bodyView = bodyView

            setNativeAd(nativeAd)
        }

        return nativeAdView
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android\app\src\main\kotlin\com\example\name` 경로(본인의 패키지 이름)에 `ListTileNativeAdFactory.kt` 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764078249198&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.name ✅패키지 이름 수정

import io.flutter.embedding.android.FlutterActivity
import com.rainypoint.alarm.ListTileNativeAdFactory
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.googlemobileads.GoogleMobileAdsPlugin

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        GoogleMobileAdsPlugin.registerNativeAdFactory(
            flutterEngine, &quot;adFactoryExample&quot;, ListTileNativeAdFactory(context)
        )
    }

    override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
        super.cleanUpFlutterEngine(flutterEngine)

        GoogleMobileAdsPlugin.unregisterNativeAdFactory(flutterEngine, &quot;adFactoryExample&quot;)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android\app\src\main\kotlin\com\example\name` 경로의 `MainActivity.kt` 파일을 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764078787287&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../main.dart';

class NativePlatformAdWidget extends StatefulWidget {
  const NativePlatformAdWidget({super.key});

  @override
  State&amp;lt;NativePlatformAdWidget&amp;gt; createState() =&amp;gt; _NativePlatformAdWidgetState();
}

class _NativePlatformAdWidgetState extends State&amp;lt;NativePlatformAdWidget&amp;gt; {
  late NativeAd _ad;
  bool isLoaded = false;

  @override
  void initState() {
    super.initState();

    _ad = NativeAd(
      adUnitId: admobNativeUnitId,
      factoryId: &quot;adFactoryExample&quot;,
      request: const AdRequest(),
      listener: NativeAdListener(onAdLoaded: (ad) {
        setState(() {
          _ad = ad as NativeAd;
          isLoaded = true;
        });
      }, onAdFailedToLoad: (ad, error) {
        ad.dispose();
      }),
    );
    _ad.load();
  }

  @override
  void dispose() {
    super.dispose();
    _ad.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (isLoaded == true) {
      return SizedBox(
        height: 60,
        width: double.infinity,
        child: AdWidget(ad: _ad),
      );
    } else {
      return const SizedBox(height: 0);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플랫폼 네이티브 광고는 네이티브 코드를 직접 수정해야 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘 크기, 텍스트, 버튼, 배경, 표시 방식 등을 완전히 커스텀할 수 있기 때문에 앱 UI에 최적화된 형태로 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 결과&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 624px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 603px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; height: 603px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mi6kP/dJMcafyg45R/JmBUBXEXpgf4jxSmtI1v01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mi6kP/dJMcafyg45R/JmBUBXEXpgf4jxSmtI1v01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mi6kP/dJMcafyg45R/JmBUBXEXpgf4jxSmtI1v01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmi6kP%2FdJMcafyg45R%2FJmBUBXEXpgf4jxSmtI1v01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2400&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 603px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DEhi8/dJMcabo6s9w/vxk1PkVZpDfM0Lk3j7dlGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DEhi8/dJMcabo6s9w/vxk1PkVZpDfM0Lk3j7dlGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DEhi8/dJMcabo6s9w/vxk1PkVZpDfM0Lk3j7dlGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDEhi8%2FdJMcabo6s9w%2Fvxk1PkVZpDfM0Lk3j7dlGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2400&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 603px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_1764248091.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKaWtO/dJMcagKIH9x/idSSLXO72CEmKyYFcXuAuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKaWtO/dJMcagKIH9x/idSSLXO72CEmKyYFcXuAuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKaWtO/dJMcagKIH9x/idSSLXO72CEmKyYFcXuAuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKaWtO%2FdJMcagKIH9x%2FidSSLXO72CEmKyYFcXuAuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;2400&quot; data-filename=&quot;Screenshot_1764248091.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;Adaptive Banner&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;Platform Setup 기본&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; text-align: center; height: 21px;&quot;&gt;&lt;b&gt;Platform Setup UI 수정&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이티브 플랫폼 방식을 통해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;광고를 UI 구성 요소의 일부처럼 보이도록 자연스럽게 배치함으로써&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 경험을 최대한 해치지 않기 위해 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/admob/flutter/test-ads?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;테스트 광고 사용 설정하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/admob/flutter/banner?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;배너 광고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/admob/flutter/native?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;네이티브 광고&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codelabs.developers.google.com/codelabs/admob-inline-ads-in-flutter?hl=ko#7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;네이티브 광고 추가&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://deku.posstree.com/ko/flutter/admob/&quot;&gt;[Flutter]&amp;nbsp;Admob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://jh-industry.tistory.com/122&quot;&gt;[Flutter]&amp;nbsp;admob(애드몹)&amp;nbsp;-&amp;nbsp;앱&amp;nbsp;만들어서&amp;nbsp;네이티브&amp;nbsp;광고로&amp;nbsp;돈&amp;nbsp;벌기,&amp;nbsp;수익&amp;nbsp;내기(Native&amp;nbsp;Ads)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Flutter</category>
      <category>AdMob</category>
      <category>Android</category>
      <category>Flutter</category>
      <category>광고</category>
      <category>네이티브광고</category>
      <category>배너광고</category>
      <category>안드로이드</category>
      <category>애드몹</category>
      <category>앱개발</category>
      <category>플러터</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/596</guid>
      <comments>https://divheer.tistory.com/596#entry596comment</comments>
      <pubDate>Thu, 27 Nov 2025 15:16:59 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 크레인 인형뽑기</title>
      <link>https://divheer.tistory.com/595</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/64061&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/64061&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;게임개발자인 &quot;죠르디&quot;는 크레인 인형뽑기 기계를 모바일 게임으로 만들려고 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&quot;죠르디&quot;는 게임의 재미를 높이기 위해 화면 구성과 규칙을 다음과 같이 게임 로직에 반영하려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LCrNq/dJMcagDWi1S/iY7tZHWOIBa3hZwchLrcWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LCrNq/dJMcagDWi1S/iY7tZHWOIBa3hZwchLrcWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LCrNq/dJMcagDWi1S/iY7tZHWOIBa3hZwchLrcWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLCrNq%2FdJMcagDWi1S%2FiY7tZHWOIBa3hZwchLrcWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;게임 화면은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;1 x 1&quot;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;크기의 칸들로 이루어진&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;N x N&quot;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;크기의 정사각 격자이며 위쪽에는 크레인이 있고 오른쪽에는 바구니가 있습니다. (위 그림은 &quot;5 x 5&quot; 크기의 예시입니다). 각 격자 칸에는 다양한 인형이 들어 있으며 인형이 없는 칸은 빈칸입니다. 모든 인형은 &quot;1 x 1&quot; 크기의 격자 한 칸을 차지하며&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;격자의 가장 아래 칸부터 차곡차곡 쌓여 있습니다.&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;게임 사용자는 크레인을 좌우로 움직여서 멈춘 위치에서 가장 위에 있는 인형을 집어 올릴 수 있습니다. 집어 올린 인형은 바구니에 쌓이게 되는 데, 이때 바구니의 가장 아래 칸부터 인형이 순서대로 쌓이게 됩니다. 다음 그림은 [1번, 5번, 3번] 위치에서 순서대로 인형을 집어 올려 바구니에 담은 모습입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by1xIP/dJMcac9j7pv/rv6v6ci3A8iH1pAQ0dUr40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by1xIP/dJMcac9j7pv/rv6v6ci3A8iH1pAQ0dUr40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by1xIP/dJMcac9j7pv/rv6v6ci3A8iH1pAQ0dUr40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby1xIP%2FdJMcac9j7pv%2Frv6v6ci3A8iH1pAQ0dUr40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;만약 같은 모양의 인형 두 개가 바구니에 연속해서 쌓이게 되면 두 인형은 터뜨려지면서 바구니에서 사라지게 됩니다. 위 상태에서 이어서 [5번] 위치에서 인형을 집어 바구니에 쌓으면 같은 모양 인형&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;두 개&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가 없어집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnJm3f/dJMcadf5vq7/13YVk8hNaeLnGVGbKskkO0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnJm3f/dJMcadf5vq7/13YVk8hNaeLnGVGbKskkO0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnJm3f/dJMcadf5vq7/13YVk8hNaeLnGVGbKskkO0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bnJm3f/dJMcadf5vq7/13YVk8hNaeLnGVGbKskkO0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;크레인 작동 시 인형이 집어지지 않는 경우는 없으나 만약 인형이 없는 곳에서 크레인을 작동시키는 경우에는 아무런 일도 일어나지 않습니다. 또한 바구니는 모든 인형이 들어갈 수 있을 만큼 충분히 크다고 가정합니다. (그림에서는 화면표시 제약으로 5칸만으로 표현하였음)&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;게임 화면의 격자의 상태가 담긴 2차원 배열 board와 인형을 집기 위해 크레인을 작동시킨 위치가 담긴 배열 moves가 매개변수로 주어질 때, 크레인을 모두 작동시킨 후 터트려져 사라진 인형의 개수를 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;board 배열은 2차원 배열로 크기는 &quot;5 x 5&quot; 이상 &quot;30 x 30&quot; 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;board의 각 칸에는 0 이상 100 이하인 정수가 담겨있습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0은 빈 칸을 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 ~ 100의 각 숫자는 각기 다른 인형의 모양을 의미하며 같은 숫자는 같은 모양의 인형을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;moves 배열의 크기는 1 이상 1,000 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;moves 배열 각 원소들의 값은 1 이상이며 board 배열의 가로 크기 이하인 자연수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfCOh8/dJMcac9j7r6/iIRiA6jdx5Ck7SVxO3uPhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfCOh8/dJMcac9j7r6/iIRiA6jdx5Ck7SVxO3uPhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfCOh8/dJMcac9j7r6/iIRiA6jdx5Ck7SVxO3uPhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfCOh8%2FdJMcac9j7r6%2FiIRiA6jdx5Ck7SVxO3uPhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;772&quot; height=&quot;94&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;인형의 처음 상태는 문제에 주어진 예시와 같습니다. 크레인이 [1, 5, 3, 5, 1, 2, 1, 4] 번 위치에서 차례대로 인형을 집어서 바구니에 옮겨 담은 후, 상태는 아래 그림과 같으며 바구니에 담는 과정에서 터트려져 사라진 인형은 4개 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJKAPF/dJMcafruiUY/sjbe3TRoMGX74B2jTLz3Ik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJKAPF/dJMcafruiUY/sjbe3TRoMGX74B2jTLz3Ik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJKAPF/dJMcafruiUY/sjbe3TRoMGX74B2jTLz3Ik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJKAPF%2FdJMcafruiUY%2Fsjbe3TRoMGX74B2jTLz3Ik%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;500&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(board, moves):
    n = len(board)
    answer = 0
    
    stack = [[] for _ in range(n + 1)]
    basket = []
    board.reverse()
    
    for i in range(n):
        for j in range(n):
            if board[j][i]:
                stack[i + 1].append(board[j][i])
        
    for i in moves:
        if stack[i]:
            next = stack[i].pop()
            
            if not basket:
                basket.append(next)
            elif basket[-1] == next:
                answer += 2
                basket.pop()
            else:
                basket.append(next)
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 받은 `board`를 번호 칸마다 인형이 쌓인 스택 형태로 변환해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`stack[i]`에 직접 접근해서 `pop()` 하는 방식으로 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(board, moves):
    stack = []
    answer = 0
        
    for i in moves:
        for j in range(len(board)):
            if board[j][i-1]:
                stack.append(board[j][i-1])
                board[j][i-1] = 0
                
                if len(stack) &amp;gt; 1:
                    if stack[-1] == stack[-2]:
                        stack.pop()
                        stack.pop()
                        answer += 2

                break

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값으로 받은 `board`를 그대로 사용하면서,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인형을 집어 올린 경우에는 `0`으로 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/595</guid>
      <comments>https://divheer.tistory.com/595#entry595comment</comments>
      <pubDate>Thu, 27 Nov 2025 07:58:52 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.0] 평행 | 파이썬</title>
      <link>https://divheer.tistory.com/594</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120875&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/120875&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;점 네 개의 좌표를 담은 이차원 배열 &amp;nbsp;dots가 다음과 같이 매개변수로 주어집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[[x1, y1], [x2, y2], [x3, y3], [x4, y4]]&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;주어진 네 개의 점을 두 개씩 이었을 때, 두 직선이 평행이 되는 경우가 있으면 1을 없으면 0을 return 하도록 solution 함수를 완성해보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dots의 길이 = 4&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dots의 원소는 [x, y] 형태이며 x, y는 정수입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0 &amp;le;&amp;nbsp;x, y &amp;le; 100&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;서로 다른 두개 이상의 점이 겹치는 경우는 없습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 직선이 겹치는 경우(일치하는 경우)에도 1을 return 해주세요.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;임의의 두 점을 이은 직선이 x축 또는 y축과 평행한 경우는 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDW0LJ/dJMcafkI4VV/hlgSVs7gILzSh419nmMEWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDW0LJ/dJMcafkI4VV/hlgSVs7gILzSh419nmMEWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDW0LJ/dJMcafkI4VV/hlgSVs7gILzSh419nmMEWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDW0LJ%2FdJMcafkI4VV%2FhlgSVs7gILzSh419nmMEWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;380&quot; height=&quot;144&quot; data-origin-width=&quot;380&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;점 [1, 4], [3, 8]을 잇고 [9, 2], [11, 6]를 이으면 두 선분은 평행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;점을 어떻게 연결해도 평행하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from itertools import permutations

def solution(dots):
    permutation = permutations([0, 1, 2, 3], 2)
    a, b = 0, 0
    
    for p1 in permutation: 
        n1, m1 = p1
        n2, m2 = [x for x in [0, 1, 2, 3] if x not in p1]
        
        a = (dots[n1][0] - dots[m1][0]) / (dots[n1][1] - dots[m1][1])
        b = (dots[n2][0] - dots[m2][0]) / (dots[n2][1] - dots[m2][1])
        
        if a == b:
            return 1
                
    return 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`permutations`를 이용해 4개의 점 중 2개를 고르는 모든 순서쌍을 만들고,&lt;br /&gt;남은 두 점을 또 하나의 쌍으로 구성해 기울기를 구하는 공식을 이용해 비교했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(dots):
    (x1, y1), (x2, y2), (x3, y3), (x4, y4) = dots
    res1 = (x1 - x2) / (y1 - y2) == (x3 - x4) / (y3 - y4)
    res2 = (x1 - x3) / (y1 - y3) == (x2 - x4) / (y2 - y4)
    res3 = (x1 - x4) / (y1 - y4) == (x2 - x3) / (y2 - y3)
    return 1 if res1 or res2 or res3 else 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평행 비교를 위해서는 하나의 점을 기준으로 세 가지 경우만 비교하면 되는 것이었다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/594</guid>
      <comments>https://divheer.tistory.com/594#entry594comment</comments>
      <pubDate>Wed, 26 Nov 2025 09:59:38 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 공원 산책 | 파이썬</title>
      <link>https://divheer.tistory.com/593</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/172928&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/172928&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;지나다니는 길을 'O', 장애물을 'X'로 나타낸 직사각형 격자 모양의 공원에서 로봇 강아지가 산책을 하려합니다. 산책은 로봇 강아지에 미리 입력된 명령에 따라 진행하며, 명령은 다음과 같은 형식으로 주어집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[&quot;방향 거리&quot;, &quot;방향 거리&quot; &amp;hellip; ]&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 &quot;E 5&quot;는 로봇 강아지가 현재 위치에서 동쪽으로 5칸 이동했다는 의미입니다. 로봇 강아지는 명령을 수행하기 전에 다음 두 가지를 먼저 확인합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 두 가지중 어느 하나라도 해당된다면, 로봇 강아지는 해당 명령을 무시하고 다음 명령을 수행합니다.&lt;br /&gt;공원의 가로 길이가 W, 세로 길이가 H라고 할 때, 공원의 좌측 상단의 좌표는 (0, 0), 우측 하단의 좌표는 (H - 1, W - 1) 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xjaiP/dJMcaawWav6/hkmkJmv3twVKIsYm9sqmqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xjaiP/dJMcaawWav6/hkmkJmv3twVKIsYm9sqmqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xjaiP/dJMcaawWav6/hkmkJmv3twVKIsYm9sqmqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxjaiP%2FdJMcaawWav6%2FhkmkJmv3twVKIsYm9sqmqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;739&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;공원을 나타내는 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;park&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 로봇 강아지가 수행할 명령이 담긴 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;routes&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가 매개변수로 주어질 때, 로봇 강아지가 모든 명령을 수행 후 놓인 위치를 [세로 방향 좌표, 가로 방향 좌표] 순으로 배열에 담아 return 하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;park의 길이 &amp;le; 50
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;park[i]의 길이 &amp;le; 50
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;park[i]는 다음 문자들로 이루어져 있으며 시작지점은 하나만 주어집니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;S : 시작 지점&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;O : 이동 가능한 통로&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;X : 장애물&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;park는 직사각형 모양입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;routes의 길이 &amp;le; 50
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;routes의 각 원소는 로봇 강아지가 수행할 명령어를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;로봇 강아지는&lt;span&gt;&amp;nbsp;&lt;/span&gt;routes의 첫 번째 원소부터 순서대로 명령을 수행합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;routes의 원소는 &quot;op n&quot;과 같은 구조로 이루어져 있으며, op는 이동할 방향, n은 이동할 칸의 수를 의미합니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;op는 다음 네 가지중 하나로 이루어져 있습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;N : 북쪽으로 주어진 칸만큼 이동합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;S : 남쪽으로 주어진 칸만큼 이동합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;W : 서쪽으로 주어진 칸만큼 이동합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;E : 동쪽으로 주어진 칸만큼 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; n &amp;le; 9&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/obuMq/dJMcac2xNcY/9tiqLhmmaVwii1gWtQTcfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/obuMq/dJMcac2xNcY/9tiqLhmmaVwii1gWtQTcfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/obuMq/dJMcac2xNcY/9tiqLhmmaVwii1gWtQTcfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FobuMq%2FdJMcac2xNcY%2F9tiqLhmmaVwii1gWtQTcfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;164&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력된 명령대로 동쪽으로 2칸, 남쪽으로 2칸, 서쪽으로 1칸 이동하면 [0,0] -&amp;gt; [0,2] -&amp;gt; [2,2] -&amp;gt; [2,1]이 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력된 명령대로라면 동쪽으로 2칸, 남쪽으로 2칸, 서쪽으로 1칸 이동해야하지만 남쪽으로 2칸 이동할 때 장애물이 있는 칸을 지나기 때문에 해당 명령을 제외한 명령들만 따릅니다. 결과적으로는 [0,0] -&amp;gt; [0,2] -&amp;gt; [0,1]이 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #3&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;처음 입력된 명령은 공원을 나가게 되고 두 번째로 입력된 명령 또한 장애물을 지나가게 되므로 두 입력은 제외한 세 번째 명령만 따르므로 결과는 다음과 같습니다. [0,1] -&amp;gt; [0,0]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(park, routes):
    m, n = len(park), len(park[0])
    cur = [0, 0]
    
    def check_is_possible(x, y, dx, dy):
        is_dx_movable = True
        
        if y != dy:
            is_dx_movable = False
       
        if is_dx_movable:
            for i in range(x, dx + 1):
                if park[i][y] == 'X':
                    return False
        else:
            for i in range(y, dy + 1):
                if park[x][i] == 'X':
                    return False    

        return True
    
    for i in range(n):
        if 'S' in park[i]:
            cur = [i, park[i].index('S')]
            break

    for s in routes:
        op, d = s.split()
        d = int(d)
        x, y = cur[0], cur[1]
        
        if op == 'N':
            nx, ny = x - d, y
        elif op == 'S':
            nx, ny = x + d, y
        elif op == 'W':
            nx, ny = x, y - d
        elif op == 'E':
            nx, ny = x, y + d
        
        if 0 &amp;lt;= nx &amp;lt; m and 0 &amp;lt;= ny &amp;lt; n:
            if check_is_possible(min(x, nx), min(y, ny), max(x, nx), max(y, ny)):
                cur = [nx, ny]
     
    return cur&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;245&quot; data-start=&quot;126&quot; data-ke-size=&quot;size16&quot;&gt;이 문제에서 핵심은 &lt;b&gt;이동 과정 중에 장애물이 하나라도 있는지를 판별하는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;245&quot; data-start=&quot;126&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;399&quot; data-start=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;먼저, 이동 명령을 읽을 때마다 다음 위치를 계산하여 `nx, ny`에 저장한다.&lt;br /&gt;그다음에는 &lt;b&gt;공원 범위를 벗어나는지 1차로 검사&lt;/b&gt;한 뒤,&lt;br /&gt;범위 안이라면 &lt;b&gt;`check_is_possible()`을 이동 경로에 장애물이 있는지를 확인&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-end=&quot;399&quot; data-start=&quot;247&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;569&quot; data-start=&quot;401&quot; data-ke-size=&quot;size16&quot;&gt;경로 이동 로직을 통일하기 위해 `check_is_possible()`에 전달하는 네 좌표는&lt;br /&gt;`min()`과 `max()`를 사용해 &amp;ldquo;작은 좌표 &amp;rarr; 큰 좌표&amp;rdquo; 순으로 전달하도록 했다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;733&quot; data-start=&quot;571&quot; data-ke-size=&quot;size16&quot;&gt;`check_is_possible()` 내부에서는&lt;br /&gt;이동 방향이 x축인지 y축인지를 먼저 판단하고,&lt;br /&gt;해당 방향으로 좌표를 하나씩 움직이며 'X'가 있는지를 검사한다.&lt;/p&gt;
&lt;p data-end=&quot;733&quot; data-start=&quot;571&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;중간에 장애물이 발견되면 즉시 `False`를 반환하고,&lt;br /&gt;끝까지 장애물이 발견되지 않는다면 `True`를 반환한다.&lt;/p&gt;
&lt;p data-end=&quot;733&quot; data-start=&quot;571&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;800&quot; data-start=&quot;735&quot; data-ke-size=&quot;size16&quot;&gt;이 값이 `True`라면 경로에 장애물이 없는 것이므로&lt;br /&gt;현재 위치 `cur`을 새 좌표로 갱신해 이동을 완료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(park, routes):
    dirs = {'N': (-1, 0), 'S': (1, 0), 'W': (0, -1), 'E': (0, 1)}
    m, n = len(park), len(park[0])
    answer = [0, 0]
    
    for i in range(n):
        if 'S' in park[i]:
            answer = [i, park[i].index('S')]
            break

    for route in routes:
        dir, dist = route.split()
        dist = int(dist)
        x, y = answer[0], answer[1] 
        
        for i in range(1, dist + 1):
            nx, ny = x + (dirs[dir][0] * i), y + (dirs[dir][1] * i)
            
            if nx &amp;lt; 0 or nx &amp;gt;= m or ny &amp;lt; 0 or ny &amp;gt;= n:
                break
            if park[nx][ny] == 'X':
                break
        else:
            answer = [nx, ny]
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;196&quot; data-start=&quot;117&quot; data-ke-size=&quot;size16&quot;&gt;각 방향에 대해 이동해야 하는 변화량 `(dx, dy)`를 딕셔너리에 미리 저장해서&lt;/p&gt;
&lt;p data-end=&quot;196&quot; data-start=&quot;117&quot; data-ke-size=&quot;size16&quot;&gt;`dirs[dir]`에 `i`를 곱해가며 좌표를 계산하기 때문에 방향별 이동 계산이 훨씬 단순해졌다.&lt;/p&gt;
&lt;p data-end=&quot;363&quot; data-start=&quot;198&quot; data-ke-size=&quot;size16&quot;&gt;이에 따라 이동해야 할 거리만큼 1칸씩 직접 이동해 보며 경로 전체를 바로바로 검사할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;467&quot; data-start=&quot;444&quot; data-ke-size=&quot;size16&quot;&gt;또한 for-else 구문을 활용해 경로 전체를 안전하게 통과했을 때만 else문이 실행되어 새로운 좌표로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/593</guid>
      <comments>https://divheer.tistory.com/593#entry593comment</comments>
      <pubDate>Sun, 23 Nov 2025 22:32:39 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 신고 결과 받기 | 파이썬</title>
      <link>https://divheer.tistory.com/592</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/92334&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/92334&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;신입사원 무지는 게시판 불량 이용자를 신고하고 처리 결과를 메일로 발송하는 시스템을 개발하려 합니다. 무지가 개발하려는 시스템은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 유저는 한 번에 한 명의 유저를 신고할 수 있습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;신고 횟수에 제한은 없습니다. 서로 다른 유저를 계속해서 신고할 수 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;한 유저를 여러 번 신고할 수도 있지만, 동일한 유저에 대한 신고 횟수는 1회로 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;k번 이상 신고된 유저는 게시판 이용이 정지되며, 해당 유저를 신고한 모든 유저에게 정지 사실을 메일로 발송합니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;유저가 신고한 모든 내용을 취합하여 마지막에 한꺼번에 게시판 이용 정지를 시키면서 정지 메일을 발송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;이용자의 ID가 담긴 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;id_list&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 각 이용자가 신고한 이용자의 ID 정보가 담긴 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;report&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 정지 기준이 되는 신고 횟수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;k&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가 매개변수로 주어질 때, 각 유저별로 처리 결과 메일을 받은 횟수를 배열에 담아 return 하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;id_list의 길이 &amp;le; 1,000
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;id_list의 원소 길이 &amp;le; 10&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;id_list의 원소는 이용자의 id를 나타내는 문자열이며 알파벳 소문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;id_list에는 같은 아이디가 중복해서 들어있지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;report의 길이 &amp;le; 200,000
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;report의 원소 길이 &amp;le; 21&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;report의 원소는 &quot;이용자id 신고한id&quot;형태의 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;예를 들어 &quot;muzi frodo&quot;의 경우 &quot;muzi&quot;가 &quot;frodo&quot;를 신고했다는 의미입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;id는 알파벳 소문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이용자id와 신고한id는 공백(스페이스)하나로 구분되어 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;자기 자신을 신고하는 경우는 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;k&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 200,&lt;span&gt;&amp;nbsp;&lt;/span&gt;k는 자연수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;return 하는 배열은&lt;span&gt;&amp;nbsp;&lt;/span&gt;id_list에 담긴 id 순서대로 각 유저가 받은 결과 메일 수를 담으면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;172&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0QtfD/dJMcahbJktd/QKhL3rxbff5xd6Nd5VNIYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0QtfD/dJMcahbJktd/QKhL3rxbff5xd6Nd5VNIYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0QtfD/dJMcahbJktd/QKhL3rxbff5xd6Nd5VNIYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0QtfD%2FdJMcahbJktd%2FQKhL3rxbff5xd6Nd5VNIYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1038&quot; height=&quot;172&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;172&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import defaultdict

def solution(id_list, report, k):
    dict = defaultdict(set)
    
    for r in report:
        a, b = r.split()
        dict[b].add(a)
       
    answer = [0] * len(id_list)
    
    for a in dict:
        if len(dict[a]) &amp;gt;= k:
            for b in dict[a]:
                i = id_list.index(b)
                answer[i] += 1
    
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;450&quot; data-start=&quot;397&quot; data-ke-size=&quot;size16&quot;&gt;각 유저가 누구에게 신고당했는지 저장하기 위해 딕셔너리를&amp;nbsp;사용했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;500&quot; data-start=&quot;452&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;470&quot; data-start=&quot;452&quot;&gt;key: 신고당한 사람&lt;/li&gt;
&lt;li data-end=&quot;500&quot; data-start=&quot;471&quot;&gt;value: 신고한 사람들의 집합(set)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값은 중복 신고의 경우를 제외하기 위해 `set`을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신고 기록을 딕셔너리에 모두 추가한 후 k번 이상 신고된 유저를 찾아,&lt;br /&gt;그 유저를 신고한 사람들에게 `answer`의 값을 1씩 증가시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(id_list, report, k):
    reports = {x: 0 for x in id_list}
    answer = {x: 0 for x in id_list}
    
    for r in set(report):
        a, b = r.split()
        reports[b] += 1
    
    for r in set(report):
        a, b = r.split()
        if reports[b] &amp;gt;= k:
            answer[a] += 1

    return list(answer.values())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`set(report)`를 통해 탐색 대상에서부터 중복을 제거하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딕셔너리의 값에는 정수를 저장해서 신고 횟수를 카운트한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 신고자별 메일 전송 횟수를 카운트하기 위해 미리 딕셔너리를 만들어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`index()`가 아닌 딕셔너리 키 값을 통해 접근하도록 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/592</guid>
      <comments>https://divheer.tistory.com/592#entry592comment</comments>
      <pubDate>Tue, 18 Nov 2025 08:28:04 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.0] 안전지대 | 파이썬</title>
      <link>https://divheer.tistory.com/591</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/120866&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/120866&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;다음 그림과 같이 지뢰가 있는 지역과 지뢰에 인접한 위, 아래, 좌, 우 대각선 칸을 모두 위험지역으로 분류합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boknGD/dJMcaacA79G/2QkefqPi3IfmNCeqdYmOl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boknGD/dJMcaacA79G/2QkefqPi3IfmNCeqdYmOl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boknGD/dJMcaacA79G/2QkefqPi3IfmNCeqdYmOl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboknGD%2FdJMcaacA79G%2F2QkefqPi3IfmNCeqdYmOl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;301&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;지뢰는 2차원 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;board&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;에 1로 표시되어 있고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;board&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;에는 지뢰가 매설 된 지역 1과, 지뢰가 없는 지역 0만 존재합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;지뢰가 매설된 지역의 지도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;board&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가&amp;nbsp;매개변수로 주어질 때, 안전한 지역의 칸 수를 return하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;board는 n * n 배열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; n &amp;le; 100&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;지뢰는 1로 표시되어 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;board에는 지뢰가 있는 지역 1과 지뢰가 없는 지역 0만 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFBDZ0/dJMcagKEp8O/qAtgAAnNhaKzMb1lrjzckK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFBDZ0/dJMcagKEp8O/qAtgAAnNhaKzMb1lrjzckK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFBDZ0/dJMcagKEp8O/qAtgAAnNhaKzMb1lrjzckK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFBDZ0%2FdJMcagKEp8O%2FqAtgAAnNhaKzMb1lrjzckK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;908&quot; height=&quot;188&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(3, 2)에 지뢰가 있으므로 지뢰가 있는 지역과 지뢰와 인접한 위, 아래, 좌, 우, 대각선 총 8칸은 위험지역입니다. 따라서 16을 return합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(3, 2), (3, 3)에 지뢰가 있으므로 지뢰가 있는 지역과 지뢰와 인접한 위, 아래, 좌, 우, 대각선은 위험지역입니다. 따라서 위험지역을 제외한 칸 수 13을 return합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #3&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;모든 지역에 지뢰가 있으므로 안전지역은 없습니다. 따라서 0을 return합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(board):
    n = len(board)
    moves = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
    answer = 0
    
    for i in range(n):
        for j in range(n):
            if board[i][j] == 1:
                for dx, dy in moves:
                    nx, ny = i + dx, j + dy
                    if 0 &amp;lt;= nx &amp;lt; n and 0 &amp;lt;= ny &amp;lt; n and board[nx][ny] != 1:
                        board[nx][ny] = -1
    
    for line in board:
        answer += line.count(0)
                
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌표 움직임을 `moves`에 미리 생성해놓고, 지뢰가 있는 경우 `board`의 범위 안에서 이동하면서 위험지역을 `-1`로 표시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 지뢰가 위치한 `1`, 지뢰 인접 지역인 `-1`을 제외하고 안전지역인 `0`의 개수만 카운트해서 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이1&lt;/h2&gt;
&lt;pre id=&quot;code_1763336155666&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(board):
    n = len(board)
    answer = 0
    
    for i in range(n):
        for j in range(n):
            if board[i][j] == 1:
                for nx in range(max(0, i - 1), min(n, i + 2)):
                    for ny in range(max(0, j - 1), min(n, j + 2)):
                        if board[nx][ny] != 1:
                            board[nx][ny] = -1
    
    for line in board:
        answer += line.count(0)     
                
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 풀이와 비슷한 방식인데 좌표 내에서 이동하기 위해 조건문을 사용하는 것이 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`max, min`을 이용해서 처음부터 이동 범위를 제한했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이2&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(board):
    n = len(board)
    danger = set()
    
    for i in range(n):
        for j in range(n):
            if board[i][j] == 1:
                danger.update((i + dx, j + dy) for dx in [-1, 0, 1] for dy in [-1, 0, 1])
                
    return n * n - sum(0 &amp;lt;= x &amp;lt; n and 0 &amp;lt;= y &amp;lt; n for x, y in danger)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`set()`에 위험 지역을 모두 표시하는데, 이때 한 번에 여러 개의 요소를 추가할 수 있는 `set.update()`를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 지뢰에 인접한 모든 지역을 `set()`에 저장한 후, 카운트 하는 시점에 조건문으로 좌표를 벗어난 지역을 제외한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 시작하고 6개월 쯤에 처음 도전했던 문제인데 그때는 손도 못 댈 정도로 어려워했던 기억이 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 문제 보고 바로 풀 수 있어서 새삼 많이 발전했다는 생각이 든다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/591</guid>
      <comments>https://divheer.tistory.com/591#entry591comment</comments>
      <pubDate>Mon, 17 Nov 2025 08:40:05 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 무인도 여행 | 파이썬</title>
      <link>https://divheer.tistory.com/590</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/154540&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/154540&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;메리는 여름을 맞아 무인도로 여행을 가기 위해 지도를 보고 있습니다. 지도에는 바다와 무인도들에 대한 정보가 표시돼 있습니다. 지도는 1 x 1크기의 사각형들로 이루어진 직사각형 격자 형태이며, 격자의 각 칸에는 'X' 또는 1에서 9 사이의 자연수가 적혀있습니다. 지도의 'X'는 바다를 나타내며, 숫자는 무인도를 나타냅니다. 이때, 상, 하, 좌, 우로 연결되는 땅들은 하나의 무인도를 이룹니다. 지도의 각 칸에 적힌 숫자는 식량을 나타내는데, 상, 하, 좌, 우로 연결되는 칸에 적힌 숫자를 모두 합한 값은 해당 무인도에서 최대 며칠동안 머물 수 있는지를 나타냅니다. 어떤 섬으로 놀러 갈지 못 정한 메리는 우선 각 섬에서 최대 며칠씩 머물 수 있는지 알아본 후 놀러갈 섬을 결정하려 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;지도를 나타내는 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;maps가 매개변수로 주어질 때, 각 섬에서 최대 며칠씩 머무를 수 있는지 배열에 오름차순으로 담아 return 하는 solution 함수를 완성해주세요. 만약 지낼 수 있는 무인도가 없다면 -1을 배열에 담아 return 해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;maps의 길이 &amp;le; 100
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;maps[i]의 길이 &amp;le; 100&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;maps[i]는 'X' 또는 1 과 9 사이의 자연수로 이루어진 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;지도는 직사각형 형태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwudXc/dJMcabWRaI8/KJGdIO1C26dDRFkhtqgu7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwudXc/dJMcabWRaI8/KJGdIO1C26dDRFkhtqgu7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwudXc/dJMcabWRaI8/KJGdIO1C26dDRFkhtqgu7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwudXc%2FdJMcabWRaI8%2FKJGdIO1C26dDRFkhtqgu7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;483&quot; height=&quot;144&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 문자열은 다음과 같은 지도를 나타냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BE0TB/dJMcabWRaJy/OJTPfLmmWcwUVwninLdPf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BE0TB/dJMcabWRaJy/OJTPfLmmWcwUVwninLdPf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BE0TB/dJMcabWRaJy/OJTPfLmmWcwUVwninLdPf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBE0TB%2FdJMcabWRaJy%2FOJTPfLmmWcwUVwninLdPf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;406&quot; height=&quot;324&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;연결된 땅들의 값을 합치면 다음과 같으며&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw8b5R/dJMcagKCX9N/Z2nF7fktF9gwirBHcTADo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw8b5R/dJMcagKCX9N/Z2nF7fktF9gwirBHcTADo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw8b5R/dJMcagKCX9N/Z2nF7fktF9gwirBHcTADo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw8b5R%2FdJMcagKCX9N%2FZ2nF7fktF9gwirBHcTADo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;408&quot; height=&quot;330&quot; data-origin-width=&quot;408&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이를 오름차순으로 정렬하면 [1, 1, 27]이 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위 문자열은 다음과 같은 지도를 나타냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3JhhD/dJMcaaczFQ3/aVrNcTz4RwD2KkKBRDAY1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3JhhD/dJMcaaczFQ3/aVrNcTz4RwD2KkKBRDAY1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3JhhD/dJMcaaczFQ3/aVrNcTz4RwD2KkKBRDAY1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3JhhD%2FdJMcaaczFQ3%2FaVrNcTz4RwD2KkKBRDAY1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;406&quot; height=&quot;406&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;섬이 존재하지 않기 때문에 -1을 배열에 담아 반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(maps):
    maps = [list(m) for m in maps]
    n, m = len(maps), len(maps[0])
    
    moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    answer = []
    
    def check(x, y, v):
        maps[x][y] = 0

        for dx, dy in moves:
            nx, ny = x + dx, y + dy
            if 0 &amp;lt;= nx &amp;lt; n and 0 &amp;lt;= ny &amp;lt; m:
                if maps[nx][ny] != 'X' and maps[nx][ny] != 0:
                    check(nx, ny, v + int(maps[nx][ny]))
                    break
        else:            
            answer.append(v)
        
    for x in range(n):
        for y in range(m):
            if 0 &amp;lt;= x &amp;lt; n and 0 &amp;lt;= y &amp;lt; m:
                if maps[x][y] != 'X' and maps[x][y] != 0:
                    check(x, y, int(maps[x][y]))

    if answer:
        return sorted(answer)
    else:
        return [-1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 케이스 25개 중에 6개만 통과했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 무인도마다 최종 점수를 출력하기 위해 `check()`의 for문에 break를 추가하고 for-else문을 사용했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;break문 때문에 중간에 경로가 끊기는 경우가 생겨서 하나의 무인도가 나눠지는 경우가 생기는 것이 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import sys
sys.setrecursionlimit(10**6)

def solution(maps):
    maps = [list(m) for m in maps]
    n, m = len(maps), len(maps[0])
    
    moves = [(-1, 0), (1, 0), (0, -1), (0, 1)]
    answer = []
    
    def check(x, y):
        maps[x][y] = 'X'

        for dx, dy in moves:
            nx, ny = x + dx, y + dy
            
            if 0 &amp;lt;= nx &amp;lt; n and 0 &amp;lt;= ny &amp;lt; m:
                if maps[nx][ny] != 'X':
                    answer[-1] += int(maps[nx][ny])
                    check(nx, ny)
        
    for x in range(n):
        for y in range(m):
            if maps[x][y] != 'X':
                answer.append(int(maps[x][y]))
                check(x, y)

    if answer:
        return sorted(answer)
    else:
        return [-1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무인도의 값을 저장하는 방식을 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 for문을 돌면서 무인도에 진입하는 경우 `answer`에 해당 직사각형의 값을 초기값으로 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `check()`에서 무인도를 순회하면서 `answer[-1]`에 접근하여 해당 직사각형의 값을 더한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 재귀함수로 인한 런타임 에러를 해결하기 위해 `sys.setrecursionlimit(10**6)`를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 흐름은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 좌표를 돌면서 'X'가 아닌 칸을 발견하면 새로운 섬 탐색을 시작하고, 그 칸의 값(초기값)을 `answer`에 추가한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;즉, for문에서 `check(x, y)`를 실행할 때마다 새로운 무인도에 진입하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;`check(x, y)`에서 네 방향을 돌며, 'X'가 아닌 경우 식량 값을 더하며 재귀적으로 탐색하고, 방문한 위치를 'X'로 바꾸어 다시 방문하지 않게 한다.&lt;/li&gt;
&lt;li&gt;for문이 끝나면 `answer`에는 각 섬의 총 식량량이 담긴다.&lt;/li&gt;
&lt;li&gt;`answer`가 비어 있지 않으면 오름차순으로 정렬해 반환하고, 섬이 하나도 없으면 `[-1]`을 반환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/590</guid>
      <comments>https://divheer.tistory.com/590#entry590comment</comments>
      <pubDate>Sat, 15 Nov 2025 09:21:19 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 괄호 변환 | 파이썬</title>
      <link>https://divheer.tistory.com/589</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/60058&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/60058&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;카카오에 신입 개발자로 입사한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&quot;콘&quot;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;은 선배 개발자로부터 개발역량 강화를 위해 다른 개발자가 작성한 소스 코드를 분석하여 문제점을 발견하고 수정하라는 업무 과제를 받았습니다. 소스를 컴파일하여 로그를 보니 대부분 소스 코드 내 작성된 괄호가 개수는 맞지만 짝이 맞지 않은 형태로 작성되어 오류가 나는 것을 알게 되었습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;수정해야 할 소스 파일이 너무 많아서 고민하던 &quot;콘&quot;은 소스 코드에 작성된 모든 괄호를 뽑아서 올바른 순서대로 배치된 괄호 문자열을 알려주는 프로그램을 다음과 같이 개발하려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;용어의 정의&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'('&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;')'&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;로만 이루어진 문자열이 있을 경우, '(' 의 개수와 ')' 의 개수가 같다면 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;균형잡힌 괄호 문자열&lt;/b&gt;이라고 부릅니다.&lt;br /&gt;그리고 여기에 '('와 ')'의 괄호의 짝도 모두 맞을 경우에는 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;올바른 괄호 문자열&lt;/b&gt;이라고 부릅니다.&lt;br /&gt;예를 들어,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(()))(&quot;와 같은 문자열은 &quot;균형잡힌 괄호 문자열&quot; 이지만 &quot;올바른 괄호 문자열&quot;은 아닙니다.&lt;br /&gt;반면에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(())()&quot;와 같은 문자열은 &quot;균형잡힌 괄호 문자열&quot; 이면서 동시에 &quot;올바른 괄호 문자열&quot; 입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;'(' 와 ')' 로만 이루어진 문자열 w가 &quot;균형잡힌 괄호 문자열&quot; 이라면 다음과 같은 과정을 통해 &quot;올바른 괄호 문자열&quot;로 변환할 수 있습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;
&lt;pre class=&quot;lsl&quot; style=&quot;background-color: #e9ecf3; color: #44576c;&quot;&gt;&lt;code&gt;1. 입력이 빈 문자열인 경우, 빈 문자열을 반환합니다. 
2. 문자열 w를 두 &quot;균형잡힌 괄호 문자열&quot; u, v로 분리합니다. 단, u는 &quot;균형잡힌 괄호 문자열&quot;로 더 이상 분리할 수 없어야 하며, v는 빈 문자열이 될 수 있습니다. 
3. 문자열 u가 &quot;올바른 괄호 문자열&quot; 이라면 문자열 v에 대해 1단계부터 다시 수행합니다. 
  3-1. 수행한 결과 문자열을 u에 이어 붙인 후 반환합니다. 
4. 문자열 u가 &quot;올바른 괄호 문자열&quot;이 아니라면 아래 과정을 수행합니다. 
  4-1. 빈 문자열에 첫 번째 문자로 '('를 붙입니다. 
  4-2. 문자열 v에 대해 1단계부터 재귀적으로 수행한 결과 문자열을 이어 붙입니다. 
  4-3. ')'를 다시 붙입니다. 
  4-4. u의 첫 번째와 마지막 문자를 제거하고, 나머지 문자열의 괄호 방향을 뒤집어서 뒤에 붙입니다. 
  4-5. 생성된 문자열을 반환합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;균형잡힌 괄호 문자열&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;p가 매개변수로 주어질 때, 주어진 알고리즘을 수행해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;올바른 괄호 문자열&quot;&lt;/b&gt;로 변환한 결과를 return 하도록 solution 함수를 완성해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;매개변수 설명&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;p는 '(' 와 ')' 로만 이루어진 문자열이며 길이는 2 이상 1,000 이하인 짝수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;문자열 p를 이루는 '(' 와 ')' 의 개수는 항상 같습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;만약 p가 이미 &quot;올바른 괄호 문자열&quot;이라면 그대로 return 하면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZzRya/dJMcacuGfkQ/O6niThlTlN7bvEccJhxvKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZzRya/dJMcacuGfkQ/O6niThlTlN7bvEccJhxvKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZzRya/dJMcacuGfkQ/O6niThlTlN7bvEccJhxvKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZzRya%2FdJMcacuGfkQ%2FO6niThlTlN7bvEccJhxvKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;314&quot; height=&quot;191&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;br /&gt;이미 &quot;올바른 괄호 문자열&quot; 입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 문자열 u, v로 분리합니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;)(&quot;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;v =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u가 &quot;올바른 괄호 문자열&quot;이 아니므로 다음과 같이 새로운 문자열을 만듭니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;v에 대해 1단계부터 재귀적으로 수행하면 빈 문자열이 반환됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u의 앞뒤 문자를 제거하고, 나머지 문자의 괄호 방향을 뒤집으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;&quot;이 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;따라서 생성되는 문자열은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;)&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;&quot;이며, 최종적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;로 변환됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #3&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 문자열 u, v로 분리합니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;v =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;))((()&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;문자열 u가 &quot;올바른 괄호 문자열&quot;이므로 그대로 두고, v에 대해 재귀적으로 수행합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;다시 두 문자열 u, v로 분리합니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;))((&quot;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;v =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u가 &quot;올바른 괄호 문자열&quot;이 아니므로 다음과 같이 새로운 문자열을 만듭니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;v에 대해 1단계부터 재귀적으로 수행하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;이 반환됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u의 앞뒤 문자를 제거하고, 나머지 문자의 괄호 방향을 뒤집으면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;이 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;따라서 생성되는 문자열은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;)&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;이며, 최종적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(())()&quot;를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;처음에 그대로 둔 문자열에 반환된 문자열을 이어 붙이면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;(())()&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;()(())()&quot;가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import deque

def reverse_str(u):
    new = &quot;(&quot;
    for s in u:
        if s == &quot;(&quot;:
            new += &quot;)&quot;
        else:
            new += &quot;(&quot;
    new += &quot;)&quot;

    return new

def is_valid(u):
    stack = []
    
    for s in u:
        if s == '(':
            stack.append(s)
        elif not stack:
            return False
    else:
        return True

def make_valid_str(queue):
    if not queue:
        return &quot;&quot;
    
    s, e = 0, 0
    u = &quot;&quot;

    while queue:
        cur = queue.popleft()
        u += cur
        
        if cur == &quot;(&quot;:
            s += 1
        else:
            e += 1
            
        if s &amp;gt; 0 and e &amp;gt; 0 and s == e:
            break
            
    if not is_valid(u):
        u = reverse_str(u[1:-1])

    return u + make_valid_str(queue)

def solution(p):
    queue = deque(p)
    answer = make_valid_str(queue)
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;25개 중 5개를 성공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이는 `make_valid_str`에서 `u, v` 값을 구한 후 출력하는 부분과 `u`가 아닌 `v`를 `()`로 감싸려 했던 부분이 틀렸다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 친절하게 단계별로 문자열 변환 절차를 알려줬는데 큰 흐름만 훑어서 주관적으로 해석하려 했던 점이 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import deque

def reverse_str(u):
    res = &quot;&quot;
    for s in u:
        if s == &quot;(&quot;:
            res += &quot;)&quot;
        else:
            res += &quot;(&quot;

    return res

def is_valid(u):
    stack = []
    
    for s in u:
        if s == '(':
            stack.append(s)
        elif not stack:
            return False
        else:
            stack.pop()
        
    if stack:
        return False
    else:
        return True

def solution(p):
    if not p:
        return &quot;&quot;
    
    queue = deque(p)
    s, e = 0, 0
    u = &quot;&quot;

    while queue:
        cur = queue.popleft()
        u += cur
        
        if cur == &quot;(&quot;:
            s += 1
        else:
            e += 1
            
        if s &amp;gt; 0 and e &amp;gt; 0 and s == e:
            break
    
    v = solution(&quot;&quot;.join(queue))
   
    if is_valid(u):
        return u + v
    else:
        return &quot;(&quot; + v + &quot;)&quot; + reverse_str(u[1:-1])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;문제에서 제시하는 대로 `u, v`를 구한 후에&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`v`는 항상 빈 문자열이 될 때까지 재귀적으로 1단계부터 다시 수행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`u`가 올바른 문자열인지에 따라 분기 처리해서 값을 출력하도록 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에 힌트와 답이 있다! 문제 꼼꼼하게 읽기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/589</guid>
      <comments>https://divheer.tistory.com/589#entry589comment</comments>
      <pubDate>Thu, 13 Nov 2025 07:44:19 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] Middleware로 토큰 만료 시 Access Token 자동 갱신 구현하기</title>
      <link>https://divheer.tistory.com/588</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 상황: 토큰 갱신 후 쿠키 저장 중 에러 발생&lt;/h2&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Next.js에서 JWT 기반 인증을 구현하면서, 리프레시 토큰을 통해 액세스 토큰을 자동으로 재발급받는 기능을 구현하고자 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;처음에는 서버 액션에서 `getUserInfo()`를 호출하고, 액세스 토큰이 만료되어 401 에러가 발생하면 `refreshToken()` 함수를 호출해 쿠키에 새로운 토큰을 저장하는 방식으로 구성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 쿠키에 새로운 토큰을 저장하는 과정에서 다음과 같은 에러가 발생했다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-pm-slice=&quot;1 1 []&quot; data-ke-style=&quot;style3&quot;&gt;Cookies can only be modified in a Server Action or Route Handler&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이는 `refreshToken()` 함수가 다른 서버 액션(`getUserInfo`) 안에서 일반 함수처럼 호출되면서, 원래 의도대도 &quot;서버 액션&quot;으로 기능하지 않았기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; 일반 함수처럼 사용된 함수 내부에서 `cookies().set()`을 호출하자, Next.js가 허용하지 않는 방식으로 쿠키를 수정하려 했다고 판단하고 에러를 발생시킨 것이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결 방안: 미들웨어 활용&lt;/h2&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 문제를 해결하기 위해 Next.js의 미들웨어를 사용했다. &lt;/span&gt;&lt;span&gt;미들웨어는 서버 측에서 요청을 가로채어 처리할 수 있는 기능으로, 특정 경로 진입 전에 토큰을 검사하고 필요한 경우 자동으로 갱신할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;미들웨어는 서버 컴포넌트의 렌더링 이전에 동작하므로, 이 레이어에서 토큰을 갱신하고 쿠키를 설정하면 이후 컴포넌트에서는 최신 인증 상태를 바탕으로 동작할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미들웨어 토큰 갱신 흐름 요약&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-pm-slice=&quot;3 3 []&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;`/admin`&lt;/span&gt;&lt;span&gt; 경로로 진입 시 middleware 실행 ( 관리자 페이지 접근 보호 )&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; 요청 쿠키에서 &amp;nbsp;accessToken, refreshToken 추출&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; refreshToken이 없으면 `/login` 경로로 리다이렉트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;accessToken만 없거나 토큰이 만료되었다면 재발급 시도&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;토큰 재발급 성공 시 새로운 토큰을 쿠키에 저장 &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;구현 방식: 미들웨어에서 토큰 유효성 검증 및 재발급&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. 미들웨어 함수&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1762776795663&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// 미들웨어 실행 페이지
export const config = {
  matcher: ['/admin/:path*'],
};

const REDIRECT_PATH = '/login';

// 관리자 페이지 접근 시 토큰 유효성 검사 및 자동 갱신 처리
export async function middleware(request: NextRequest) {
  // 요청 쿠키에서 토큰 추출
  const accessToken = request.cookies.get('accessToken')?.value;
  const refreshToken = request.cookies.get('refreshToken')?.value;

  // 리프레시 토큰이 없으면 로그인 페이지로 이동
  if (!refreshToken) {
    console.warn('[Middleware] 리프레시 토큰 없음 &amp;rarr; 로그인 페이지로 리다이렉트');
    return redirectToLogin(request);
  }

  // 액세스 토큰이 없더라도 리프레시 토큰이 있다면 재발급 시도
  if (!accessToken) {
    console.warn('[Middleware] 액세스 토큰 없음 &amp;rarr; 리프레시 토큰으로 재발급 시도');
    const refreshed = await refreshTokens(refreshToken);
    if (refreshed) {
      return refreshed;
    }
    console.error('[Middleware] 액세스 토큰 재발급 실패 &amp;rarr; 로그인 페이지로 리다이렉트');
    return redirectToLogin(request);
  }

  // 토큰 만료 여부 확인
  const { isAccessTokenValid, isRefreshTokenValid } = isValidToken({
    accesstoken: accessToken,
    refreshtoken: refreshToken,
  });

  // 리프레시 토큰이 만료된 경우 로그아웃 처리
  if (!isRefreshTokenValid) {
    console.warn('[Middleware] 리프레시 토큰 만료 &amp;rarr; 로그인 페이지로 리다이렉트');
    return redirectToLogin(request);
  }

  // 액세스 토큰만 만료된 경우 재발급 시도
  if (!isAccessTokenValid) {
    console.info('[Middleware] 액세스 토큰 만료 &amp;rarr; 재발급 시도');
    const refreshed = await refreshTokens(refreshToken);
    if (refreshed) {
      return refreshed;
    }
    // 재발급 실패 시 로그인 페이지로 이동
    console.error('[Middleware] 액세스 토큰 재발급 실패 &amp;rarr; 로그인 페이지로 리다이렉트',);
    return redirectToLogin(request);
  }

  return NextResponse.next();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트의 요청이 들어오면 먼저 쿠키에서 토큰 값을 추출하고, 만료 여부를 검사한 뒤 조건에 따라 재발급 또는 로그인 페이지로 리다이렉트 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. JWT 유효성 확인 함수&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1762776853372&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JWT 토큰 만료 여부 검사
function isValidToken({
  accesstoken,
  refreshtoken,
}: {
  accesstoken?: string;
  refreshtoken?: string;
}): {
  isAccessTokenValid?: boolean;
  isRefreshTokenValid?: boolean;
} {
  // 현재 시간을 초 단위로 가져오기 (Unix Timestamp 형식)
  const currentTime = Math.floor(Date.now() / 1000);

  // 결과 객체를 초기화 (유효성 여부를 저장)
  const result: {
    isAccessTokenValid?: boolean;
    isRefreshTokenValid?: boolean;
  } = {};

  try {
    // 액세스 토큰이 존재하면 디코딩하여 만료 시간(`exp`) 확인
    if (accesstoken) {
      // JWT의 payload 부분(base64) 디코딩
      const accessTokenPayload = JSON.parse(atob(accesstoken.split('.')[1]));
      // 현재 시간과 만료 시간을 비교하여 유효성 판단
      result.isAccessTokenValid = accessTokenPayload.exp &amp;gt; currentTime;
    }

    // 리프레쉬 토큰이 존재하면 디코딩하여 만료 시간(`exp`) 확인
    if (refreshtoken) {
      // JWT의 payload 부분(base64) 디코딩
      const refreshTokenPayload = JSON.parse(atob(refreshtoken.split('.')[1]));
      // 현재 시간과 만료 시간을 비교하여 유효성 판단
      result.isRefreshTokenValid = refreshTokenPayload.exp &amp;gt; currentTime;
    }
  } catch (error) {
    console.error('토큰 디코딩 실패:', error);
  }

  // 유효성 결과를 반환
  return result;
}

// 로그인 페이지로 리다이렉트
function redirectToLogin(request: NextRequest) {
  return NextResponse.redirect(new URL(REDIRECT_PATH, request.url));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;JWT의 `payload`에서 `exp` 값을 디코딩하여 현재 시간과 비교함으로써 유효 여부를 판단한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;3. 토큰 재발급 함수&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1762776928779&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async function refreshTokens(
  refreshToken: string,
): Promise&amp;lt;NextResponse | null&amp;gt; {
  try {
    const apiBaseUrl = process.env.NEXT_PUBLIC_API_URL;

    if (!apiBaseUrl) {
      console.error('[Middleware] NEXT_PUBLIC_API_URL이 설정되어 있지 않습니다.');
      return null;
    }

    // 백엔드 토큰 갱신 API 호출
    const refreshResponse = await fetch(`${apiBaseUrl}/auth/refresh`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ refreshToken }),
    });

    if (!refreshResponse.ok) {
      console.error('[Middleware] 토큰 갱신 요청 실패:', refreshResponse.status);
      return null;
    }

    // API 응답에서 토큰 추출
    const json = (await refreshResponse.json()) as {
      success?: boolean;
      data?: { accessToken?: string; refreshToken?: string };
    };

    if (!json.success || !json.data?.accessToken || !json.data.refreshToken) {
      console.error('[Middleware] 토큰 갱신 응답 형식이 올바르지 않습니다.', json);
      return null;
    }

    const response = NextResponse.next();

    // 재발급된 토큰을 쿠키에 저장
    response.cookies.set('accessToken', json.data.accessToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24, // 하루
      path: '/',
    });

    response.cookies.set('refreshToken', json.data.refreshToken, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24 * 30, // 30일
      path: '/',
    });
    return response;
  } catch (error) {
    console.error('[Middleware] 토큰 갱신 중 오류 발생:', error);
    return null;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 API로 리프레시 토큰을 전송해 새 토큰을 발급받고, 이를 쿠키에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;참고 문서&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@clydehan/Next.js%EB%A1%9C-Access-Token-%EB%A7%8C%EB%A3%8C-%ED%99%95%EC%9D%B8-%EB%B0%8F-%EC%9E%AC%EB%B0%9C%EA%B8%89-%EB%B0%9B%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Next.js로&amp;nbsp;Access&amp;nbsp;Token&amp;nbsp;만료&amp;nbsp;확인&amp;nbsp;및&amp;nbsp;재발급&amp;nbsp;받기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@min_jae/duqtyjmv&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Nextjs]&amp;nbsp;Next.js&amp;nbsp;Middleware로&amp;nbsp;JWT&amp;nbsp;Access&amp;nbsp;Token&amp;nbsp;갱신하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ianlog.me/blog/2024/server-component-cookie&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nextjs&amp;nbsp;앱라우터&amp;nbsp;서버컴포넌트에서&amp;nbsp;쿠키&amp;nbsp;세팅이&amp;nbsp;안되는&amp;nbsp;이유&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>JavaScript</category>
      <category>jwt</category>
      <category>Middleware</category>
      <category>next.js</category>
      <category>리프레시토큰</category>
      <category>미들웨어</category>
      <category>액세스토큰</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/588</guid>
      <comments>https://divheer.tistory.com/588#entry588comment</comments>
      <pubDate>Mon, 10 Nov 2025 22:59:30 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 수식 최대화 | 파이썬</title>
      <link>https://divheer.tistory.com/587</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/67257&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/67257&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;IT 벤처 회사를 운영하고 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;라이언은 매년 사내 해커톤 대회를 개최하여 우승자에게 상금을 지급하고 있습니다.&lt;br /&gt;이번 대회에서는 우승자에게 지급되는 상금을 이전 대회와는 다르게 다음과 같은 방식으로 결정하려고 합니다.&lt;br /&gt;해커톤 대회에 참가하는 모든 참가자들에게는 숫자들과 3가지의 연산문자(+, -, *) 만으로 이루어진 연산 수식이 전달되며, 참가자의 미션은 전달받은 수식에 포함된 연산자의 우선순위를 자유롭게 재정의하여 만들 수 있는 가장 큰 숫자를 제출하는 것입니다.&lt;br /&gt;단, 연산자의 우선순위를 새로 정의할 때, 같은 순위의 연산자는 없어야 합니다. 즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;등과 같이 연산자 우선순위를 정의할 수 있으나&lt;span&gt;&amp;nbsp;&lt;/span&gt;+,*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+,-처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다. 수식에 포함된 연산자가 2개라면 정의할 수 있는 연산자 우선순위 조합은 2! = 2가지이며, 연산자가 3개라면 3! = 6가지 조합이 가능합니다.&lt;br /&gt;만약 계산된 결과가 음수라면 해당 숫자의 절댓값으로 변환하여 제출하며 제출한 숫자가 가장 큰 참가자를 우승자로 선정하며, 우승자가 제출한 숫자를 우승상금으로 지급하게 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 참가자 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;네오가 아래와 같은 수식을 전달받았다고 가정합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;100-200*300-500+20&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 수학 및 전산학에서 약속된 연산자 우선순위에 따르면 더하기와 빼기는 서로 동등하며 곱하기는 더하기, 빼기에 비해 우선순위가 높아&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+,-&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 우선순위가 정의되어 있습니다.&lt;br /&gt;대회 규칙에 따라&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;등과 같이 연산자 우선순위를 정의할 수 있으나&lt;span&gt;&amp;nbsp;&lt;/span&gt;+,*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+,-&lt;span&gt;&amp;nbsp;&lt;/span&gt;처럼 2개 이상의 연산자가 동일한 순위를 가지도록 연산자 우선순위를 정의할 수는 없습니다.&lt;br /&gt;수식에 연산자가 3개 주어졌으므로 가능한 연산자 우선순위 조합은 3! = 6가지이며, 그 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 연산자 우선순위를 정한다면 결괏값은 22,000원이 됩니다.&lt;br /&gt;반면에&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 연산자 우선순위를 정한다면 수식의 결괏값은 -60,420 이지만, 규칙에 따라 우승 시 상금은 절댓값인 60,420원이 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;참가자에게 주어진 연산 수식이 담긴 문자열 expression이 매개변수로 주어질 때, 우승 시 받을 수 있는 가장 큰 상금 금액을 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;expression은 길이가 3 이상 100 이하인 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;expression은 공백문자, 괄호문자 없이 오로지 숫자와 3가지의 연산자(+, -, *) 만으로 이루어진 올바른 중위표기법(연산의 두 대상 사이에 연산기호를 사용하는 방식)으로 표현된 연산식입니다. 잘못된 연산식은 입력으로 주어지지 않습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;402+-561*&quot;처럼 잘못된 수식은 올바른 중위표기법이 아니므로 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;expression의 피연산자(operand)는 0 이상 999 이하의 숫자입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;즉,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;100-2145*458+12&quot;처럼 999를 초과하는 피연산자가 포함된 수식은 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;-56+100&quot;처럼 피연산자가 음수인 수식도 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;expression은 적어도 1개 이상의 연산자를 포함하고 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;연산자 우선순위를 어떻게 적용하더라도, expression의 중간 계산값과 최종 결괏값은 절댓값이 263&lt;span&gt;&amp;nbsp;&lt;/span&gt;- 1 이하가 되도록 입력이 주어집니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;같은 연산자끼리는 앞에 있는 것의 우선순위가 더 높습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MNDMh/dJMb8Ywv5wD/H7WBzKxnETBfuvYDgBkaZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MNDMh/dJMb8Ywv5wD/H7WBzKxnETBfuvYDgBkaZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MNDMh/dJMb8Ywv5wD/H7WBzKxnETBfuvYDgBkaZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMNDMh%2FdJMb8Ywv5wD%2FH7WBzKxnETBfuvYDgBkaZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;351&quot; height=&quot;141&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;br /&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 연산자 우선순위를 정했을 때, 가장 큰 절댓값을 얻을 수 있습니다.&lt;br /&gt;연산 순서는 아래와 같습니다.&lt;br /&gt;100-200*300-500+20&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;100-(200*300)-500+20&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;100-60000-(500+20)&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;(100-60000)-520&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;(-59900-520)&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;-60420&lt;br /&gt;따라서, 우승 시 받을 수 있는 상금은 |-60420| = 60420 입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;br /&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 연산자 우선순위를 정했을 때, 가장 큰 절댓값을 얻을 수 있습니다.&lt;br /&gt;연산 순서는 아래와 같습니다.(expression에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;연산자는 나타나지 않았으므로, 고려할 필요가 없습니다.)&lt;br /&gt;50*6-3*2&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;50*(6-3)*2&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;(50*3)*2&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;150*2&lt;br /&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;300&lt;br /&gt;따라서, 우승 시 받을 수 있는 상금은 300 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from itertools import permutations

def solution(expression):
    ops = ['*', '+', '-']
    order = permutations(ops, 3)
    
    arr = []
    answer = 0
    
    op_index = 0
    
    # 숫자와 연산자 분리하기
    for i in range(len(expression)):
        if expression[i] in ops:
            arr += [int(expression[op_index:i]), expression[i]]
            op_index = i + 1
            
    arr.append(int(expression[op_index:]))
    
    # 경우의 수에 따라 최대값 계산하기
    for per in order:
        temp = arr[:]
        for op in per:
            for i in range(len(temp)):
                if temp[i] == op:
                    if op == '*':
                        temp[i-1:i+2] = ['/', '/', temp[i-1] * temp[i+1]]
                    elif op == '+':
                        temp[i-1:i+2] = ['/', '/', temp[i-1] + temp[i+1]]
                    elif op == '-':
                        temp[i-1:i+2] = ['/', '/', temp[i-1] - temp[i+1]]
            temp = [x for x in temp if x != '/']
            
        answer = max(answer, abs(temp[0]))            

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;291&quot; data-start=&quot;137&quot; data-ke-size=&quot;size16&quot;&gt;먼저 문자열 형태로 주어진 계산식을 &lt;b&gt;숫자와 연산자로 분리&lt;/b&gt;해 리스트에 저장한다.&lt;br /&gt;이를 위해 인덱스를 추적하면서 숫자가 끝날 때마다 리스트에 `[숫자, 연산자]` 순서로 추가하고,&lt;br /&gt;마지막 숫자는 따로 추가해 전체 수식을 숫자와 기호가 번갈아 있는 형태로 만든다.&lt;/p&gt;
&lt;p data-end=&quot;291&quot; data-start=&quot;137&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;392&quot; data-start=&quot;293&quot; data-ke-size=&quot;size16&quot;&gt;그다음, 세 연산자 `*, +, -`의 모든 우선순위 경우의 수를 구해서&lt;br /&gt;각 조합별로 계산을 수행하며 가능한 결과 중 절댓값의 최대값을 찾는다.&lt;/p&gt;
&lt;p data-end=&quot;392&quot; data-start=&quot;293&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;542&quot; data-start=&quot;394&quot; data-ke-size=&quot;size16&quot;&gt;연산은 우선순위가 높은 연산자부터 순서대로 진행하며,&lt;br /&gt;계산된 구간은 `/` 문자열로 임시 표시한 뒤,&lt;br /&gt;하나의 연산자 계산이 끝날 때마다 `/`를 제거해 리스트를 정리한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;615&quot; data-start=&quot;544&quot; data-ke-size=&quot;size16&quot;&gt;모든 우선순위 경우의 수에 따라 계산해 본 뒤,&lt;br /&gt;결과값들의 절댓값 중 가장 큰 값을 `answer`로 갱신해서 최종적으로 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/587</guid>
      <comments>https://divheer.tistory.com/587#entry587comment</comments>
      <pubDate>Tue, 28 Oct 2025 08:04:22 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 삼각 달팽이 | 파이썬</title>
      <link>https://divheer.tistory.com/586</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/68645&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/68645&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;정수 n이 매개변수로 주어집니다. 다음 그림과 같이 밑변의 길이와 높이가 n인 삼각형에서 맨 위 꼭짓점부터 반시계 방향으로 달팽이 채우기를 진행한 후, 첫 행부터 마지막 행까지 모두 순서대로 합친 새로운 배열을 return 하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVphhb/dJMb9XYEo4a/s2TXTjxauI33kuQya95NSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVphhb/dJMb9XYEo4a/s2TXTjxauI33kuQya95NSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVphhb/dJMb9XYEo4a/s2TXTjxauI33kuQya95NSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVphhb%2FdJMb9XYEo4a%2Fs2TXTjxauI33kuQya95NSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1189&quot; height=&quot;525&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;n은 1 이상 1,000 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr76VR/dJMb9LqsqVL/aiBDNRxk1FT8zAA5z0cjSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr76VR/dJMb9LqsqVL/aiBDNRxk1FT8zAA5z0cjSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr76VR/dJMb9LqsqVL/aiBDNRxk1FT8zAA5z0cjSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr76VR%2FdJMb9LqsqVL%2FaiBDNRxk1FT8zAA5z0cjSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;188&quot; data-origin-width=&quot;688&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(n):
    snail = [[0] * i for i in range(1, n + 1)]
    moves = [(1, 0), (0, 1), (-1, -1)]
    
    turns = [0]
    for i in range(n, 0, -1):
        turns.append(turns[-1] + i)
    
    r, c, j = -1, 0, 0
    for i in range(1, n + 1):
        dr, dc = moves[j]
        
        for num in range(turns[i - 1] + 1, turns[i] + 1):  
            r += dr
            c += dc
            snail[r][c] = num
            
        j = (j + 1) % 3
    
    answer = []
    for arr in snail:
        answer += arr
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`moves`에 아래, 오른쪽, 위로 움직이는 세 가지 이동 방향을 순서대로 저장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`turns`에는 한 번 꺾을 때마다 이동 횟수가 `n, n-1, n-2, &amp;hellip;`씩 줄어드는 규칙을 이용해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누적합 형태로 꺾이는 시점을 미리 저장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`turns`를 기준으로 각 구간이 끝날 때마다 `moves`의 방향을 전환하며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달팽이 모양으로 숫자를 채워나가도록 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/586</guid>
      <comments>https://divheer.tistory.com/586#entry586comment</comments>
      <pubDate>Fri, 24 Oct 2025 08:01:39 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Android 16KB 페이지 크기 지원 대응 방법</title>
      <link>https://divheer.tistory.com/585</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #424242; text-align: left;&quot;&gt;구글 플레이 16KB 페이지 크기 요구사항&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v7rhU/dJMb8Vs05XI/sCZkQaUVM9kt47HfK7X3T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v7rhU/dJMb8Vs05XI/sCZkQaUVM9kt47HfK7X3T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v7rhU/dJMb8Vs05XI/sCZkQaUVM9kt47HfK7X3T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv7rhU%2FdJMb8Vs05XI%2FsCZkQaUVM9kt47HfK7X3T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;331&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #424242; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 Android 15 이상을 타겟팅하는 모든 앱은 16KB 페이지 사이즈를 지원해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #424242; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2025년 11월 1일부터는 16KB 페이지 사이즈를 지원하지 않는 앱은 Google Play에 업데이트를 출시할 수 없다고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #424242; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot; data-text=&quot;APK Analyzer를 사용하여 네이티브 라이브러리 식별&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;16KB 페이지 크기 지원의 장점&lt;/span&gt;&lt;/h2&gt;
&lt;p data-end=&quot;405&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 안드로이드에서 4KB 메모리 페이지 크기만 지원했지만,&lt;br /&gt;안드로이드 15부터는 16KB 페이지 크기를 사용하는 기기를 지원하게 되었기 때문에&lt;br /&gt;앱이 이러한 16KB 기기에서도 정상적으로 작동하도록 다시 빌드해야 한다고 한다.&lt;/p&gt;
&lt;p data-end=&quot;405&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;492&quot; data-start=&quot;407&quot; data-ke-size=&quot;size16&quot;&gt;16KB 페이지 크기로 구성된 기기는 평균적으로 약간 더 많은 메모리를 사용하지만,&lt;br /&gt;시스템과 앱 모두에서 다양한 &lt;b&gt;성능 개선 효과&lt;/b&gt;가 확인되었다고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;674&quot; data-start=&quot;494&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;555&quot; data-start=&quot;494&quot;&gt;시스템 메모리 부족 시 앱 실행 시간 평균 3.16% 단축, 일부 앱은 최대 30% 개선&lt;/li&gt;
&lt;li data-end=&quot;581&quot; data-start=&quot;556&quot;&gt;전력 소모 평균 4.56% 감소&lt;/li&gt;
&lt;li data-end=&quot;637&quot; data-start=&quot;582&quot;&gt;카메라 실행 속도 향상: 핫 스타트 4.48%, 콜드 스타트 6.60% 빨라짐&lt;/li&gt;
&lt;li data-end=&quot;674&quot; data-start=&quot;638&quot;&gt;시스템 부팅 시간 평균 8% (약 950ms) 단축&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;759&quot; data-start=&quot;676&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;identify-native-code&quot; style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot; data-text=&quot;APK Analyzer를 사용하여 네이티브 라이브러리 식별&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;APK Analyzer로 네이티브 라이브러리 확인하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 어떤 라이브러리가 문제가 되는지 확인하기 위해 Android Studio의 &lt;b&gt;APK Analyzer&lt;/b&gt; 기능을 사용했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. Android Studio &amp;gt; File &amp;gt; Open을 클릭한 후 프로젝트를 선택한다.&lt;br /&gt;2. 상단 메뉴에서 Build &amp;gt; Analyze APK...을 클릭한다.&lt;br /&gt;3. 분석하려는 APK 파일을 선택한다.&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqEyB8/dJMb81msSg2/kkBPSINnGxUumZTPISlNl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqEyB8/dJMb81msSg2/kkBPSINnGxUumZTPISlNl0/img.png&quot; data-alt=&quot;경고 메시지 뜨는 화면 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqEyB8/dJMb81msSg2/kkBPSINnGxUumZTPISlNl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqEyB8%2FdJMb81msSg2%2FkkBPSINnGxUumZTPISlNl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;493&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;경고 메시지 뜨는 화면 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 도구를 통해 `Alignment` 항목에서 정렬 문제를 바로 확인할 수 있다고 한다.&lt;br /&gt;하지만 내 경우에는 별다른 경고 메시지가 표시되지 않아서 어디를 수정해야 할지 몰라 더 혼란스러웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;구글 플레이 콘솔에서 최신 번들 세부정보 확인하기&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCvWQ/dJMb9P7sYtL/pOKxolZKJRFIF8qHv1xeX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCvWQ/dJMb9P7sYtL/pOKxolZKJRFIF8qHv1xeX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCvWQ/dJMb9P7sYtL/pOKxolZKJRFIF8qHv1xeX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCvWQ%2FdJMb9P7sYtL%2FpOKxolZKJRFIF8qHv1xeX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;850&quot; height=&quot;225&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;구&lt;/span&gt;&lt;span&gt;글링을 하던 중에 레딧에서 유용한 댓글을 하나 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 플레이 콘솔의 앱 번들 페이지에서 어떤 라이브러리가 문제인지 세부정보를 확인할 수 있다는 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7aqzQ/dJMb9VzLtCW/KQIMPjOhkK4sxdfKWnp5C0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7aqzQ/dJMb9VzLtCW/KQIMPjOhkK4sxdfKWnp5C0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7aqzQ/dJMb9VzLtCW/KQIMPjOhkK4sxdfKWnp5C0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7aqzQ%2FdJMb9VzLtCW%2FKQIMPjOhkK4sxdfKWnp5C0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1130&quot; height=&quot;129&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;실제로 `테스트 및 출시 &amp;gt; 최신 버전 및 번들`로 가서 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;최신 번들 세부정보 페이지 하단에서 `메모리 페이지 크기`라는 항목을 찾았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;압축되지 않은 네이티브 라이브러리가 앱 내에서 16KB로 압축정렬(zipalign)되지 않았습니다. 이 문제를 해결하려면 AGP 구성을 수정하세요.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;세부정보에서 안내하는 대로 AGP 구성을 수정하기로 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;agp_version_851_or_higher&quot; style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot; data-text=&quot;AGP 버전 8.5.1 이상&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;AGP 버전 업그레이드&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1761218589388&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// android/settings.gradle
plugins {
    id &quot;com.android.application&quot; version &quot;8.5.1&quot; apply false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공식 문서에서는 앱을 16KB로 정렬하기 위해 &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;&lt;b&gt;AGP 버전을 8.5.1 이상으로 업그레이드&lt;/b&gt;하는 것을 권장하고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761218657768&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// android/app/build.gradle
android {
  ...
  packagingOptions {
      jniLibs {
        useLegacyPackaging true
      }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;만약 &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;AGP를 버전 8.5.1 이상으로 업그레이드할 수 없는 경우에는&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;앱을 패키징할 때 Gradle이 공유 라이브러리를 압축하도록 설정할 수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정책 위반 해결 완료&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHMnjG/dJMb9XElCHK/FaicqHoZKEIlGo9PyLlYP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHMnjG/dJMb9XElCHK/FaicqHoZKEIlGo9PyLlYP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHMnjG/dJMb9XElCHK/FaicqHoZKEIlGo9PyLlYP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHMnjG%2FdJMb9XElCHK%2FFaicqHoZKEIlGo9PyLlYP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;341&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;711&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;AGP를 8.5.1로 업그레이드한&lt;/b&gt; 후에 다행히 정책 위반이 해결되었다는 알림을 받았다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/guide/practices/page-sizes?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt; &lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;16KB 페이지 크기 지원&lt;/span&gt; &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/expo/comments/1nep9sc/app_must_support_16_kb_memory_page_sizes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; text-align: start;&quot;&gt;App&amp;nbsp;must&amp;nbsp;support&amp;nbsp;16&amp;nbsp;KB&amp;nbsp;memory&amp;nbsp;page&amp;nbsp;sizes????&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>16KB</category>
      <category>Android</category>
      <category>Flutter</category>
      <category>googleplay</category>
      <category>구글플레이</category>
      <category>안드로이드</category>
      <category>앱개발</category>
      <category>플러터</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/585</guid>
      <comments>https://divheer.tistory.com/585#entry585comment</comments>
      <pubDate>Thu, 23 Oct 2025 21:04:39 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 주차 요금 계산 | 파이썬</title>
      <link>https://divheer.tistory.com/584</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/92341&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/92341&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주차장의 요금표와 차량이 들어오고(입차) 나간(출차) 기록이 주어졌을 때, 차량별로 주차 요금을 계산하려고 합니다. 아래는 하나의 예시를 나타냅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;요금표&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기본 시간(분)&lt;/td&gt;
&lt;td&gt;기본 요금(원)&lt;/td&gt;
&lt;td&gt;단위 시간(분)&lt;/td&gt;
&lt;td&gt;단위 요금(원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;180&lt;/td&gt;
&lt;td&gt;5000&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입/출차 기록&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 210px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;시각(시:분)&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;차량 번호&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;내역&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;05:34&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;5961&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;입차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;06:00&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;0000&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;입차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;06:34&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;0000&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;출차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;07:59&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;5961&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;출차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;07:59&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;0148&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;입차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;18:59&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;0000&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;입차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;19:09&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;0148&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;출차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;22:59&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;5961&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;입차&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;23:00&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;5961&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;출차&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자동차별 주차 요금&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;차량 번호&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;누적 주차 시간(분)&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;주차 요금(원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;0000&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;34 + 300 = 334&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;5000 +&amp;nbsp;&amp;lceil;(334 - 180) / 10&amp;rceil;&amp;nbsp;x 600 = 14600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;0148&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;670&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;5000 +&amp;lceil;(670 - 180) / 10&amp;rceil;x 600 = 34400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;5961&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;145 + 1 = 146&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;5000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 차량이 입차된 후에 출차된 내역이 없다면, 23:59에 출차된 것으로 간주합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;0000번 차량은 18:59에 입차된 이후, 출차된 내역이 없습니다. 따라서, 23:59에 출차된 것으로 간주합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;00:00부터 23:59까지의 입/출차 내역을 바탕으로 차량별 누적 주차 시간을 계산하여 요금을 일괄로 정산합니다.&lt;/li&gt;
&lt;li&gt;누적 주차 시간이&amp;nbsp;기본 시간이하라면,&amp;nbsp;기본 요금을 청구합니다.&lt;/li&gt;
&lt;li&gt;누적 주차 시간이&amp;nbsp;기본 시간을 초과하면,&amp;nbsp;기본 요금에 더해서, 초과한 시간에 대해서&amp;nbsp;단위 시간&amp;nbsp;마다&amp;nbsp;단위 요금을 청구합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초과한 시간이&amp;nbsp;단위 시간으로 나누어 떨어지지 않으면,&amp;nbsp;올림합니다.&lt;/li&gt;
&lt;li&gt;&amp;lceil;a&amp;rceil;&amp;nbsp;: a보다 작지 않은 최소의 정수를 의미합니다. 즉,&amp;nbsp;올림을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주차 요금을 나타내는 정수 배열&amp;nbsp;fees, 자동차의 입/출차 내역을 나타내는 문자열 배열&amp;nbsp;records가 매개변수로 주어집니다.&amp;nbsp;&lt;b&gt;차량 번호가 작은 자동차부터&lt;/b&gt;&amp;nbsp;청구할 주차 요금을 차례대로 정수 배열에 담아서 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;fees의 길이 = 4
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;fees[0] =&lt;span&gt;&amp;nbsp;&lt;/span&gt;기본 시간(분)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; fees[0] &amp;le; 1,439&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;fees[1] =&lt;span&gt;&amp;nbsp;기본 요금(원)&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0 &amp;le; fees[1] &amp;le; 100,000&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;fees[2] =&lt;span&gt;&amp;nbsp;&lt;/span&gt;단위 시간(분)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; fees[2] &amp;le; 1,439&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;fees[3] =&lt;span&gt;&amp;nbsp;&lt;/span&gt;단위 요금(원)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le; fees[3] &amp;le; 10,000&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;records의 길이 &amp;le; 1,000
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;records의 각 원소는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;시각 차량번호 내역&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식의 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;시각,&lt;span&gt;&amp;nbsp;&lt;/span&gt;차량번호,&lt;span&gt;&amp;nbsp;&lt;/span&gt;내역은 하나의 공백으로 구분되어 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;시각은 차량이 입차되거나 출차된 시각을 나타내며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;HH:MM&lt;span&gt;&amp;nbsp;&lt;/span&gt;형식의 길이 5인 문자열입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;HH:MM은 00:00부터 23:59까지 주어집니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;잘못된 시각(&quot;25:22&quot;, &quot;09:65&quot; 등)은 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;차량번호는 자동차를 구분하기 위한, `0'~'9'로 구성된 길이 4인 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;내역은 길이 2 또는 3인 문자열로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;IN&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;OUT입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;IN은 입차를,&lt;span&gt;&amp;nbsp;&lt;/span&gt;OUT은 출차를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;records의 원소들은 시각을 기준으로 오름차순으로 정렬되어 주어집니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;records는 하루 동안의 입/출차된 기록만 담고 있으며, 입차된 차량이 다음날 출차되는 경우는 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;같은 시각에, 같은 차량번호의 내역이 2번 이상 나타내지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;마지막 시각(23:59)에 입차되는 경우는 입력으로 주어지지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;아래의 예를 포함하여, 잘못된 입력은 주어지지 않습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;주차장에 없는 차량이 출차되는 경우&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;주차장에 이미 있는 차량(차량번호가 같은 차량)이 다시 입차되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXqNry/btsRaRLjyd4/qzCARy5SiTDhXUPKoNrci0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXqNry/btsRaRLjyd4/qzCARy5SiTDhXUPKoNrci0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXqNry/btsRaRLjyd4/qzCARy5SiTDhXUPKoNrci0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXqNry%2FbtsRaRLjyd4%2FqzCARy5SiTDhXUPKoNrci0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1152&quot; height=&quot;285&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import defaultdict
import math

def solution(fees, records):
    free_time, free_fee, minute, fee = fees
    cur = defaultdict(int)
    total = defaultdict(int)
    answer = []
    
    for record in records:
        time, car, type = record.split()
        h, m = map(int, time.split(':'))
        cur_minute = (h * 60) + m
        
        if type == 'IN':
            cur[car] = cur_minute
        else:
            total[car] += cur_minute - cur[car]
            cur[car] = -1
    
    for car in cur:
        if cur[car] != -1:
            total[car] += ((23 * 60) + 59) - cur[car]

        total_fee = free_fee
        if total[car] &amp;gt; free_time:
            total_fee += math.ceil((total[car] - free_time) / minute) * fee
        
        answer.append((int(car), int(total_fee)))
    
    return [x[1] for x in sorted(answer)]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`cur` 딕셔너리에는 현재 이용 시간을 분 단위로 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 입차 후 출차까지 이루어진 경우에는 `-1`로 따로 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `total` 딕셔너리에는 누적 시간을 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출차를 모두 기록하고 난 후 `cur` 딕셔너리를 탐색하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 `-1`이 아닌 경우, 즉 입차만 하고 출차한 기록이 없는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;23시 59분에 출차한 것으로 간주하고 누적 시간을 더해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`answer` 리스트에 ` (차량 번호, 주차 요금)` 튜플 형태로 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 `answer` 리스트를 차량 번호 오름차순으로 정렬한 후 주차 요금만 추출해서 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/584</guid>
      <comments>https://divheer.tistory.com/584#entry584comment</comments>
      <pubDate>Thu, 16 Oct 2025 08:41:09 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 키패드 누르기 | 파이썬</title>
      <link>https://divheer.tistory.com/583</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/67256&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/67256&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;스마트폰 전화 키패드의 각 칸에 다음과 같이 숫자들이 적혀 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUJ8Y3/btsQ7zxvyel/mphbwpY3VzCs187K25DjnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUJ8Y3/btsQ7zxvyel/mphbwpY3VzCs187K25DjnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUJ8Y3/btsQ7zxvyel/mphbwpY3VzCs187K25DjnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUJ8Y3%2FbtsQ7zxvyel%2FmphbwpY3VzCs187K25DjnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;457&quot; height=&quot;1000&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.&lt;br /&gt;맨 처음 왼손 엄지손가락은&lt;span&gt;&amp;nbsp;&lt;/span&gt;*&lt;span&gt;&amp;nbsp;&lt;/span&gt;키패드에 오른손 엄지손가락은&lt;span&gt;&amp;nbsp;&lt;/span&gt;#&lt;span&gt;&amp;nbsp;&lt;/span&gt;키패드 위치에서 시작하며, 엄지손가락을 사용하는 규칙은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;왼쪽 열의 3개의 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;4,&lt;span&gt;&amp;nbsp;&lt;/span&gt;7을 입력할 때는 왼손 엄지손가락을 사용합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;오른쪽 열의 3개의 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;3,&lt;span&gt;&amp;nbsp;&lt;/span&gt;6,&lt;span&gt;&amp;nbsp;&lt;/span&gt;9를 입력할 때는 오른손 엄지손가락을 사용합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;가운데 열의 4개의 숫자&lt;span&gt;&amp;nbsp;&lt;/span&gt;2,&lt;span&gt;&amp;nbsp;&lt;/span&gt;5,&lt;span&gt;&amp;nbsp;&lt;/span&gt;8,&lt;span&gt;&amp;nbsp;&lt;/span&gt;0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.&lt;br /&gt;4-1. 만약 두 엄지손가락의 거리가 같다면, 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;numbers 배열의 크기는 1 이상 1,000 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;hand는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;left&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;또는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;right&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;left&quot;는 왼손잡이,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;right&quot;는 오른손잡이를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;왼손 엄지손가락을 사용한 경우는&lt;span&gt;&amp;nbsp;&lt;/span&gt;L, 오른손 엄지손가락을 사용한 경우는&lt;span&gt;&amp;nbsp;&lt;/span&gt;R을 순서대로 이어붙여 문자열 형태로 return 해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFZJ0a/btsQ9YJg7we/g2yaMgooHO5KOB5yNcJTY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFZJ0a/btsQ9YJg7we/g2yaMgooHO5KOB5yNcJTY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFZJ0a/btsQ9YJg7we/g2yaMgooHO5KOB5yNcJTY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFZJ0a%2FbtsQ9YJg7we%2Fg2yaMgooHO5KOB5yNcJTY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;585&quot; height=&quot;188&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;keypad = [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

def get_row(n):
    for i, line in enumerate(keypad):
        if n in line:
            return i

def solution(numbers, hand):
    keypad = [2, 5, 8, 11]
    column = [3, 1, 2]
    
    l, r = 10, 12
    answer = ''
    
    for n in numbers:   
        if n == 0:
            n = 11
      
        if n in [1, 4, 7]:
            answer += 'L'
            l = n
        elif n in [3, 6, 9]:
            answer += 'R'
            r = n
        else:
            next_row = keypad.index(n)
            next_column = column[n % 3]
            
            left = abs(get_row(l) - next_row) + abs(column[l % 3] - next_column)
            right = abs(get_row(r) - next_row) + abs(column[r % 3] - next_column)
            
            if left == right:
                if hand == 'right':
                    answer += 'R'
                    r = n
                else:
                    answer += 'L'
                    l = n
            elif left &amp;gt; right:
                answer += 'R'
                r = n
            else:
                answer += 'L'
                l = n
            
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키패드 상에서 두 개의 숫자 간의 거리를 구하는 것이 관건이었던 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해서 a, b 두 숫자 간에 행&amp;middot;열의 차이를 각각 구해서 더해주는 방식으로 거리를 계산했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 숫자의 열과 행의 위치를 비교할 수 있는 방식으로 찾기 위해 고민을 많이 했던 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(numbers, hand):
    dict = {1: (1, 1), 2: (1, 2), 3: (1, 3), 4: (2, 1), 5: (2, 2), 6: (2, 3), 7: (3, 1), 8: (3, 2), 9: (3, 3), '*': (4, 1), 0: (4, 2), '#': (4, 3)}
    
    l, r = '*', '#'
    answer = ''
    
    for n in numbers:   
        if n in [1, 4, 7]:
            answer += 'L'
            l = n
        elif n in [3, 6, 9]:
            answer += 'R'
            r = n
        else:
            next_row, next_column = dict[n]
            left = abs(dict[l][0] - next_row) + abs(dict[l][1] - next_column)
            right = abs(dict[r][0] - next_row) + abs(dict[r][1] - next_column)
            
            if left == right:
                if hand == 'right':
                    answer += 'R'
                    r = n
                else:
                    answer += 'L'
                    l = n
            elif left &amp;gt; right:
                answer += 'R'
                r = n
            else:
                answer += 'L'
                l = n
            
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 숫자의 좌표를 담은 딕셔너리를 생성해서 키로 접근해 거리를 간단하게 계산할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/583</guid>
      <comments>https://divheer.tistory.com/583#entry583comment</comments>
      <pubDate>Tue, 14 Oct 2025 09:59:26 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 순위 검색 | 파이썬</title>
      <link>https://divheer.tistory.com/582</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72412&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/72412&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[본 문제는 정확성과 효율성 테스트 각각 점수가 있는 문제입니다.]&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;카카오는 하반기 경력 개발자 공개채용을 진행 중에 있으며 현재 지원서 접수와 코딩테스트가 종료되었습니다. 이번 채용에서 지원자는 지원서 작성 시 아래와 같이 4가지 항목을 반드시 선택하도록 하였습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;코딩테스트 참여 개발언어 항목에 cpp, java, python 중 하나를 선택해야 합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;지원 직군 항목에 backend와 frontend 중 하나를 선택해야 합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;지원 경력구분 항목에 junior와 senior 중 하나를 선택해야 합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선호하는 소울푸드로 chicken과 pizza 중 하나를 선택해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;인재영입팀에 근무하고 있는&lt;span&gt;&amp;nbsp;&lt;/span&gt;니니즈는 코딩테스트 결과를 분석하여 채용에 참여한 개발팀들에 제공하기 위해 지원자들의 지원 조건을 선택하면 해당 조건에 맞는 지원자가 몇 명인 지 쉽게 알 수 있는 도구를 만들고 있습니다.&lt;br /&gt;예를 들어, 개발팀에서 궁금해하는 문의사항은 다음과 같은 형태가 될 수 있습니다.&lt;br /&gt;코딩테스트에 java로 참여했으며, backend 직군을 선택했고, junior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 50점 이상 받은 지원자는 몇 명인가?&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;물론 이 외에도 각 개발팀의 상황에 따라 아래와 같이 다양한 형태의 문의가 있을 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;코딩테스트에 python으로 참여했으며, frontend 직군을 선택했고, senior 경력이면서, 소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;코딩테스트에 cpp로 참여했으며, senior 경력이면서, 소울푸드로 pizza를 선택한 사람 중 코딩테스트 점수를 100점 이상 받은 사람은 모두 몇 명인가?&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;backend 직군을 선택했고, senior 경력이면서 코딩테스트 점수를 200점 이상 받은 사람은 모두 몇 명인가?&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;소울푸드로 chicken을 선택한 사람 중 코딩테스트 점수를 250점 이상 받은 사람은 모두 몇 명인가?&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;코딩테스트 점수를 150점 이상 받은 사람은 모두 몇 명인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 개발팀에서 궁금해하는 내용은 다음과 같은 형태를 갖습니다.&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;
&lt;pre class=&quot;asciidoc&quot; style=&quot;background-color: #e9ecf3; color: #44576c;&quot;&gt;&lt;code&gt;* [조건]을 만족하는 사람 중 코딩테스트 점수를 X점 이상 받은 사람은 모두 몇 명인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;지원자가 지원서에 입력한 4가지의 정보와 획득한 코딩테스트 점수를 하나의 문자열로 구성한 값의 배열 info, 개발팀이 궁금해하는 문의조건이 문자열 형태로 담긴 배열 query가 매개변수로 주어질 때,&lt;br /&gt;각 문의조건에 해당하는 사람들의 숫자를 순서대로 배열에 담아 return 하도록 solution 함수를 완성해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;info 배열의 크기는 1 이상 50,000 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;info 배열 각 원소의 값은 지원자가 지원서에 입력한 4가지 값과 코딩테스트 점수를 합친 &quot;개발언어 직군 경력 소울푸드 점수&quot; 형식입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;개발언어는 cpp, java, python 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;직군은 backend, frontend 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;경력은 junior, senior 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;소울푸드는 chicken, pizza 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;점수는 코딩테스트 점수를 의미하며, 1 이상 100,000 이하인 자연수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 단어는 공백문자(스페이스 바) 하나로 구분되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;query 배열의 크기는 1 이상 100,000 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;query의 각 문자열은 &quot;[조건] X&quot; 형식입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[조건]은 &quot;개발언어 and 직군 and 경력 and 소울푸드&quot; 형식의 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;언어는 cpp, java, python, - 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;직군은 backend, frontend, - 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;경력은 junior, senior, - 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;소울푸드는 chicken, pizza, - 중 하나입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;'-' 표시는 해당 조건을 고려하지 않겠다는 의미입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;X는 코딩테스트 점수를 의미하며 조건을 만족하는 사람 중 X점 이상 받은 사람은 모두 몇 명인 지를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 단어는 공백문자(스페이스 바) 하나로 구분되어 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;예를 들면, &quot;cpp and - and senior and pizza 500&quot;은 &quot;cpp로 코딩테스트를 봤으며, 경력은 senior 이면서 소울푸드로 pizza를 선택한 지원자 중 코딩테스트 점수를 500점 이상 받은 사람은 모두 몇 명인가?&quot;를 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHTob/btsQ3nqFr2S/mSuItgwuJNo9tG1HoekxUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHTob/btsQ3nqFr2S/mSuItgwuJNo9tG1HoekxUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHTob/btsQ3nqFr2S/mSuItgwuJNo9tG1HoekxUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHTob%2FbtsQ3nqFr2S%2FmSuItgwuJNo9tG1HoekxUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1379&quot; height=&quot;199&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;199&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이 (정확성O / 효율성X)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(info, query):
    answer = []
    
    for query_str in query:
        b1, _, b2, _, b3, _, b4, b5 = query_str.split()
        cnt = 0
        
        for info_str in info:
            for q in [b1, b2, b3, b4, b5]:
                if q == '-':
                    continue
                elif q.isdigit():
                    if int(info_str.split()[4]) &amp;lt; int(q):
                        break
                elif q not in info_str:
                    break       
            else: 
                cnt += 1
             
        answer.append(cnt)    

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from itertools import combinations

def solution(info, query):
    answer = []
    info_hash = {}
    
    # info 전처리
    for i in info:
        splited_info = i.split()
        score = splited_info.pop()
        
        for j in range(5):
            combi = combinations(range(4), j)
            
            for com in combi:
                key = splited_info[:]
                
                for el in com:
                    key[el] = '-'
                
                key = ' '.join(key)
                if key in info_hash:
                    info_hash[key].append(int(score))
                else:
                    info_hash[key] = [int(score)]
    
    # 점수 리스트 정렬
    for key in info_hash:
        info_hash[key].sort()
    
    # query 처리
    for q in query:
        # 조건에 해당하는 점수 찾기
        splited_q = q.replace('and ', '').split()
        target_score = int(splited_q.pop())
        target_key = ' '.join(splited_q)
        
        if target_key not in info_hash:
            answer.append(0)
            continue
            
        score_list = info_hash[target_key]
        
        # upper bound 이진 탐색
        start, end = 0, len(score_list)
        mid = (start + end) // 2
        
        while start &amp;lt; end:
            mid = (start + end) // 2
            mid_score = score_list[mid]
            
            if target_score &amp;gt; mid_score:
                start = mid + 1
            else:
                end = mid
        
        answer.append(len(score_list) - start)
        

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도를 줄인 방법&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2705&quot; data-start=&quot;2662&quot;&gt;&lt;b&gt;조건 조합을 미리 생성&lt;/b&gt;해서 조회를 `O(1)`로 바꾸고,&lt;/li&gt;
&lt;li data-end=&quot;2753&quot; data-start=&quot;2706&quot;&gt;점수 검색을 `O(log N)`으로 줄이기 위해 &lt;b&gt;이진 탐색&lt;/b&gt;을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 풀이 흐름&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2233&quot; data-start=&quot;2146&quot;&gt;&lt;b&gt;조건 조합을 미리 생성&lt;/b&gt;하고 해시 테이블(`info_hash`)에 저장한다.&lt;br /&gt;&amp;rarr; &quot;-&quot; 조건도 미리 처리되어 있기 때문에 빠른 조회가 가능하다.&lt;/li&gt;
&lt;li data-end=&quot;2273&quot; data-start=&quot;2234&quot;&gt;&lt;b&gt;점수 리스트를 정렬&lt;/b&gt;해 이진 탐색이 가능하도록 준비한다.&lt;/li&gt;
&lt;li data-end=&quot;2319&quot; data-start=&quot;2274&quot;&gt;쿼리를 &lt;b&gt;동일한 key 형식으로 전처리&lt;/b&gt; 후 점수 리스트를 가져온다.&lt;/li&gt;
&lt;li data-end=&quot;2379&quot; data-start=&quot;2320&quot;&gt;&lt;b&gt;upper bound&lt;/b&gt;를 통해 `target_score` 이상 지원자 수를 효율적으로 계산한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@dlgosla/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%88%9C%EC%9C%84%EA%B2%80%EC%83%89-python&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[프로그래머스]&amp;nbsp;순위검색&amp;nbsp;-&amp;nbsp;python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>이진탐색</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/582</guid>
      <comments>https://divheer.tistory.com/582#entry582comment</comments>
      <pubDate>Fri, 10 Oct 2025 17:01:12 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 메뉴 리뉴얼 | 파이썬</title>
      <link>https://divheer.tistory.com/581</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/72411&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/72411&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;레스토랑을 운영하던&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스카피&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;는 코로나19로 인한 불경기를 극복하고자 메뉴를 새로 구성하려고 고민하고 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;기존에는 단품으로만 제공하던 메뉴를 조합해서 코스요리 형태로 재구성해서 새로운 메뉴를 제공하기로 결정했습니다. 어떤 단품메뉴들을 조합해서 코스요리 메뉴로 구성하면 좋을 지 고민하던 &quot;스카피&quot;는 이전에 각 손님들이 주문할 때 가장 많이 함께 주문한 단품메뉴들을 코스요리 메뉴로 구성하기로 했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;단, 코스요리 메뉴는 최소 2가지 이상의 단품메뉴로 구성하려고 합니다. 또한, 최소 2명 이상의 손님으로부터 주문된 단품메뉴 조합에 대해서만 코스요리 메뉴 후보에 포함하기로 했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;각 손님들이 주문한 단품메뉴들이 문자열 형식으로 담긴 배열 orders, &quot;스카피&quot;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;추가하고 싶어하는&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;코스요리를 구성하는 단품메뉴들의 갯수가 담긴 배열 course가 매개변수로 주어질 때, &quot;스카피&quot;가 새로 추가하게 될 코스요리의 메뉴 구성을 문자열 형태로 배열에 담아 return 하도록 solution 함수를 완성해 주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;orders 배열의 크기는 2 이상 20 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;orders 배열의 각 원소는 크기가 2 이상 10 이하인 문자열입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 문자열은 알파벳 대문자로만 이루어져 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 문자열에는 같은 알파벳이 중복해서 들어있지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;course 배열의 크기는 1 이상 10 이하입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;course 배열의 각 원소는 2 이상 10 이하인 자연수가&lt;span&gt;&amp;nbsp;&lt;/span&gt;오름차순으로 정렬되어 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;course 배열에는 같은 값이 중복해서 들어있지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;정답은 각 코스요리 메뉴의 구성을 문자열 형식으로 배열에 담아 사전 순으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;오름차순&lt;span&gt;&amp;nbsp;&lt;/span&gt;정렬해서 return 해주세요.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;배열의 각 원소에 저장된 문자열 또한 알파벳&lt;span&gt;&amp;nbsp;&lt;/span&gt;오름차순으로 정렬되어야 합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;만약 가장 많이 함께 주문된 메뉴 구성이 여러 개라면, 모두 배열에 담아 return 하면 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;orders와 course 매개변수는 return 하는 배열의 길이가 1 이상이 되도록 주어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oWoXa/btsQ2PVdTpm/pjuKAtn9XZGa5Dwerl7v80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oWoXa/btsQ2PVdTpm/pjuKAtn9XZGa5Dwerl7v80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oWoXa/btsQ2PVdTpm/pjuKAtn9XZGa5Dwerl7v80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoWoXa%2FbtsQ2PVdTpm%2FpjuKAtn9XZGa5Dwerl7v80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;190&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #1&lt;/b&gt;&lt;br /&gt;문제의 예시와 같습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;br /&gt;AD가 세 번, CD가 세 번, ACD가 두 번, ADE가 두 번, XYZ 가 두 번 주문됐습니다.&lt;br /&gt;요리 5개를 주문한 손님이 1명 있지만, 최소 2명 이상의 손님에게서 주문된 구성만 코스요리 후보에 들어가므로, 요리 5개로 구성된 코스요리는 새로 추가하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #3&lt;/b&gt;&lt;br /&gt;WX가 두 번, XY가 두 번 주문됐습니다.&lt;br /&gt;3명의 손님 모두 단품메뉴를 3개씩 주문했지만, 최소 2명 이상의 손님에게서 주문된 구성만 코스요리 후보에 들어가므로, 요리 3개로 구성된 코스요리는 새로 추가하지 않습니다.&lt;br /&gt;또, 단품메뉴를 4개 이상 주문한 손님은 없으므로, 요리 4개로 구성된 코스요리 또한 새로 추가하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import sys
sys.setrecursionlimit(10 ** 6)

def solution(orders, course):
    max_len = max(course)
    result = {}
    
    def dfs(cur, cur_list, order):
        if len(cur) in course:
            if cur in result:
                result[cur] += 1
            else:
                result[cur] = 1
            cur_list.append(cur)
                
        if len(cur) == max_len:
            return

        for menu in order:
            new_cur = ''.join(sorted(list(cur + menu)))
            if menu not in cur:
                new_cur = ''.join(sorted(list(cur + menu)))
                if new_cur not in cur_list:
                    dfs(new_cur, cur_list, order)

    for order in orders:
        dfs(&quot;&quot;, [], order)
    
    count = [0] * 11
    courses = {x: [] for x in course}
    answer = []
    
    for order in result:
        cnt = result[order]
        i = len(order)
        
        if cnt &amp;gt; count[i] and cnt &amp;gt;= 2:
            courses[i] = [order]
            count[i] = cnt
        elif cnt == count[i]:
            courses[i] += [order]
    
    for course in courses:
        answer += courses[course]
    
    return sorted(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20개의 테스트 케이스 중에 5개를 시간 초과로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1760010367561&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from itertools import combinations

def solution(orders, course):
    answer = []
    
    for n in course:
        result = {}
        
        for order in orders:
            for new_order in combinations(order, n):
                new_order = &quot;&quot;.join(sorted(new_order))
                
                if new_order in result:
                    result[new_order] += 1
                else:
                    result[new_order] = 1

        if result:
            max_order = max(result.values())
            if max_order &amp;gt;= 2:
                for order in result:
                    if result[order] == max_order:
                        answer.append(order)                  
    
    return sorted(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;`itertools` 라이브러리의 `combinations` 함수를 이용하면&lt;br /&gt;각 주문에서 원하는 길이의 메뉴 조합을 간단하게 추출할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;234&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;323&quot; data-ke-size=&quot;size16&quot;&gt;코스요리를 구성할 메뉴 개수마다&amp;nbsp;가능한 조합을 생성하고,&lt;br /&gt;각 조합이 등장한 횟수를 딕셔너리에 기록한다.&lt;/p&gt;
&lt;p data-end=&quot;406&quot; data-start=&quot;323&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;491&quot; data-start=&quot;408&quot; data-ke-size=&quot;size16&quot;&gt;이후 각 코스 길이별로 가장 많이 등장한 조합의 등장 횟수를 구하고&lt;br /&gt;그 최대 등장 횟수가 2회 이상인 경우에 최종 코스 후보로 선택한다.&lt;/p&gt;
&lt;p data-end=&quot;526&quot; data-start=&quot;493&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 선택된 코스 조합들을 사전순으로 정렬해 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/581</guid>
      <comments>https://divheer.tistory.com/581#entry581comment</comments>
      <pubDate>Thu, 9 Oct 2025 20:54:41 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.3] 표현 가능한 이진트리 | 파이썬</title>
      <link>https://divheer.tistory.com/580</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150367&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150367&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;당신은 이진트리를 수로 표현하는 것을 좋아합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이진트리를 수로 표현하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이진수를 저장할 빈 문자열을 생성합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;주어진 이진트리에 더미 노드를 추가하여 포화 이진트리로 만듭니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;루트 노드는 그대로 유지합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;만들어진 포화 이진트리의 노드들을 가장 왼쪽 노드부터 가장 오른쪽 노드까지, 왼쪽에 있는 순서대로 살펴봅니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;노드의 높이는 살펴보는 순서에 영향을 끼치지 않습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;살펴본 노드가 더미 노드라면, 문자열 뒤에 0을 추가합니다. 살펴본 노드가 더미 노드가 아니라면, 문자열 뒤에 1을 추가합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;문자열에 저장된 이진수를 십진수로 변환합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이진트리에서 리프 노드가 아닌 노드는 자신의 왼쪽 자식이 루트인 서브트리의 노드들보다 오른쪽에 있으며, 자신의 오른쪽 자식이 루트인 서브트리의 노드들보다 왼쪽에 있다고 가정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;다음은 이진트리를 수로 표현하는 예시입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dg6gyF/btsQ4nwvBh9/9dKeMwCdjusCqK9mngCP2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dg6gyF/btsQ4nwvBh9/9dKeMwCdjusCqK9mngCP2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dg6gyF/btsQ4nwvBh9/9dKeMwCdjusCqK9mngCP2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdg6gyF%2FbtsQ4nwvBh9%2F9dKeMwCdjusCqK9mngCP2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;583&quot; height=&quot;343&quot; data-origin-width=&quot;583&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;노드들을 왼쪽에 있는 순서대로 살펴보며 0과 1을 생성한 문자열에 추가하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;0111010&quot;이 됩니다. 이 이진수를 십진수로 변환하면 58입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;당신은 수가 주어졌을때, 하나의 이진트리로 해당 수를 표현할 수 있는지 알고 싶습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이진트리로 만들고 싶은 수를 담은 1차원 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;numbers가 주어집니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;numbers에 주어진 순서대로 하나의 이진트리로 해당 수를 표현할 수 있다면 1을, 표현할 수 없다면 0을 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;numbers의 길이 &amp;le; 10,000
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;numbers의 원소 &amp;le; 1015&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kW9kB/btsQ5VsgJA5/PHdMFJHnPsoU5YqTEyw5Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kW9kB/btsQ5VsgJA5/PHdMFJHnPsoU5YqTEyw5Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kW9kB/btsQ5VsgJA5/PHdMFJHnPsoU5YqTEyw5Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkW9kB%2FbtsQ5VsgJA5%2FPHdMFJHnPsoU5YqTEyw5Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;267&quot; height=&quot;140&quot; data-origin-width=&quot;267&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def check(bin_num):
    mid = len(bin_num) // 2
    
    if mid == 0:
        return True
    elif bin_num[mid] == &quot;0&quot; and &quot;1&quot; in bin_num:
        return False
    else:
        return check(bin_num[:mid]) and check(bin_num[mid + 1:])

def solution(numbers):
    answer = []
    
    for num in numbers:
        bin_num, height = bin(num)[2:], 0
        
        while 2 ** height &amp;lt;= len(bin_num):
            height += 1
            
        bin_num = bin_num.zfill(2 ** height - 1)
        
        if check(bin_num):
            answer.append(1)
        else:
            answer.append(0)
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;191&quot; data-start=&quot;164&quot; data-ke-size=&quot;size16&quot;&gt;먼저, 주어진 수를 &lt;b&gt;이진수로 변환&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-end=&quot;388&quot; data-start=&quot;193&quot; data-ke-size=&quot;size16&quot;&gt;그 다음, 포화 이진트리가 항상 `2ⁿ - 1`개의 노드를 가진다는 점을 이용해 트리의 높이를 구하고,&lt;br /&gt;&lt;b&gt;포화 이진트리의 형태를 만들기 위해 이진수 앞쪽에 '0'을 추가&lt;/b&gt;&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-end=&quot;487&quot; data-start=&quot;395&quot; data-ke-size=&quot;size16&quot;&gt;이제 만들어진 이진수 문자열을 통해 &lt;b&gt;트리 구조의 유효성을 검사&lt;/b&gt;한다.&lt;br /&gt;검사는 루트 노드를 기준으로 왼쪽과 오른쪽 서브트리로 나누어 &lt;b&gt;재귀적으로 진행&lt;/b&gt;한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;756&quot; data-start=&quot;489&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;540&quot; data-start=&quot;489&quot;&gt;이진 문자열의 중간 인덱스(`mid`) 는 현재 서브트리의 루트 노드를 의미한다.&lt;/li&gt;
&lt;li data-end=&quot;591&quot; data-start=&quot;541&quot;&gt;만약 문자열의 길이가 1이라면 더 이상 확인할 노드가 없으므로 `True`를 반환한다.&lt;/li&gt;
&lt;li data-end=&quot;702&quot; data-start=&quot;592&quot;&gt;루트 노드가 0인데 그 서브트리에 1이 존재한다면,&lt;br /&gt;이는 부모가 더미 노드인데 자식이 실제 노드인 경우이므로 트리 구조를 만족하지 않아 `False`를 반환한다.&lt;/li&gt;
&lt;li data-end=&quot;756&quot; data-start=&quot;703&quot;&gt;위 조건에 걸리지 않는다면, 왼쪽과 오른쪽 서브트리에 대해 같은 검사를 재귀적으로 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.promleeblog.com/blog/post/165-python-2023kakao-rbt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[PGM]&amp;nbsp;#&amp;nbsp;2023&amp;nbsp;KAKAO&amp;nbsp;BLIND&amp;nbsp;RECRUITMENT&amp;nbsp;표현&amp;nbsp;가능한&amp;nbsp;이진트리&amp;nbsp;(Python)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/580</guid>
      <comments>https://divheer.tistory.com/580#entry580comment</comments>
      <pubDate>Thu, 9 Oct 2025 10:05:28 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.3] 미로 탈출 명령어 | 파이썬</title>
      <link>https://divheer.tistory.com/579</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150365&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150365&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;n&lt;span&gt;&amp;nbsp;&lt;/span&gt;x&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;span&gt;&amp;nbsp;&lt;/span&gt;격자 미로가 주어집니다. 당신은 미로의 (x, y)에서 출발해 (r, c)로 이동해서 탈출해야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;단, 미로를 탈출하는 조건이 세 가지 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;격자의 바깥으로는 나갈 수 없습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(x, y)에서 (r, c)까지 이동하는 거리가 총&lt;span&gt;&amp;nbsp;&lt;/span&gt;k여야 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;이때, (x, y)와 (r, c)격자를 포함해, 같은 격자를 두 번 이상 방문해도 됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;미로에서 탈출한 경로를 문자열로 나타냈을 때, 문자열이 사전 순으로 가장 빠른 경로로 탈출해야 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이동 경로는 다음과 같이 문자열로 바꿀 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;l: 왼쪽으로 한 칸 이동&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;r: 오른쪽으로 한 칸 이동&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;u: 위쪽으로 한 칸 이동&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;d: 아래쪽으로 한 칸 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 왼쪽으로 한 칸, 위로 한 칸, 왼쪽으로 한 칸 움직였다면, 문자열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;lul&quot;로 나타낼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;미로에서는 인접한 상, 하, 좌, 우 격자로 한 칸씩 이동할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같이 3 x 4 격자가 있다고 가정해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;95&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o4Oa3/btsQ4BBoRrv/2svBokhK5xKYzwVR1waQyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o4Oa3/btsQ4BBoRrv/2svBokhK5xKYzwVR1waQyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o4Oa3/btsQ4BBoRrv/2svBokhK5xKYzwVR1waQyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo4Oa3%2FbtsQ4BBoRrv%2F2svBokhK5xKYzwVR1waQyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;95&quot; height=&quot;109&quot; data-origin-width=&quot;95&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;미로의 좌측 상단은 (1, 1)이고 우측 하단은 (3, 4)입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;.은 빈 공간,&lt;span&gt;&amp;nbsp;&lt;/span&gt;S는 출발 지점,&lt;span&gt;&amp;nbsp;&lt;/span&gt;E는 탈출 지점입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;탈출까지 이동해야 하는 거리&lt;span&gt;&amp;nbsp;&lt;/span&gt;k가 5라면 다음과 같은 경로로 탈출할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;lldud&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;ulldd&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;rdlll&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dllrl&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dllud&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;...&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이때 dllrl보다 사전 순으로 빠른 경로로 탈출할 수는 없습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;격자의 크기를 뜻하는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;n,&lt;span&gt;&amp;nbsp;&lt;/span&gt;m, 출발 위치를 뜻하는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;x,&lt;span&gt;&amp;nbsp;&lt;/span&gt;y, 탈출 지점을 뜻하는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c, 탈출까지 이동해야 하는 거리를 뜻하는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;k가 매개변수로 주어집니다. 이때, 미로를 탈출하기 위한 경로를 return 하도록 solution 함수를 완성해주세요.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;단, 위 조건대로 미로를 탈출할 수 없는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;impossible&quot;을 return 해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;n&lt;span&gt;&amp;nbsp;&lt;/span&gt;(= 미로의 세로 길이) &amp;le; 50&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;span&gt;&amp;nbsp;&lt;/span&gt;(= 미로의 가로 길이) &amp;le; 50&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;x&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;n&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;y&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;r&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;n&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;c&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(x,&lt;span&gt;&amp;nbsp;&lt;/span&gt;y) &amp;ne; (r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;k&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 2,500&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5giuF/btsQ2zxWxX9/Dfsamra6sgdyQUNy26y5A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5giuF/btsQ2zxWxX9/Dfsamra6sgdyQUNy26y5A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5giuF/btsQ2zxWxX9/Dfsamra6sgdyQUNy26y5A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5giuF%2FbtsQ2zxWxX9%2FDfsamra6sgdyQUNy26y5A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;190&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import sys
sys.setrecursionlimit(10**6)

def solution(n, m, x, y, r, c, k):
    answer = []
    
    def dfs(a, b, cnt, route):
        if cnt == k:
            if a == r and b == c:
                answer.append(route)
            return
        
        if b &amp;gt; 1:
            dfs(a, b - 1, cnt + 1, route + &quot;l&quot;)
        if b &amp;lt; m:
            dfs(a, b + 1, cnt + 1, route + &quot;r&quot;)
        if a &amp;gt; 1:
            dfs(a - 1, b, cnt + 1, route + &quot;u&quot;)
        if a &amp;lt; n:
            dfs(a + 1, b, cnt + 1, route + &quot;d&quot;)
    
    dfs(x, y, 0, &quot;&quot;)
    answer.sort()

    return answer[0] if answer else 'impossible'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 초과로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 따라 탐색을 중단하는 장치가 필요할 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import sys
sys.setrecursionlimit(10**6)

dirs = [(1, 0, &quot;d&quot;), (0, -1, &quot;l&quot;), (0, 1, &quot;r&quot;), (-1, 0, &quot;u&quot;)]

def solution(n, m, x, y, r, c, k):
    dist = abs(x - r) + abs(y - c)
    if dist &amp;gt; k:
        return 'impossible'
    if dist % 2 != k % 2:
        return 'impossible'
    
    def dfs(a, b, cnt, path):
        if cnt == k:
            if a == r and b == c:
                return path
        else:
            for dx, dy, dir in dirs:
                nx, ny = a + dx, b + dy
                if 1 &amp;lt;= nx &amp;lt;= n and 1 &amp;lt;= ny &amp;lt;= m:
                    dist = abs(nx - r) + abs(ny - c)
                    if dist &amp;gt; k - (cnt + 1):
                        continue        
                    return dfs(nx, ny, cnt + 1, path + dir)
    
    answer = dfs(x, y, 0, &quot;&quot;)
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS에 가지치기를 적용&lt;/b&gt;해야 시간 초과를 통과할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이를 위해 먼저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;시작 지점에서 종료 지점까지의 최소 이동 거리(맨해튼 거리)&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 계산한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 거리가 K보다 크거나, 이동 거리의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;짝/홀수 패턴이 일치하지 않는 경우에는&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;도착이 불가능하므로 &lt;b&gt;&quot;impossible&quot;을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;early return&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ` &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;dirs` 배열을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;사전순(d &amp;rarr; l &amp;rarr; r &amp;rarr; u)으로 정렬&lt;/b&gt;해 두면&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;DFS 과정에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;가장 먼저 완성되는 경로가 곧 사전순으로 가장 빠른 경로&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;이동할 때마다 남은 거리를 계산&lt;/b&gt;해서 남은 이동 횟수가 부족하다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;그 경로는 더 이상 탐색하지 않도록&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;가지치기&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 과정을 통해 불필요한 재귀 호출을 줄여 효율적으로 탐색할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;663&quot; data-start=&quot;521&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 거리를 계산해서 탐색을 제한하는 부분까지 떠올렸었는데 직접 실행이 옮기지 않았던 것이 아쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://maximum-curry30.tistory.com/659&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[프로그래머스] 미로 탈출 명령어 파이썬&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/579</guid>
      <comments>https://divheer.tistory.com/579#entry579comment</comments>
      <pubDate>Wed, 8 Oct 2025 13:47:09 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.3] 표 병합 | 파이썬</title>
      <link>https://divheer.tistory.com/578</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150366&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150366&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;당신은 표 편집 프로그램을 작성하고 있습니다.&lt;br /&gt;표의 크기는 50 &amp;times; 50으로 고정되어있고 초기에 모든 셀은 비어 있습니다.&lt;br /&gt;각 셀은 문자열 값을 가질 수 있고, 다른 셀과 병합될 수 있습니다.&lt;br /&gt;&lt;br /&gt;위에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;r번째, 왼쪽에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;c번째 위치를 (r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c)라고 표현할 때, 당신은 다음 명령어들에 대한 기능을 구현하려고 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UPDATE r c value&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c) 위치의 셀을 선택합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 셀의 값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;value로 바꿉니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UPDATE value1 value2&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;value1을 값으로 가지고 있는 모든 셀을 선택합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 셀의 값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;value2로 바꿉니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;MERGE r1 c1 r2 c2&quot;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c1) 위치의 셀과 (r2,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c2) 위치의 셀을 선택하여 병합합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 두 위치의 셀이 같은 셀일 경우 무시합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 두 셀은 서로 인접하지 않을 수도 있습니다. 이 경우 (r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c1) 위치의 셀과 (r2,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c2) 위치의 셀만 영향을 받으며, 그 사이에 위치한 셀들은 영향을 받지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 셀 중 한 셀이 값을 가지고 있을 경우 병합된 셀은 그 값을 가지게 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 셀 모두 값을 가지고 있을 경우 병합된 셀은 (r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c1) 위치의 셀 값을 가지게 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이후 (r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c1) 와 (r2,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c2) 중 어느 위치를 선택하여도 병합된 셀로 접근합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UNMERGE r c&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c) 위치의 셀을 선택하여 해당 셀의 모든 병합을 해제합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 셀이 포함하고 있던 모든 셀은 프로그램 실행 초기의 상태로 돌아갑니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;병합을 해제하기 전 셀이 값을 가지고 있었을 경우 (r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c) 위치의 셀이 그 값을 가지게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;PRINT r c&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;(r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c) 위치의 셀을 선택하여 셀의 값을 출력합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;선택한 셀이 비어있을 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;EMPTY&quot;를 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;commands의 길이 &amp;le; 1,000&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;commands의 각 원소는 아래 5가지 형태 중 하나입니다.
&lt;ol style=&quot;list-style-type: decimal; color: #000000;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UPDATE r c value&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c는 선택할 셀의 위치를 나타내며, 1~50 사이의 정수입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;value는 셀에 입력할 내용을 나타내며, 알파벳 소문자와 숫자로 구성된 길이 1~10 사이인 문자열입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UPDATE value1 value2&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;value1은 선택할 셀의 값,&lt;span&gt;&amp;nbsp;&lt;/span&gt;value2는 셀에 입력할 내용을 나타내며, 알파벳 소문자와 숫자로 구성된 길이 1~10 사이인 문자열입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;MERGE r1 c1 r2 c2&quot;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;r1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c1,&lt;span&gt;&amp;nbsp;&lt;/span&gt;r2,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c2는 선택할 셀의 위치를 나타내며, 1~50 사이의 정수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;UNMERGE r c&quot;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c는 선택할 셀의 위치를 나타내며, 1~50 사이의 정수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;&quot;PRINT r c&quot;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;r,&lt;span&gt;&amp;nbsp;&lt;/span&gt;c는 선택할 셀의 위치를 나타내며, 1~50 사이의 정수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;commands는 1개 이상의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;PRINT r c&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어를 포함하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVKFPM/btsQ2qVvdLx/tEdguPrTtRak5VwhcrzwO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVKFPM/btsQ2qVvdLx/tEdguPrTtRak5VwhcrzwO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVKFPM/btsQ2qVvdLx/tEdguPrTtRak5VwhcrzwO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVKFPM%2FbtsQ2qVvdLx%2FtEdguPrTtRak5VwhcrzwO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1059&quot; height=&quot;300&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;table = [['EMPTY'] * 51 for _ in range(51)]
pointer = [[(r, c) for c in range(51)] for r in range(51)]
answer = []

def insert(r, c, v):
    r, c = pointer[r][c]
    table[r][c] = v

def update(v1, v2):
    for i in range(51):
        for j in range(51):
            if table[i][j] == v1:
                table[i][j] = v2

def merge(r1, c1, r2, c2):
    r1, c1 = pointer[r1][c1]
    r2, c2 = pointer[r2][c2]
    
    if table[r1][c1] == 'EMPTY':
        table[r1][c1] = table[r2][c2]
        
    for i in range(51):
        for j in range(51):
            if pointer[i][j] == (r2, c2):
                pointer[i][j] = (r1, c1)

def unmerge(r1, c1):
    r2, c2 = pointer[r1][c1]
    v = table[r2][c2]

    for i in range(51):
        for j in range(51):  
            if pointer[i][j] == (r2, c2):
                pointer[i][j] = (i, j)
                table[i][j] = 'EMPTY'
    
    table[r1][c1] = v

def print_value(r, c):
    r, c = pointer[r][c]
    answer.append(table[r][c])
    
def solution(commands):  
    for c in commands:
        c = c.split()
        if c[0] == 'UPDATE':
            if len(c) == 4:
                insert(int(c[1]), int(c[2]), c[3])
            else:
                update(c[1], c[2])
        elif c[0] == 'MERGE':
            merge(int(c[1]), int(c[2]), int(c[3]), int(c[4]))            
        elif c[0] == 'UNMERGE':
            unmerge(int(c[1]), int(c[2]))
        elif c[0] == 'PRINT':
            print_value(int(c[1]), int(c[2]))
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 셀마다 포인터를 따로 기록하는 방법으로 표를 merge하거나 unmerge하는 것이 이 풀이의 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 셀의 값을 가져올 때는 포인터에 저장되어 있는 좌표를 통해 테이블에 접근해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/578</guid>
      <comments>https://divheer.tistory.com/578#entry578comment</comments>
      <pubDate>Tue, 7 Oct 2025 22:08:40 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 이모티콘 할인 행사 | 파이썬</title>
      <link>https://divheer.tistory.com/577</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150368&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150368&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;카카오톡에서는 이모티콘을 무제한으로 사용할 수 있는 이모티콘 플러스 서비스 가입자 수를 늘리려고 합니다.&lt;br /&gt;이를 위해 카카오톡에서는 이모티콘 할인 행사를 하는데, 목표는 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이모티콘 플러스 서비스 가입자를 최대한 늘리는 것.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이모티콘 판매액을 최대한 늘리는 것.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;u&gt;1번 목표가 우선이며, 2번 목표가 그 다음입니다.&lt;/u&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이모티콘 할인 행사는 다음과 같은 방식으로 진행됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;n명의 카카오톡 사용자들에게 이모티콘&lt;span&gt;&amp;nbsp;&lt;/span&gt;m개를 할인하여 판매합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;이모티콘마다 할인율은 다를 수 있으며, 할인율은 10%, 20%, 30%, 40% 중 하나로 설정됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;카카오톡 사용자들은 다음과 같은 기준을 따라 이모티콘을 사거나, 이모티콘 플러스 서비스에 가입합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 사용자들은 자신의 기준에 따라 일정 비율 이상 할인하는 이모티콘을 모두 구매합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 사용자들은 자신의 기준에 따라 이모티콘 구매 비용의 합이 일정 가격 이상이 된다면, 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;카카오톡 사용자&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;n&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;명의 구매 기준을 담은 2차원 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;users&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 이모티콘&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;m&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;개의 정가를 담은 1차원 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;emoticons&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가 주어집니다. 이때, 행사 목적을 최대한으로 달성했을 때의 이모티콘 플러스 서비스 가입 수와 이모티콘 매출액을 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;users의 길이 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;n&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 100
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;users의 원소는 [비율,&lt;span&gt;&amp;nbsp;&lt;/span&gt;가격]의 형태입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;users[i]는&lt;span&gt;&amp;nbsp;&lt;/span&gt;i+1번 고객의 구매 기준을 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;비율% 이상의 할인이 있는 이모티콘을 모두 구매한다는 의미입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;비율&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 40&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;가격이상의 돈을 이모티콘 구매에 사용한다면, 이모티콘 구매를 모두 취소하고 이모티콘 플러스 서비스에 가입한다는 의미입니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;100 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가격&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 1,000,000&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;가격은 100의 배수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;emoticons의 길이 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 7
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;emoticons[i]는&lt;span&gt;&amp;nbsp;&lt;/span&gt;i+1번 이모티콘의 정가를 의미합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;100 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;emoticons의 원소 &amp;le; 1,000,000&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;emoticons의 원소는 100의 배수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gcbm3/btsQ2eArMQZ/rmnwq8gKMG9GjWrPPVJOok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gcbm3/btsQ2eArMQZ/rmnwq8gKMG9GjWrPPVJOok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gcbm3/btsQ2eArMQZ/rmnwq8gKMG9GjWrPPVJOok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGcbm3%2FbtsQ2eArMQZ%2Frmnwq8gKMG9GjWrPPVJOok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1188&quot; height=&quot;149&quot; data-origin-width=&quot;1188&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(users, emoticons):
    answer = [0, 0]
    arr = []
    
    def dfs(rates, i):
        if i == len(emoticons):
            arr.append(rates[:])
            return
        
        for rate in [10, 20, 30, 40]:
            rates[i] = rate
            dfs(rates, i + 1)
            
    dfs([0] * len(emoticons), 0)
    
    for rates in arr:
        temp = [0, 0]

        for i in range(len(users)):
            user_price = 0
            
            for j in range(len(emoticons)):
                if rates[j] &amp;gt;= users[i][0]:
                    user_price += emoticons[j] * ((100 - rates[j]) / 100)

                    if user_price &amp;gt;= users[i][1]:
                        temp[0] += 1
                        user_price = 0
                        break
                        
            temp[1] += int(user_price)
        
        if temp[0] &amp;gt; answer[0]:
            answer = [temp[0], temp[1]]
        elif temp[0] == answer[0] and temp[1] &amp;gt; answer[1]:
            answer[1] = temp[1]             
            
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dfs()` 함수로 모든 경우의 수를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 할인율에 따라 모든 경우의 수를 탐색해서 행사 목적을 최대로 달성할 수 있는 결과값을 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 것보다도 모든 경우의 수를 생성해내는 DFS 부분이 제일 어려웠던 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>dfs</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/577</guid>
      <comments>https://divheer.tistory.com/577#entry577comment</comments>
      <pubDate>Sun, 5 Oct 2025 17:07:27 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 택배 배달과 수거하기 | 파이썬</title>
      <link>https://divheer.tistory.com/576</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150369&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150369&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9mWQm/btsQ30Ow09A/5JFyLQfIv7wf9O4Km0SI90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9mWQm/btsQ30Ow09A/5JFyLQfIv7wf9O4Km0SI90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9mWQm/btsQ30Ow09A/5JFyLQfIv7wf9O4Km0SI90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9mWQm%2FbtsQ30Ow09A%2F5JFyLQfIv7wf9O4Km0SI90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;158&quot; data-origin-width=&quot;533&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;당신은 일렬로 나열된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;n&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;개의 집에 택배를 배달하려 합니다. 배달할 물건은 모두 크기가 같은 재활용 택배 상자에 담아 배달하며, 배달을 다니면서 빈 재활용 택배 상자들을 수거하려 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;배달할 택배들은 모두 재활용 택배 상자에 담겨서 물류창고에 보관되어 있고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;i&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;번째 집은 물류창고에서 거리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;i&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;만큼 떨어져 있습니다. 또한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;i&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;번째 집은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;j&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;번째 집과 거리&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;j - i&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;만큼 떨어져 있습니다. (1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;i&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;j&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;n&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;트럭에는 재활용 택배 상자를 최대&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;cap&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;개 실을 수 있습니다. 트럭은 배달할 재활용 택배 상자들을 실어 물류창고에서 출발해 각 집에 배달하면서, 빈 재활용 택배 상자들을 수거해 물류창고에 내립니다. 각 집마다 배달할 재활용 택배 상자의 개수와 수거할 빈 재활용 택배 상자의 개수를 알고 있을 때, 트럭 하나로 모든 배달과 수거를 마치고 물류창고까지 돌아올 수 있는 최소 이동 거리를 구하려 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;각 집에 배달 및 수거할 때, 원하는 개수만큼 택배를 배달 및 수거할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;트럭에 실을 수 있는 재활용 택배 상자의 최대 개수를 나타내는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;cap&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 배달할 집의 개수를 나타내는 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;n&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;, 각 집에 배달할 재활용 택배 상자의 개수를 담은 1차원 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;deliveries&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;와 각 집에서 수거할 빈 재활용 택배 상자의 개수를 담은 1차원 정수 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;pickups&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;가 매개변수로 주어집니다. 이때, 트럭 하나로 모든 배달과 수거를 마치고 물류창고까지 돌아올 수 있는 최소 이동 거리를 return 하도록 solution 함수를 완성해 주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;cap&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 50&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;n&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 100,000&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;deliveries의 길이 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;pickups의 길이 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;n
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;deliveries[i]는 i+1번째 집에 배달할 재활용 택배 상자의 개수를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;pickups[i]는 i+1번째 집에서 수거할 빈 재활용 택배 상자의 개수를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;deliveries의 원소 &amp;le; 50&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;0 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;pickups의 원소 &amp;le; 50&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;트럭의 초기 위치는 물류창고입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wsxCg/btsQ1meDprS/l6PhROBopRV9x8MmUa4wVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wsxCg/btsQ1meDprS/l6PhROBopRV9x8MmUa4wVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wsxCg/btsQ1meDprS/l6PhROBopRV9x8MmUa4wVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwsxCg%2FbtsQ1meDprS%2Fl6PhROBopRV9x8MmUa4wVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;143&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(cap, n, deliveries, pickups):
    dist = 0
    delivery = 0
    pickup = 0
    
    for i in range(n - 1, -1, -1):
        delivery += deliveries[i]
        pickup += pickups[i]
        
        while delivery &amp;gt; 0 or pickup &amp;gt; 0:
            delivery -= cap
            pickup -= cap
            dist += i + 1
    
    return dist * 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;90&quot; data-ke-size=&quot;size16&quot;&gt;for문을 역순으로 탐색하면서 가장 먼 거리의 택배부터 처리한다.&lt;/p&gt;
&lt;p data-end=&quot;132&quot; data-start=&quot;90&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;215&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;아직 배달하거나 픽업할 택배가 남아 있다면,&lt;br /&gt;트럭은 한 번 왕복에 처리 가능한 최대 개수(cap) 만큼만 배달&amp;middot;수거를 수행한다.&lt;/p&gt;
&lt;p data-end=&quot;215&quot; data-start=&quot;134&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;298&quot; data-start=&quot;217&quot; data-ke-size=&quot;size16&quot;&gt;트럭이 움직일 때마다 현재 집의 거리를 누적하고,&lt;br /&gt;모든 배달&amp;middot;수거를 마친 뒤에는 왕복 거리이므로 결과에 &amp;times;2를 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/576</guid>
      <comments>https://divheer.tistory.com/576#entry576comment</comments>
      <pubDate>Sat, 4 Oct 2025 22:48:30 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 개인정보 수집 유효기간 | 파이썬</title>
      <link>https://divheer.tistory.com/575</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/150370&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/150370&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;고객의 약관 동의를 얻어서 수집된 1~n번으로 분류되는 개인정보&lt;span&gt;&amp;nbsp;&lt;/span&gt;n개가 있습니다. 약관 종류는 여러 가지 있으며 각 약관마다 개인정보 보관 유효기간이 정해져 있습니다. 당신은 각 개인정보가 어떤 약관으로 수집됐는지 알고 있습니다. 수집된 개인정보는 유효기간 전까지만 보관 가능하며, 유효기간이 지났다면 반드시 파기해야 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, A라는 약관의 유효기간이 12 달이고, 2021년 1월 5일에 수집된 개인정보가 A약관으로 수집되었다면 해당 개인정보는 2022년 1월 4일까지 보관 가능하며 2022년 1월 5일부터 파기해야 할 개인정보입니다.&lt;br /&gt;당신은 오늘 날짜로 파기해야 할 개인정보 번호들을 구하려 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 달은 28일까지 있다고 가정합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다음은 오늘 날짜가&lt;span&gt;&amp;nbsp;&lt;/span&gt;2022.05.19일 때의 예시입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0AwxY/btsQZMwsrMC/gTv0lykvlzNIA8RWKHPVR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0AwxY/btsQZMwsrMC/gTv0lykvlzNIA8RWKHPVR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0AwxY/btsQZMwsrMC/gTv0lykvlzNIA8RWKHPVR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0AwxY%2FbtsQZMwsrMC%2FgTv0lykvlzNIA8RWKHPVR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;391&quot; height=&quot;416&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;첫 번째 개인정보는 A약관에 의해 2021년 11월 1일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 번째 개인정보는 B약관에 의해 2022년 6월 28일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;세 번째 개인정보는 C약관에 의해 2022년 5월 18일까지 보관 가능하며, 유효기간이 지났으므로 파기해야 할 개인정보입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;네 번째 개인정보는 C약관에 의해 2022년 5월 19일까지 보관 가능하며, 유효기간이 지나지 않았으므로 아직 보관 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 파기해야 할 개인정보 번호는 [1, 3]입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;오늘 날짜를 의미하는 문자열&lt;span&gt;&amp;nbsp;&lt;/span&gt;today, 약관의 유효기간을 담은 1차원 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;terms와 수집된 개인정보의 정보를 담은 1차원 문자열 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;privacies가 매개변수로 주어집니다. 이때 파기해야 할 개인정보의 번호를 오름차순으로 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;today는 &quot;YYYY.MM.DD&quot; 형태로 오늘 날짜를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;terms의 길이 &amp;le; 20
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;terms의 원소는 &quot;약관 종류&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효기간&quot; 형태의&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류와&lt;span&gt;&amp;nbsp;&lt;/span&gt;유효기간을 공백 하나로 구분한 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;약관 종류는&lt;span&gt;&amp;nbsp;&lt;/span&gt;A~Z중 알파벳 대문자 하나이며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;terms&lt;span&gt;&amp;nbsp;&lt;/span&gt;배열에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류는 중복되지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;유효기간은 개인정보를 보관할 수 있는 달 수를 나타내는 정수이며, 1 이상 100 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;privacies의 길이 &amp;le; 100
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;privacies[i]는&lt;span&gt;&amp;nbsp;&lt;/span&gt;i+1번 개인정보의 수집 일자와 약관 종류를 나타냅니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;privacies의 원소는 &quot;날짜&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류&quot; 형태의&lt;span&gt;&amp;nbsp;&lt;/span&gt;날짜와&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류를 공백 하나로 구분한 문자열입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;날짜는 &quot;YYYY.MM.DD&quot; 형태의 개인정보가 수집된 날짜를 나타내며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;today&lt;span&gt;&amp;nbsp;&lt;/span&gt;이전의 날짜만 주어집니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;privacies의&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류는 항상&lt;span&gt;&amp;nbsp;&lt;/span&gt;terms에 나타난&lt;span&gt;&amp;nbsp;&lt;/span&gt;약관 종류만 주어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;today와&lt;span&gt;&amp;nbsp;&lt;/span&gt;privacies에 등장하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;날짜의&lt;span&gt;&amp;nbsp;&lt;/span&gt;YYYY는 연도,&lt;span&gt;&amp;nbsp;&lt;/span&gt;MM은 월,&lt;span&gt;&amp;nbsp;&lt;/span&gt;DD는 일을 나타내며 점(.) 하나로 구분되어 있습니다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2000 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;YYYY&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 2022&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;MM&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 12&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;MM이 한 자릿수인 경우 앞에 0이 붙습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DD&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;le; 28&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;DD가 한 자릿수인 경우 앞에 0이 붙습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;파기해야 할 개인정보가 하나 이상 존재하는 입력만 주어집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVrVSp/btsQ1p1nxiH/VB1AQtkMC7ARmjzLaFD0aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVrVSp/btsQ1p1nxiH/VB1AQtkMC7ARmjzLaFD0aK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVrVSp/btsQ1p1nxiH/VB1AQtkMC7ARmjzLaFD0aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVrVSp%2FbtsQ1p1nxiH%2FVB1AQtkMC7ARmjzLaFD0aK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1382&quot; height=&quot;144&quot; data-origin-width=&quot;1382&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(today, terms, privacies):
    dic = {}
    dates = []
    
    for term in terms:
        type, month = term.split()
        dic[type] = int(month)
    
    for i, p in enumerate(privacies):
        date, type = p.split()
        y, m, d = map(int, date.split('.'))
        
        d = d - 1 if d - 1 else 28
        if d == 28:
            m -= 1
            
        y = y + ((m + dic[type]) // 13)
        m = (m + dic[type]) % 12 if (m + dic[type]) % 12 else 12

        str_date = str(y) + '.' + str(m).zfill(2) + '.' + str(d).zfill(2)
        dates.append((str_date, i + 1))

    answer = []
    
    for date, i in dates:
        if today &amp;gt; date:
            answer.append(i)

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 케이스 절반을 통과하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y, m, d를 각각 계산하기에는 예외 상황이 너무 많기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확하계 계산하지 못해서 실패한 거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(today, terms, privacies):
    dic = {}
    answer = []
    
    for term in terms:
        type, month = term.split()
        dic[type] = int(month)
        
    y, m, d = map(int, today.split('.'))
    t_days = (y * 28 * 12) + (m * 28) + d
    
    for i, p in enumerate(privacies):
        date, type = p.split()
        y, m, d = map(int, date.split('.'))
        days = (y * 28 * 12) + ((m + dic[type]) * 28) + d
        
        if t_days &amp;gt;= days:
            answer.append(i + 1)

    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력받은 날짜를 '일' 단위로 변환해서 총 일자 수를 계산했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계산도 매우 간단하고 정수 형태라서 오늘 날짜와 비교하기에도 편리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거의 1시간 동안 계속 수정하면서 시도해 보다가 다른 방식으로 바꿨더니 5분만에 풀었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청 고생했는데 사실은 아주 간단한 문제였음... &lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/575</guid>
      <comments>https://divheer.tistory.com/575#entry575comment</comments>
      <pubDate>Thu, 2 Oct 2025 09:07:07 +0900</pubDate>
    </item>
    <item>
      <title>[백준/골드5] 1068번 트리 | 파이썬</title>
      <link>https://divheer.tistory.com/574</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1068&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1068&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트리에서 리프 노드란, 자식의 개수가 0인 노드를 말한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트리가 주어졌을 때, 노드 하나를 지울&amp;nbsp;것이다. 그 때, 남은 트리에서 리프 노드의 개수를 구하는 프로그램을 작성하시오. 노드를 지우면 그 노드와 노드의 모든 자손이 트리에서 제거된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 다음과 같은 트리가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;239&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mAXIN/btsQWgkJ7bb/GTZHUkVk0kSnry7vHvOzIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mAXIN/btsQWgkJ7bb/GTZHUkVk0kSnry7vHvOzIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mAXIN/btsQWgkJ7bb/GTZHUkVk0kSnry7vHvOzIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmAXIN%2FbtsQWgkJ7bb%2FGTZHUkVk0kSnry7vHvOzIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;323&quot; height=&quot;239&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;239&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;현재 리프 노드의 개수는 3개이다. (초록색 색칠된 노드) 이때, 1번을 지우면, 다음과 같이 변한다. 검정색으로 색칠된 노드가 트리에서 제거된 노드이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;313&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lR0ow/btsQWjn9zFH/ITnzPXTFzxfOyl02rgdsKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lR0ow/btsQWjn9zFH/ITnzPXTFzxfOyl02rgdsKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lR0ow/btsQWjn9zFH/ITnzPXTFzxfOyl02rgdsKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlR0ow%2FbtsQWjn9zFH%2FITnzPXTFzxfOyl02rgdsKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;313&quot; height=&quot;224&quot; data-origin-width=&quot;313&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;이제 리프 노드의 개수는 1개이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 트리의 노드의 개수 N이 주어진다. N은 50보다 작거나 같은 자연수이다. 둘째 줄에는 0번 노드부터 N-1번 노드까지, 각 노드의 부모가 주어진다. 만약 부모가 없다면 (루트) -1이 주어진다. 셋째 줄에는 지울 노드의 번호가 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 입력으로 주어진 트리에서 입력으로 주어진 노드를 지웠을 때, 리프 노드의 개수를 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLKne6/btsQWubUZME/O8LA6FuGaS0GYMtMcnOgU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLKne6/btsQWubUZME/O8LA6FuGaS0GYMtMcnOgU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLKne6/btsQWubUZME/O8LA6FuGaS0GYMtMcnOgU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLKne6%2FbtsQWubUZME%2FO8LA6FuGaS0GYMtMcnOgU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1168&quot; height=&quot;784&quot; data-origin-width=&quot;1168&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

n = int(input())
arr = list(map(int, input().split()))

tree = [[] for _ in range(n)]
child_count = [0] * n

for i in range(1, n):
    tree[arr[i]].append(i)
    child_count[arr[i]] += 1

r = int(input())
queue = deque([r])

while queue:
    cur = queue.popleft()
    child_count[cur] += 1

    for child in tree[cur]:
        queue.append(child)

print(child_count.count(0))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제출 결과 1%에서 틀렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

n = int(input())
arr = list(map(int, input().split()))

tree = [[] for _ in range(n)]
child_count = [0] * n

for i in range(n):
    if arr[i] != -1:
        tree[arr[i]].append(i)
        child_count[arr[i]] += 1

r = int(input())
queue = deque([r])

while queue:
    cur = queue.popleft()
    child_count[cur] += 1

    for child in tree[cur]:
        queue.append(child)

print(child_count.count(0))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 0번 노드가 루트 노드가 되지 않을 수도 있겠다는 생각이 들어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`tree`와 `chile_node`에 값을 추가하는 for문 부분을 수정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정 후에는 77%까지 채점 후 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이는 루트 노드와 루트의 자식 노드 2개로만 구성되어 있을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 노드를 제거하면 루트 노드를 리프 노드로 카운트하지 못한다는 문제점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1759292369353&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

n = int(input())
tree = list(map(int, input().split()))
r = int(input())

queue = deque([r])

# 노드 제거하기
while queue:
    cur = queue.popleft()
    tree[cur] = -2 # 제거된 노드는 -2로 표시

    for i in range(n):
        if tree[i] == cur:
            queue.append(i)

answer = 0

# 리프 노드 개수 세기
for i in range(n):
    if tree[i] != -2 and i not in tree:
        answer += 1

print(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 단계에 걸쳐서 문제를 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노드 제거&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에 제거할 노드 번호를 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에서 노드를 하나씩 꺼내면서 해당 노드와 그 자식 노드를 모두 `-2`로 바꿔준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 `-2`인 것은 이미 제거된 노드라는 표시가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리프 노드 개수 카운트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 노드를 순회하면서 두 가지 조건 모두 만족하면 `answer`에 1을 더한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;355&quot; data-start=&quot;318&quot;&gt;`tree[i] != -2` &amp;rarr; 제거되지 않은 노드여야 한다.&lt;/li&gt;
&lt;li data-end=&quot;409&quot; data-start=&quot;361&quot;&gt;`i not in tree` &amp;rarr; 다른 노드의 부모가 아니다 == 자식이 없는 노드다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 복잡하게 생각했던 거 같다. 때로는 가장 처음에 생각했던 방식이 정답일 때도 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/574</guid>
      <comments>https://divheer.tistory.com/574#entry574comment</comments>
      <pubDate>Wed, 1 Oct 2025 13:33:34 +0900</pubDate>
    </item>
    <item>
      <title>[백준/골드3] 1005번 ACM Craft | DP | 파이썬</title>
      <link>https://divheer.tistory.com/573</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1005&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1005&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서기 2012년! 드디어 2년간 수많은 국민들을 기다리게&amp;nbsp;한 게임 ACM Craft (Association of Construction Manager Craft)가 발매되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 게임은 지금까지 나온 게임들과는 다르게 ACM크래프트는 다이나믹한 게임 진행을 위해 건물을 짓는 순서가 정해져 있지 않다. 즉, 첫 번째 게임과 두 번째 게임이 건물을 짓는 순서가 다를 수도 있다. 매 게임시작 시 건물을 짓는 순서가 주어진다. 또한 모든 건물은 각각 건설을 시작하여 완성이 될 때까지 Delay가 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;335&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccexGP/btsQUcH5dzc/Nwch35MTmmVvEKUpTQ5Ehk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccexGP/btsQUcH5dzc/Nwch35MTmmVvEKUpTQ5Ehk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccexGP/btsQUcH5dzc/Nwch35MTmmVvEKUpTQ5Ehk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccexGP%2FbtsQUcH5dzc%2FNwch35MTmmVvEKUpTQ5Ehk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;335&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;335&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 예시를 보자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 게임에서는 다음과 같이 건설 순서 규칙이 주어졌다.&amp;nbsp;1번 건물의 건설이 완료된다면 2번과 3번의 건설을 시작할수 있다. (동시에 진행이 가능하다)&amp;nbsp;그리고 4번 건물을 짓기 위해서는 2번과 3번 건물이 모두 건설 완료되어야지만 4번건물의 건설을 시작할수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 4번건물의 건설을 완료하기 위해서는 우선 처음 1번 건물을 건설하는데 10초가 소요된다. 그리고 2번 건물과 3번 건물을 동시에 건설하기 시작하면 2번은 1초뒤에 건설이 완료되지만 아직 3번 건물이 완료되지 않았으므로 4번 건물을 건설할 수 없다. 3번 건물이 완성되고 나면 그때 4번 건물을 지을수 있으므로 4번 건물이 완성되기까지는 총 120초가 소요된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로게이머 최백준은 애인과의 데이트 비용을 마련하기 위해 서강대학교배 ACM크래프트 대회에 참가했다! 최백준은 화려한 컨트롤 실력을 가지고 있기 때문에 모든 경기에서 특정 건물만 짓는다면 무조건 게임에서 이길 수 있다.&amp;nbsp;그러나 매 게임마다 특정건물을 짓기 위한 순서가 달라지므로 최백준은 좌절하고 있었다.&amp;nbsp;백준이를 위해 특정건물을 가장 빨리 지을 때까지 걸리는 최소시간을 알아내는 프로그램을 작성해주자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에는 테스트케이스의 개수 T가 주어진다. 각 테스트 케이스는 다음과 같이 주어진다.&amp;nbsp;첫째 줄에 건물의 개수 N과 건물간의 건설순서 규칙의 총 개수 K이 주어진다.&amp;nbsp;(건물의 번호는 1번부터 N번까지 존재한다)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;둘째 줄에는 각 건물당 건설에 걸리는 시간 D1, D2, ..., DN이 공백을 사이로 주어진다.&amp;nbsp;셋째 줄부터 K+2줄까지 건설순서 X Y가 주어진다.&amp;nbsp;(이는 건물 X를 지은 다음에 건물 Y를 짓는 것이 가능하다는 의미이다)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막 줄에는 백준이가 승리하기 위해 건설해야&amp;nbsp;할 건물의 번호 W가 주어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;건물 W를 건설완료 하는데 드는 최소 시간을 출력한다.&amp;nbsp;편의상 건물을 짓는 명령을 내리는 데는 시간이 소요되지 않는다고 가정한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;건설순서는 모든 건물이 건설 가능하도록 주어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5Cg7s/btsQSlzHvV4/EtS7GKdG7clVW9JJ5ZvvZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Cg7s/btsQSlzHvV4/EtS7GKdG7clVW9JJ5ZvvZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Cg7s/btsQSlzHvV4/EtS7GKdG7clVW9JJ5ZvvZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Cg7s%2FbtsQSlzHvV4%2FEtS7GKdG7clVW9JJ5ZvvZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1450&quot; height=&quot;745&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque
import sys
input = sys.stdin.readline

t = int(input())

for _ in range(t):
    n, k = map(int, input().split())
    time = [-1] + list(map(int, input().split()))

    order = [[] for _ in range(n + 1)]
    for _ in range(k):
        x, y = map(int, input().split())
        order[y].append(x)
    
    w = int(input())

    queue = deque([w])
    dp = [0] * (n + 1)
    dp[w] = time[w]

    while queue:
        cur = queue.popleft()

        for next in order[cur]:
            queue.append(next)
            dp[next] = max(dp[next], dp[cur] + time[next])

        if not queue:
            print(dp[cur])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간 초과&lt;/b&gt;로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque
import sys
input = sys.stdin.readline

t = int(input())

for _ in range(t):
    n, k = map(int, input().split())
    time = [-1] + list(map(int, input().split()))

    # 진입 차수 배열 (선행 조건 개수)
    in_dgree = [0] * (n + 1) 
    graph = [[] for _ in range(n + 1)]

    for _ in range(k):
        x, y = map(int, input().split())
        graph[x].append(y)
        in_dgree[y] += 1
    
    w = int(input())

    queue = deque([])
    dp = [-1] * (n + 1)

    # 진입 차수가 0인 건물(선행 조건 없는 건물)을 큐에 삽입
    for i in range(1, n + 1):
        if in_dgree[i] == 0:
            queue.append(i)
            dp[i] = time[i]

    while queue:
        cur = queue.popleft()

        for next in graph[cur]:
            dp[next] = max(dp[next], dp[cur] + time[next])
            in_dgree[next] -= 1

            # 모든 선행 조건 해결되면 큐에 추가 (건설 시작 가능)
            if in_dgree[next] == 0:
                queue.append(next)

    print(dp[w])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위상 정렬 단계를 추가해 중복 탐색을 제거&lt;/b&gt;함으로써 시간 초과 문제를 해결할 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;172&quot; data-start=&quot;112&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;위상 정렬은 &lt;b&gt;그래프의 모든 노드를 순서대로 나열하는 과정&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;위상정렬을 통해 선행 조건을 전부 처리한 뒤에 후행 건물을 계산함으로써&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;건물을 실제로 &amp;ldquo;지을 수 있는 순서&amp;rdquo;를 찾아낼 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;285&quot; data-start=&quot;174&quot; data-ke-size=&quot;size16&quot;&gt;`in_degree` 리스트를 사용해 각 노드의 진입 차수(선행 조건 개수)를 관리한다.&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;진입 차수가 0인 노드들은 선행 조건이 없기 때문에, 큐에 삽입해서 탐색을 시작한다.&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;(선행 조건이 없기 때문에 건물을 바로 지을 수 있다)&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;이후 그래프를 따라가며 간선을 하나씩 제거하면 후행 노드들의 진입 차수가 감소한다.&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;특정 노드의 진입 차수가 0이 되면, 모든 선행 조건이 해결된 것이므로 큐에 추가한다.&lt;/p&gt;
&lt;p data-end=&quot;447&quot; data-start=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;449&quot; data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면 각 노드는 큐에 한 번씩만 들어가기 때문에 탐색이 중복되지 않으며,&lt;/p&gt;
&lt;p data-end=&quot;536&quot; data-start=&quot;449&quot; data-ke-size=&quot;size16&quot;&gt;선행 조건을 모두 만족하는 올바른 순서대로 탐색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>dp</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>위상정렬</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/573</guid>
      <comments>https://divheer.tistory.com/573#entry573comment</comments>
      <pubDate>Tue, 30 Sep 2025 08:47:39 +0900</pubDate>
    </item>
    <item>
      <title>[백준/골드4] 1967번 트리의 지름 | DFS, BFS | 파이썬</title>
      <link>https://divheer.tistory.com/572</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1967&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1967&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;트리(tree)는 사이클이 없는 무방향 그래프이다. 트리에서는 어떤 두 노드를 선택해도 둘 사이에 경로가 항상 하나만 존재하게 된다. 트리에서 어떤 두 노드를 선택해서 양쪽으로 쫙 당길 때, 가장 길게 늘어나는 경우가 있을 것이다. 이럴 때 트리의 모든 노드들은 이 두 노드를 지름의 끝 점으로 하는 원 안에 들어가게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mvYWb/btsQKRlc1S9/6srabPrxnyga6Bkp1Uhpwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mvYWb/btsQKRlc1S9/6srabPrxnyga6Bkp1Uhpwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mvYWb/btsQKRlc1S9/6srabPrxnyga6Bkp1Uhpwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmvYWb%2FbtsQKRlc1S9%2F6srabPrxnyga6Bkp1Uhpwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;310&quot; height=&quot;123&quot; data-origin-width=&quot;310&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 두 노드 사이의 경로의 길이를 트리의 지름이라고 한다. 정확히 정의하자면 트리에 존재하는 모든 경로들 중에서 가장 긴 것의 길이를 말한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;입력으로 루트가 있는 트리를 가중치가 있는 간선들로 줄 때, 트리의 지름을 구해서 출력하는 프로그램을 작성하시오. 아래와 같은 트리가 주어진다면 트리의 지름은 45가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxPw4S/btsQMK6aQcy/SmxlyGzFhZKHY4nDGXKA5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxPw4S/btsQMK6aQcy/SmxlyGzFhZKHY4nDGXKA5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxPw4S/btsQMK6aQcy/SmxlyGzFhZKHY4nDGXKA5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxPw4S%2FbtsQMK6aQcy%2FSmxlyGzFhZKHY4nDGXKA5k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;312&quot; height=&quot;152&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;152&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;트리의 노드는 1부터 n까지 번호가 매겨져 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;파일의 첫 번째 줄은 노드의 개수 n(1 &amp;le; n &amp;le; 10,000)이다. 둘째 줄부터 n-1개의 줄에 각 간선에 대한 정보가 들어온다. 간선에 대한 정보는 세 개의 정수로 이루어져 있다. 첫 번째 정수는 간선이 연결하는 두 노드 중 부모 노드의 번호를 나타내고, 두 번째 정수는 자식 노드를, 세 번째 정수는 간선의 가중치를 나타낸다. 간선에 대한 정보는 부모 노드의 번호가 작은 것이 먼저 입력되고, 부모 노드의 번호가 같으면 자식 노드의 번호가 작은 것이 먼저 입력된다. 루트 노드의 번호는 항상 1이라고 가정하며, 간선의 가중치는 100보다 크지 않은 양의 정수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 트리의 지름을 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1457&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rLy2n/btsQMPfkenV/XBYRUS83oJMoychEBQYrqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rLy2n/btsQMPfkenV/XBYRUS83oJMoychEBQYrqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rLy2n/btsQMPfkenV/XBYRUS83oJMoychEBQYrqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrLy2n%2FbtsQMPfkenV%2FXBYRUS83oJMoychEBQYrqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1457&quot; height=&quot;529&quot; data-origin-width=&quot;1457&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이1 (DFS - 재귀함수)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline
sys.setrecursionlimit(10**6)

n = int(input())
tree = [[] for _ in range(n + 1)]

for _ in range(n - 1):
    p, c, d = map(int, input().split())
    tree[p].append((c, d))
    tree[c].append((p, d))

def dfs(cur):
    for next, dist in tree[cur]:
        if visited[next] == -1:
            visited[next] = visited[cur] + dist
            dfs(next)

visited = [-1] * (n + 1)
visited[1] = 0

dfs(1)
start_node = visited.index(max(visited))

visited = [-1] * (n + 1)
visited[start_node] = 0

dfs(start_node)
print(max(visited))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리의 지름을 구하기 위해서는 두 단계에 걸쳐 접근해야 한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;루트 노드(1번)부터 시작해서 가장 멀리 있는 노드를 구한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;1번에서 구한 노드에서 시작해서 다시 가장 멀리 있는 노드를 구한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번과 2번에서 구한 두 노드 사이의 거리가 트리의 지름이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 멀리 있는 노드는 재귀 함수, 스택, 큐로 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 경우 모두 `visited`에 시작 노드로부터 해당 인덱스의 노드의 거리를 저장하는 방식으로 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이2 (DFS - 스택)&lt;/h2&gt;
&lt;pre id=&quot;code_1758775774587&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline

n = int(input())
tree = [[] for _ in range(n + 1)]

for _ in range(n - 1):
    p, c, d = map(int, input().split())
    tree[p].append((c, d))
    tree[c].append((p, d))

def dfs(start):
    visited = [-1] * (n + 1)
    visited[start] = 0
    stack = [start]

    while stack:
        cur = stack.pop()
        for next, dist in tree[cur]:
            if visited[next] == -1:
                stack.append(next)
                visited[next] = visited[cur] + dist
    
    max_dist = max(visited)
    max_node = visited.index(max_dist)

    return [max_node, max_dist]

start_node, start_dist = dfs(1)
end_node, max_dist = dfs(start_node)
print(max_dist)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;풀이3 (BFS - 큐)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque
import sys
input = sys.stdin.readline

n = int(input())
tree = [[] for _ in range(n + 1)]

for _ in range(n - 1):
    p,c, d = map(int, input().split())
    tree[p].append((c, d))
    tree[c].append((p, d))

def bfs(start):
    visited = [-1] * (n + 1)
    visited[start] = 0
    queue = deque([start])

    while queue:
        cur = queue.popleft()
        for next, dist in tree[cur]:
            if visited[next] == -1:
                queue.append(next)
                visited[next] = visited[cur] + dist
    
    max_dist = max(visited)
    max_node = visited.index(max_dist)

    return [max_node, max_dist]

start_node, start_dist = bfs(1)
end_node, max_dist = bfs(start_node)
print(max_dist)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://hstory0208.tistory.com/entry/Python%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B0%B1%EC%A4%80-1967-%ED%8A%B8%EB%A6%AC%EC%9D%98-%EC%A7%80%EB%A6%84-DFS-BFS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Python/파이썬]&amp;nbsp;백준&amp;nbsp;1967&amp;nbsp;-&amp;nbsp;트리의&amp;nbsp;지름&amp;nbsp;(DFS&amp;nbsp;/&amp;nbsp;BFS)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>BFS</category>
      <category>dfs</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/572</guid>
      <comments>https://divheer.tistory.com/572#entry572comment</comments>
      <pubDate>Fri, 26 Sep 2025 08:30:12 +0900</pubDate>
    </item>
    <item>
      <title>[백준/실버3] 9095번 1, 2, 3 더하기 | DP | 파이썬</title>
      <link>https://divheer.tistory.com/571</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/9095&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/9095&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;1+1+1+1&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;1+1+2&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;1+2+1&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;2+1+1&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;2+2&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;1+3&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;3+1&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcxvsp/btsQIV188WN/BYMLecEtYVaTYShuRlDsf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcxvsp/btsQIV188WN/BYMLecEtYVaTYShuRlDsf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcxvsp/btsQIV188WN/BYMLecEtYVaTYShuRlDsf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcxvsp%2FbtsQIV188WN%2FBYMLecEtYVaTYShuRlDsf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1450&quot; height=&quot;267&quot; data-origin-width=&quot;1450&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;t = int(input())

dp = [0] * 11
dp[1] = 1
dp[2] = 2
dp[3] = 4

for i in range(4, 11):
    dp[i] = dp[i-1] + dp[i-2] + dp[i-3]

for _ in range(t):
    n = int(input())
    print(dp[n])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[n]`은 정수 n을 1, 2, 3의 합으로 나타내는 경우의 수를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;233&quot; data-ke-size=&quot;size16&quot;&gt;정수 n을 만드는 방법은 세 가지로 나눌 수 있다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;493&quot; data-start=&quot;267&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;341&quot; data-start=&quot;267&quot;&gt;&lt;b&gt;마지막에 1을 더한 경우&lt;/b&gt;&lt;br /&gt;&amp;rarr; n-1을 만드는 경우의 수 `dp[n-1]`에 1을 붙이면 n이 됨&lt;/li&gt;
&lt;li data-end=&quot;417&quot; data-start=&quot;343&quot;&gt;&lt;b&gt;마지막에 2를 더한 경우&lt;/b&gt;&lt;br /&gt;&amp;rarr; n-2를 만드는 경우의 수 `dp[n-2]`에 2를 붙이면 n이 됨&lt;/li&gt;
&lt;li data-end=&quot;493&quot; data-start=&quot;419&quot;&gt;&lt;b&gt;마지막에 3을 더한 경우&lt;/b&gt;&lt;br /&gt;&amp;rarr; n-3을 만드는 경우의 수 `dp[n-3]`에 3을 붙이면 n이 됨&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 `dp[i] = dp[i-1] + dp[i-2] + dp[i-3]`라는 점화식이 성립한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 `dp[4] = dp[3] + dp[2] + dp[1]`의 경우에&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;754&quot; data-start=&quot;601&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;667&quot; data-start=&quot;601&quot;&gt;`dp[3] = 4` &amp;rarr; (111, 12, 21, 3)에 1을 붙이면:&lt;br /&gt;1111, 121, 211, 31&lt;/li&gt;
&lt;li data-end=&quot;715&quot; data-start=&quot;669&quot;&gt;`dp[2] = 2` &amp;rarr; (11, 2)에 2를 붙이면:&lt;br /&gt;112, 22&lt;/li&gt;
&lt;li data-end=&quot;754&quot; data-start=&quot;717&quot;&gt;`dp[1] = 1` &amp;rarr; (1)에 3을 붙이면:&lt;br /&gt;13&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;810&quot; data-start=&quot;756&quot; data-ke-size=&quot;size16&quot;&gt;전부 합치면 1111, 121, 211, 31, 112, 22, 13 &amp;rarr; 총 7가지&lt;/p&gt;
&lt;p data-end=&quot;833&quot; data-start=&quot;812&quot; data-ke-size=&quot;size16&quot;&gt;즉, `dp[4] = 7`이 된다.&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>dp</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/571</guid>
      <comments>https://divheer.tistory.com/571#entry571comment</comments>
      <pubDate>Tue, 23 Sep 2025 08:13:41 +0900</pubDate>
    </item>
    <item>
      <title>[백준/실버1] 2178번 미로 탐색 | BFS | 파이썬</title>
      <link>https://divheer.tistory.com/570</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2178&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2178&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;N&amp;times;M크기의 배열로 표현되는 미로가 있다.&lt;/p&gt;
&lt;table style=&quot;background-color: #ffffff; color: #333333; text-align: start; border-collapse: collapse; width: 35%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.51163%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.17941%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.91366%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.04653%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 5.23259%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 4.8837%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.51163%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.17941%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.91366%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.04653%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.23259%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 4.8837%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.51163%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.17941%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.91366%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.04653%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.23259%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 4.8837%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 6.51163%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.17941%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 5.91366%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 6.04653%; text-align: center;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 5.23259%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 4.8837%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;미로에서 1은 이동할 수 있는 칸을 나타내고, 0은 이동할 수 없는 칸을 나타낸다. 이러한 미로가 주어졌을 때, (1, 1)에서 출발하여 (N, M)의 위치로 이동할 때 지나야 하는 최소의 칸 수를 구하는 프로그램을 작성하시오. 한 칸에서 다른 칸으로 이동할 때, 서로 인접한 칸으로만 이동할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위의 예에서는 15칸을 지나야 (N, M)의 위치로 이동할 수 있다. 칸을 셀 때에는 시작 위치와 도착 위치도 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 두 정수 N, M(2 &amp;le; N, M &amp;le; 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;붙어서&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;입력으로 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 지나야 하는 최소의 칸 수를 출력한다. 항상 도착위치로 이동할 수 있는 경우만 입력으로 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwmNbi/btsQIIHGVhG/aoyPQk6vfaZOMAsicJkoKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwmNbi/btsQIIHGVhG/aoyPQk6vfaZOMAsicJkoKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwmNbi/btsQIIHGVhG/aoyPQk6vfaZOMAsicJkoKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwmNbi%2FbtsQIIHGVhG%2FaoyPQk6vfaZOMAsicJkoKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1307&quot; height=&quot;770&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import deque

n, m = map(int, input().split())
graph = [[int(i) for i in input()] for _ in range(n)]

moves = [(1, 0), (-1, 0), (0, 1), (0, -1)]
queue = deque([(0, 0)])

def bfs():
    while queue:
        x, y = queue.popleft()

        for dx, dy in moves:
            nx, ny = x + dx, y + dy
            
            if 0 &amp;lt;= nx &amp;lt; n and 0 &amp;lt;= ny &amp;lt; m:
                if graph[nx][ny] == 1:
                    graph[nx][ny] = graph[x][y] + 1
                    queue.append((nx, ny))
    
    print(graph[n-1][m-1])

bfs()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;93&quot; data-ke-size=&quot;size16&quot;&gt;최단 경로 문제이므로 &lt;b&gt;BFS &lt;/b&gt;알고리즘을 사용했다.&lt;br /&gt;BFS는 시작점에서 가까운 칸부터 차례대로 탐색하므로, 도착 지점에 처음 도달했을 때가 곧 &lt;b&gt;최단 경로&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-end=&quot;204&quot; data-start=&quot;93&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;319&quot; data-start=&quot;206&quot; data-ke-size=&quot;size16&quot;&gt;탐색 과정에서 `graph[nx][ny] == 1` 조건을 이용해 &lt;b&gt;아직 방문하지 않은 칸만 이동&lt;/b&gt;한다.&lt;br /&gt;따라서 어떤 칸에 처음 도착했을 때, 그 칸까지 온 경로는 &lt;b&gt;항상 가장 짧은 거리&lt;/b&gt;가 된다.&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>BFS</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/570</guid>
      <comments>https://divheer.tistory.com/570#entry570comment</comments>
      <pubDate>Mon, 22 Sep 2025 09:00:57 +0900</pubDate>
    </item>
    <item>
      <title>[백준/골드3] 1520번 내리막 길 | DP | 파이썬</title>
      <link>https://divheer.tistory.com/569</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1520&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1520&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;여행을 떠난 세준이는 지도를 하나 구하였다. 이 지도는 아래 그림과 같이 직사각형 모양이며 여러 칸으로 나뉘어져 있다. 한 칸은 한 지점을 나타내는데 각 칸에는 그 지점의 높이가 쓰여 있으며, 각 지점 사이의 이동은 지도에서 상하좌우 이웃한 곳끼리만 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;189&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LDrH7/btsQCpB1Uzl/xsbgLKkWOVK3s1QiwtX2N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LDrH7/btsQCpB1Uzl/xsbgLKkWOVK3s1QiwtX2N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LDrH7/btsQCpB1Uzl/xsbgLKkWOVK3s1QiwtX2N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLDrH7%2FbtsQCpB1Uzl%2FxsbgLKkWOVK3s1QiwtX2N1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;189&quot; height=&quot;151&quot; data-origin-width=&quot;189&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;현재 제일 왼쪽 위 칸이 나타내는 지점에 있는 세준이는 제일 오른쪽 아래 칸이 나타내는 지점으로 가려고 한다. 그런데 가능한 힘을 적게 들이고 싶어 항상 높이가 더 낮은 지점으로만 이동하여 목표 지점까지 가고자 한다. 위와 같은 지도에서는 다음과 같은 세 가지 경로가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1ntg/btsQEpnpeWS/MpFL8enp3INUdQfPkw2Sb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1ntg/btsQEpnpeWS/MpFL8enp3INUdQfPkw2Sb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1ntg/btsQEpnpeWS/MpFL8enp3INUdQfPkw2Sb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1ntg%2FbtsQEpnpeWS%2FMpFL8enp3INUdQfPkw2Sb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;580&quot; height=&quot;158&quot; data-origin-width=&quot;580&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;지도가 주어질 때 이와 같이 제일 왼쪽 위 지점에서 출발하여 제일 오른쪽 아래 지점까지 항상 내리막길로만 이동하는 경로의 개수를 구하는 프로그램을 작성하시오.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에는 지도의 세로의 크기 M과 가로의 크기 N이 빈칸을 사이에 두고 주어진다. 이어 다음 M개 줄에 걸쳐 한 줄에 N개씩 위에서부터 차례로 각 지점의 높이가 빈 칸을 사이에 두고 주어진다. M과 N은 각각 500이하의 자연수이고, 각 지점의 높이는 10000이하의 자연수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 이동 가능한 경로의 수 H를 출력한다. 모든 입력에 대하여 H는 10억 이하의 음이 아닌 정수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSpWM9/btsQDZvXlCL/9GiMoznIvtWbkb8BNdyoEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSpWM9/btsQDZvXlCL/9GiMoznIvtWbkb8BNdyoEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSpWM9/btsQDZvXlCL/9GiMoznIvtWbkb8BNdyoEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSpWM9%2FbtsQDZvXlCL%2F9GiMoznIvtWbkb8BNdyoEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1443&quot; height=&quot;300&quot; data-origin-width=&quot;1443&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline

m, n = map(int, input().split())
map = [list(map(int, input().split())) for _ in range(m)]

dp = [[0] * n for _ in range(m)]
dp[0][0] = 1

for i in range(m):
    for j in range(n):
        if dp[i][j] &amp;gt; 0:
            if i &amp;gt; 0 and map[i][j] &amp;gt; map[i-1][j]: # 위로
                dp[i-1][j] += dp[i][j]
            if i &amp;lt; (m - 1) and map[i][j] &amp;gt; map[i+1][j]: # 아래로
                dp[i+1][j] += dp[i][j]
            if j &amp;gt; 0 and map[i][j] &amp;gt; map[i][j-1]: # 왼쪽으로
                dp[i][j-1] += dp[i][j]
            if j &amp;lt; (n - 1) and map[i][j] &amp;gt; map[i][j+1]: # 오른쪽으로
                    dp[i][j+1] += dp[i][j]

print(dp[m-1][n-1])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이는 for문에 따라 오른쪽 아래로 내려가면서 횟수를 카운트하기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위나 왼쪽으로 이동하는 경우에는 횟수를 반영하지 못하는 경우의 수가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import sys
input = sys.stdin.readline
sys.setrecursionlimit(10**6)

m, n = map(int, input().split())
map = [list(map(int, input().split())) for _ in range(m)]

dp = [[-1] * n for _ in range(m)]
moves = [(0, 1), (0, -1), (1, 0), (-1, 0)]

def dfs(x, y):
    # 도착점인 경우
    if x == (m - 1) and y == (n - 1):
        return 1
    
    # 이미 방문한 적이 있을 경우
    if dp[x][y] != -1:
        return dp[x][y]
    
    dp[x][y] = 0   # 방문 표시 + 초기화

    for dx, dy in moves:
        nx, ny = x + dx, y + dy
        if 0 &amp;lt;= nx &amp;lt; m and 0 &amp;lt;= ny &amp;lt; n:
            if map[x][y] &amp;gt; map[nx][ny]:
                dp[x][y] += dfs(nx, ny)
    
    return dp[x][y]

print(dfs(0, 0))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 칸에서 도착점까지 갈 수 있는 모든 경로를 탐색하고 결과를 저장하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[x][y]`에는&amp;nbsp;&lt;b&gt;좌표 (x,y)에서 도착점까지 갈 수 있는 모든 경로 수&lt;/b&gt;를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS로 탐색하면서, 이미 방문한 칸은 DP 테이블에서 값을 가져오고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 방문하지 않은 칸만 직접 계산하는 메모이제이션을 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/569</guid>
      <comments>https://divheer.tistory.com/569#entry569comment</comments>
      <pubDate>Sun, 21 Sep 2025 08:57:37 +0900</pubDate>
    </item>
    <item>
      <title>[백준/골드4] 2293번 동전 1 | DP | 파이썬</title>
      <link>https://divheer.tistory.com/568</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2293&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2293&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n가지 종류의 동전이 있다. 각각의 동전이 나타내는 가치는 다르다. 이 동전을 적당히 사용해서, 그 가치의 합이 k원이 되도록 하고 싶다. 그 경우의 수를 구하시오. 각각의 동전은 몇 개라도 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사용한 동전의 구성이 같은데, 순서만 다른 것은 같은 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 n, k가 주어진다. (1 &amp;le; n &amp;le; 100, 1 &amp;le;&amp;nbsp;k &amp;le; 10,000) 다음 n개의 줄에는 각각의 동전의 가치가 주어진다. 동전의 가치는 100,000보다 작거나 같은 자연수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 경우의 수를 출력한다. 경우의 수는 2&lt;/span&gt;31&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;보다 작다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/seLsp/btsQzNJm64p/tFMiMGqhvGffmLLa05lWKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/seLsp/btsQzNJm64p/tFMiMGqhvGffmLLa05lWKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/seLsp/btsQzNJm64p/tFMiMGqhvGffmLLa05lWKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FseLsp%2FbtsQzNJm64p%2FtFMiMGqhvGffmLLa05lWKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1448&quot; height=&quot;267&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n, k = map(int, input().split())
coins = [int(input()) for _ in range(n)]

dp = [0] * (k + 1)
dp[0] = 1

for c in coins:
    for i in range(c, k + 1):
        dp[i] += dp[i - c]

print(dp[k])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[x]`에 &lt;b&gt;x원을 만드는 경우의 수&lt;/b&gt;를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[0] = 1`으로 초기화하고, (0원을 만드는 방법은 아무 동전도 사용하지 않는 방법 한 가지)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 동전의 값부터 `k`까지 순회하면서 횟수를 누적해나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[i] += dp[i - c]` 점화식은 다음과 같은 의미를 가진다.&lt;/p&gt;
&lt;p data-end=&quot;278&quot; data-start=&quot;207&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`i`원을 만드는 경우의 수를 구하려면, &lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;278&quot; data-start=&quot;207&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;`i-c`원을 만드는 경우의 수에 동전 c를 하나 더 사용하는 방법을 추가한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;278&quot; data-start=&quot;207&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;335&quot; data-start=&quot;297&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 동전이 `{2}`만 있다고 하고, `i = 6`원을 만든다고 해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;419&quot; data-start=&quot;337&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;367&quot; data-start=&quot;337&quot;&gt;`dp[6] += dp[6 - 2]`&lt;/li&gt;
&lt;li data-end=&quot;419&quot; data-start=&quot;368&quot;&gt;즉, &quot;6원을 만드는 방법&quot;은 &quot;4원을 만드는 방법에 2원을 하나 더 사용하는 것&quot;이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 DP에 경우의 수에 따른 값을 모두 저장하는 방법을 생각했었는데, 간단하게 횟수 자체를 저장하면 되는 거였다. 이런 문제 유형 자주 나오는 거 같아서 많이 익숙해져야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://didu-story.tistory.com/440&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[백준]&amp;nbsp;(Python)&amp;nbsp;2293번&amp;nbsp;-&amp;nbsp;동전1&amp;nbsp;(골드5,&amp;nbsp;dp)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>dp</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/568</guid>
      <comments>https://divheer.tistory.com/568#entry568comment</comments>
      <pubDate>Tue, 16 Sep 2025 10:23:51 +0900</pubDate>
    </item>
    <item>
      <title>[백준/실버1] 1991번 트리 순회 | 파이썬</title>
      <link>https://divheer.tistory.com/567</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1991&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1991&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;이진 트리를 입력받아 전위 순회(preorder traversal), 중위 순회(inorder traversal), 후위 순회(postorder traversal)한 결과를 출력하는 프로그램을 작성하시오.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KXiw8/btsQwDHxoGc/U8m5XhJvBnKi2ZZ3whiVSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KXiw8/btsQwDHxoGc/U8m5XhJvBnKi2ZZ3whiVSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KXiw8/btsQwDHxoGc/U8m5XhJvBnKi2ZZ3whiVSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKXiw8%2FbtsQwDHxoGc%2FU8m5XhJvBnKi2ZZ3whiVSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;265&quot; height=&quot;220&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 위와 같은 이진 트리가 입력되면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;전위 순회한 결과 : ABDCEFG // (루트) (왼쪽 자식) (오른쪽 자식)&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;중위 순회한 결과 : DBAECFG // (왼쪽 자식) (루트) (오른쪽 자식)&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;후위 순회한 결과 : DBEGFCA // (왼쪽 자식) (오른쪽 자식) (루트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에는 이진 트리의 노드의 개수 N(1 &amp;le; N &amp;le; 26)이 주어진다. 둘째 줄부터 N개의 줄에 걸쳐 각 노드와 그의 왼쪽 자식 노드, 오른쪽 자식 노드가 주어진다. 노드의 이름은 A부터 차례대로 알파벳 대문자로 매겨지며, 항상 A가 루트 노드가 된다. 자식 노드가 없는 경우에는 .으로 표현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;첫째 줄에 전위 순회, 둘째 줄에 중위 순회, 셋째 줄에 후위 순회한 결과를 출력한다. 각 줄에 N개의 알파벳을 공백 없이 출력하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw8aL2/btsQxbKJIia/HGECSut6HHKCTei3t9x9HK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw8aL2/btsQxbKJIia/HGECSut6HHKCTei3t9x9HK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw8aL2/btsQxbKJIia/HGECSut6HHKCTei3t9x9HK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw8aL2%2FbtsQxbKJIia%2FHGECSut6HHKCTei3t9x9HK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1449&quot; height=&quot;389&quot; data-origin-width=&quot;1449&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;n = int(input())
tree = {}

for _ in range(n):
    root, left, right = input().split()
    tree[root] = (left, right)

def preorder(node):
    if node != '.':
        print(node, end='')
        preorder(tree[node][0])
        preorder(tree[node][1])

def inorder(node):
    if node != '.':
        inorder(tree[node][0])
        print(node, end='')
        inorder(tree[node][1])

def postorder(node):
    if node != '.':
        postorder(tree[node][0])
        postorder(tree[node][1])  
        print(node, end='')

preorder('A')
print()
inorder('A')
print()
postorder('A')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀함수와 출력 시 구분자(sep)를 이용해 루트의 순서에 따라 결과를 한 줄에 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://bongseok.tistory.com/58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[백준&amp;nbsp;1991]&amp;nbsp;트리&amp;nbsp;순회&amp;nbsp;-&amp;nbsp;파이썬&amp;nbsp;풀이&amp;nbsp;(실버1)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/567</guid>
      <comments>https://divheer.tistory.com/567#entry567comment</comments>
      <pubDate>Mon, 15 Sep 2025 08:09:08 +0900</pubDate>
    </item>
    <item>
      <title>[백준/실버3] 1003번 피보나치 함수 | DP | 파이썬</title>
      <link>https://divheer.tistory.com/566</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1003&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1003&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;다음 소스는 N번째 피보나치 수를 구하는 C++ 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1757564653975&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int fibonacci(int n) {
    if (n == 0) {
        printf(&quot;0&quot;);
        return 0;
    } else if (n == 1) {
        printf(&quot;1&quot;);
        return 1;
    } else {
        return fibonacci(n‐1) + fibonacci(n‐2);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;fibonacci(3)을 호출하면 다음과 같은 일이 일어난다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;fibonacci(3)은&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(2)와&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;(첫 번째 호출)을 호출한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;fibonacci(2)는&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;(두 번째 호출)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(0)을 호출한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;두 번째 호출한&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)은 1을 출력하고 1을 리턴한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;fibonacci(0)은 0을 출력하고, 0을 리턴한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;fibonacci(2)는&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)과&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(0)의 결과를 얻고, 1을 리턴한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;첫 번째 호출한&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)은 1을 출력하고, 1을 리턴한다.&lt;/li&gt;
&lt;li style=&quot;color: #555555;&quot;&gt;fibonacci(3)은&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(2)와&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(1)의 결과를 얻고, 2를 리턴한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1은 2번 출력되고, 0은 1번 출력된다. N이 주어졌을 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;fibonacci(N)을 호출했을 때, 0과 1이 각각 몇 번 출력되는지 구하는 프로그램을 작성하시오.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫째 줄에 테스트 케이스의 개수 T가 주어진다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 테스트 케이스는 한 줄로 이루어져 있고, N이 주어진다. N은 40보다 작거나 같은 자연수 또는 0이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;각 테스트 케이스마다 0이 출력되는 횟수와 1이 출력되는 횟수를 공백으로 구분해서 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KWpDk/btsQsLS0XKy/gcNq278iRtLHMdMg54ZVP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KWpDk/btsQsLS0XKy/gcNq278iRtLHMdMg54ZVP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KWpDk/btsQsLS0XKy/gcNq278iRtLHMdMg54ZVP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKWpDk%2FbtsQsLS0XKy%2FgcNq278iRtLHMdMg54ZVP1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1445&quot; height=&quot;504&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;t = int(input())
nums = [int(input()) for _ in range(t)]
n = max(nums)

zero = [0] * 41
one = [0] * 41

zero[0] = 1
one[1] = 1

for i in range(2, n + 1):
    zero[i] = zero[i - 1] + zero[i - 2]
    one[i] = one[i - 1] + one[i - 2]

for i in nums:
    print(zero[i], one[i])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0과 1 출력 횟수를 저장하는 리스트를 각각 생성하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 테스트 케이스의 최대값만큼 DP를 계산한 후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 테스트 케이스에 맞는 답을 출력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 코딩테스트를 준비하기 위해 DP랑 DFS 등 내가 어려워하는 문제들을 좀 풀어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/566</guid>
      <comments>https://divheer.tistory.com/566#entry566comment</comments>
      <pubDate>Thu, 11 Sep 2025 13:28:58 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] shimmer 패키지로 스켈레톤 UI 적용하기</title>
      <link>https://divheer.tistory.com/565</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 목표&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;비포알리미 스켈레톤 UI.gif&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UBPJE/btsQubo9OjG/3qaSFuRSGHL8I9KkHFDE91/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UBPJE/btsQubo9OjG/3qaSFuRSGHL8I9KkHFDE91/img.gif&quot; data-alt=&quot;테스트를 위해 로딩 속도를 일부러 지연시켰다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UBPJE/btsQubo9OjG/3qaSFuRSGHL8I9KkHFDE91/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/UBPJE/btsQubo9OjG/3qaSFuRSGHL8I9KkHFDE91/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;353&quot; height=&quot;590&quot; data-filename=&quot;비포알리미 스켈레톤 UI.gif&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트를 위해 로딩 속도를 일부러 지연시켰다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;273&quot; data-start=&quot;163&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 화면 중앙에 &lt;b&gt;로딩 스피너&lt;/b&gt;를 배치했는데,&lt;/p&gt;
&lt;p data-end=&quot;273&quot; data-start=&quot;163&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 불러온 뒤에 시선이 다시 화면 상단으로 이동해야 하는 점이 다소 불편하다고 느꼈다.&lt;/p&gt;
&lt;p data-end=&quot;273&quot; data-start=&quot;163&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;275&quot; data-ke-size=&quot;size16&quot;&gt;그래서 시선 이동을 최소화하고 사용자 경험을 높이기 위해&lt;/p&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;275&quot; data-ke-size=&quot;size16&quot;&gt;홈 화면 로딩 시 로딩 스피너 대신 &lt;b&gt;스켈레톤 UI&lt;/b&gt;를 적용하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;shimmer 패키지&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flutter shimmer.gif&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DnyxJ/btsQrVuBZv7/r2jKx8i9dbZFRni5DQGU4k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DnyxJ/btsQrVuBZv7/r2jKx8i9dbZFRni5DQGU4k/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DnyxJ/btsQrVuBZv7/r2jKx8i9dbZFRni5DQGU4k/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/DnyxJ/btsQrVuBZv7/r2jKx8i9dbZFRni5DQGU4k/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;676&quot; height=&quot;597&quot; data-filename=&quot;flutter shimmer.gif&quot; data-origin-width=&quot;676&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;462&quot; data-start=&quot;346&quot; data-ke-size=&quot;size16&quot;&gt;플러터에서 손쉽게 &lt;b&gt;shimmer 효과&lt;/b&gt;를 적용할 수 있도록 도와주는 패키지다.&lt;/p&gt;
&lt;p data-end=&quot;548&quot; data-start=&quot;464&quot; data-ke-size=&quot;size16&quot;&gt;스켈레톤 UI를 만들 때 자주 활용되며, `baseColor`, `highlightColor`, `child`만 지정하면 간단하게 적용할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;548&quot; data-start=&quot;464&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvMtks/btsQsLye43Y/0IPkGgXJlEqvugetYruy70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvMtks/btsQsLye43Y/0IPkGgXJlEqvugetYruy70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvMtks/btsQsLye43Y/0IPkGgXJlEqvugetYruy70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvMtks%2FbtsQsLye43Y%2F0IPkGgXJlEqvugetYruy70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;655&quot; height=&quot;288&quot; data-origin-width=&quot;655&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;548&quot; data-start=&quot;464&quot; data-ke-size=&quot;size16&quot;&gt;그 외에는 다음과 같은 옵션들을 제공한다.&lt;/p&gt;
&lt;figure id=&quot;og_1757492673537&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;shimmer | Flutter package&quot; data-og-description=&quot;A package provides an easy way to add shimmer effect in Flutter project&quot; data-og-host=&quot;pub.dev&quot; data-og-source-url=&quot;https://pub.dev/packages/shimmer&quot; data-og-url=&quot;https://pub.dev/packages/shimmer&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rHVCc/hyZIWiZ6kv/k13knxF3hDRA3X7zMn8gtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/QkyC7/hyZIMHtWRB/acV2JeCt0TaWE2onZituy0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://pub.dev/packages/shimmer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pub.dev/packages/shimmer&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rHVCc/hyZIWiZ6kv/k13knxF3hDRA3X7zMn8gtK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/QkyC7/hyZIMHtWRB/acV2JeCt0TaWE2onZituy0/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;shimmer | Flutter package&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A package provides an easy way to add shimmer effect in Flutter project&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pub.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공용 스켈레톤 위젯 만들기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wCRDz/btsQs7A18bP/L0N4Cgzgwy0BXJkU5mU9lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wCRDz/btsQs7A18bP/L0N4Cgzgwy0BXJkU5mU9lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wCRDz/btsQs7A18bP/L0N4Cgzgwy0BXJkU5mU9lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwCRDz%2FbtsQs7A18bP%2FL0N4Cgzgwy0BXJkU5mU9lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;353&quot; height=&quot;208&quot; data-origin-width=&quot;353&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 화면 구조가 단순하기 때문에, 공통 스켈레톤 카드 위젯을 만들고 높이만 전달하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757492193619&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';

// 공용 스켈레톤 카드
class SkeletonCard extends StatelessWidget {
  final double height;

  const SkeletonCard({
    super.key,
    required this.height,
  });

  @override
  Widget build(BuildContext context) {
    return Shimmer.fromColors(
      baseColor: Colors.grey[200]!,
      highlightColor: Colors.grey[100]!,
      child: Padding(
        padding: const EdgeInsets.all(2.0),
        child: Container(
          height: height,
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.circular(12),
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;홈 화면 전용 스켈레톤 UI&lt;/h2&gt;
&lt;pre id=&quot;code_1757492342802&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 로딩 시 표시하는 스켈레톤 UI 위젯
class HomeBodySkeleton extends StatelessWidget {
  const HomeBodySkeleton({super.key});

  @override
  Widget build(BuildContext context) {
    return const Padding(
      padding: EdgeInsets.symmetric(horizontal: 2.0),
      child: Column(
        children: [
          SizedBox(height: 1),
          SkeletonCard(height: 110), // 첫 번째 카드
          SizedBox(height: 11),
          SkeletonCard(height: 60), // 두 번째 카드
        ],
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈 화면의 구조와 크기에 맞춰 `HomeBodySkeleton`이라는 위젯을 따로 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757492395857&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;isLoading ? const HomeBodySkeleton() : Column(...)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`isLoading` 여부에 따라 스켈레톤 UI 또는 실제 화면을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter</category>
      <category>shimmer</category>
      <category>skeleton ui</category>
      <category>로딩화면</category>
      <category>스켈레톤UI</category>
      <category>플러터</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/565</guid>
      <comments>https://divheer.tistory.com/565#entry565comment</comments>
      <pubDate>Wed, 10 Sep 2025 17:31:14 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 프렌즈4블록 | 파이썬</title>
      <link>https://divheer.tistory.com/564</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17679&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/17679&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;블라인드 공채를 통과한 신입 사원 라이언은 신규 게임 개발 업무를 맡게 되었다. 이번에 출시할 게임 제목은 &quot;프렌즈4블록&quot;.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;같은 모양의 카카오프렌즈 블록이 2 &amp;times;2 형태로 4개가 붙어있을 경우 사라지면서 점수를 얻는 게임이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clQeD1/btsQofNCIIY/Zwfvcu5Kk46WGZUDIyl6k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clQeD1/btsQofNCIIY/Zwfvcu5Kk46WGZUDIyl6k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clQeD1/btsQofNCIIY/Zwfvcu5Kk46WGZUDIyl6k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclQeD1%2FbtsQofNCIIY%2FZwfvcu5Kk46WGZUDIyl6k1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;343&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;만약 판이 위와 같이 주어질 경우, 라이언이 2&amp;times;2로 배치된 7개 블록과 콘이 2&amp;times;2로 배치된 4개 블록이 지워진다. 같은 블록은 여러 2&amp;times;2에 포함될 수 있으며, 지워지는 조건에 만족하는 2&amp;times;2 모양이 여러 개 있다면 한꺼번에 지워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W2w0u/btsQparvDbc/ki5fHZr1pkWPCMGbf2Q2uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W2w0u/btsQparvDbc/ki5fHZr1pkWPCMGbf2Q2uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W2w0u/btsQparvDbc/ki5fHZr1pkWPCMGbf2Q2uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW2w0u%2FbtsQparvDbc%2Fki5fHZr1pkWPCMGbf2Q2uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;345&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;블록이 지워진 후에 위에 있는 블록이 아래로 떨어져 빈 공간을 채우게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBukHs/btsQqApttdh/uMdeXx8Ku2hJaCc9zXlll0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBukHs/btsQqApttdh/uMdeXx8Ku2hJaCc9zXlll0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBukHs/btsQqApttdh/uMdeXx8Ku2hJaCc9zXlll0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBukHs%2FbtsQqApttdh%2FuMdeXx8Ku2hJaCc9zXlll0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;343&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;만약 빈 공간을 채운 후에 다시 2&amp;times;2 형태로 같은 모양의 블록이 모이면 다시 지워지고 떨어지고를 반복하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCfemK/btsQqXq9T6Y/xUOOMPaVXTVkkB8pn55M60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCfemK/btsQqXq9T6Y/xUOOMPaVXTVkkB8pn55M60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCfemK/btsQqXq9T6Y/xUOOMPaVXTVkkB8pn55M60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCfemK%2FbtsQqXq9T6Y%2FxUOOMPaVXTVkkB8pn55M60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;345&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;위 초기 배치를 문자로 표시하면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;TTTANT &lt;br /&gt;RRFACC &lt;br /&gt;RRRFCC &lt;br /&gt;TRRRAA &lt;br /&gt;TTMMMF &lt;br /&gt;TMMTTJ&lt;/blockquote&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;각 문자는 라이언(R), 무지(M), 어피치(A), 프로도(F), 네오(N), 튜브(T), 제이지(J), 콘(C)을 의미한다&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력으로 블록의 첫 배치가 주어졌을 때, 지워지는 블록은 모두 몇 개인지 판단하는 프로그램을 제작하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입력 형식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;입력으로 판의 높이&lt;span&gt;&amp;nbsp;&lt;/span&gt;m, 폭&lt;span&gt;&amp;nbsp;&lt;/span&gt;n과 판의 배치 정보&lt;span&gt;&amp;nbsp;&lt;/span&gt;board가 들어온다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;2 ≦&lt;span&gt;&amp;nbsp;&lt;/span&gt;n,&lt;span&gt;&amp;nbsp;&lt;/span&gt;m&lt;span&gt;&amp;nbsp;&lt;/span&gt;≦ 30&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;board는 길이&lt;span&gt;&amp;nbsp;&lt;/span&gt;n인 문자열&lt;span&gt;&amp;nbsp;&lt;/span&gt;m개의 배열로 주어진다. 블록을 나타내는 문자는 대문자 A에서 Z가 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;출력 형식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;입력으로 주어진 판 정보를 가지고 몇 개의 블록이 지워질지 출력하라.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입출력 예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2n3Wl/btsQpylrv7B/fJKTXxdlbVmeNm7oeX8Ozk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2n3Wl/btsQpylrv7B/fJKTXxdlbVmeNm7oeX8Ozk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2n3Wl/btsQpylrv7B/fJKTXxdlbVmeNm7oeX8Ozk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2n3Wl%2FbtsQpylrv7B%2FfJKTXxdlbVmeNm7oeX8Ozk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;936&quot; height=&quot;143&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;143&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(m, n, board):
    board = [list(str) for str in board]
    answer = 0
    count = 1
    
    while True:
        new_answer = answer
        if count % 2 != 0:
            res = [[0] * n for _ in range(m)]
            
            for i in range(m-1):
                for j in range(n-1):
                    if board[i][j] == '/':
                        continue
                        
                    if board[i][j] == board[i][j+1] == board[i+1][j] == board[i+1][j+1]:
                        res[i][j:j+2], res[i+1][j:j+2] = [1, 1], [1, 1]
            
            for i in range(m):
                for j in range(n):
                    if res[i][j]:
                        new_answer += 1
                        board[i][j] = &quot;/&quot;

        else:
            for i in range(m-1):
                for j in range(n):
                    if board[i+1][j] == &quot;/&quot;:
                        board[i+1][j], board[i][j] = board[i][j], board[i+1][j]
           
        if count % 2 != 0 and answer == new_answer:
            break
        else:
            count += 1
            answer = new_answer
            
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 지저분하더라도 일단 풀어보자는 마음으로 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 케이스 11개 중에 2개를 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이는 한 번에 하나의 블록만 내릴 수 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 빈 공간에 두 개의 블록을 한 번에 내릴 수 없어서 문제가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(m, n, board):
    board = [list(str) for str in board]
    res = [[0] * n for _ in range(m)]
    
    answer = 0
    count = 1
    
    while True:
        count += 1
        new_answer = answer
        
        if count % 2 != 0:
            res = [[0] * n for _ in range(m)]
            
            for i in range(m-1):
                for j in range(n-1):
                    if board[i][j] == '/':
                        continue
                        
                    if board[i][j] == board[i][j+1] == board[i+1][j] == board[i+1][j+1]:
                        res[i][j:j+2], res[i+1][j:j+2] = [1, 1], [1, 1]
            
            for i in range(m):
                for j in range(n):
                    if res[i][j]:
                        new_answer += 1
                        board[i][j] = &quot;/&quot;
                        
            if answer == new_answer:
                break
            else:
                answer = new_answer

        else:
            for i in range(m-1, 0, -1):
                for j in range(n):
                    if board[i][j] == &quot;/&quot;:
                        for z in range(i-1, -1, -1):
                            if board[z][j] != &quot;/&quot;:
                                board[i][j], board[z][j] = board[z][j], board[i][j]
                                break
            
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록을 아래로 떨어뜨리는 로직을 수정해서 성공했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 너무 지저분해서 풀이를 좀 정리한 3번에서 설명하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이3 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1757461547486&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(m, n, board):
    board = [list(str) for str in board]
    total_removed = 0
    
    while True:
        to_remove = [[0] * n for _ in range(m)]
        removed_this_round = 0
            
        # 1. 제거 대상 찾기
        for i in range(m - 1):
            for j in range(n - 1):
                if board[i][j] == '/':
                    continue
                if board[i][j] == board[i][j+1] == board[i+1][j] == board[i+1][j+1]:
                    to_remove[i][j:j+2], to_remove[i+1][j:j+2] = [1, 1], [1, 1]
        
        # 2. 블록 제거
        for i in range(m):
            for j in range(n):
                if to_remove[i][j]:
                    board[i][j] = &quot;/&quot;
                    removed_this_round += 1
        
        # 제거된 블록이 없으면 종료
        if removed_this_round == 0:
            break

        total_removed += removed_this_round
        
        # 3. 블록 아래로 떨어뜨리기
        for i in range(m - 1, 0, -1):
            for j in range(n):
                if board[i][j] == &quot;/&quot;:
                    for z in range(i-1, -1, -1):
                        if board[z][j] != &quot;/&quot;:
                            board[i][j], board[z][j] = board[z][j], board[i][j]
                            break
            
    return total_removed&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일단 블록의 각 요소를 수정하기 위해 문자열이 담겨 있던 `board`를 2차원 배열로 변환했다.&lt;/li&gt;
&lt;li&gt;모든 블록을 순회하면서 2x2 형태의 4개의 블록이 있는지 찾고, 있다면 `to_remove` 리스트의 요소를 `0 -&amp;gt; 1`로 변환하면서 체크한다.&lt;/li&gt;
&lt;li&gt;`to_remove` 리스트에 체크한 것을 이용해 실제 `board`에 제거된 블록을 `&quot;/&quot;`로 표시하고, 이번 라운드에서 제거된 블록의 개수를 센다.&lt;/li&gt;
&lt;li&gt;만약 이번 라운드에서 제거된 블록이 없다면 while문을 종료하고, 제거된 블록이 있다면 `total_removed` 값을 업데이트한다.&lt;/li&gt;
&lt;li&gt;이제 블록을 아래로 떨어뜨려서 제거된 블록들의 빈 공간을 채운다. 블록 가장 아래에서 두 번째 줄부터 탐색하면서, 만약 빈 블록이라면 위로 한 줄씩 탐색해나가면서 `&quot;/&quot;` 값이 아닌 떨어뜨릴 수 있는 블록을 찾는다. 떨어뜨릴 블록이 존재한다면 빈 블록과 해당 블록의 위치를 교환한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 문제가 너무 복잡해 보여서 막막했었는데 일단 어떻게든 풀어보자는 마인드로 접근했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 풀면서 많은 수정을 거치긴 했지만 다 풀고 나서 다른 사람들의 풀이와 비교했을 때 다들 비슷한 방식으로 풀어서 내가 어렵게 어렵게 찾아 낸 길이 맞는 길이었구나 싶어서 뿌듯했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음부턴 변수명 더 직관적으로 짓기 위해 노력해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/564</guid>
      <comments>https://divheer.tistory.com/564#entry564comment</comments>
      <pubDate>Wed, 10 Sep 2025 08:58:08 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 방문 길이 | 파이썬</title>
      <link>https://divheer.tistory.com/563</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/49994&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/49994&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;게임 캐릭터를 4가지 명령어를 통해 움직이려 합니다. 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;U: 위쪽으로 한 칸 가기&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;D: 아래쪽으로 한 칸 가기&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;R: 오른쪽으로 한 칸 가기&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;L: 왼쪽으로 한 칸 가기&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;캐릭터는 좌표평면의 (0, 0) 위치에서 시작합니다. 좌표평면의 경계는 왼쪽 위(-5, 5), 왼쪽 아래(-5, -5), 오른쪽 위(5, 5), 오른쪽 아래(5, -5)로 이루어져 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BzToy/btsQoibBeya/0gLYTLYASOK7NDxQwRzIZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BzToy/btsQoibBeya/0gLYTLYASOK7NDxQwRzIZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BzToy/btsQoibBeya/0gLYTLYASOK7NDxQwRzIZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBzToy%2FbtsQoibBeya%2F0gLYTLYASOK7NDxQwRzIZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;322&quot; height=&quot;314&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;예를 들어, &quot;ULURRDLLU&quot;로 명령했다면&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AlI8b/btsQnTQDOrH/loxldVplrUWY14lbuoCS8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AlI8b/btsQnTQDOrH/loxldVplrUWY14lbuoCS8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AlI8b/btsQnTQDOrH/loxldVplrUWY14lbuoCS8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAlI8b%2FbtsQnTQDOrH%2FloxldVplrUWY14lbuoCS8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;255&quot; height=&quot;61&quot; data-origin-width=&quot;255&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1번 명령어부터 7번 명령어까지 다음과 같이 움직입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjFDcF/btsQpFcKlaZ/KKMxbriXUx9XWzEERSPlk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjFDcF/btsQpFcKlaZ/KKMxbriXUx9XWzEERSPlk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjFDcF/btsQpFcKlaZ/KKMxbriXUx9XWzEERSPlk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjFDcF%2FbtsQpFcKlaZ%2FKKMxbriXUx9XWzEERSPlk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;371&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;8번 명령어부터 9번 명령어까지 다음과 같이 움직입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q8rDy/btsQmOvu52M/SwkVcZnnk6wvgH8eJztPAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q8rDy/btsQmOvu52M/SwkVcZnnk6wvgH8eJztPAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q8rDy/btsQmOvu52M/SwkVcZnnk6wvgH8eJztPAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq8rDy%2FbtsQmOvu52M%2FSwkVcZnnk6wvgH8eJztPAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;382&quot; height=&quot;373&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;이때, 우리는 게임 캐릭터가 지나간 길 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;캐릭터가 처음 걸어본 길의 길이&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;를 구하려고 합니다. 예를 들어 위의 예시에서 게임 캐릭터가 움직인 길이는 9이지만, 캐릭터가 처음 걸어본 길의 길이는 7이 됩니다. (8, 9번 명령어에서 움직인 길은 2, 3번 명령어에서 이미 거쳐 간 길입니다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;단, 좌표평면의 경계를 넘어가는 명령어는 무시합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, &quot;LULLLLLLU&quot;로 명령했다면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nwgf3/btsQoqm3eG1/QU2ZWA61cW6QPNNqYLQ8rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nwgf3/btsQoqm3eG1/QU2ZWA61cW6QPNNqYLQ8rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nwgf3/btsQoqm3eG1/QU2ZWA61cW6QPNNqYLQ8rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNwgf3%2FbtsQoqm3eG1%2FQU2ZWA61cW6QPNNqYLQ8rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;257&quot; height=&quot;62&quot; data-origin-width=&quot;257&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;1번 명령어부터 6번 명령어대로 움직인 후, 7, 8번 명령어는 무시합니다. 다시 9번 명령어대로 움직입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgXxso/btsQnAcJFwW/ZvEuWTJdwSrXHxWh91kslk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgXxso/btsQnAcJFwW/ZvEuWTJdwSrXHxWh91kslk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgXxso/btsQnAcJFwW/ZvEuWTJdwSrXHxWh91kslk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgXxso%2FbtsQnAcJFwW%2FZvEuWTJdwSrXHxWh91kslk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;384&quot; height=&quot;374&quot; data-origin-width=&quot;384&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이때 캐릭터가 처음 걸어본 길의 길이는 7이 됩니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;명령어가 매개변수 dirs로 주어질 때, 게임 캐릭터가 처음 걸어본 길의 길이를 구하여 return 하는 solution 함수를 완성해 주세요.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dirs는 string형으로 주어지며, 'U', 'D', 'R', 'L' 이외에 문자는 주어지지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;dirs의 길이는 500 이하의 자연수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTiZ62/btsQmPA93Vg/5EaW44U79skcZ8YcalmiY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTiZ62/btsQmPA93Vg/5EaW44U79skcZ8YcalmiY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTiZ62/btsQmPA93Vg/5EaW44U79skcZ8YcalmiY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTiZ62%2FbtsQmPA93Vg%2F5EaW44U79skcZ8YcalmiY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;277&quot; height=&quot;148&quot; data-origin-width=&quot;277&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(dirs):
    dir = {'U': (0, 1), 'D': (0, -1), 'R': (1, 0), 'L': (-1, 0)}
    horizontal = [[0] * 11 for _ in range(11)]
    vertical = [[0] * 11 for _ in range(11)]
    x, y = 0, 0
    
    for d in dirs:
        if -5 &amp;lt;= x + dir[d][0] &amp;lt;= 5 and -5 &amp;lt;= y + dir[d][1] &amp;lt;= 5:
            x += dir[d][0]
            y += dir[d][1]
        
        if d == 'U' or d == 'D':
            vertical[x+5][y+5] = 1
        elif d == 'R' or d == 'L':
            horizontal[x+5][y+5] = 1
    
    answer = 0
    
    for h, z in zip(horizontal, vertical):
        answer += sum(h) + sum(z)
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt; 20개 중 14개의 테스트 케이스에 실패했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;+) 이 방식은 간선이 아닌 좌표만을 체크하기 때문에 애초에 올바른 풀이가 아니었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;다른 사람의 풀이&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1757289719216&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def solution(dirs):
    move = {'U': (0, 1), 'D': (0, -1), 'R': (1, 0), 'L': (-1, 0)}
    visited = set()
    x, y = 0, 0
    
    for d in dirs:
        dx, dy = move[d]
        nx, ny = x + dx, y + dy
        
        if -5 &amp;lt;= nx &amp;lt;= 5 and -5 &amp;lt;= ny &amp;lt;= 5:
            visited.add(((x, y), (nx, ny)))
            visited.add(((nx, ny), (x, y)))
            x, y = nx, ny
    
    return len(visited) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`(현재 좌표, 다음 좌표)` 와 `(다음 좌표, 현재 좌표)` 두 방향 모두를 `set`에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해야 왕복할 때도 동일한 길로 인식된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 `(0,0) &amp;rarr; (0,1)` 와 `(0,1) &amp;rarr; (0,0)` 은 같은 경로로 취급되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 한쪽 방향만 저장한다면 &quot;U&quot;와 &quot;D&quot;가 서로 다른 길로 인식되어 2개로 잘못 세게 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`set`에 왕복하는 경로를 2개씩 저장했기 때문에 최종적으로 `len(visited) // 2`를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/563</guid>
      <comments>https://divheer.tistory.com/563#entry563comment</comments>
      <pubDate>Mon, 8 Sep 2025 09:17:43 +0900</pubDate>
    </item>
    <item>
      <title>[GitHub] 이슈 &amp;amp; PR 템플릿 생성하기 (예시 코드 포함)</title>
      <link>https://divheer.tistory.com/562</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;깃헙 템플릿.png&quot; data-origin-width=&quot;1389&quot; data-origin-height=&quot;738&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/luRBr/btsQoC8BZs0/rbaDnkAinKXLqKp4IGCKW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/luRBr/btsQoC8BZs0/rbaDnkAinKXLqKp4IGCKW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/luRBr/btsQoC8BZs0/rbaDnkAinKXLqKp4IGCKW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FluRBr%2FbtsQoC8BZs0%2FrbaDnkAinKXLqKp4IGCKW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1389&quot; height=&quot;738&quot; data-filename=&quot;깃헙 템플릿.png&quot; data-origin-width=&quot;1389&quot; data-origin-height=&quot;738&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;263&quot; data-start=&quot;115&quot; data-ke-size=&quot;size16&quot;&gt;깃허브에서 이슈나 PR을 만들 때, 매번 동일한 형식을 직접 작성하는 것은 너무 번거롭다.&lt;br /&gt;이때 템플릿 파일을 만들어 두면, 새로운 이슈나 PR을 생성할 때 자동으로 템플릿이 불러와진다.&lt;/p&gt;
&lt;p data-end=&quot;321&quot; data-start=&quot;265&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 이슈 템플릿과 PR 템플릿을 적용하는 방법을 예시 코드와 함께 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이슈 템플릿 적용하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGqUCq/btsQmPgNBjM/T7nZZZeWnPXszUQc1kRFV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGqUCq/btsQmPgNBjM/T7nZZZeWnPXszUQc1kRFV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGqUCq/btsQmPgNBjM/T7nZZZeWnPXszUQc1kRFV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGqUCq%2FbtsQmPgNBjM%2FT7nZZZeWnPXszUQc1kRFV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;114&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 경로에 `./.github/ISSUE_TEMPLATE/bug-report-template.md` 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757233955759&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---
name: &quot;\U0001F41EBug&quot;
about: 버그 리포트 템플릿
title: ''
labels: ''
assignees: ''

---

&amp;lt;!-- 작성하지 않은 항목은 모두 지워주세요 --&amp;gt;

##  버그 설명
&amp;lt;!-- 어떤 상황에서 어떤 문제가 발생했는지 구체적으로 작성해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

##  에러 로그
&amp;lt;!-- 콘솔에 찍힌 에러 로그나 네트워크 응답 에러가 있다면 코드 블럭에 포함해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

## ✅작업할 내용
&amp;lt;!-- 이 버그를 해결하기 위해 예상되는 작업을 세분화해서 체크박스 형태로 작성해주세요 --&amp;gt;

&amp;lt;br/&amp;gt;

##  스크린샷
&amp;lt;!-- 시각적으로 이해를 돕기 위한 스크린샷이 있다면 첨부해주세요 --&amp;gt;

&amp;lt;br/&amp;gt;


##  참고 문서
&amp;lt;!-- 참고할 수 있는 자료가 있다면 적어주세요 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`bug-report-template.md` 파일에 원하는 템플릿 내용을 마크다운 문법으로 작성한다.&lt;br /&gt;이슈는 기능 추가, 버그 제보 등 다양한 템플릿을 만들 수 있지만, 여기서는 &lt;b&gt;버그 리포트 템플릿&lt;/b&gt;만 작성해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lmN7Q/btsQpBahaD4/DgP8lc7T3ZvKnYsZdyAW2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lmN7Q/btsQpBahaD4/DgP8lc7T3ZvKnYsZdyAW2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lmN7Q/btsQpBahaD4/DgP8lc7T3ZvKnYsZdyAW2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlmN7Q%2FbtsQpBahaD4%2FDgP8lc7T3ZvKnYsZdyAW2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1018&quot; height=&quot;324&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1018&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일을 `main` 브랜치에 반영하면, 새로운 이슈를 생성할 때 &lt;b&gt;템플릿 선택 화면&lt;/b&gt;이 나타난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QE1fg/btsQoH9VqYW/Uu8CRQSBxZdiOijXAFXSg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QE1fg/btsQoH9VqYW/Uu8CRQSBxZdiOijXAFXSg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QE1fg/btsQoH9VqYW/Uu8CRQSBxZdiOijXAFXSg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQE1fg%2FbtsQoH9VqYW%2FUu8CRQSBxZdiOijXAFXSg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;553&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위에서 만든 템플릿에 맞춰 이슈를 작성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;PR 템플릿 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 경로에 `./.github\pull_request_template.md` 파일을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757234269409&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 제목 예시: [feat] 관리자 페이지 구현 --&amp;gt;
&amp;lt;!-- 작성하지 않은 항목은 모두 지워주세요 --&amp;gt;

##  개요 
&amp;lt;!-- 구현한 기능에 대해 간단하게 설명해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

##  작업 내용
&amp;lt;!-- 구현한 기능에 대한 구체적인 내용을 작성해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

##  변경 사항
&amp;lt;!-- 컴포넌트, API, 로직 등 코드 변경으로 인해 협업 시 다른 개발자가 주의해야 할 내용이 있다면 작성해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

##  UI 스크린샷
&amp;lt;!-- UI에 변경이 있을 경우, 실제 화면을 캡처해서 첨부해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;

##  패키지 설치
&amp;lt;!-- 새로 설치한 패키지와 설치 이유를 설명해주세요 --&amp;gt;


&amp;lt;br/&amp;gt;


## #️⃣관련 이슈
&amp;lt;!-- 해당 PR과 관련된 이슈 번호가 있다면 &quot;- #22&quot; 형태로 작성해주세요 --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시나 마크다운 문법으로 PR에 필요한 항목들을 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r6E6C/btsQnHv30A6/P6cAi4T4YpaPtmoFk16lsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r6E6C/btsQnHv30A6/P6cAi4T4YpaPtmoFk16lsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r6E6C/btsQnHv30A6/P6cAi4T4YpaPtmoFk16lsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr6E6C%2FbtsQnHv30A6%2FP6cAi4T4YpaPtmoFk16lsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;709&quot; height=&quot;656&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 파일 또한 `main` 브랜치에 반영하면, PR 작성 시 자동으로 템플릿이 불러와진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 기록장</category>
      <category>github</category>
      <category>pr템플릿</category>
      <category>깃허브</category>
      <category>깃헙</category>
      <category>이슈템플릿</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/562</guid>
      <comments>https://divheer.tistory.com/562#entry562comment</comments>
      <pubDate>Sun, 7 Sep 2025 17:47:52 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.1] 숫자 짝꿍 | 파이썬</title>
      <link>https://divheer.tistory.com/561</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/131128&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/131128&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;두 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 임의의 자리에서 공통으로 나타나는 정수 k(0 &amp;le; k &amp;le; 9)들을 이용하여 만들 수 있는 가장 큰 정수를 두 수의 짝꿍이라 합니다(단, 공통으로 나타나는 정수 중 서로 짝지을 수 있는 숫자만 사용합니다).&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍이 존재하지 않으면, 짝꿍은 -1입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍이 0으로만 구성되어 있다면, 짝꿍은 0입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;span&gt;&amp;nbsp;&lt;/span&gt;X&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 3403이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 13203이라면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;X와&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍은&lt;span&gt;&amp;nbsp;&lt;/span&gt;X와&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y에서 공통으로 나타나는 3, 0, 3으로 만들 수 있는 가장 큰 정수인 330입니다. 다른 예시로&lt;span&gt;&amp;nbsp;&lt;/span&gt;X&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 5525이고&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y&lt;span&gt;&amp;nbsp;&lt;/span&gt;= 1255이면&lt;span&gt;&amp;nbsp;&lt;/span&gt;X와&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍은&lt;span&gt;&amp;nbsp;&lt;/span&gt;X와&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y에서 공통으로 나타나는 2, 5, 5로 만들 수 있는 가장 큰 정수인 552입니다(X에는 5가 3개,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y에는 5가 2개 나타나므로 남는 5 한 개는 짝 지을 수 없습니다.)&lt;br /&gt;두 정수&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y가 주어졌을 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍을 return하는 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;3 &amp;le;&lt;span&gt;&amp;nbsp;&lt;/span&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 길이(자릿수) &amp;le; 3,000,000입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y는 0으로 시작하지 않습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;X,&lt;span&gt;&amp;nbsp;&lt;/span&gt;Y의 짝꿍은 상당히 큰 정수일 수 있으므로, 문자열로 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnEQrz/btsQmtx68Hh/kBOF5c2HvY7Sq7GtkGDmJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnEQrz/btsQmtx68Hh/kBOF5c2HvY7Sq7GtkGDmJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnEQrz/btsQmtx68Hh/kBOF5c2HvY7Sq7GtkGDmJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnEQrz%2FbtsQmtx68Hh%2FkBOF5c2HvY7Sq7GtkGDmJ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;270&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이1 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import Counter

def solution(X, Y):
    x = Counter(X)
    y = Counter(Y)
    answer = &quot;&quot;
    
    for i in range(9, -1, -1):
        i = str(i)
        if i in x and y:
            answer += i * min(x[i], y[i])
    
    if answer:
        return str(int(answer))
    else:
        return &quot;-1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;19개의 테스트 케이스 중에 5개를 시간 초과로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이2 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import Counter

def solution(X, Y):
    x = Counter(X)
    y = Counter(Y)
    dic = x &amp;amp; y
    
    answer = []
    
    for n in dic:
        n = str(n)
        answer += [n] * dic[n]
    
    if answer:
        return str(int(&quot;&quot;.join(sorted(answer, reverse=True))))
    else:
        return &quot;-1&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Counter` 딕셔너리의 교집합을 이용하는 방법으로 수정했는데 이 방법도 시간초과로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이3 (성공)&lt;/h2&gt;
&lt;pre id=&quot;code_1757115607045&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from collections import Counter

def solution(X, Y):
    x = Counter(X)
    y = Counter(Y)
    dic = x &amp;amp; y
    
    if not dic:
        return &quot;-1&quot;
    
    answer = &quot;&quot;
    
    for i in range(9, -1, -1):
        answer += str(i) * dic[str(i)]
    
    if answer[0] == &quot;0&quot;:
        return &quot;0&quot;
    else:
        return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 풀이와 2번 풀이를 혼합해서 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X와 Y를 `Counter`를 이용해 딕셔너리로 만들고 교집합을 추출했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 `9`부터 `0`까지 순회하면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 형태의 `answer`에 딕셔너리의 키의 값만큼 숫자를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`answer`를 내림차순으로 문자열 형태로 생성했기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불필요한 리스트 생성과 정렬 절차 없이 그대로 출력하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/561</guid>
      <comments>https://divheer.tistory.com/561#entry561comment</comments>
      <pubDate>Sat, 6 Sep 2025 08:47:20 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Android에서 FCM 알림 아이콘 변경하기</title>
      <link>https://divheer.tistory.com/560</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 목표&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;알림 아이콘 변경 전후.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;401&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciCWSC/btsQoo3dSLf/KHZw49ILc32iPza9nAN9b1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciCWSC/btsQoo3dSLf/KHZw49ILc32iPza9nAN9b1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciCWSC/btsQoo3dSLf/KHZw49ILc32iPza9nAN9b1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciCWSC%2FbtsQoo3dSLf%2FKHZw49ILc32iPza9nAN9b1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;204&quot; data-filename=&quot;알림 아이콘 변경 전후.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;401&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸시 알림 전송 시 기본적으로 앱 아이콘이 사용되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 알림 전용 아이콘으로 수정해서 더 깔끔하고 직관적으로 보이도록 하고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알림 아이콘 이미지 추가하기&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;617&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOKJn8/btsQorZ0xbM/G5977ayi17PYc77BRPkl61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOKJn8/btsQorZ0xbM/G5977ayi17PYc77BRPkl61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOKJn8/btsQorZ0xbM/G5977ayi17PYc77BRPkl61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOKJn8%2FbtsQorZ0xbM%2FG5977ayi17PYc77BRPkl61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;330&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 아이콘은 다음과 같이 &lt;b&gt;흰색 단색 + 투명 배경&lt;/b&gt; 이미지만 허용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BlS9v/btsQmVOoJsd/fZBniPhfqbZsWKHwavbmdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BlS9v/btsQmVOoJsd/fZBniPhfqbZsWKHwavbmdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BlS9v/btsQmVOoJsd/fZBniPhfqbZsWKHwavbmdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBlS9v%2FbtsQmVOoJsd%2FfZBniPhfqbZsWKHwavbmdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;304&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;304&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android/app/src/main/res/drawable/ic_stat_ic_notification.png` 경로에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성한 아이콘 파일을&lt;span&gt; 추가했다. &lt;/span&gt;(파일 이름은 원하는 대로 작성하면 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;알림 아이콘 이미지 깨짐 현상.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMyKyH/btsQm2z6aB4/DNDaL8djpJSg8PnYrfWoI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMyKyH/btsQm2z6aB4/DNDaL8djpJSg8PnYrfWoI1/img.png&quot; data-alt=&quot;단일 파일만 추가했던 왼쪽의 경우 아이콘 테두리가 매끄럽지 않은 느낌을 준다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMyKyH/btsQm2z6aB4/DNDaL8djpJSg8PnYrfWoI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMyKyH%2FbtsQm2z6aB4%2FDNDaL8djpJSg8PnYrfWoI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1207&quot; height=&quot;312&quot; data-filename=&quot;알림 아이콘 이미지 깨짐 현상.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단일 파일만 추가했던 왼쪽의 경우 아이콘 테두리가 매끄럽지 않은 느낌을 준다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 `drawble` 디렉터리에만 PNG 파일을 추가해도 되지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 선명한 아이콘 이미지나, 색상을 변경하는 등 디테일한 설정을 원한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;`drawable-mdpi`, `drawable-hdpi`, `drawable-xhdpi` 등&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기기 해상도별로 이미지 파일을 따로 준비하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0OYqz/btsQlYE1dE5/A5WOUM0LQuD5GC1WCu4YOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0OYqz/btsQlYE1dE5/A5WOUM0LQuD5GC1WCu4YOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0OYqz/btsQlYE1dE5/A5WOUM0LQuD5GC1WCu4YOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0OYqz%2FbtsQlYE1dE5%2FA5WOUM0LQuD5GC1WCu4YOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;642&quot; height=&quot;187&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://romannurik.github.io/AndroidAssetStudio/icons-notification#source.type=clipart&amp;amp;source.clipart=ac_unit&amp;amp;source.space.trim=1&amp;amp;source.space.pad=0&amp;amp;name=ic_stat_ac_unit&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android Asset Studio&lt;/a&gt;에서 아이콘을 등록하면 각 dpi별 이미지 파일을 자동으로 만들어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이미지 파일들을 `android/app/src/main/res`의 각 폴더에 추가해 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;AndroidManifest.xml에 기본 아이콘 등록하기&lt;/h2&gt;
&lt;pre id=&quot;code_1757059934284&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;application
    android:label=&quot;비포알리미&quot;
    android:icon=&quot;@mipmap/ic_launcher&quot;&amp;gt;

    &amp;lt;!-- FCM 알림 기본 아이콘 설정 --&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;com.google.firebase.messaging.default_notification_icon&quot;
        android:resource=&quot;@drawable/ic_stat_ic_notification&quot; /&amp;gt;

    &amp;lt;!-- 알림 아이콘 색상(선택사항) --&amp;gt;
    &amp;lt;meta-data
        android:name=&quot;com.google.firebase.messaging.default_notification_color&quot;
        android:resource=&quot;@color/notification_icon_color&quot; /&amp;gt;
&amp;lt;/application&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`android\app\src\main\AndroidManifest.xml` 파일에서 기본 알림 아이콘을 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1757060369705&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;resources&amp;gt;
    &amp;lt;color name=&quot;notification_icon_color&quot;&amp;gt;#9EF2FF&amp;lt;/color&amp;gt;
&amp;lt;/resources&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬러는 `android\app\src\main\res\values\colors.xml` 파일을 생성해서 따로 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;알림 아이콘 색상 변경.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YPTGh/btsQoSiKvyx/PBSkYWpJ9erIWlokNo0Py1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YPTGh/btsQoSiKvyx/PBSkYWpJ9erIWlokNo0Py1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YPTGh/btsQoSiKvyx/PBSkYWpJ9erIWlokNo0Py1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYPTGh%2FbtsQoSiKvyx%2FPBSkYWpJ9erIWlokNo0Py1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;338&quot; data-filename=&quot;알림 아이콘 색상 변경.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;662&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림 아이콘은 상단바에서는 기본적으로 모두 회색으로 보이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알림창에서 봤을 때는 흰색이라 잘 보이지가 않아서 좀 더 눈에 띄는 색상으로 변경했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;flutter_local_notifications 패키지에서 아이콘 지정하기&lt;/h2&gt;
&lt;pre id=&quot;code_1757060485422&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const AndroidNotificationDetails androidDetails =
	AndroidNotificationDetails(
        'channel_id',
        'channel_name',
        channelDescription: 'channel_description',
        icon: '@drawable/ic_stat_ic_notification', // 아이콘 추가
        color: Color(0x009EF2FF), // 아이콘 색상 지정
    );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`flutter_local_notifications` 패키지를 사용하는 경우에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`AndroidNotificationDetails`에서 알림 아이콘을 따로 지정해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Android</category>
      <category>FCM알림아이콘지정</category>
      <category>Flutter</category>
      <category>안드로이드</category>
      <category>알림아이콘</category>
      <category>플러터</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/560</guid>
      <comments>https://divheer.tistory.com/560#entry560comment</comments>
      <pubDate>Fri, 5 Sep 2025 17:50:34 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.3] 단속카메라 | 파이썬</title>
      <link>https://divheer.tistory.com/559</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42884&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42884&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;고속도로를 이동하는 모든 차량이 고속도로를 이용하면서 단속용 카메라를 한 번은 만나도록 카메라를 설치하려고 합니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;고속도로를 이동하는 차량의 경로 routes가 매개변수로 주어질 때, 모든 차량이 한 번은 단속용 카메라를 만나도록 하려면 최소 몇 대의 카메라를 설치해야 하는지를 return 하도록 solution 함수를 완성하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;차량의 대수는 1대 이상 10,000대 이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;routes에는 차량의 이동 경로가 포함되어 있으며 routes[i][0]에는 i번째 차량이 고속도로에 진입한 지점, routes[i][1]에는 i번째 차량이 고속도로에서 나간 지점이 적혀 있습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;차량의 진입/진출 지점에 카메라가 설치되어 있어도 카메라를 만난것으로 간주합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;차량의 진입 지점, 진출 지점은 -30,000 이상 30,000 이하입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yrYjl/btsQk9LRAwb/9hCUtfb6S8ABTyfAoAPw21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yrYjl/btsQk9LRAwb/9hCUtfb6S8ABTyfAoAPw21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yrYjl/btsQk9LRAwb/9hCUtfb6S8ABTyfAoAPw21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyrYjl%2FbtsQk9LRAwb%2F9hCUtfb6S8ABTyfAoAPw21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;473&quot; height=&quot;101&quot; data-origin-width=&quot;473&quot; data-origin-height=&quot;101&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;-5 지점에 카메라를 설치하면 두 번째, 네 번째 차량이 카메라를 만납니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;-15 지점에 카메라를 설치하면 첫 번째, 세 번째 차량이 카메라를 만납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(routes):
    routes.sort(key=lambda x: x[1])
    camera = routes[0][1]
    answer = 1
    
    for start, end in routes:
        if start &amp;gt; camera:
            answer += 1
            camera = end
        
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차량 이동 경로를 &quot;진출 지점&quot;을 기준으로 정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 카메라 위치를 첫 번째 차량의 진출 지점으로 초기화한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 현재 차량의 진입 지점이 카메라 위치보다 크다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라가 현재 차량을 커버할 수 없는 위치에 있는 것이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 위치를 현재 차량의 진출 지점으로 변경하고, 카운트를 `1` 더한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>탐욕법</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/559</guid>
      <comments>https://divheer.tistory.com/559#entry559comment</comments>
      <pubDate>Thu, 4 Sep 2025 08:44:51 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 오픈채팅방 | 파이썬</title>
      <link>https://divheer.tistory.com/558</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/42888&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/42888&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다. 채팅방에 누군가 들어오면 다음 메시지가 출력된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;[닉네임]님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;채팅방에서 누군가 나가면 다음 메시지가 출력된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;[닉네임]님이 나갔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;채팅방에서 닉네임을 변경한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 채팅방에 &quot;Muzi&quot;와 &quot;Prodo&quot;라는 닉네임을 사용하는 사람이 순서대로 들어오면 채팅방에는 다음과 같이 메시지가 출력된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Muzi님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;채팅방에 있던 사람이 나가면 채팅방에는 다음과 같이 메시지가 남는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Muzi님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Muzi님이 나갔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Muzi가 나간후 다시 들어올 때, Prodo 라는 닉네임으로 들어올 경우 기존에 채팅방에 남아있던 Muzi도 Prodo로 다음과 같이 변경된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 나갔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;채팅방은 중복 닉네임을 허용하기 때문에, 현재 채팅방에는 Prodo라는 닉네임을 사용하는 사람이 두 명이 있다. 이제, 채팅방에 두 번째로 들어왔던 Prodo가 Ryan으로 닉네임을 변경하면 채팅방 메시지는 다음과 같이 변경된다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Ryan님이 들어왔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 나갔습니다.&quot;&lt;br /&gt;&quot;Prodo님이 들어왔습니다.&quot;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;채팅방에 들어오고 나가거나, 닉네임을 변경한 기록이 담긴 문자열 배열 record가 매개변수로 주어질 때, 모든 기록이 처리된 후, 최종적으로 방을 개설한 사람이 보게 되는 메시지를 문자열 배열 형태로 return 하도록 solution 함수를 완성하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;record는 다음과 같은 문자열이 담긴 배열이며, 길이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;1&lt;span&gt;&amp;nbsp;&lt;/span&gt;이상&lt;span&gt;&amp;nbsp;&lt;/span&gt;100,000&lt;span&gt;&amp;nbsp;&lt;/span&gt;이하이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;다음은 record에 담긴 문자열에 대한 설명이다.
&lt;ul style=&quot;list-style-type: disc; color: #000000;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;모든 유저는 [유저 아이디]로 구분한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - &quot;Enter [유저 아이디] [닉네임]&quot; (ex. &quot;Enter uid1234 Muzi&quot;)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[유저 아이디] 사용자가 채팅방에서 퇴장 - &quot;Leave [유저 아이디]&quot; (ex. &quot;Leave uid1234&quot;)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;[유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - &quot;Change [유저 아이디] [닉네임]&quot; (ex. &quot;Change uid1234 Muzi&quot;)&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;첫 단어는 Enter, Leave, Change 중 하나이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;유저 아이디와 닉네임의 길이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;1&lt;span&gt;&amp;nbsp;&lt;/span&gt;이상&lt;span&gt;&amp;nbsp;&lt;/span&gt;10&lt;span&gt;&amp;nbsp;&lt;/span&gt;이하이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FGSBi/btsQiVG70n0/BGArOz10OuBEhY2lp9yQuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FGSBi/btsQiVG70n0/BGArOz10OuBEhY2lp9yQuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FGSBi/btsQiVG70n0/BGArOz10OuBEhY2lp9yQuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFGSBi%2FbtsQiVG70n0%2FBGArOz10OuBEhY2lp9yQuk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;194&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(record):
    answer = []
    dict = {}
    
    for r in record:
        arr = r.split()
        if arr[0] in ['Enter', 'Change']:
            dict[arr[1]] = arr[2]
    
    for r in record:
        arr = r.split()
        if arr[0] == 'Enter':
            answer.append(f'{dict[arr[1]]}님이 들어왔습니다.')
        elif arr[0] == 'Leave':
            answer.append(f'{dict[arr[1]]}님이 나갔습니다.')
    
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`record`에 대해 for문을 두 번 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 for문에서는 딕셔너리에 유저 아이디별로 최종적으로 변경된 닉네임을 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 for문에서는 딕셔너리의 유저 아이디를 키 값으로 접근해서 해당 유저의 닉네임을 이용해 메시지를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/558</guid>
      <comments>https://divheer.tistory.com/558#entry558comment</comments>
      <pubDate>Wed, 3 Sep 2025 08:10:25 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 파일명 정렬 | 파이썬</title>
      <link>https://divheer.tistory.com/557</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/17686&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/17686&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;세 차례의 코딩 테스트와 두 차례의 면접이라는 기나긴 블라인드 공채를 무사히 통과해 카카오에 입사한 무지는 파일 저장소 서버 관리를 맡게 되었다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;저장소 서버에는 프로그램의 과거 버전을 모두 담고 있어, 이름 순으로 정렬된 파일 목록은 보기가 불편했다. 파일을 이름 순으로 정렬하면 나중에 만들어진 ver-10.zip이 ver-9.zip보다 먼저 표시되기 때문이다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;버전 번호 외에도 숫자가 포함된 파일 목록은 여러 면에서 관리하기 불편했다. 예컨대 파일 목록이 [&quot;img12.png&quot;, &quot;img10.png&quot;, &quot;img2.png&quot;, &quot;img1.png&quot;]일 경우, 일반적인 정렬은 [&quot;img1.png&quot;, &quot;img10.png&quot;, &quot;img12.png&quot;, &quot;img2.png&quot;] 순이 되지만, 숫자 순으로 정렬된 [&quot;img1.png&quot;, &quot;img2.png&quot;, &quot;img10.png&quot;, img12.png&quot;] 순이 훨씬 자연스럽다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;무지는 단순한 문자 코드 순이 아닌, 파일명에 포함된 숫자를 반영한 정렬 기능을 저장소 관리 프로그램에 구현하기로 했다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;소스 파일 저장소에 저장된 파일명은 100 글자 이내로, 영문 대소문자, 숫자, 공백(&quot; &quot;), 마침표(&quot;.&quot;), 빼기 부호(&quot;-&quot;)만으로 이루어져 있다. 파일명은 영문자로 시작하며, 숫자를 하나 이상 포함하고 있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파일명은 크게 HEAD, NUMBER, TAIL의 세 부분으로 구성된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;HEAD는 숫자가 아닌 문자로 이루어져 있으며, 최소한 한 글자 이상이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;NUMBER는 한 글자에서 최대 다섯 글자 사이의 연속된 숫자로 이루어져 있으며, 앞쪽에 0이 올 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;0부터&lt;span&gt;&amp;nbsp;&lt;/span&gt;99999&lt;span&gt;&amp;nbsp;&lt;/span&gt;사이의 숫자로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;00000이나&lt;span&gt;&amp;nbsp;&lt;/span&gt;0101&lt;span&gt;&amp;nbsp;&lt;/span&gt;등도 가능하다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;TAIL은 그 나머지 부분으로, 여기에는 숫자가 다시 나타날 수도 있으며, 아무 글자도 없을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcTfzo/btsPSlFFjr2/pacCsPA9sVZnMF84BTjyw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcTfzo/btsPSlFFjr2/pacCsPA9sVZnMF84BTjyw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcTfzo/btsPSlFFjr2/pacCsPA9sVZnMF84BTjyw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcTfzo%2FbtsPSlFFjr2%2FpacCsPA9sVZnMF84BTjyw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;186&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;파일명을 세 부분으로 나눈 후, 다음 기준에 따라 파일명을 정렬한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;파일명은 우선 HEAD 부분을 기준으로 사전 순으로 정렬한다. 이때, 문자열 비교 시 대소문자 구분을 하지 않는다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;MUZI와&lt;span&gt;&amp;nbsp;&lt;/span&gt;muzi,&lt;span&gt;&amp;nbsp;&lt;/span&gt;MuZi는 정렬 시에 같은 순서로 취급된다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;파일명의 HEAD 부분이 대소문자 차이 외에는 같을 경우, NUMBER의 숫자 순으로 정렬한다. 9 &amp;lt; 10 &amp;lt; 0011 &amp;lt; 012 &amp;lt; 13 &amp;lt; 014 순으로 정렬된다. 숫자 앞의 0은 무시되며, 012와 12는 정렬 시에 같은 같은 값으로 처리된다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;두 파일의 HEAD 부분과, NUMBER의 숫자도 같을 경우, 원래 입력에 주어진 순서를 유지한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;MUZI01.zip과&lt;span&gt;&amp;nbsp;&lt;/span&gt;muzi1.png가 입력으로 들어오면, 정렬 후에도 입력 시 주어진 두 파일의 순서가 바뀌어서는 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;무지를 도와 파일명 정렬 프로그램을 구현하라.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입력 형식&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력으로 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;files가 주어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;files는 1000 개 이하의 파일명을 포함하는 문자열 배열이다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 파일명은 100 글자 이하 길이로, 영문 대소문자, 숫자, 공백(&quot; &quot;), 마침표(&quot;.&quot;), 빼기 부호(&quot;-&quot;)만으로 이루어져 있다. 파일명은 영문자로 시작하며, 숫자를 하나 이상 포함하고 있다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;중복된 파일명은 없으나, 대소문자나 숫자 앞부분의 0 차이가 있는 경우는 함께 주어질 수 있다. (muzi1.txt,&lt;span&gt;&amp;nbsp;&lt;/span&gt;MUZI1.txt,&lt;span&gt;&amp;nbsp;&lt;/span&gt;muzi001.txt,&lt;span&gt;&amp;nbsp;&lt;/span&gt;muzi1.TXT는 함께 입력으로 주어질 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력: [&quot;img12.png&quot;, &quot;img10.png&quot;, &quot;img02.png&quot;, &quot;img1.png&quot;, &quot;IMG01.GIF&quot;, &quot;img2.JPG&quot;]&lt;br /&gt;출력: [&quot;img1.png&quot;, &quot;IMG01.GIF&quot;, &quot;img02.png&quot;, &quot;img2.JPG&quot;, &quot;img10.png&quot;, &quot;img12.png&quot;]&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;입력: [&quot;F-5 Freedom Fighter&quot;, &quot;B-50 Superfortress&quot;, &quot;A-10 Thunderbolt II&quot;, &quot;F-14 Tomcat&quot;]&lt;br /&gt;출력: [&quot;A-10 Thunderbolt II&quot;, &quot;B-50 Superfortress&quot;, &quot;F-5 Freedom Fighter&quot;, &quot;F-14 Tomcat&quot;]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이1&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(files):
    answer = []
    
    for file in files:
        head, number, tail = '', '', ''
        
        for i, s in enumerate(file):
            if s.isdigit():
                number += s
            elif not number:
                head += s
            else:
                tail = file[i:]
                break
        answer.append((head, number, tail))

    answer.sort(key=lambda x:(x[0].upper(), int(x[1])))            
            
    return [''.join(x) for x in answer]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 파일을 head, number, tail로 나눠서 튜플에 담은 후에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lambda를 이용해 head와 number 두 가지 값을 이용해서 정렬한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 각각의 튜플을 하나의 문자열로 합쳐서 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이2&lt;/h2&gt;
&lt;pre id=&quot;code_1755040885864&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import re

def solution(files):
    a = sorted(files, key=lambda file : int(re.findall('\d+', file)[0]))
    b = sorted(a, key=lambda file : re.split('\d+', file.lower())[0])
    return b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;number 부분으로 먼저 정렬한 후, head 순으로 정렬했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자가 1글자 이상 연속한 부분을 추출하는 `\d+` 정규표현식을 통해 코드를 깔끔하게 작성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/557</guid>
      <comments>https://divheer.tistory.com/557#entry557comment</comments>
      <pubDate>Wed, 13 Aug 2025 08:40:05 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.2] 2 x n 타일링 | 파이썬</title>
      <link>https://divheer.tistory.com/556</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12900&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12900&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;가로 길이가 2이고 세로의 길이가 1인 직사각형모양의 타일이 있습니다. 이 직사각형 타일을 이용하여 세로의 길이가 2이고 가로의 길이가 n인 바닥을 가득 채우려고 합니다. 타일을 채울 때는 다음과 같이 2가지 방법이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;타일을 가로로 배치 하는 경우&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;타일을 세로로 배치 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;예를들어서 n이 7인 직사각형은 다음과 같이 채울 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pqHvA/btsPLZEmF3b/zetULq0l9wPeLedxrtqF2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pqHvA/btsPLZEmF3b/zetULq0l9wPeLedxrtqF2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pqHvA/btsPLZEmF3b/zetULq0l9wPeLedxrtqF2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpqHvA%2FbtsPLZEmF3b%2FzetULq0l9wPeLedxrtqF2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;430&quot; height=&quot;126&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;직사각형의 가로의 길이 n이 매개변수로 주어질 때, 이 직사각형을 채우는 방법의 수를 return 하는 solution 함수를 완성해주세요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;가로의 길이 n은 60,000이하의 자연수 입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;경우의 수가 많아 질 수 있으므로, 경우의 수를 1,000,000,007으로 나눈 나머지를 return해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cftE8T/btsPLGygDZN/m76nmTW1JBOmn6xrvsUYs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cftE8T/btsPLGygDZN/m76nmTW1JBOmn6xrvsUYs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cftE8T/btsPLGygDZN/m76nmTW1JBOmn6xrvsUYs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcftE8T%2FbtsPLGygDZN%2Fm76nmTW1JBOmn6xrvsUYs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;177&quot; height=&quot;93&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;다음과 같이 5가지 방법이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BOoHN/btsPNl0WhOl/ngzD3fAMGE8cam1IT1qbg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BOoHN/btsPNl0WhOl/ngzD3fAMGE8cam1IT1qbg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BOoHN/btsPNl0WhOl/ngzD3fAMGE8cam1IT1qbg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBOoHN%2FbtsPNl0WhOl%2FngzD3fAMGE8cam1IT1qbg0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;319&quot; height=&quot;506&quot; data-origin-width=&quot;319&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LWlcS/btsPOGQ23n4/JKcvYa4Zzytmx04Wvmn8vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LWlcS/btsPOGQ23n4/JKcvYa4Zzytmx04Wvmn8vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LWlcS/btsPOGQ23n4/JKcvYa4Zzytmx04Wvmn8vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLWlcS%2FbtsPOGQ23n4%2FJKcvYa4Zzytmx04Wvmn8vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;333&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이 (실패)&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import sys
sys.setrecursionlimit(10**6)

def DFS(n):
    global answer
    
    if sum(arr) &amp;gt;= n:
        if sum(arr) == n:
            answer += 1
        return
    
    for i in [1, 2]:
        arr.append(i)
        DFS(n)
        arr.pop()

def solution(n):
    global arr, answer
    arr = []
    answer = 0
    
    DFS(n)

    return answer % 10000000007&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 함수로 풀어봤는데 역시나 시간 초과로 실패했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;def solution(n):
    dp = [0] * n
    dp[0], dp[1] = 1, 2
    
    for i in range(2, n):
        dp[i] = (dp[i-1] + dp[i-2]) % 1000000007
    
    return dp[n-1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[n] = dp[n-1] + dp[n-2]`이라는 점화식을 만들어서 동적계획법으로 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`dp[n]`을 완성하는 방법은 두 가지가 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;`dp[n-1]`에 세로 타일을 1개 추가해서 한 칸을 채운다.&lt;/li&gt;
&lt;li&gt;`dp[n-2]`에 위아래로 가로 타일 2개를 추가해서 2칸을 채운다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 `dp[4]`의 경우에 타일의 경우의 수는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;157&quot; data-start=&quot;133&quot;&gt;n=2: 11, 2 &amp;rarr; 112, 22 (가로 타일 2개 추가)&lt;/li&gt;
&lt;li data-end=&quot;190&quot; data-start=&quot;158&quot;&gt;n=3: 111, 21, 12 &amp;rarr; 1111, 211, 121 (세로 타일 1개 추가)&lt;/li&gt;
&lt;li data-end=&quot;239&quot; data-start=&quot;191&quot;&gt;n=4: 1111, 211, 112, 121, 22 &amp;rarr; 2 + 3 = 5가지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>dp</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/556</guid>
      <comments>https://divheer.tistory.com/556#entry556comment</comments>
      <pubDate>Mon, 11 Aug 2025 08:25:18 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스/Lv.3] 숫자 게임 | 파이썬</title>
      <link>https://divheer.tistory.com/555</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/12987&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/12987&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;xx 회사의 2xN명의 사원들은 N명씩 두 팀으로 나눠 숫자 게임을 하려고 합니다. 두 개의 팀을 각각 A팀과 B팀이라고 하겠습니다. 숫자 게임의 규칙은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;먼저 모든 사원이 무작위로 자연수를 하나씩 부여받습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 사원은 딱 한 번씩 경기를 합니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;각 경기당 A팀에서 한 사원이, B팀에서 한 사원이 나와 서로의 수를 공개합니다. 그때 숫자가 큰 쪽이 승리하게 되고, 승리한 사원이 속한 팀은 승점을 1점 얻게 됩니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;만약 숫자가 같다면 누구도 승점을 얻지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;전체 사원들은 우선 무작위로 자연수를 하나씩 부여받았습니다. 그다음 A팀은 빠르게 출전순서를 정했고 자신들의 출전 순서를 B팀에게 공개해버렸습니다. B팀은 그것을 보고 자신들의 최종 승점을 가장 높이는 방법으로 팀원들의 출전 순서를 정했습니다. 이때의 B팀이 얻는 승점을 구해주세요.&lt;br /&gt;A 팀원들이 부여받은 수가 출전 순서대로 나열되어있는 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;A와 i번째 원소가 B팀의 i번 팀원이 부여받은 수를 의미하는 배열&lt;span&gt;&amp;nbsp;&lt;/span&gt;B가 주어질 때, B 팀원들이 얻을 수 있는 최대 승점을 return 하도록 solution 함수를 완성해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;제한사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;A와&lt;span&gt;&amp;nbsp;&lt;/span&gt;B의 길이는 같습니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;A와&lt;span&gt;&amp;nbsp;&lt;/span&gt;B의 길이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;1&lt;span&gt;&amp;nbsp;&lt;/span&gt;이상&lt;span&gt;&amp;nbsp;&lt;/span&gt;100,000&lt;span&gt;&amp;nbsp;&lt;/span&gt;이하입니다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit; color: #000000;&quot;&gt;A와&lt;span&gt;&amp;nbsp;&lt;/span&gt;B의 각 원소는&lt;span&gt;&amp;nbsp;&lt;/span&gt;1&lt;span&gt;&amp;nbsp;&lt;/span&gt;이상&lt;span&gt;&amp;nbsp;&lt;/span&gt;1,000,000,000&lt;span&gt;&amp;nbsp;&lt;/span&gt;이하의 자연수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;입출력 예&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKMPhS/btsPOpO4jN6/6bnABrLn0v11X2LJzNzaR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKMPhS/btsPOpO4jN6/6bnABrLn0v11X2LJzNzaR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKMPhS/btsPOpO4jN6/6bnABrLn0v11X2LJzNzaR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKMPhS%2FbtsPOpO4jN6%2F6bnABrLn0v11X2LJzNzaR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;140&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;span style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot;&gt;입출력 예 #1&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bax2Za/btsPOkGZCEe/RD5gk4PTeOZQ5We0tgYNHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bax2Za/btsPOkGZCEe/RD5gk4PTeOZQ5We0tgYNHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bax2Za/btsPOkGZCEe/RD5gk4PTeOZQ5We0tgYNHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbax2Za%2FbtsPOkGZCEe%2FRD5gk4PTeOZQ5We0tgYNHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;165&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;A 팀은 숫자 5를 부여받은 팀원이 첫번째로 출전하고, 이어서 1,3,7을 부여받은 팀원들이 차례대로 출전합니다.&lt;br /&gt;B 팀원들을 4번, 2번, 3번, 1번의 순서대로 출전시킬 경우 팀원들이 부여받은 숫자들은 차례대로 8,2,6,2가 됩니다. 그러면, 첫 번째, 두 번째, 세 번째 경기에서 승리하여 3점을 얻게 되고, 이때가 최대의 승점입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입출력 예 #2&lt;/b&gt;&lt;br /&gt;B 팀원들을 어떤 순서로 출전시켜도 B팀의 승점은 0점입니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #263747; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1742364604833&quot; class=&quot;python&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import deque

def solution(A, B):
    A.sort()
    B.sort()
    B = deque(B)
    answer = 0
    
    for a in A:
        while B:
            if B[0] &amp;gt; a:
                answer += 1
                B.popleft()
                break
            else:
                B.popleft()
    
    return answer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A와 B팀을 정렬한 후에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B팀을 앞에서부터 한 명씩 제거해 가면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B팀의 가장 작은 숫자와 A팀의 숫자와 비교했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Programmers</category>
      <category>Python</category>
      <category>알고리즘</category>
      <category>파이썬</category>
      <category>프로그래머스</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/555</guid>
      <comments>https://divheer.tistory.com/555#entry555comment</comments>
      <pubDate>Sat, 9 Aug 2025 08:27:12 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Android에서 FCM 연동(3) - Topic으로 알림 수신 개별 설정하기</title>
      <link>https://divheer.tistory.com/554</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;구현 목표&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1260&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xu7Ks/btsPK4LJnng/Te5v8IiK6TdyDTfXoxivpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xu7Ks/btsPK4LJnng/Te5v8IiK6TdyDTfXoxivpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xu7Ks/btsPK4LJnng/Te5v8IiK6TdyDTfXoxivpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXu7Ks%2FbtsPK4LJnng%2FTe5v8IiK6TdyDTfXoxivpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;326&quot; height=&quot;1260&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1260&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 사용자들에게 동일하게 전송되는 알림이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 화면에서 각각의 알림을 개별적으로 On/Off 할 수 있도록 구현해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Token과 Topic의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCM(Firebase Cloud Messaging)을 통해 사용자가 알림을 수신하는 방식에는 대표적으로 두 가지가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;730&quot; data-start=&quot;688&quot;&gt;&lt;b&gt;Token 방식&lt;/b&gt;: 특정 사용자에게만 알림을 보낼 때 사용&lt;/li&gt;
&lt;li data-end=&quot;776&quot; data-start=&quot;731&quot;&gt;&lt;b&gt;Topic 방식&lt;/b&gt;: 다수의 사용자에게 동일한 알림을 보낼 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;560&quot; data-start=&quot;241&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style15&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;항목&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&lt;b&gt; Token&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot;&gt;&lt;b&gt; Topic&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;340&quot; data-start=&quot;305&quot;&gt;
&lt;td style=&quot;width: 16.1628%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;310&quot; data-start=&quot;305&quot;&gt;대상&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;322&quot; data-start=&quot;310&quot;&gt;&lt;b&gt;단일 기기 &lt;/b&gt;(1:1)&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;340&quot; data-start=&quot;322&quot;&gt;&lt;b&gt;여러 기기&lt;/b&gt; (그룹)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;397&quot; data-start=&quot;341&quot;&gt;
&lt;td style=&quot;width: 16.1628%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;349&quot; data-start=&quot;341&quot;&gt;사용 목적&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;373&quot; data-start=&quot;349&quot;&gt;사용자마다 다른 알림 전송&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;397&quot; data-start=&quot;373&quot;&gt;동일한 알림을 다수에게 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.1628%;&quot;&gt;사용 예시&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 1:1 메시지, 주문 상태 등 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이벤트, 공지 등&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;501&quot; data-start=&quot;448&quot;&gt;
&lt;td style=&quot;width: 16.1628%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;459&quot; data-start=&quot;448&quot;&gt;전송 방식&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;472&quot; data-start=&quot;459&quot;&gt;개별 토큰으로 전송&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;501&quot; data-start=&quot;472&quot;&gt;topic으로 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;560&quot; data-start=&quot;502&quot;&gt;
&lt;td style=&quot;width: 16.1628%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;510&quot; data-start=&quot;502&quot;&gt;유지 조건&lt;/td&gt;
&lt;td style=&quot;width: 43.2558%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;541&quot; data-start=&quot;510&quot;&gt;앱 삭제&amp;middot;재설치 시 토큰 변경될 수 있음&lt;/td&gt;
&lt;td style=&quot;width: 40.4651%;&quot; data-col-size=&quot;sm&quot; data-end=&quot;560&quot; data-start=&quot;541&quot;&gt;구독 취소 전까지 유지됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Token 등록 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1754562611500&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  token:&quot;APA91bGHXQBB...9QgnYOEURwm0I3lmyqzk2TXQ&quot;,
  notification: {
    title: &quot;새 메시지&quot;,
    body: &quot;친구가 메시지를 보냈어요&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: 사용자가 새 메시지를 받음&lt;br /&gt;&amp;rarr; 해당 &lt;b&gt;사용자의 FCM 토큰&lt;/b&gt;을 알아야만 알림을 보낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 앱을 재설치하거나 장시간 미사용 시 만료될 수 있기 때문에,&lt;br /&gt;토큰을 최신 상태로 유지하기 위한 별도의 로직이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Topic 구독 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1754562778247&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  topic: &quot;event&quot;,
  notification: {
    title: &quot;특가 이벤트&quot;,
    body: &quot;오늘부터 이벤트가 진행됩니다.&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: 모든 사용자에게 이벤트 알림을 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754563397930&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;await FirebaseMessaging.instance.subscribeToTopic('event'); // 구독
await FirebaseMessaging.instance.unsubscribeFromTopic('event'); // 구독 해제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &quot;event&quot; 알림을 구독한 사용자들에게만 알림이 전송된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버에서 Topic으로 메시지 전송하기 (Node.js)&lt;/h2&gt;
&lt;pre id=&quot;code_1754563776220&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// The topic name can be optionally prefixed with &quot;/topics/&quot;.
const topic = 'highScores';

const message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
  .then((response) =&amp;gt; {
    // Response is a message ID string.
    console.log('Successfully sent message:', response);
  })
  .catch((error) =&amp;gt; {
    console.log('Error sending message:', error);
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지에 topic을 지정하면, 해당 토픽을 구독한 기기에만 알림이 전송된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;subscribe_the_client_app_to_a_topic&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-text=&quot;클라이언트 앱에서 주제 구독&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;클라이언트에서 주제 구독하기 (Flutter)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;앱 첫 실행 시 모든 알림 구독&lt;/h3&gt;
&lt;pre id=&quot;code_1754565766526&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// main.dart
Future&amp;lt;void&amp;gt; subscribeToAllTopicsOnFirstLaunch() async {
  final prefs = await SharedPreferences.getInstance();
  final alreadySubscribed = prefs.getBool('fcmSubscribed') ?? false;

  if (!alreadySubscribed) {
    await FirebaseMessaging.instance.subscribeToTopic('all'); // 모든 주제 구독
    await prefs.setBool('fcmSubscribed', true);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알림 개별 수신 설정&lt;/h3&gt;
&lt;pre id=&quot;code_1754565845347&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 알림 토픽 상수 정의
class FcmTopics {
  static const String pointExists = &quot;pointExists&quot;;
  static const String pointLink = &quot;pointLink&quot;;
  static const String pointUseReminder = &quot;pointUseReminder&quot;;
}

// 알림 설정 스위치 토글 시 알림 구독 또는 구독 해제
Future&amp;lt;void&amp;gt; _onSwitchChanged(String topic, bool value) async {
    final prefs = await SharedPreferences.getInstance();
    prefs.setBool(topic, value);

    if (value) {
      await FirebaseMessaging.instance.subscribeToTopic(topic);
    } else {
      await FirebaseMessaging.instance.unsubscribeFromTopic(topic);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 `Switch` 위젯을 통해 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;각 알림 항목을&lt;span&gt; &lt;/span&gt;&lt;/span&gt;구독 또는 구독 해제할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;section-11&quot; style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;플러터 FCM 연동 시리즈&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://divheer.tistory.com/540&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Flutter] Android에서 FCM 연동(1) - 푸시 알림 수신 테스트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://divheer.tistory.com/552&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Flutter]&amp;nbsp;Android에서&amp;nbsp;FCM&amp;nbsp;연동(2)&amp;nbsp;-&amp;nbsp;알림&amp;nbsp;클릭&amp;nbsp;시&amp;nbsp;웹&amp;nbsp;링크로&amp;nbsp;이동하기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 문서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/manage-tokens?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FCM&amp;nbsp;등록&amp;nbsp;토큰&amp;nbsp;관리를&amp;nbsp;위한&amp;nbsp;권장사항&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com/docs/cloud-messaging/android/topic-messaging?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Android에서 주제 메시징&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Android</category>
      <category>FCM</category>
      <category>FCM Topic</category>
      <category>firebase</category>
      <category>FirebaseCloudMessaging</category>
      <category>Flutter</category>
      <category>안드로이드</category>
      <category>파이어베이스</category>
      <category>푸시알림</category>
      <category>플러터</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/554</guid>
      <comments>https://divheer.tistory.com/554#entry554comment</comments>
      <pubDate>Fri, 8 Aug 2025 08:47:36 +0900</pubDate>
    </item>
    <item>
      <title>[백준/브론즈2] 6359번 만취한 상범 | 파이썬</title>
      <link>https://divheer.tistory.com/553</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/6359&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/6359&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서강대학교 곤자가 기숙사의 지하에는 n개의 방이 일렬로 늘어선 감옥이 있다. 각 방에는 벌점을 많이 받은 학생이 구금되어있다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러던 어느 날, 감옥 간수인 상범이는 지루한 나머지 정신나간 게임을 하기로 결정했다. 게임의 첫 번째 라운드에서 상범이는 위스키를 한 잔 들이키고, 달려가며 감옥을 한 개씩 모두 연다. 그 다음 라운드에서는 2, 4, 6, ... 번 방을 다시 잠그고, 세 번째 라운드에서는 3, 6, 9, ... 번 방이 열려있으면 잠그고, 잠겨있다면 연다. k번째 라운드에서는 번호가 k의 배수인 방이 열려 있으면 잠그고, 잠겨 있다면 연다. 이렇게 n번째 라운드까지 진행한 이후, 상범이는 위스키의 마지막 병을 마시고 쓰러져 잠든다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구금되어있는 몇 명(어쩌면 0명)의 학생들은 자신의 방을 잠그지 않은 채 상범이가 쓰러져버렸단 것을 깨닫고 즉시 도망친다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;방의 개수가 주어졌을 때, 몇 명의 학생들이 도주할 수 있는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;입력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;입력의 첫 번째 줄에는 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄에 한 개씩 방의 개수 n(5&amp;nbsp;&amp;le; n &amp;le; 100)이 주어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출력&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555; text-align: start;&quot;&gt;한 줄에 한 개씩 각 테스트 케이스의 답, 즉 몇 명이 탈출할 수 있는지를 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예제&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1453&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3XHR7/btsPJAdHykd/RrcR4OcAlPHZ7tXsG1Kj2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3XHR7/btsPJAdHykd/RrcR4OcAlPHZ7tXsG1Kj2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3XHR7/btsPJAdHykd/RrcR4OcAlPHZ7tXsG1Kj2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3XHR7%2FbtsPJAdHykd%2FRrcR4OcAlPHZ7tXsG1Kj2k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1453&quot; height=&quot;231&quot; data-origin-width=&quot;1453&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;나의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879817720&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;t = int(input())

for _ in range(t):
    n = int(input())
    arr = [1] * n

    for i in range(2, n+1):
        for j in range(i, n+1, i):
            if arr[j-1] == 1:
                arr[j-1] = 0
            else:
                arr[j-1] = 1
    
    print(sum(arr))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1부터 n까지 모든 배수를 탐색하면서 최종적으로 열려 있는 방의 개수의 합을 출력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 사람의 풀이&lt;/h2&gt;
&lt;pre id=&quot;code_1730879832036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;t = int(input())

for _ in range(t):
    n = int(input())
    answer = 0

    for i in range(1, n+1):
        if i ** 2 &amp;lt;= n:
            answer += 1
        else:
            break
    
    print(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방 번호의 약수의 개수가 홀수인 방만 최종적으로 열리게 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;약수의 개수가 홀수인 수는 제곱수&lt;/b&gt;라는 점을 이용해서 풀었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;293&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;일반적으로 약수의 개수는 짝수다.&lt;/p&gt;
&lt;p data-end=&quot;293&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;왜냐하면, &lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;와 &lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;/span&gt;가 &lt;span&gt;&lt;span&gt;n&lt;/span&gt;&lt;/span&gt;의 약수라면, &lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;/span&gt;와 &lt;span&gt;&lt;span&gt;b&lt;/span&gt;&lt;/span&gt;는 서로 다른 약수쌍을 이루기 때문이다.&lt;/p&gt;
&lt;p data-end=&quot;293&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;293&quot; data-start=&quot;180&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 12의 경우:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;359&quot; data-start=&quot;294&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;309&quot; data-start=&quot;294&quot;&gt;1과 12는 쌍을 이루고&lt;/li&gt;
&lt;li data-end=&quot;324&quot; data-start=&quot;310&quot;&gt;2와 6은 쌍을 이루며&lt;/li&gt;
&lt;li data-end=&quot;359&quot; data-start=&quot;325&quot;&gt;3과 4는 쌍을 이루기 때문에 총 6개의 약수가 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;이처럼 대부분의 수는 &lt;b&gt;약수가 두 개씩 짝을 이루어&lt;/b&gt; 나오므로 약수의 개수는 &lt;b&gt;짝수&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;하지만 제곱수는 예외다.&lt;/p&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; 제곱수의 경우 중간에 하나의 중복된 약수를 가지기 때문에 약수가 홀수 개가 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;416&quot; data-start=&quot;361&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;532&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, &lt;b&gt;9&lt;/b&gt;는 제곱수다. (&lt;span&gt;&lt;span&gt;3^2&lt;/span&gt;&lt;/span&gt;)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;631&quot; data-start=&quot;533&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;533&quot;&gt;9의 약수는 1, 3, 9이므로, 총 3개이다.&lt;/li&gt;
&lt;li data-end=&quot;609&quot; data-start=&quot;554&quot;&gt;여기서 3은 &lt;b&gt;두 번 세어지지 않는다.&lt;/b&gt; (왜냐하면 3 * 3 = 9 이므로 한 번만 셈)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/Baekjoon</category>
      <category>Python</category>
      <category>백준</category>
      <category>알고리즘</category>
      <category>제곱수</category>
      <category>파이썬</category>
      <author>Horang_</author>
      <guid isPermaLink="true">https://divheer.tistory.com/553</guid>
      <comments>https://divheer.tistory.com/553#entry553comment</comments>
      <pubDate>Fri, 8 Aug 2025 08:25:21 +0900</pubDate>
    </item>
  </channel>
</rss>