반응형

오늘의 학습 키워드

그리디 알고리즘


알고리즘 문제

LeetCode Medium - 1338. Reduce Array Size to The Half


공부한 내용

그리디 알고리즘

  • 최적해를 구하는 알고리즘
  • 전체 최적해를 구하기 위해 각 단계에서 최선의 선택을 한다.
  • 문제를 해결하는 과정에서 현재 상황에서의 최적의 해법을 찾는 과정을 반복하며 최종 해답을 도출한다.

오늘의 회고

처음 시도

  • 완전 탐색을 해야해서 DFS를 사용해야 하나 생각했다. 
    • 문제1. 배열 최대 크기가 10^5인데, 모두 탐색하면 2^10^5 = 1,125,899,906,842,624 번을 탐색해야 한다.
    • 문제2. DFS로 최적의 해를 찾더라도, 다시 돌아와서 다른 경로를 탐색하면 최적의 해를 놓칠 수 있다.

해결

  • 그리디 알고리즘으로 가장 빈도수가 많은 숫자부터 주어진 배열의 전체 길이의 반이 될때까지 빈도수를 더해서 답을 구할 수 있다.

문제 풀이

import java.util.*;

class Solution {
    public int minSetSize(int[] arr) {
        int len = arr.length;
        Map<Integer, Integer> map = new HashMap<>();
        for (int n : arr) {
            map.put(n, map.getOrDefault(n, 0)+1);
        }
        List<Integer> values = new ArrayList<>(map.values());
        Collections.sort(values);

        int res = 0, sum = 0;
        for (int i = values.size()-1; i >= 0; i--) {
            sum += values.get(i);
            res++;

            if (sum >= Math.round(len/2)) break;
        }

        return res;
    }
반응형
반응형

오늘의 학습 키워드


우선순위 큐


알고리즘 문제


공부한 내용

PriorityQueue


오늘의 회고

처음 시도

  • 처음엔 "데이터를 추가하고 꺼내야 한다." 는 생각으로 stack, queue를 생각하다가 "숫자가 작은 순서대로 꺼내야 한다." 조건에서 priorityQueue로 풀어야겠다고 생각했다. 

해결

  • 자료구조를 바로 생각해내고, 15분 이내로 풀어서 성공적인 느낌!
  • 입력 값인 n만큼 큐에 좌석(숫자)를 넣어놓고, 예약하면 큐에서 꺼내고 예약 취소 시 매개변수인 좌석 번호를 다시 큐에 넣는다.

문제 풀이

import java.util.*;

class SeatManager {
    private PriorityQueue<Integer> queue;
    public SeatManager(int n) {
        this.queue = new PriorityQueue<>();
        for (int i = 0; i < n; i++) {
            queue.offer(i+1);
        }
    }
    
    public int reserve() {
        return queue.poll();
    }
    
    public void unreserve(int seatNumber) {
        queue.offer(seatNumber);
    }
반응형
반응형

오늘의 학습 키워드

Stack
Queue


알고리즘 문제

LeetCode Medium. 341. Flatten Nested List Iterator


공부한 내용

Queue, 재귀 함수를 활용


오늘의 회고

  • 갈수록 문제 해결 시간이 빨라지고, 스스로 해결할 수 있는 문제도 많아져서 좋다.
  • 처음에 재귀 함수를 만들어서 푸는 문제가 이해가 되지도 않고 어려웠는데, 이제는 어떤 느낌인지 조금은 감이 오는 것 같다. => 어떤느낌? 배열/리스트, 큐/스택 자료구조를 사용하면서 같은 Format의 형태를 탐색해야 한다거나 등등.

처음 시도

  • 주어진 nestedList를 보면 숫자와 숫자로 된 리스트가 섞여있는데, 차례대로 숫자 배열로 반환하는 게 목표였다.
  • nestedList 형태를 봤을 때, NestedInteger 타입으로 되어 있고, NestedInteger는 숫자 또는 리스트 형태로 된 걸 확인했을 때, 아! 반복해서 nestedList를 순서대로 꺼내서 Integer로 된 큐에 담아야 겠다고 생각했다.
  • 같은 형태의 반복, Integer를 담을 큐, 순서대로 반복 => DFS 방식으로 풀면 좋겠다!

해결

