public class Solution {
	
	// 자물쇠와 열쇠
	// key, lock 3이상 20이하 크기의 2차원 배열
	// key는 회전과 이동이 가능
	
	public static boolean openLock(int[][] checkArr, int point, int lockLength) {
		
		// point : 체크용 배열 checkArr에서 실제 lock의 시작 위치
		// lockLength : lock의 길이
		
		for (int i = 0; i < lockLength; i++) {
			
			for (int j = 0; j < lockLength; j++) {
				
				if (checkArr[point + i][point + j] != 1) return false; // 0 : 채워지지 않은 공간, 2 : 튀어나온 부분끼리 만난 공간 // lock 해제 불가
			}
		}
		
		return true;
	}
	
	public static boolean solution(int[][] key, int[][] lock) {
		int point = key.length - 1; // 2
		int checkArrSize = key.length * 2 + lock.length - 2; // 7
		
		// STEP 1. key 회전
		for (int rot = 0; rot < 4; rot++) { // 0도, 90도, 180도, 270도
			
			// STEP 2. key 이동
			for (int i = 0; i < point + lock.length; i++) { // key의 행 이동 경로
				
				for (int j = 0; j < point + lock.length; j++) { // key의 열 이동 경로
					
					// STEP 3. 체크용 배열 생성 & 자물쇠의 값 세팅
					int[][] checkArr = new int[checkArrSize][checkArrSize]; // key와 lock을 포함할 체크용 배열 생성
					
					for (int r = 0; r < lock.length; r++) {
						
						for (int c = 0; c < lock.length; c++) {
							
							checkArr[point + r][point + c] = lock[r][c]; // 체크용 배열에 lock의 값 넣기
						}
					}
					
					// STEP 4. 체크용 배열에 key의 값 추가
					for (int r = 0; r < key.length; r++) {
						
						for (int c = 0; c < key.length; c++) {
							
							if (rot == 0) { // 원본
								checkArr[i + r][j + c] += key[r][c];
							} else if (rot == 1) { // 90도 회전했을 때
								checkArr[i + r][j + c] += key[c][key.length - 1 - r];
							} else if (rot == 2) { // 180도 회전했을 때
								checkArr[i + r][j + c] += key[key.length - 1 - r][key.length - 1 - c];
							} else { // 270도 회전했을 때
								checkArr[i + r][j + c] += key[key.length - 1 - c][r];
							}
						}
					}
					
					// STEP 5. checkArr의 값을 확인해 현재 key의 위치에서 lock을 열 수 있는지 체크
					if (openLock(checkArr, point, lock.length)) return true; // lock 해제 가능하면 true 리턴
				}
			}
		}
		
		return false;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[][] key = {{0, 0, 0}, {1, 0, 0}, {0, 1, 1}};
		int[][] lock = {{1, 1, 1}, {1, 1, 0}, {1, 0, 1}};
		
		// key : 90도 회전 -> 오른쪽 한 칸 -> 아래쪽 한 칸
		
		System.out.println(solution(key, lock)); // true
	}
}

2차원 배열 시계 방향으로 90도, 180도, 270도, 360도 회전

public static int[][] rotate(int[][] arr, int degree) { // 2차원 배열 시계 방향으로 회전
	int n = arr.length;
	int m = arr[0].length;
	int[][] rotatedArr = {};
		
	// 90도 회전
	if (degree == 90) {
			
		rotatedArr = new int[m][n];
			
		for (int i = 0; i < rotatedArr.length; i++) {
				
			for (int j = 0; j < rotatedArr[i].length; j++) {
				rotatedArr[i][j] = arr[n - 1 - j][i];
			}
		}
	}
		
	// 180도 회전
	if (degree == 180) {
			
		rotatedArr = new int[n][m];
			
		for (int i = 0; i < rotatedArr.length; i++) {
				
			for (int j = 0; j < rotatedArr[i].length; j++) {
				rotatedArr[i][j] = arr[n - 1 - i][m - 1 - j];
			}
		}
	}
		
	// 270도 회전
	if (degree == 270) {
			
		rotatedArr = new int[m][n];
			
		for (int i = 0; i < rotatedArr.length; i++) {
				
			for (int j = 0; j < rotatedArr[i].length; j++) {
				rotatedArr[i][j] = arr[j][m - 1 - i];
			}
		}
	}
		
	// 360도 회전
	if (degree == 360) {
			
		rotatedArr = new int[n][m];
			
		for (int i = 0; i < rotatedArr.length; i++) {
				
			for (int j = 0; j < rotatedArr[i].length; j++) {
				rotatedArr[i][j] = arr[i][j];
			}
		}
	}
		
	return rotatedArr;
}

프로그래머스 자물쇠와 열쇠 문제 풀이 Java 소스 코드

TDD(Test Driven Development)란?

테스트 코드를 만들고, 이를 통과하는 최소한의 코드로 시작하여

점진적으로 개선, 확장해가는 개발 방식을 말한다.

 

Article 서비스를 검증하는 테스트 코드를 작성해보자

 

Article 서비스에서 테스트를 원하는 메소드명 우클릭 Generate > Test를 클릭하고

열린 창 하단에 테스트를 할 메소드명을 체크 후 OK를 클릭한다.

 

ArticleServiceTest라는 클래스 파일이 만들어지고

이 파일의 경로는 src > test > java > com.example.firstproject > service가 된다.

 

H2 DB를 사용하고 있었기 때문에 data.sql을 참고해 테스트 코드를 작성한다.

 

예상 시나리오 작성 -> 실제 결과와 비교하여 검증

 

<data.sql>

INSERT INTO ARTICLE(ID, TITLE, CONTENT) VALUES (2, 'AAAA', '1111');
INSERT INTO ARTICLE(ID, TITLE, CONTENT) VALUES (3, 'BBBB', '2222');
INSERT INTO ARTICLE(ID, TITLE, CONTENT) VALUES (4, 'CCCC', '3333');

<ArticleServiceTest>

package com.example.firstproject.service;

import com.example.firstproject.dto.ArticleDto;
import com.example.firstproject.entity.Article;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest // 해당 클래스는 스프링부트와 연동되어 테스팅된다.
class ArticleServiceTest {

    @Autowired ArticleService articleService;

