도현이는 바구니를 총 N개 가지고 있고, 각각의 바구니에는 1번부터 N번까지 번호가 순서대로 적혀져 있다. 바구니는 일렬로 놓여져 있고, 가장 왼쪽 바구니를 1번째 바구니, 그 다음 바구니를 2번째 바구니, ..., 가장 오른쪽 바구니를 N번째 바구니라고 부른다.
도현이는 앞으로 M번 바구니의 순서를 역순으로 만들려고 한다. 도현이는 한 번 순서를 역순으로 바꿀 때, 순서를 역순으로 만들 범위를 정하고, 그 범위에 들어있는 바구니의 순서를 역순으로 만든다.
바구니의 순서를 어떻게 바꿀지 주어졌을 때, M번 바구니의 순서를 역순으로 만든 다음, 바구니에 적혀있는 번호를 가장 왼쪽 바구니부터 출력하는 프로그램을 작성하시오.
입력
첫째 줄에 N (1 ≤ N ≤ 100)과 M (1 ≤ M ≤ 100)이 주어진다.
둘째 줄부터 M개의 줄에는 바구니의 순서를 역순으로 만드는 방법이 주어진다. 방법은 i j로 나타내고, 왼쪽으로부터 i번째 바구니부터 j번째 바구니의 순서를 역순으로 만든다는 뜻이다. (1 ≤ i ≤ j ≤ N)
도현이는 입력으로 주어진 순서대로 바구니의 순서를 바꾼다.
출력
모든 순서를 바꾼 다음에, 가장 왼쪽에 있는 바구니부터 바구니에 적혀있는 순서를 공백으로 구분해 출력한다.
접근 방법
* 도현이는 바구니를 총 N개 가지고 있고, 각각의 바구니에는 1번부터 N번까지 번호가 순서대로 적혀져 있다.
-> 크기가 N+1인 배열을 만들고, 각 인덱스의 데이터를 인덱스 번호로 초기화한다. '순서대로' 적혀 있다고 했으니 1차원 배열을 써도 무난하겠다고 판단!! int[] arr = new int[N+1]; // arr[0]을 낭비하긴 하지만, 문제를 해결하는데 있어 편하게 하기 위해서!!
* 바구니는 일렬로 놓여져 있고, 가장 왼쪽 바구니를 1번째 바구니, 그 다음 바구니를 2번째 바구니, ..., 가장 오른쪽 바구니를 N번째 바구니라고 부른다. 도현이는 앞으로 M번 바구니의 순서를 역순으로 만들려고 한다. 도현이는 한 번 순서를 역순으로 바꿀 때, 순서를 역순으로 만들 범위를 정하고, 그 범위에 들어있는 바구니의 순서를 역순으로 만든다.
-> 그림과 같이 i는 1씩 증가를, j는 1씩 감소를 하며 양 끝에 있는 값끼리 위치를 바꿔준다. 그리고 i가 j보다 작아질 때라는 조건을 부여하여 탈출 조건을 작성한다.
* 바구니의 순서를 어떻게 바꿀지 주어졌을 때, M번 바구니의 순서를 역순으로 만든 다음, 바구니에 적혀있는 번호를 가장 왼쪽 바구니부터 출력하는 프로그램을 작성하시오.
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
// 1. N+1 크기의 배열을 만든다. index를 다루기 편하게 하기 위함
int N = sc.nextInt();
int M = sc.nextInt();
int[] arr = new int[N+1];
// 2. 각 배열의 초기값을 index값과 일치하게 초기화
for (int i = 0; i < arr.length; i++) {
arr[i] = i;
}
for (int k = 0; k < M; k++) {
int i = sc.nextInt();
int j = sc.nextInt();
while (i < j) {
int temp = arr[i];
arr[i++] = arr[j];
arr[j--] = temp;
}
}
for (int answer : arr) {
if(answer != 0)
System.out.print(answer + " ");
}
}
}
정리
while문을 사용하여 양 끝단에서 한 칸씩 줄어들며 버블정렬을 하면 쉽게 풀 수 있는 문제이다.
회고
처음에 for 문 안에 i와 j 두 값을 한번에 바꿀 수 없을까?하는 생각에 잠겨서 30분 정도 고민했던 것 같다. 길을 잘못들어섰으면 빨리 뒤돌아서 다른 방법을 찾는 것도 하나의 방법!
X대학 M교수님은 프로그래밍 수업을 맡고 있다. 교실엔 학생이 30명이 있는데, 학생 명부엔 각 학생별로 1번부터 30번까지 출석번호가 붙어 있다. 교수님이 내준 특별과제를 28명이 제출했는데, 그 중에서 제출 안 한 학생 2명의 출석번호를 구하는 프로그램을 작성하시오.
입력
입력은 총 28줄로 각 제출자(학생)의 출석번호 n(1 ≤ n ≤ 30)가 한 줄에 하나씩 주어진다. 출석번호에 중복은 없다.
출력
출력은 2줄이다. 1번째 줄엔 제출하지 않은 학생의 출석번호 중 가장 작은 것을 출력하고, 2번째 줄에선 그 다음 출석번호를 출력한다. (예제 입력은 밑에 더 있다. 보기가 길어질까봐 잘랐음)
접근 방법
- 1차원 배열로 쉽게 풀 수 있는 문제이다. 그런데, list를 사용해서 sort를 해서 풀 수 있겠다는 생각이 들었고, 두 방법으로 모두 풀어보았다.
1. 1차원 배열을 사용한 풀이
import java.io.*;
import java.util.*;
public class Main {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in);
int[] arr = new int[31];
for (int i = 0; i < 28; i++) {
int input = sc.nextInt();
arr[input] = 1;
}
for (int j = 1; j < arr.length; j++) {
if (arr[j] != 1) {
System.out.println(j);
}
}
sc.close();
}
}
1차원 배열을 생성하여, for문을 28번 돌리며 각 시행마다 input값을 받아서 그것에 해당하는 배열 인덱스의 값을 1로 지정한다. 그리고 다음 번 for문에서 배열의 원소가 1이 아닌 값을 출력한다.
장점 : 1차원 배열을 사용했기 때문에 순서가 정해져있다. 즉, sort를 할 필요 없이 오름차순으로 출력할 수 있다.
단점 : 수행 시간이 조금 걸린다(근데 이 문제에서는 크게 신경안써도 될 듯..!)
2. ArrayList를 사용한 풀이
import java.io.*;
import java.util.*;
public class Main {
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
List<Integer> alist = new ArrayList<>();
List<Integer> list = new ArrayList<>();
for (int j = 0; j < 28; j++) {
int numlist = Integer.parseInt(br.readLine());
alist.add(numlist);
}
for (int i = 1; i <= 30; i++) {
if (!alist.contains(i)) {
list.add(i);
}
}
// 정렬을 꼭 해줘야 함. list에 저장된 값이 무엇인지 모르기 때문에
Collections.sort(list);
for (int j : list) {
System.out.println(j);
}
}
}
여기서 중요한 부분은, list를 꼭 sort해줘야 하는 것이다. 왜냐하면, 출력 방식이 오름차순이기 때문이다.
정리
코드 자체는 1차원 배열을 사용하는 것이 편하지만, 자료구조를 배우면 효율적인 코드를 짤 수 있다. 수행 시간도 줄일 수 있을 분더러, 메모리를 효율적으로 사용할 수 있다. 물론, 무조건 Collection class를 이용한다고 해서 더 좋다는 것은 아니다.
거스름돈의 액수가 주어지면 리암이 줘야할 쿼터(Quarter,$0.25)의 개수, 다임(Dime,$0.10)의 개수, 니켈(Nickel,$0.05)의 개수, 페니(Penny,$0.01)의 개수를 구하는 프로그램을 작성하시오. 거스름돈은 항상$5.00 이하이고, 손님이 받는 동전의 개수를 최소로 하려고 한다. 예를 들어,$1.24를 거슬러 주어야 한다면, 손님은 4쿼터, 2다임, 0니켈, 4페니를 받게 된다.
입력
첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 거스름돈 C를 나타내는 정수 하나로 이루어져 있다. C의 단위는 센트이다. (1달러 = 100센트) (1<=C<=500)
출력
각 테스트케이스에 대해 필요한 쿼터의 개수, 다임의 개수, 니켈의 개수, 페니의 개수를 공백으로 구분하여 출력한다.
접근 방법
- 우선 Greedy 문제인 것을 직감적으로 알 수 있다. 입력받은 센트를 쿼터(Quarter,$0.25)로 나눴을 때 몫을 따로 저장하고, 나머지는 다시 피제수(나눠지는 수)가 된다. 그 피제수(나눠지는 수)를 다시 다임(Dime,$0.10)으로 나누고, 몫을 따로 저장하고 나머지는 다시 니켈(Nickel,$0.05)로 나눈다. 마지막으로 페니(Penny,$0.01)로 같은 작업을 반복하여 몫을 LinkedList에 저장하고 출력하면 된다.
정리
1. 쿼터, 다임, 니켈, 페니를 각각 25, 10, 5, 1 센트 단위로 생각하여 num배열을 하나 만든다. 그리고 몇 번 반복할지 T 값을 받는다.
2. 접근 방법에 해당하는 알고리즘을 구현한다.
3. Iterator를 사용하여 LinkedList의 원소를 출력한다.
import java.io.*;
import java.util.*;
class Main {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
int[] num = {25, 10, 5, 1};
int T = sc.nextInt();
// // 1. LinkedList를 T 크기 만큼 만든다.
LinkedList<Integer> arr = new LinkedList<>();
for (int i = 0; i < T; i++) {
arr.add(sc.nextInt());
}
// 2. for문으로 각 값의 몫과 나머지를 구한다
for (int i = 0; i < arr.size(); i++) {
LinkedList<Integer> li = new LinkedList<>();
for (int j = 0; j < num.length; j++) {
// 몫 추가
li.add(arr.get(i) / num[j]);
// 나머지 처리
arr.add(i, arr.get(i) % num[j]);
arr.remove(i + 1);
}
Iterator<Integer> it = li.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
}
}
}
회고
- 해당 코드에서 핵심은 몫과 나머지를 어떻게 처리하냐의 문제이다. 반복 횟수가 많지는 않은데, ArrayList와 LinkedList 중 어떤 것을 써야 더 효율적일까를 고민해보았고, 물리적으로 비연속적인 LinkedList가 삽입/삭제에 더 유리하기 때문에 LinkedList를 채택했다. 사실 ArrayList를 써도 되지만, 각각의 특징을 공부하고 나니까 간단한 문제에도 이렇게 고민을 하게 되는 것 같다.
그리고, List형태를 출력할 때 for each문 보다 Iterator를 쓰는게 효율적이라는 것을 알게 되었다. for each문은 Map클래스를 출력할 때 용이하다.
JDK 17이 Gradle 8.1.1과 호환이 안된다는 것이니, 환경변수를 설정하라는 내용이었다. 일단? 내가 알기론 Gradle 8.1.1은 JDK 17과 호환이 되는데, 왜 저런 문구가 뜨지 싶어서 환경 변수 설정을 하려고 봤는데.. 알고보니 JDK 11로 설정되어 있었다. 그래서 17로 다시 바꿔줬다.
환경 변수 설정하는 법
- window키 + s
- '고급 시스템 설정 보기' 검색
- 환경 변수 클릭
2. 프로젝트 JDK 17 변경
File -> Project Structure
Project SDK에서 자바 17version으로 바꿨다.
또한, File -> Settings -> Gradle 검색
Build and run using과 Run tests using을 Gradle에서 Intellij IDEA로 변경했다.
기본 설정값은 Gradle인데, Gradle 공식 홈페이지에서는 Gradle을 수행하는데 있어 오류가 없게 하도록 하기 위해 디폴트 값을 Gradle로 해놨다고 한다. 근데 Gradle로 컴파일 하면 속도가 느리다는 단점이 있어서, Intellij IDEA로 설정했다.
그리고 Gradle을 다시 빌드하니 오류가 나지 않았고, 해당 강의대로 (아무것도 없는)main 클래스를 실행했을 때 Tomcat이 8080포트로 시작된 것을 확인할 수 있다. 이를 확인하기 위해 localhost:8080으로 접속하면 아래와 같은 오류 페이지가 뜬다. 이로써 셋팅 완료!
Thecompileandruntimeconfigurations have been removed with Gradle 7.0. Please refer to theupgrade guidehow to migrate toimplementationandapiconfigurations`. - 출처 : gradle 공식 홈페이지 -
해당 책은 gradle 4.10.2 version을 사용하고 있어서, 의존성을 추가할 때 compile, testCompile을 사용했다. 하지만, 지금은 책이 출간한지 5년 정도가 지났기에.. 나는 gradle 7.1 version을 사용하고 있었다. 그래서 위와 같은 코드를 작성했을 때 오류가 났다.
앞서 말한 것처럼, compile을 implementation으로, testCompile을 testImpementation으로 변경하니 빌드가 성공됐다.
자바를 공부하다 보면 헷갈리는 용어가 많은데, 특히 오버로딩(Overloading)과 오버라이딩(Overriding)이 비슷해서 많이 헷갈린다. 그런데, 사실 둘은 아무 관계 없는 내용이다. 둘의 차이점을 알아보자.
오버로딩(Overloading)
오버라이딩(Overriding)
접근 제어
달라도 됨
같음
반환형
달라도 됨
같음
메서드 이름
같음
같음
매개변수
달라도 됨
같음
1. 오버로딩(Overloading)
같은 클래스 내에서 매소드를 확장하기 위한 것. 메소드 명은 같지만, 매개 변수, 반환형 등을 다르게 하여 편리하게 사용 가능하다.
메서드 이름이 같다.
매개 변수, 반환형(return)은 달라도 된다.
같은 클래스 내에 선언 되어야 한다.
만약 매개 변수의 개수가 같다면 자료형이 달라야 하고, 자료형이 모두 같다면 매개 변수의 개수를 다르게 해야 한다.
코드 예시를 통해 개념을 익혀보자.
class Hello {
void overL() {
System.out.println("오버로딩을 위한 메서드");
}
void overL(String str){
System.out.println("매개 변수 타입이 String 이고, 값은 " + str);
}
void overL(int k){
System.out.println("매개 변수 타입이 int 이고, 값은 " + k);
}
int overL(int x, int y){
System.out.println("반환형이 다른 메서드");
return x;
}
}
public class HelloJava {
public static void main(String[] args) {
Hello ov = new Hello();
ov.overL();
ov.overL("오버로딩 예제");
int num = ov.overL(5, 6);
System.out.println(num);
}
}
2. 오버라이딩(Overriding)
부모 클래스의 메서드를 자식 클래스에서 메서드를 '재정의'하는 것이다.
메서드 명, 매개 변수, 반환형, 접근 제어자가 모두 같다.
상속 받은 부모 클래스의 메서드를 재정의 하는 것이다. 다른 클래스의 메서드를 재정의 하는 것이 아니다!
class Animal {
public void makeSound() {
System.out.println("동물이 소리를 내고 있습니다.");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("강아지가 멍멍 소리를 내고 있습니다.");
}
}
public class HelloJava {
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
animal.makeSound(); // "동물이 소리를 내고 있습니다." 출력
dog.makeSound(); // "강아지가 멍멍 소리를 내고 있습니다." 출력
}
}