  • 주어진 nestedList를 순서대로 꺼내서 Integer 타입이면 바로 Queue에 넣고, List 타입이면 다시 순서대로 꺼내서 Queue에 넣는 것을 반복하는 재귀 함수를 사용한 DFS 방식으로 풀었다.

문제 풀이

내가 푼 풀이 (Queue + DFS 방식)

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * public interface NestedInteger {
 *
 *     // @return true if this NestedInteger holds a single integer, rather than a nested list.
 *     public boolean isInteger();
 *
 *     // @return the single integer that this NestedInteger holds, if it holds a single integer
 *     // Return null if this NestedInteger holds a nested list
 *     public Integer getInteger();
 *
 *     // @return the nested list that this NestedInteger holds, if it holds a nested list
 *     // Return empty list if this NestedInteger holds a single integer
 *     public List<NestedInteger> getList();
 * }
 */
import java.util.*;

public class NestedIterator implements Iterator<Integer> {
    private Queue<Integer> queue;
    public NestedIterator(List<NestedInteger> nestedList) {
        this.queue = new LinkedList<>();
        reculsive(nestedList);
    }

    public void reculsive(List<NestedInteger> list) {
        for (int i = 0; i < list.size(); i++) {
            if (!list.get(i).isInteger()) {
                reculsive(list.get(i).getList()); 
            } else {
                queue.offer(list.get(i).getInteger());
            }
        }
    }

    @Override
    public Integer next() {
        return queue.poll();
    }

    @Override
    public boolean hasNext() {
        return !queue.isEmpty();
    }
}

/**
 * Your NestedIterator object will be instantiated and called as such:
 * NestedIterator i = new NestedIterator(nestedList);
 * while (i.hasNext()) v[f()] = i.next();
 */

 

다른 풀이 (Stack + DFS 방식)

/**
 * // This is the interface that allows for creating nested lists.
 * // You should not implement it, or speculate about its implementation
 * public interface NestedInteger {
 *
 *     // @return true if this NestedInteger holds a single integer, rather than a nested list.
 *     public boolean isInteger();
 *
 *     // @return the single integer that this NestedInteger holds, if it holds a single integer
 *     // Return null if this NestedInteger holds a nested list
 *     public Integer getInteger();
 *
 *     // @return the nested list that this NestedInteger holds, if it holds a nested list
 *     // Return empty list if this NestedInteger holds a single integer
 *     public List<NestedInteger> getList();
 * }
 */
public class NestedIterator implements Iterator<Integer> {
    private LinkedList<NestedInteger> stack; // Stack to keep track of NestedInteger objects

    public NestedIterator(List<NestedInteger> nestedList) {
        stack = new LinkedList<>();
        // Push nestedList elements in reverse order onto the stack
        for (int i = nestedList.size() - 1; i >= 0; i--) {
            stack.push(nestedList.get(i));
        }
    }

    @Override
    public Integer next() {
        return stack.pop().getInteger(); // Return the integer from the top of the stack
    }

    @Override
    public boolean hasNext() {
        while (!stack.isEmpty()) {
            NestedInteger curr = stack.peek();
            if (curr.isInteger()) {
                return true; // Found an integer
            }

            // Flatten nested list
            stack.pop(); // Remove the list
            for (int i = curr.getList().size() - 1; i >= 0; i--) {
                stack.push(curr.getList().get(i)); // Push its elements in reverse order
            }
        }
        return false; // No more integers
    }
}

/**
 * Your NestedIterator object will be instantiated and called as such:
 * NestedIterator i = new NestedIterator(nestedList);
 * while (i.hasNext()) v[f()] = i.next();
 */
반응형
반응형

오늘의 학습 키워드

DFS
Combination


알고리즘 문제

LeetCode Medium 1286. Iterator for Combination


공부한 내용

오늘은 스스로 생각한 DFS를 활용해서 처음부터 끝까지 혼자힘으로 풀었다. 너무 기분이 좋다!


오늘의 회고

처음 시도

  • 주어진 문자열의 한 문자씩 모두 탐색해서 원하는 길이의 문자열을 만드는(조합하는) 문제였기 때문에 DFS로 문자열의 0번 인덱스부터 마지막 인덱스까지 순서대로 조합을 찾는 방법을 생각했다.
    • 문제에서 이미 오름차순으로 정렬된 문자열이 주어지기 때문에 앞에서부터 순서대로 자신의 이후 인덱스부터 차례로 조합하면서 모든 조합을 찾을 수 있다.
  • DFS로 생각했고, 큐(Queue) 자료구조를 사용해 조합한 문자열을 큐에 넣고, 오름차순으로 정렬되서 이미 큐에 들어가기 때문에 그대로 앞에서부터 꺼내면 된다.
  • 그 다음으로 재귀 종료 조건을 생각해야 하는데 반복하면서 문자열에 1씩 증가하는 인덱스의 값을 더할때마다 카운팅을 하면서 원하는 문자열 길이만큼 조합을 만들고, 원하는 문자열 길이만큼 카운팅이 되었을 때 return하도록 생각했다.

해결