    @Test
    void readAll() {
        // 예상
        Article a = new Article(2L, "AAAA", "1111");
        Article b = new Article(3L, "BBBB", "2222");
        Article c = new Article(4L, "CCCC", "3333");
        List<Article> expectedList = new ArrayList<Article>(Arrays.asList(a, b, c));

        // 실제
        List<Article> articleList = articleService.readAll();

        // 비교
        assertEquals(expectedList.toString(), articleList.toString());
    }

    @Test
    void read_success() { // read 메소드 성공 테스트
        // 예상
        Long id = 2L;
        Article expected = new Article(id, "AAAA", "1111");

        // 실제
        Article article = articleService.read(id);

        // 비교
        assertEquals(expected.toString(), article.toString());
    }

    @Test
    void read_fail() { // read 메소드 실패 테스트 // 존재하지 않는 id를 입력한 경우
        // 예상
        Long id = -1L;
        Article expected = null;

        // 실제
        Article article = articleService.read(id); // return articleRepository.findById(id).orElse(null); 이렇게 작성되어 있으므로 null 값을 갖는 expected로 비교

        // 비교
        assertEquals(expected, article); // null은 toString 메소드를 호출할 수 없음
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void create_success() { // create 메소드 성공 테스트 // title과 content만 있는 dto 입력
        // 예상
        String title = "DDDD";
        String content = "4444";
        ArticleDto dto = new ArticleDto(null, title, content);
        Article expected = new Article(1L, title, content);

        // 실제
        Article article = articleService.create(dto);

        // 비교
        assertEquals(expected.toString(), article.toString());
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void create_fail() { // create 메소드 실패 테스트 // id가 포함된 dto 입력
        // 예상
        String title = "DDDD";
        String content = "4444";
        ArticleDto dto = new ArticleDto(2L, title, content);
        Article expected = null;

        // 실제
        Article article = articleService.create(dto); // id가 존재하는 경우 null return하도록 작성되어 있음

        // 비교
        assertEquals(expected, article);
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void update_success_1() { // update 메소드 성공 테스트 케이스 1 // 존재하는 id와 변경할 title, content만 있는 dto 입력
        // 예상
        Long id = 2L; // 대상 id
        String title = "AAAAAAAA"; // 변경할 title
        String content = "11111111"; // 변경할 content
        ArticleDto dto = new ArticleDto(null, title, content);
        Article expected = new Article(id, "AAAAAAAA", "11111111");

        // 실제
        Article article = articleService.update(id, dto);

        // 비교
        assertEquals(expected.toString(), article.toString());
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void update_success_2() { // update 메소드 성공 테스트 케이스 2 작성해보기
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void update_fail_1() { // update 메소드 실패 테스트 케이스 1 // 존재하지 않는 id와 변경할 title, content만 있는 dto 입력
        // 예상
        Long id = -1L; // 대상 id
        String title = "AAAAAAAA"; // 변경할 title
        String content = "11111111"; // 변경할 content
        ArticleDto dto = new ArticleDto(null, title, content);
        Article expected = null;

        // 실제
        Article article = articleService.update(id, dto); // id에 해당하는 엔티티가 없는 경우 null return하도록 작성되어 있음

        // 비교
        assertEquals(expected, article);
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void update_fail_2() { // update 메소드 실패 테스트 케이스 2 작성해보기
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void delete_success_1() { // delete 메소드 성공 테스트 케이스 1 // 존재하는 id 입력
        // 예상
        Long id = 2L;
        Article expected = new Article(id, "AAAA", "1111");

        // 실제
        Article article = articleService.delete(id);

        // 비교
        assertEquals(expected.toString(), article.toString());
    }

    @Test
    @Transactional // 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.
    void delete_fail_1() { // delete 메소드 실패 테스트 케이스 1 // 존재하지 않는 id 입력
        // 예상
        Long id = -1L;
        Article expected = null;

        // 실제
        Article article = articleService.delete(id);

        // 비교
        assertEquals(expected, article);
    }
}

테스트 코드 작성 후 메소드 옆 재생 버튼 클릭 > Run ArticleServiceTest 클릭하면 해당 메소드의 테스트가 시작된다.

화면 좌측 하단 Show Passed, Show Ignored를 선택하고

메소드명 옆에 녹색 체크(테스트 정상) 또는 X 표시(테스트 실패)를 확인한다.

모든 메소드에 대해 테스트를 진행하고자 한다면 클래스명 옆에 있는 재생 버튼을 클릭하면 된다.

조회가 아닌 생성, 변경, 삭제 테스트의 경우

Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다. (어노테이션 추가 @Transactional)

<JavaScript>

그리드 복사 행 붙여넣기 : extension.module.js

/**
 * 붙여넣기
 * @param {cpr.core.AppInstance} app
 * @param {common.AppKit} appKit
 * @param {#grid} psGridId
 */
"7" : function(app, appKit, psGridId){
    /** @type cpr.controls.Grid */
    var vcGrid = app.lookup(psGridId);
    var Index = appKit.Grid.getIndex(app, vcGrid.id);
    /** @type String */
    var vsInfo = vcGrid.dataSet.info;
    //pk컬럼 배열
    var vaPkColumns = [];
    if(!ValueUtil.isNull(vsInfo)){
        vaPkColumns = vsInfo.split(",");
    }

    /** @type Array */
    var vaPasteRows = [];
    if(!ValueUtil.isNull(vcGrid._copyRowData)){
        /** @type cpr.data.RowConfigInfo */
        var vaCopyRowData = vcGrid._copyRowData;
        vaCopyRowData.forEach(function(/* cpr.data.RowConfigInfo */rowData, i){
            vaPkColumns.forEach(function(/* String */psColumn){
                //PK컬럼을 rowData 객체에서 제거
                delete rowData[psColumn];
            });

            // sylee 추가(프로그램관리, 광고소재, 운행소재에서 붙여넣기 시)
            if(vcGrid.id == "BS_PGM_INPUT_G10" || vcGrid.id == "BS_CM_MTRL_INPUT_G01" || vcGrid.id == "BS_PLAY_MTRL_INPUT_G01"){
                rowData.clipYn = "";
                rowData.clipYn1 = "";
                rowData.clipYn2 = "";
                rowData.clipYn3 = "";
                rowData.arcYn = "";
            }

            var voRow = vcGrid.dataSet.getRow(Index+i);
            if(!ValueUtil.isNull(voRow)){
                voRow.setRowData(rowData);
                vaPasteRows.push(voRow);
            }else{
                var voInsertRow = vcGrid.dataSet.insertRowData(Index+i, true, rowData);
                vaPasteRows.push(voInsertRow);
            }
        });
    }

    var voOption = {
        "gridId"		: vcGrid.id,
        "pasteRows"		: vaPasteRows
    }

    //붙여넣기 이벤트 디스패치
    app.dispatchEvent(new cpr.events.CUIEvent("gridKitRowPaste", voOption));
}

EOM 시간 계산 : common.module.js

/**
 * 시작시각, 운행길이로 종료시각을 가져온다
 * @param {string} psBroadHour 방송시
 * @param {string} psBroadMin 방송분
 * @param {string} psBroadSec 방송초
 * @param {string} psBroadFrm 방송프레임
 * @param {string} psBroadRunHour 운행시
 * @param {string} psBroadRunMin 운행분
 * @param {string} psBroadRunSec 운행초
 * @param {string} psBroadRunFrm 운행프레임
 * @return {string} 종료시각
 * @since 2020-05-06
 * @author sylee
 */
AppKit.prototype.calcEom = function(psBroadHour, psBroadMin, psBroadSec, psBroadFrm, psBroadRunHour, psBroadRunMin, psBroadRunSec, psBroadRunFrm, psFrameRate) {
	var vnBroadHour = Number(psBroadHour) + Number(psBroadRunHour);
	var vnBroadMin = Number(psBroadMin) + Number(psBroadRunMin);
	var vnBroadSec = Number(psBroadSec) + Number(psBroadRunSec);
	var vnBroadFrm = Number(psBroadFrm) + Number(psBroadRunFrm);
	vnBroadHour = vnBroadHour + Math.floor(vnBroadMin / 60);
	vnBroadMin = (vnBroadMin % 60) + Math.floor(vnBroadSec / 60);
	
	// 2020.09.10 프레임 크기에 따른 시간 계산 구분 추가
	if (psFrameRate == "60") {
		vnBroadSec = (vnBroadSec % 60) + Math.floor(vnBroadFrm / 60);
		vnBroadFrm = (vnBroadFrm % 60);
	} else {
		vnBroadSec = (vnBroadSec % 60) + Math.floor(vnBroadFrm / 30);
		vnBroadFrm = (vnBroadFrm % 30);
	}

	if(vnBroadSec >= 60) {
		vnBroadSec = (vnBroadSec % 60);
		vnBroadMin++;
	}
	if(vnBroadMin >= 60) {
		vnBroadMin = (vnBroadMin % 60);
		vnBroadHour++;
	}
	
	var vsResult = "";
	if(vnBroadHour < 10) {
		vsResult += "0" + vnBroadHour;
	} else {
		vsResult += String(vnBroadHour);
	}
	if(vnBroadMin < 10) {
		vsResult += "0" + vnBroadMin;
	} else {
		vsResult += String(vnBroadMin);
	}
	if(vnBroadSec < 10) {
		vsResult += "0" + vnBroadSec;
	} else {
		vsResult += String(vnBroadSec);
	}
	if(vnBroadFrm < 10) {
		vsResult += "0" + vnBroadFrm;
	} else {
		vsResult += String(vnBroadFrm);
	}
	
	return vsResult;
}

<Java>

EOM 시간 계산 : CommonUtil.java

public static String calPlayTime(String vs24Yn, String vsType, String vsPlayTime, String vsDuration) throws Exception {
    int iHH1, iHH2, iMM1, iMM2, iSS1, iSS2, iFF1, iFF2;
    Long iCalFrame1, iCalFrame2, iRevFrame, iCalFrame;
    int iHH = 0, iMM = 0, iSS = 0, iFF = 0;
    String rtnPlayTime = "";

    if (vsPlayTime == null || "".equals(vsPlayTime)) {
        vsPlayTime = "00000000";
    }

    if (vsDuration == null || "".equals(vsDuration)) {
        vsDuration = "00000000";
    }

    if (vsType != null && "DF".equals(vsType)) {
        iHH1 = Integer.parseInt(vsPlayTime.substring(0, 2));
        iMM1 = Integer.parseInt(vsPlayTime.substring(2, 4)) + (iHH1 * 60);
        iSS1 = Integer.parseInt(vsPlayTime.substring(4, 6));
        iFF1 = Integer.parseInt(vsPlayTime.substring(6, 8));

        iHH2 = Integer.parseInt(vsDuration.substring(0, 2));
        iMM2 = Integer.parseInt(vsDuration.substring(2, 4)) + (iHH2 * 60);
        iSS2 = Integer.parseInt(vsDuration.substring(4, 6));
        iFF2 = Integer.parseInt(vsDuration.substring(6, 8));

        iCalFrame1 = (long)(iMM1 * 1798 + ((int)(iMM1 / 10) * 2) + (iSS1 * 30) + iFF1 + 1);
        iCalFrame2 = (long) (iMM2 * 1798 + ((int)(iMM2 / 10) * 2) + (iSS2 * 30) + iFF2 + 1);

        iCalFrame = iCalFrame1 + iCalFrame2 - 1;
        iMM = (int) (iCalFrame / 1798);

        iRevFrame = (long) ((int)(iMM / 10) * 2);

        if (((iCalFrame % 1798) - iRevFrame - 1) < 0) {
            iRevFrame = (long) ((int)(iMM / 10) * 2);
            iCalFrame = (iCalFrame % 1798) + 1798 - iRevFrame - 1;
        } else {
            iCalFrame = (iCalFrame % 1798) - iRevFrame - 1;
        }

        iHH = (int)(iMM / 60);
        iMM = iMM % 60;
        iSS = (int)(iCalFrame / 30);
        iFF = (int) (iCalFrame % 30);

        rtnPlayTime = setLPad(Integer.toString(iHH), 2, "0") + setLPad(Integer.toString(iMM), 2, "0") + setLPad(Integer.toString(iSS), 2, "0") + setLPad(Integer.toString(iFF), 2, "0");
    } else {
        iHH1 = Integer.parseInt(vsPlayTime.substring(0, 2));
        iMM1 = Integer.parseInt(vsPlayTime.substring(2, 4));
        iSS1 = Integer.parseInt(vsPlayTime.substring(4, 6));

        iHH2 = Integer.parseInt(vsDuration.substring(0, 2));
        iMM2 = Integer.parseInt(vsDuration.substring(2, 4));
        iSS2 = Integer.parseInt(vsDuration.substring(4, 6));

        if ("FF".equals(vsType) || "NF".equals(vsType)) {
            iFF1 = Integer.parseInt(vsPlayTime.substring(6, 8));
            iFF2 = Integer.parseInt(vsDuration.substring(6, 8));

            iSS = (int)((iFF1 + iFF2) / 30);
            iFF = (iFF1 + iFF2) % 30;
        }

        iMM = (int)((iSS + iSS1 + iSS2) / 60);
        iSS = (iSS + iSS1 + iSS2) % 60;

        iHH = (int)((iMM + iMM1 + iMM2) / 60);
        iMM = (iMM + iMM1 + iMM2) % 60;

        iHH = iHH + iHH1 + iHH2;

        if ("N".equals(vs24Yn) && iHH >= 24) {
            iHH -= 24;
        }

        rtnPlayTime = setLPad(Integer.toString(iHH), 2, "0") + setLPad(Integer.toString(iMM), 2, "0") + setLPad(Integer.toString(iSS), 2, "0");

        if ("FF".equals(vsType) || "NF".equals(vsType)) {
            rtnPlayTime += setLPad(Integer.toString(iFF), 2, "0");
        }
    }

    return rtnPlayTime;
}

public static String setLPad(String strContext, int iLen, String strChar) {
    String strResult = "";
    StringBuilder sbAddChar = new StringBuilder();

    for(int i = strContext.length(); i < iLen; i++) {
        // iLen길이 만큼 strChar문자로 채운다.
        sbAddChar.append( strChar );
    }
    strResult = sbAddChar + strContext; // LPAD이므로, 채울문자열 + 원래문자열로 Concate

    return strResult;
}

SFTP 방식 파일 업로드

commons-net-3.8.0.jar 추가 (C:\~\workspaceCis\build\lib\XBLib\commons-net-3.8.0.jar)
cis-web 우클릭 Properties > Java Build Path > XBLib > Edit > User Libraries > Add JARs > build\lib\XBLib\commons-net-3.8.0.jar 선택 > OK > Finish > Apply (cis-app에서 우클릭 했더니 적용 안됐었는데 확인 필요)
FtpClient 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\FtpClient.java)
RestApiUtil 코드 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\RestApiUtil.java)
FileUploadService 코드 추가
jsch-0.1.49.jar 추가 (C:\~\workspaceCis\build\lib\XBLib\jsch-0.1.49.jar) (commons-net 에서는 SFTP 를 지원 안함 => jsch 라이브러리 이용해서 SFTP 사용)
cis-web 우클릭 Properties > Java Build Path 적용
JschWrapper 추가 (C:\~\workspaceCis\cis-core\src\org\tmt\core\util\JschWrapper.java)

 

1. FileUploadController.java

package org.cis.com.controller;

import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.cis.com.service.FileUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.tmt.core.auth.Authentication;
import org.tmt.core.model.RequestData;
import org.tmt.core.vo.DataMap;

@Controller
@RequestMapping("/FileUpload")
public class FileUploadController {

	@Autowired
	private FileUploadService fileUploadService;

	@RequestMapping(value="/upload.do")
	@ResponseBody
    public View upload(@RequestParam(required = false) Map<String, Object> param,
		HttpServletRequest request, HttpServletResponse response,
		ModelMap modelMap, RequestData requestData, Authentication authentication) throws Exception {

//		List<DataMap> fileInfo = fileUploadService.uploadFile(requestData, authentication); // 기존 방식 : 31, 32 운영 서버의 공유 폴더에 업로드가 되지 않아 FTP 방식으로 변경 -> commons-net-3.8.0.jar가 SFTP를 지원하지 않는 관계로 SFTP 방식으로 변경
		
		List<DataMap> fileInfo = fileUploadService.sftpUploadFile(request, requestData, authentication); // 2022.03.30 sylee SFTP 방식의 파일 업로드
		
		requestData.setResponse("dsUpload", fileInfo);

		return requestData.getDefaultView();    		
    }
}

2. FileUploadService.java

public List<DataMap> sftpUploadFile(HttpServletRequest request, RequestData requestData, Authentication authentication) throws Exception {
    List<DataMap> fileList = new ArrayList<DataMap>();
    DataMap paramMap = new DataMap();
    String strGlobalFileStorePath = "";
    String strFileStorePath = "";
    String strFtpIp = "";
    int iFtpPort = 0;
    String strFtpId = "";
    String strFtpPw = "";
    String strFtpDir = "";
    String strTempFilePath = "";
    String strMenuId = requestData.getString("_AUTH_MENU_KEY");
    strGlobalFileStorePath = AppWorkProperties.getProperty("Globals.fileStorePath"); // appworks.properties > Globals.fileStorePath=/FILE 추가

    if (strMenuId.contains("AR_BOARD")) {
        strFileStorePath = "/AR_BOARD";
    } else {
        strFileStorePath = AppWorkProperties.getProperty(strMenuId + ".fileStorePath"); // appworks.properties > NOTICE.fileStorePath=/NOTICE 추가
    }

    DataMap data = commonService.selectSysDate();

    paramMap.put("userId", authentication.getUserId());

    if("".equals(StringUtil.fixNull(strFileStorePath))){
        //첨부파일을 저장할 저장소 경로가 존재하지 않습니다.
        throw new AppWorksException("첨부파일을 저장할 저장소 경로가 존재하지 않습니다.", Alert.ERROR);
    }

    int iFileCnt = 0;

    Map<String, UploadFile[]> uploadFiles = requestData.getUploadFiles();

    if(uploadFiles != null && uploadFiles.size() > 0) {

        Set<Entry<String, UploadFile[]>> entries = uploadFiles.entrySet();

        for(Entry<String, UploadFile[]> entry : entries) {
            DataMap fileInfo = new DataMap();
            UploadFile[] uFiles = entry.getValue();
            DataMap KeyNmData = commonService.selectAttachFileKeyNm(paramMap); // 업로드되는 파일이 중복되지 않도록 key값 생성

            for(UploadFile uFile : uFiles){
                File file = uFile.getFile();
                String strFileName = uFile.getFileName(); //파일명.jpg
                String[] arr = strFileName.split("\\."); // [파일명, .jpg]
                String strFileSize = Long.toString(file.length()); //파일 사이즈
                String strFileExt = FileUtil.getFileExtNm(strFileName);
                String strTempPath = file.getPath(); // 임시 파일 업로드 경로 C:\Users\champ\AppData\Local\Temp\파일명.jpg.tmp
                String strKeyNm = "(" + KeyNmData.getString("fileKeyNm") + ")." + arr[arr.length - 1]; // (key값).jpg

                strKeyNm = arr[arr.length - 2] + strKeyNm; // 파일명(key값).jpg

                // 보안에 위배되는 파일 확장자 유형인 경우
                if(!SecurityWebUtil.securedFileType(strKeyNm)){
                    // {0} 확장자는 업로드 할 수 없습니다.
                    throw new AppWorksException("SYS.CMMN@CMMN011", Alert.ERROR, FileUtil.getFileExtNm(strKeyNm));
                }
                
                strFtpIp = "000.00.00.00";
                iFtpPort = 22;
                strFtpId = "root";
                strFtpPw = "root";
                strFtpDir = strGlobalFileStorePath+strFileStorePath+"/"; // 업로드할 경로 "/BIS_Storage/BS_PD_REQUEST_MST/";
                strTempFilePath = strTempPath;

                boolean result = sftpSendFile(strKeyNm, strFtpIp, iFtpPort, strFtpId, strFtpPw, strFtpDir, strTempFilePath);

                fileInfo.put("fileNm", strFileName);
                fileInfo.put("upLoadPath", strGlobalFileStorePath+strFileStorePath+"/");
                fileInfo.put("fileSize", strFileSize);
                fileInfo.put("fileExt", strFileExt);
                fileInfo.put("saveFileNm", strKeyNm);

            }
            fileList.add(fileInfo);
            iFileCnt++;
        }
    }

    return fileList;
}

public boolean sftpSendFile(String strFileNm, String strFtpIp, int iFtpPort, String strFtpId, String strFtpPw, String strFtpDir, String strTempFilePath) throws Exception{

    boolean result = false;

    JschWrapper jw = null;

    try {
        // JschWrapper 생성 (주소, 포트번호, 사용자아이디, 패스워드)
        jw = new JschWrapper(strFtpIp, iFtpPort, strFtpId, strFtpPw);

        // SFTP 접속하기 (주소, 포트번호, 사용자아이디, 패스워드)
        jw.connectSFTP(strFtpIp, iFtpPort, strFtpId, strFtpPw);

        // 파일 업로드 (임시 파일 경로, 업로드할 경로, 확장자명 포함한 파일명)
        jw.uploadFile(strTempFilePath, strFtpDir, strFileNm);

        result = true;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // SFTP 접속해제
        jw.disconnectSFTP();
    }

    return result;
}

3. JschWrapper.java

package org.tmt.core.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class JschWrapper { // sylee 추가
	private Session jschSession = null;
	private Channel channel = null;
	private ChannelSftp channelSftp = null;
    
	// sylee 추가
	private String serverIp;
	private int serverPort;
	private String user;
	private String password;
    
	// sylee 추가
	public JschWrapper(String serverIp, int serverPort, String user, String password) {
		this.serverIp = serverIp;
		this.serverPort = serverPort;
		this.user = user;
		this.password = password;
	}

	/**
	 * 파일 업로드 uploadFile sylee 코드 수정
	 *
	 * @param strTempFilePath // sylee 추가
	 * @param strFtpDir
	 * @param fileName
	 * @throws Exception
	 */
	public boolean uploadFile(String strTempFilePath, String strFtpDir, String fileName) throws Exception {
		boolean isSuccess = false;

		FileInputStream fis = null;
        
		try {
			// FTP 방식 mkdir
			File fileDir = new File(strFtpDir);
        	
			if(!fileDir.exists()){
//				fileDir.mkdir(); // 만들고자 하는 Folder의 상위 Folder가 존재하지 않을 경우 생성 불가
				fileDir.mkdirs(); // 만들고자 하는 Folder의 상위 Folder가 존재하지 않을 경우 상위 Folder까지 모두 생성
			}
        	
			// SFTP 방식 mkdir => 공유폴더 mkdir 안됨
//			File fileDir = new File(strFtpDir);
//        	
//			if(!fileDir.exists()){
//				String[] pathArray = strFtpDir.split("/");
//        		
//				for (int i = 1; i < pathArray.length; i++) {
//            		
//					try {
//						channelSftp.mkdir(pathArray[i]);
//						channelSftp.cd(pathArray[i]);
//					} catch(Exception e) {
//						channelSftp.cd(pathArray[i]);
//					}
//				}
//			}
        	
			// 대상폴더 이동
			channelSftp.cd(strFtpDir);

			File file = new File(strTempFilePath);
			fis = new FileInputStream(file);
            
//			String sourceFolder = file.getParent();
//			String sourceFileName = file.getName();

			// 파일 업로드
//			channelSftp.put(fis, file.getName());
			channelSftp.put(fis, fileName);
			isSuccess = true;

			System.out.println("File uploaded : " + file.getAbsolutePath() + " => " + strFtpDir + "/" + fileName);
		} catch (Exception e) {
			throw e;
		} finally {
			close(fis);
		}

		return isSuccess;
	}

    /**
     * 파일 다운로드
     *
     * @param remoteFilePath
     * @param localDirPath
     * @param overwrite
     * @return
     * @throws Exception
     */
    public boolean downloadFile(String remoteFilePath, String localDirPath, boolean overwrite) throws Exception {
        if (remoteFilePath == null || remoteFilePath.length() == 0) {
            return false;
        }

        boolean isSuccess = false;

        byte[] buffer = new byte[1024];

        BufferedInputStream bis = null;
        FileOutputStream fos = null;
        BufferedOutputStream bos = null;

        try {
            if (remoteFilePath.indexOf("\\") > -1) {
                remoteFilePath = remoteFilePath.replace("\\", "/");
            }

            String remoteFileName = "";

            // 대상폴더 이동
            int lastSlashIndex = remoteFilePath.lastIndexOf("/");
            if (lastSlashIndex > -1) {
                String cdDir = remoteFilePath.substring(0, lastSlashIndex);
                remoteFileName = remoteFilePath.substring(lastSlashIndex + 1);
                channelSftp.cd(cdDir);
            } else {
                remoteFileName = remoteFilePath;
                channelSftp.cd("/");
            }

            File destFile = new File(localDirPath + File.separator + remoteFileName);
            if (destFile.exists()) {
                if (overwrite) {
                    destFile.delete();
                } else {
                    System.out.println("File Download canceled. File already exists : " + destFile.getAbsolutePath());
                    return false;
                }
            }

            // 파일 다운로드
            bis = new BufferedInputStream(channelSftp.get(remoteFileName));
            fos = new FileOutputStream(destFile);
            bos = new BufferedOutputStream(fos);
            int readCount = 0;
            while ((readCount = bis.read(buffer)) > 0) {
                bos.write(buffer, 0, readCount);
            }

            isSuccess = true;
            System.out.println("File downloaded : " + remoteFilePath + " => " + destFile.getAbsolutePath());

        } catch (Exception e) {
            throw e;

        } finally {
            close(bos);
            close(fos);
            close(bis);
        }

        return isSuccess;
    }

    /**
     * 폴더 생성
     *
     * @param dirPath
     * @param dirName
     * @throws Exception
     */
    public boolean mkdir(String dirPath, String dirName) throws Exception {
        boolean isSuccess = false;

        String destDirPath = dirPath + "/" + dirName;

        boolean destDirExists = false;

        try {
            channelSftp.cd(destDirPath);
            destDirExists = true;

        } catch (Exception e) {
            destDirExists = false;
        }

        if (destDirExists) {
            System.out.println("Folder Creation canceled. Folder already exists : " + destDirPath);
            return false;
        }

        // 대상폴더 이동
        channelSftp.cd(dirPath);

        // 폴더 생성
        channelSftp.mkdir(dirName);
        isSuccess = true;

        System.out.println("Folder created : " + destDirPath);
        return isSuccess;
    }

    /**
     * SFTP 접속하기
     *
     * @return
     * @throws JSchException
     * @throws Exception
     */
    public void connectSFTP(String host, int port, String userName, String password) throws Exception {
        // JSch 객체를 생성
        JSch jsch = new JSch();

        // JSch 세션 객체를 생성 (사용자 이름, 접속할 호스트, 포트 전달)
        jschSession = jsch.getSession(userName, host, port);

        // 패스워드 설정
        jschSession.setPassword(password);

        // 기타설정 적용
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        jschSession.setConfig(config);

        // 접속
        jschSession.connect();

        // sftp 채널 열기
        channel = jschSession.openChannel("sftp");

        // sftp 채널 연결
        channelSftp = (ChannelSftp) channel;
        channelSftp.connect();
    }

    /**
     * SFTP 접속해제
     */
    public void disconnectSFTP() {
        try {
            if (channelSftp != null && channelSftp.isConnected()) {
                channelSftp.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channelSftp = null;
        }

        try {
            if (channel != null && channel.isConnected()) {
                channel.disconnect();
            }
        } catch (Exception e) {
        } finally {
            channel = null;
        }

        try {
            if (jschSession != null && jschSession.isConnected()) {
                jschSession.disconnect();
            }
        } catch (Exception e) {
        } finally {
            jschSession = null;
        }
    }

    /**
     * FileInputStream 객체 닫기
     *
     * @param fis
     */
    private void close(FileInputStream fis) {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (Exception e) {
        } finally {
            fis = null;
        }
    }

    /**
     * BufferedInputStream 객체 닫기
     *
     * @param bis
     */
    private void close(BufferedInputStream bis) {
        try {
            if (bis != null) {
                bis.close();
            }
        } catch (Exception e) {
        } finally {
            bis = null;
        }
    }

    /**
     * FileOutputStream 객체 닫기
     *
     * @param fos
     */
    private void close(FileOutputStream fos) {

        try {
            if (fos != null) {
                fos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (fos != null) {
                fos.close();
            }
        } catch (Exception e) {
        } finally {
            fos = null;
        }
    }

    /**
     * BufferedOutputStream 객체 닫기
     *
     * @param bos
     */
    private void close(BufferedOutputStream bos) {

        try {
            if (bos != null) {
                bos.flush();
            }
        } catch (Exception e) {
        }

        try {
            if (bos != null) {
                bos.close();
            }
        } catch (Exception e) {
        } finally {
            bos = null;
        }
    }
}
import java.util.Stack;

public class Solution {
	
	// 압축 문자열 풀기
	
	public static String solution(String compressed) {
		String answer = ""; // 최종 문자열을 담을 변수
		Stack<Character> charStack = new Stack<>(); // 영단어와 '(' 괄호를 담을 스택
		Stack<Integer> numStack = new Stack<>(); // 숫자를 담을 스택
		String tempStr = ""; // 임시 문자열을 담을 변수
		int tempNum = 0; // 임시 숫자를 담을 변수
		String repeatStr = ""; // 반복 문자열을 담을 변수
		StringBuilder sb = new StringBuilder(); // 문자열을 보다 효율적으로 이어주기 위한 StringBuilder
		
		for (int i = 0; i < compressed.length(); i++) { // compressed 한 글자씩 체크
			
			tempNum = 0; // 초기화
			
			if (compressed.charAt(i) >= '0' && compressed.charAt(i) <= '9') { // 1. 숫자라면
				
				while (compressed.charAt(i) >= '0' && compressed.charAt(i) <= '9') {
					tempNum = tempNum * 10 + compressed.charAt(i) - '0'; // 하나의 연속된 숫자라면 이전 숫자의 단위를 * 10하여 올리고 현재 숫자를 일의 자리로 더함
					i++; // 하나의 연속된 숫자라면 i 증가
				}
				
				i--; // while문 빠져나오기 전 마지막 i++가 있었으므로 다시 i--
				numStack.push(tempNum); // 숫자를 넘버 스택에 담기
			} else if (compressed.charAt(i) == '(') { // 2. 여는 괄호라면
				
				if (compressed.charAt(i - 1) >= '0' && compressed.charAt(i - 1) <= '9') { // 여는 괄호 전에 숫자가 있다면
					charStack.push(compressed.charAt(i)); // 여는 괄호 담기
				} else { // 여는 괄호 전에 숫자가 없다면 이것은 1이 생략된 것
					numStack.push(1); // 1을 넘버 스택에 담기
					charStack.push(compressed.charAt(i)); // 여는 괄호 담기
				}
			} else if (compressed.charAt(i) == ')') { // 3. 닫는 괄호라면
				
				tempStr = ""; // 초기화
				tempNum = 0; // 초기화
				sb.setLength(0); // 초기화
				
				if (!numStack.isEmpty()) {
					tempNum = numStack.pop(); // 가장 마지막에 들어온 숫자 꺼내기
				}
				
				while (!charStack.isEmpty() && charStack.peek() != '(') { // 여는 괄호가 아니면
					tempStr = charStack.pop() + tempStr; // 스택에서 꺼내며 임시 문자열 앞에 붙이기 // hi
				}
				
				if (!charStack.isEmpty() && charStack.peek() == '(') { // 여는 괄호면
					charStack.pop(); // 버리기
				}
				
				for (int j = 0; j < tempNum; j++) {
					sb.append(tempStr); // hihihihihihihihihihi
				}
				
				repeatStr = sb.toString();
				
				for (int k = 0; k < repeatStr.length(); k++) { // hihihihihihihihihihi
					charStack.push(repeatStr.charAt(k));
				}
				
				repeatStr = ""; // 초기화
				
			} else { // 4. 영단어라면
				charStack.push(compressed.charAt(i));
			}
		}
		
		while (!charStack.isEmpty()) {
			answer = charStack.pop() + answer;
		}
		
		return answer;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String compressed = "2(10(hi)co)";
		
		System.out.println(solution(compressed)); // hihihihihihihihihihicohihihihihihihihihihico
	}
}

프로그래머스 압축된 문자열 풀기 문제 풀이 Java 소스 코드

import java.util.HashMap;
import java.util.HashSet;

public class Solution {
	
	// 아이디 추천
	// S(3 ~ 6 길이 영 소문자) + N(0 ~ 6 길이 숫자) 조합의 아이디를 추천한다.
	
	public static String solution(String[] registered_list, String new_id) { // 등록된 아이디 배열, 신규 아이디
		String answer = "";
		int registeredStrEndIdx = 0; // 등록된 아이디의 S + N 조합에서 S의 마지막 인덱스를 담을 변수
		int newStrEndIdx = 0; // 신규 아이디의 S + N 조합에서 S의 마지막 인덱스를 담을 변수
		String tempStr = ""; // S 부분을 담을 변수
		int tempNum = 0; // N 부분을 담을 변수
		int recommendNum = 0; // 신규 아이디가 이미 등록되어 있을 경우 추천할 숫자
        
		HashMap<String, HashSet<Integer>> hmHs = new HashMap<>();
        
		// STEP 1. 등록된 아이디 체크하며 HashMap<String, HashSet<Integer>> 구조로 key와 value 담기
		for (String str : registered_list) { // 등록된 아이디 체크
        	
			for (int i = 0; i < str.length(); i++) {
        		
				if (str.charAt(i) >= '0' && str.charAt(i) <= '9') { // S + N 조합에서 N 시작(숫자 시작) 인덱스 확인
					registeredStrEndIdx = i - 1; // 영 소문자로 구성된 문자열 S의 마지막 인덱스
					break;
				}
			}
        	
			if (registeredStrEndIdx == 0) { // strEndIdx가 그대로라면! 즉, 등록된 이 아이디가 문자열로만 이루어져 있다면(S)
        		
				if (!hmHs.containsKey(str)) { // 이미 key 값으로 갖고있는 문자열 S인지 체크
					hmHs.put(str, new HashSet<Integer>()); // 없다면 문자열 S를 key로 넣고 value값을 담을 HashSet 생성
					hmHs.get(str).add(0); // 최초 card=[0]
				}
			} else { // 등록된 이 아이디가 영 소문자 문자열 + 숫자라면(S + N)
				tempStr = str.substring(0, registeredStrEndIdx + 1); // S
				tempNum = Integer.parseInt(str.substring(registeredStrEndIdx + 1, str.length())); // N
        		
				if (!hmHs.containsKey(tempStr)) { // 이미 key 값으로 갖고있는 문자열 S인지 체크
					hmHs.put(tempStr, new HashSet<Integer>());
					hmHs.get(tempStr).add(tempNum);
				} else {
					hmHs.get(tempStr).add(tempNum);
				}
			}
        	
			registeredStrEndIdx = 0; // 다음 등록된 아이디 확인 전 초기화
		}
        
		// hmHs => {ace=[16, 17, 13, 14], banker=[0], card=[0]}
        
		// STEP 2. 신규 아이디가 사용 가능한지 확인, 사용 불가하다면 아이디 추천받기
		for (int i = 0; i < new_id.length(); i++) {
        	
			if (new_id.charAt(i) >= '0' && new_id.charAt(i) <= '9') { // 숫자 범위라면
				newStrEndIdx = i - 1;
				break;
			}
		}
        
		if (newStrEndIdx == 0) { // newStrEndIdx가 그대로라면! 즉, 신규 아이디가 문자열로만 이루어져 있다면(S)
        	
			if (!hmHs.containsKey(new_id)) { // 신규 아이디이자, 아이디의 S가 등록된 아이디의 key로 존재하지 않아 그대로 사용 가능하다면
				answer = new_id; // 신규 아이디 S를 그대로 사용하도록 함
			} else { // 신규 아이디이자, 아이디의 S가 이미 등록되어 있다면
        		
				HashSet<Integer> tempHs = hmHs.get(new_id); // 뒤에 붙일 숫자 추천을 위해 HashSet 가져오기 // 이 경우 null이 담길 수 없음
        		
				// 1. HashSet은 null 값을 저장할 수 있다. 2. null과 isEmpty는 다르다.

				while(true) { // 숫자 추천을 위해 확인 반복 // 이 경우 신규 아이디가 이미 등록되어 있는 경우이므로 tempHs에는 null이 담겨있을 수 없기 때문에 null인 경우 무시
        			
					if (tempHs.contains(recommendNum)) { // 0부터 체크 // 해당 번호가 있다면
						recommendNum++; // 있다면 1 증가
					} else { // 0부터 체크 // 해당 번호가 없다면
            			
						if (recommendNum == 0) {
							answer = new_id; // S 그대로 사용하도록 함
						} else {
							answer = new_id + Integer.toString(recommendNum); // S + 추천 숫자
						}
						break;
					}
				}
			}
		} else { // 신규 아이디가 영 소문자 문자열 + 숫자라면(S + N)
			tempStr = new_id.substring(0, newStrEndIdx + 1); // S
			tempNum = Integer.parseInt(new_id.substring(newStrEndIdx + 1, new_id.length())); // N
    		
			HashSet<Integer> tempHs = hmHs.get(tempStr); // 없으면 HashSet에 null이 담긴다.
        	
			while(true) { // 숫자 추천을 위해 확인 반복
    			
				if (tempHs == null) { // HashSet은 null 값을 저장할 수 있기 때문에 isEmpty를 써선 안된다. // N을 그대로 사용 가능하다면
					answer = tempStr + Integer.toString(tempNum); // S + N
					break;
				} else {
    				
					if (tempHs.contains(tempNum)) { // 신규 아이디와 숫자가 이미 존재한다면
						tempNum++; // 숫자 1 증가
					} else { // 사용 가능한 숫자라면
						answer = tempStr + Integer.toString(tempNum); // S + N 또는 S + 추천 숫자
						break;
					}
				}
			}
		}
        
		return answer;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String[] registered_list = {"card", "ace13", "ace16", "banker", "ace17", "ace14"}; // 배열 길이 1 ~ 100000
		String new_id = "ace15";
		
		System.out.println(solution(registered_list, new_id)); // ace15 // S(3 ~ 6 길이 영 소문자) + N(0 ~ 6 길이 숫자) 조합의 아이디 추천
	}
}

1. HashSet은 null 값을 저장할 수 있다.

2. null과 isEmpty는 다르다. (null : 인스턴스가 생성되지 않은 상태, isEmpty : 인스턴스는 생성되었으나 비어있는 상태)

 

HashMap<String, HashSet<Integer>> hmHs = new HashMap<>();

HashSet<Integer> testOneHs = hmHs.get("test"); // 이 경우 isEmpty가 아닌 null

 

HashSet<Integer> testTwoHs = new HashSet<Integer>(); // 이 경우 null이 아닌 isEmpty

 

프로그래머스 아이디 추천 문제 풀이 Java 소스 코드

import java.util.*;

public class Solution {
	
	// 애너그램 관계
	
	public static int solution(int[] arr) {
		int answer = 0;
		String tempStr = "";
		HashSet<String> hs = new HashSet<>(); // HashSet 사용
//		HashMap<String, Integer> hm = new HashMap<>(); // HashMap 사용
        
		for (int i = 0; i < arr.length; i++) {
        	
			char[] charArr = Integer.toString(arr[i]).toCharArray();
        	
			Arrays.sort(charArr); // 오름차순 정렬
        	
			tempStr = new String(charArr); // 문자열 생성
        	
			if (!hs.contains(tempStr)) {
				hs.add(tempStr);
				answer++;
			}
        	
//			if (!hm.containsKey(tempStr)) {
//				hm.put(tempStr, 1); // Integer 자리의 경우 1이 아니어도 됨
//				answer++;
//			}
		}
		return answer;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr = {112, 1814, 121, 1481, 1184, 512, 125, 521, 80}; // 단, 자리수는 같아야 함
		
		System.out.println(solution(arr)); // 4 // {112, 121}, {1814, 1481, 1184}, {512, 125, 521}, {80} // 4개의 그룹
	}
}

프로그래머스 애너그램 문제 풀이 Java 소스 코드

public class Solution {
	
	// 등굣길
	
	static int[][] dp = {};
	
	public static int solution(int m, int n, int[][] puddles) {
		int answer = 0;
		
		dp = new int [n][m]; // n행 m열
		
		dp[0][0] = 1; // 집
		
		for (int[] puddle : puddles) {
			dp[puddle[1] - 1][puddle[0] - 1] = -1; // 웅덩이 -1로
		}
		
		for (int i = 0; i < n; i++) { // 행
			
			for (int j = 0; j < m; j++) { // 열
				
				if (dp[i][j] == -1) { // 웅덩이라면
					dp[i][j] = 0; // 0으로 값 변경 후 continue
					continue;
				}
				
				if (i != 0) {
					dp[i][j] += dp[i - 1][j] % 1000000007;
				}
				
				if (j != 0) {
					dp[i][j] += dp[i][j - 1] % 1000000007;
				}
				
				// dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
			}
		}
		
		answer = dp[n - 1][m - 1] % 1000000007;
		
		return answer;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int m = 4; // 열
		int n = 3; // 행
		int[][] puddles = {{2, 2}}; // 웅덩이 좌표 // 열, 행
		
		// (1, 1)에서 (4, 3)까지 최단경로 개수를 1,000,000,007로 나눈 나머지 리턴
		// 무조건 오른쪽 또는 아래쪽으로만 이동 가능 -> 최단 거리가 m + n - 3이라는 것도 알 수 있다.
		System.out.println(solution(m, n, puddles)); // 4
	}
}

 

 

프로그래머스 등굣길 문제 풀이 Java 소스 코드

import java.util.*;

public class Solution {
	
	// 단속카메라
	
	public static boolean intersactionCheck(int[] A, int[] B) { // 배열 A와 B가 교집합을 갖는지 확인
		int startA = A[0];
		int endA = A[1];
		int startB = B[0];
		int endB = B[1];
		int startPoint = 0;
		int endPoint = 0;
		
		startPoint = startA <= startB ? startB : startA; // 진입 지점은 둘 중 더 큰 지점
		endPoint = endA <= endB ? endA : endB; // 진출 지점은 둘 중 더 작은 지점
		
		if (startPoint <= endPoint) {
			return true;
		} else {
			return false;
		}
	}
	
	public static int solution(int[][] routes) {
		int answer = 0;
		
		Arrays.sort(routes, new Comparator<int[]>() {
			@Override
			public int compare(int[] o1, int[] o2) {
				// TODO Auto-generated method stub
				return o1[1] - o2[1]; // 진출 지점 기준 오름차순 정렬
			}
		});
		
		int cam = Integer.MIN_VALUE;
		
		for (int[] route : routes) { // 진출 지점 기준 오름차순 정렬된 상태
			
			if (cam < route[0]) { // 최초 무조건 if문에 걸리게 됨 // 다음 차량의 진입 지점이 카메라 위치보다 전이라면 설치할 필요 없고, 만약 설치한 카메라 이후 진입했다면 다시 설치를 위해 if문
				cam = route[1]; // 가장 작은 진출 지점에 단속용 카메라 설치 // 다음 차량의 진출 지점에 카메라 설치
				System.out.println(cam + "번 지점 단속용 카메라 설치");
				answer++; // 카메라 + 1
			}
		}
		
		return answer;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[][] routes = {{-20, -15}, {-14, -5}, {-18, -13}, {-5, -3}};
		
		System.out.println(solution(routes)); // 2 // -15, -5 설치
	}
}

프로그래머스 단속카메라 문제 풀이 Java 소스 코드

+ Recent posts