  • DFS 알고리즘과 Queue 자료구조로 해결했다!

문제 풀이

import java.util.*;

class CombinationIterator {
    private String characters;
    private int len;
    private Queue<String> queue;

    public CombinationIterator(String characters, int combinationLength) {
        this.characters = characters; 
        this.len = combinationLength;
        this.queue = new LinkedList<>();
        for (int i = 0; i < characters.length(); i++) {
            dfs(characters.substring(i,i+1), i, 1);
        }
    }
    
    public void dfs(String str, int index, int count) {
        if (count == len) {
            queue.offer(str);
            return;
        }

        for (int i = index+1; i < characters.length(); i++) {
            dfs(str+characters.substring(i,i+1), i, count+1);
        }
    }

    public String next() {
        return queue.poll();
    }
    
    public boolean hasNext() {
        return !queue.isEmpty();
    }
}

/**
 * Your CombinationIterator object will be instantiated and called as such:
 * CombinationIterator obj = new CombinationIterator(characters, combinationLength);
 * String param_1 = obj.next();
 * boolean param_2 = obj.hasNext();
 */
반응형
반응형

오늘의 학습 키워드

배열


알고리즘 문제


공부한 내용

다른 풀이를 보고 분석


 

오늘의 회고

처음 시도

  • 처음 내가 시도했던 방식은 List만을 사용해서 풀었다.
    1. groupSizes 배열 전체 요소를 하나씩 읽어서 size만큼 새로운 List에 담아서 그룹핑을 한다.
    2. 그룹 배열을 만들 때, 그룹에 담은 groupSizes 해당 요소는 0으로 만든다.
    3. 카운트 변수를 두어 size만큼 카운트가 되면 다음 요소로 넘어가도록 했다.
  • 그런데 내가 어렵게 꼬아서 생각한 것 같았다. 여기까지는 좋았는데.. 왜 처음에 괜히 어렵게 재귀로 풀려고 했었는지 런타임 에러나고, 케이스마다 안되는 게 생기고 로직이 복잡해졌다.
  • 그래서 다시 처음으로 돌아가 Map에 size별로 리스트를 담아서 subList로 나누기로 생각했다.

해결

  • groupSizes 배열 전체를 loop 돌면서 size로 key를 size가 같은 것끼리 value에 리스트로 HashMap에 저장했다.
  • HashMap에 모두 저장했으면, HashMap의 key를 꺼내서 해당 size만큼 다시 List로 분리해서 result 리스트에 추가했다.

문제 풀이

HashMap으로 푼 나의 풀이

import java.util.*;
class Solution {
    Map<Integer, List<Integer>> map;

    public List<List<Integer>> groupThePeople(int[] groupSizes) {
        List<List<Integer>> result = new ArrayList<>();
        Map<Integer, List<Integer>> map = new HashMap<>();

        // grouping (key: groupSize / val: index list) 
        for (int i = 0; i < groupSizes.length; i++) {
            List<Integer> values;
            if (map.containsKey(groupSizes[i])) {
                values = map.get(groupSizes[i]);
            } else {
                values = new ArrayList<>();
            }
            values.add(i);
            map.put(groupSizes[i], values);
        }
        
        // partitioning
        for (int key : map.keySet()) {
            List<Integer> list = map.get(key);
            for (int i = 0; i < list.size(); i+= key) {
                result.add(list.subList(i, Math.min(i+key, list.size())));
            }
        }

        return result;
    }
}

 

 

처음에 List로만 풀려고 했던 내 생각과 유사한 풀이

class Solution {
    public List<List<Integer>> groupThePeople(int[] groupSizes) {
        List<List<Integer>> result = new ArrayList<>();
        for(int i = 0; i<groupSizes.length; i++){
            if(groupSizes[i] == 0){
                continue;
            }
            int k = i;
            int z = groupSizes[i];
            int j = z;
            List<Integer> temp = new ArrayList<>();
            while(j>0){
                if(groupSizes[k]==z){
                    temp.add(k);
                    groupSizes[k]=0;
                    j--;
                }
                k++;
            }
            result.add(temp);
        }
        return result;
    }

}

 

이렇게도 풀 수 있구나

import java.util.ArrayList;
import java.util.List;

class Solution extends java.util.AbstractList<List<Integer>> {
  int[] gs;
  List<List<Integer>> ans;

  public List<List<Integer>> groupThePeople(int[] groupSizes) {
    this.gs = groupSizes;
    return this;
  }

  @Override
  public List<Integer> get(int index) {
    if (ans == null)
      this.size();
    return ans.get(index);
  }

  @Override
  public int size() {
    if (ans == null) {
      ans = new ArrayList<>();
      var map = new java.util.HashMap<Integer, List<Integer>>();
      for (int i = 0; i < gs.length; i++) {
        List<Integer> g = map.computeIfAbsent(gs[i], z -> new ArrayList<>());
        g.add(i);
        if (g.size() == gs[i])
          ans.add(map.remove(gs[i]));
      }
    }
    return this.ans.size();
  }
}
반응형
반응형

오늘의 학습 키워드

bitwise-xor operation


알고리즘 문제


공부한 내용

XOR 비트 연산

  • XOR 비트 연산자: ^
  • 비트 단위로 XOR 연산을 한다. (2진수)
  • 두 비트가 서로 다르면 1, 서로 같으면 0을 반환한다.

오늘의 회고

  • xor 비트연산을 실제 코드에서 사용해보지 않았는데 이번 기회에 ^ 연산자에 대해 알아볼 수 있었다.
  • 비트 연산을 해보면 a ^ b = c 형식으로 계산이 나오는데 테스트 케이스로 모든 배열 연산을 해보니 a ^ c = b 패턴이 보였다.
  •  테스트 케이스에서 [5, 2, 0, 3, 1] 로 xor 연산을 통해 [5, 7, 2, 3, 2] 결과를 반환해야 한다고 했다.
    • 5 xor ? = 2에서 ?가 인덱스 1번에 들어가야 한다.
    • 비트연산을 위해 2진수로 바꾸면 101 xor ? = 010
    • 결국 ? 는 111로 10진수로 바꾸면 7이 된다. 
    • 근데 5 xor 2 를 해보니 101 xor 010 = 111로 7이 반환되었다.
  • 이렇게 xor 연산을 직접 해보면서 피연산자와 결과를 바꿔서 연산해도 xor 연산은 바뀌지 않았다.

문제 풀이

class Solution {
    public int[] findArray(int[] pref) {
        int[] result = new int[pref.length];
        result[0] = pref[0];
        for (int i = 1; i < pref.length; i++) {
            result[i] = pref[i-1]^pref[i];

        }

        return result;
    } 
}

 

반응형
반응형

오늘의 학습 키워드

배열


알고리즘 문제


오늘의 회고

처음 시도

  • 문제를 보니 주어진 2차원 배열의 값을 리턴해주는 메서드와 값을 업데이트해주는 메서드를 만드는 게 목표였다.
  •  

해결

  • 바로 클래스에 2차원 배열 변수 private 하게 만들어서 해당 변수로 사용하도록 클래스를 만들었다.
  • 2차원 배열 값을 좌표를 input 받아서 반복문을 돌려 row1, row2는 세로 방향 인덱스 / col1, col2는 가로 방향 인덱스이므로 처음에 row로 반복문을 돌리고 그 안에서 col로 반복문을 돌려서 row,col 좌표에 newValue로 업데이트 해주었다.

문제 풀이

class SubrectangleQueries {
    private int[][] rectangle;

    public SubrectangleQueries(int[][] rectangle) {
        this.rectangle = rectangle;
    }
    
    public void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
        for (int i = row1; i <= row2; i++) {
            for (int j = col1; j <= col2; j++) {
                rectangle[i][j] = newValue;
            }
        }
    }
    
    public int getValue(int row, int col) {
        return rectangle[row][col];
    }
}

/**
 * Your SubrectangleQueries object will be instantiated and called as such:
 * SubrectangleQueries obj = new SubrectangleQueries(rectangle);
 * obj.updateSubrectangle(row1,col1,row2,col2,newValue);
 * int param_2 = obj.getValue(row,col);
 */
반응형
반응형

오늘의 학습 키워드

이진탐색


알고리즘 문제


공부한 내용

이진탐색 여러 풀이


오늘의 회고

처음 시도

  • 여러 개의 데이터가 있고, 주어진 days 안에 모든 데이터를 탐색해서 범위 최솟값을 찾는 문제였기 때문에 이진 탐색으로 범위를 반으로 줄여가며 최솟값을 찾는 알고리즘을 생각했다.
  • 먼저 생각한 건 return 값, 초기 범위 최소값(최소 capa), 초기 범위 최댓값(최대 capa)이다.
    • return은 모든 짐(weights)를 정해진 days 안에 모두 나를 수 있는 배 용량의 최솟값이다.
    • 이진 탐색 범위 초기 최솟값 min은 weights 중 가장 큰 수로 최소 capa를 의미한다. 
    • 만약 min이 weights 중 다른 값으로 초기화된다면, weights의 큰 짐을 아예 나를 수 없기 때문에 가장 큰 짐을 최소 capa로 정했다.
    • 이진 탐색 범위 초기 최댓값 max는 모든 짐을 한번에 나를 수 있는 capa로 모든 weights 합으로 초기화했다.
  • 여기까진 잘 정했는데 이진 탐색 과정에서 문제가 있었다. 
    • 처음에 문제를 좀 잘 못 이해해서 중간값 mid로 주어진 days와 같은 날짜만큼 모든 짐을 나를 수 있는지에 대해 비교하면서 오류를 범했다. => Case 3번에서 걸림.
    • 또한 if 문으로 비교 로직이 많아지면서 로직이 복잡해졌고, 반복문 안에서 continue;를 남발하게 되었다.

해결

  • 탐색 범위를 정했다면, 이제 생각해야 할 것은 중간 값으로 탐색 범위에서 어떻게 범위를 좁힐 수 있을지 고민한다.
  • 문제에서 탐색 범위 중간값 mid로 해당 capa가 days 안에 모든 짐을 나를 수 있는지 가능 여부를 판단해야 한다.
    • 해당 capa (mid)로 모든 짐을 나르는 데 걸리는 최소 날짜를 구해서 days보다 크다면 불가능
    • 해당 capa (mid)로 모든 짐을 나르는 데 걸리는 최소 날짜가 days보다 작거나 같다면 가능하다고 판단
  • 그 다음으로 가능 여부가 나오면 범위를 어떻게 좁힐 지 결정한다.
    • 해당 capa (mid)로 가능하다면, max = mid - 1로 범위를 좁히면, mid 값은 더 작은 capa으로 설정된다.
    • 해당 capa (mid)로 불가능하다면, min = mid + 1로 범위를 좁히면, mid 값은 더 큰 capa로 설정된다.
    • 이렇게 반복해서 min과 max를 중앙값의 뒤, 앞으로 움직여 중앙값으로 최적 값을 찾아가는 게임이다. 
    • 즉, max를 움직이는 이유는 가능하지만, 더 최적의 값이 있는지를 찾는 것. min을 움직이는 이유는 불가능하기 때문에 가능한 값을 찾는 것이다.

문제 풀이

내 풀이

class Solution {
    //return: 모든 짐을 정해진 days 안에 모두 나를 수 있는 배 용량의 최솟값
    int[] weights;
    int days;
    int max;
    int min;
    public void findWeight() {
        int sum = 0;
        int big = weights[0];
        for (int w : weights) {
            sum += w;
            if (w > big) big = w;
        }
        max = sum;
        min = big;
    }

    public boolean canShip(int capa) {
        int sum = 0;
        int count = 1;
        for (int w : weights) {
            sum += w;
            if (sum > capa) {
                count++;
                sum = w;
            }
            if (count > days) return false; 
        }
        return true;
    }

    public int shipWithinDays(int[] weights, int days) {
        this.weights = weights;
        this.days = days;
        findWeight();

        while (min <= max) {
            int mid = (min+max)/2;
            if(canShip(mid)) {
                max = mid - 1;
            } else {
                min = mid + 1;
            }
        }

        return min;
    }
}

 

다른 사람 풀이

class Solution {
    public int shipWithinDays(int[] weights, int days) {


        int left = 0, right = 0;

        for(int w :  weights)
        {
            left = Math.max(left, w);
            right += w;
        }

        if (days ==1) return right;
        int ans = 0;
       //  System.out.println(left +" left, right" + right);
        while (left < right)
        {
            int mid = (left+right)/2;

            if(canShipPackage(weights, mid, days)){
                right = mid;
                ans = mid;
            } else {
                left = mid+1;
            }
        }
        return ans;
    }

    boolean canShipPackage(int [] weights,int mid,int days)
    {
        int count = 1;
        int sum = 0;
        for(int w: weights)
        {
            if(sum+w > mid)
            {
                sum = 0;
                count++;
            }
            sum += w;

            //System.out.println(count +"," + sum + "," +w);
        }
       // System.out.println(count +"," + mid);


        return count <= days ? true : false;
    }

}
반응형

+ Recent posts