1. 백준 치킨배달[15686]

도시가 주어지고, 도시에는 집과 치킨가게 가 존재한다. 총 m 개의 치킨가게 가 존재할 때 도시에 주어지는 집들로부터 거리가 가장 작은 최솟값의 거리를 구하는 문제이다. 

치킨가게 와 집의 거리계산 은 주어지는 좌표 의 차에서 구한 거리이다.

 

해당 문제 는 주어지는 배열을 사용하지 않고, 치킨집과 집들을 가지고 새로운 거리 배열을 만들어서 해결해 주었다. 

다시 말해 행, 열 에는 치킨집과 집들을 배치하고, 해당 거리들을 배열에 담아 해결했다.

 

func boj15686() {
	answer := 1 << 31
	var distance [][]int
	abs := func(a int) int {
		if a < 0 {
			return -a
		}
		return a
	}
	min := func(a, b int) int {
		if a < b {
			return a
		}
		return b
	}

	minValue := func(include []int, distance [][]int) int {
		result := 0
		for i := 0; i < len(distance); i++ {
			step := 1 << 31
			for j := 0; j < len(distance[i]); j++ {
				for k := 0; k < len(include); k++ {
					if j == include[k] {
						step = min(step, distance[i][j])
					}
				}
			}
			result += step
		}
		return result
	}

	helper := func(num, depth, limit, flag, start int, out []int) {}
	helper = func(num, depth, limit, flag, start int, out []int) {
		if depth == limit {
			answer = min(answer, minValue(out, distance))
			return
		} else {
			for i := start; i < num; i++ {
				if flag&(1<<i) == 0 {
					helper(num, depth+1, limit, flag|(1<<i), i+1, append(out, i))
				}
			}
		}
	}
	reader := bufio.NewReader(os.Stdin)

	var (
		a, b int
	)
	fmt.Fscanln(reader, &a, &b)
	house := make([][]int, 0)
	chicken := make([][]int, 0)

	for i := 0; i < a; i++ {
		for j := 0; j < a; j++ {
			var tmp int
			fmt.Fscan(reader, &tmp)
			if tmp == 1 {
				house = append(house, []int{i, j})
			} else if tmp == 2 {
				chicken = append(chicken, []int{i, j})
			}
		}
	}

	distance = make([][]int, len(house))
	for i := 0; i < len(house); i++ {
		distance[i] = make([]int, len(chicken))
		for j := 0; j < len(chicken); j++ {
			distance[i][j] = abs(house[i][0]-chicken[j][0]) + abs(house[i][1]-chicken[j][1])
		}
	}

	/**
치킨집의 개수가 최대 13개이므로, 치킨집의 개수에서 M개를 뽑는 조합을 구하면 된다. (13C6) = 1716 * 집이 최대 50개이므로, 1716 * 50 = 85800
	*/
	helper(len(chicken), 0, b, 0, 0, []int{})

	fmt.Println(answer)
}

고에서 제공하는 min, abs는 타입변환이 필요해서 추가적으로 함수를 작성해 주었다.

치킨집, 일반집 은 각각 1,2의 값으로 주어지기 때문에 입력값을 받아옴과 동시에 해당 좌표들을 배열로 만들어주고, 

거리를 구하기 위해서 위에서 작성된 자표를 이용해 모든 집과 모든 치킨집의 대한 거리에 대해 배열을 작성해 주었다.

 

2. 백준 테트로미노 [14500]

왼쪽에 보이는 모양을 맵에다가 놓았을 때 최댓값을 구해야 한다. 단 주어지는 저 도형들은 회전, 대칭 등이 가능하다. 

 

코드가 엄청 길다. 해당되는 상황에 대해 모두 분기를 나눠주다 보니 아무래도 그게 더 심했던 거 같다.

 

코드보다 그림을 더 많이 그렸던 문제...

 

func solution() {
	reader := bufio.NewReader(os.Stdin)
	getMax := func(a, b int) int {
		if a > b {
			return a
		}
		return b
	}
	stick := func(arr [][]int, row, col int) (rs int) {
		//right 3steps more
		if col+3 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row][col+2]+arr[row][col+3])
		}
		//down 3steps more
		if row+3 < len(arr) {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+2][col]+arr[row+3][col])
		}
		return rs
	}
	square := func(arr [][]int, row, col int) (rs int) {
		// right 1step down 1step
		if row+1 < len(arr) && col+1 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row+1][col]+arr[row+1][col+1])
		}
		return rs
	}
	nieun := func(arr [][]int, row, col int) (rs int) {
		// right 2steps down 1step
		if row+1 < len(arr) && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+1][col+1]+arr[row+1][col+2])
		}
		// left 2steps down 1step
		if row+1 < len(arr) && col-2 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+1][col-1]+arr[row+1][col-2])
		}
		// top 1step right 2steps
		if row-1 >= 0 && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row-1][col]+arr[row-1][col+1]+arr[row-1][col+2])
		}
		// top 1step left 2steps
		if row-1 >= 0 && col-2 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row-1][col]+arr[row-1][col-1]+arr[row-1][col-2])
		}

		//right 1step down 2steps
		if row+2 < len(arr) && col+1 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row+1][col+1]+arr[row+2][col+1])
		}
		//right 1step top 2steps
		if row-2 >= 0 && col+1 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row-1][col+1]+arr[row-2][col+1])
		}
		// left 1step down 2steps
		if row+2 < len(arr) && col-1 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row][col-1]+arr[row+1][col-1]+arr[row+2][col-1])
		}
		// left 1step top 2steps
		if row-2 >= 0 && col-1 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row][col-1]+arr[row-1][col-1]+arr[row-2][col-1])
		}
		return rs
	}
	triangle := func(arr [][]int, row, col int) (rs int) {
		// right 2steps up 1step
		if row-1 >= 0 && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row][col+2]+arr[row-1][col+1])
		}
		//right 2steps down 1step
		if row+1 < len(arr) && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row][col+2]+arr[row+1][col+1])
		}

		// down 2steps right 1step
		if row+2 < len(arr) && col+1 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+2][col]+arr[row+1][col+1])
		}
		// down 2steps left 1step
		if row+2 < len(arr) && col-1 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+2][col]+arr[row+1][col-1])
		}
		return rs
	}
	etc := func(arr [][]int, row, col int) (rs int) {
		// right 2steps up 1step
		if row-1 >= 0 && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row-1][col+1]+arr[row-1][col+2])
		}
		//right 2steps down 1step
		if row+1 < len(arr) && col+2 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row][col+1]+arr[row+1][col+1]+arr[row+1][col+2])
		}

		//down 2steps right 1step
		if row+2 < len(arr) && col+1 < len(arr[0]) {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+1][col+1]+arr[row+2][col+1])
		}
		//down2steps left 1step
		if row+2 < len(arr) && col-1 >= 0 {
			rs = getMax(rs, arr[row][col]+arr[row+1][col]+arr[row+1][col-1]+arr[row+2][col-1])
		}
		return rs
	}

	check := func(arr [][]int, row, col int) int {
		result := getMax(square(arr, row, col), stick(arr, row, col))
		result2 := getMax(nieun(arr, row, col), triangle(arr, row, col))
		return getMax(result, getMax(result2, etc(arr, row, col)))
	}

	var answer int
	helper := func(arr [][]int, size int) {}
	helper = func(arr [][]int, size int) {
		if size == len(arr[0])*len(arr) {
			return
		}
		row, col := size/len(arr[0]), size%len(arr[0])
		answer = getMax(answer, check(arr, row, col))
		helper(arr, size+1)
	}

	var n, m int

	fmt.Fscanln(reader, &n, &m)

	arr := make([][]int, n)
	for i := range arr {
		arr[i] = make([]int, m)
		for j := range arr[i] {
			fmt.Fscan(reader, &arr[i][j])
		}
	}

	helper(arr, 0)
	fmt.Println(answer)
}

 

 

단순 코드가 길어서 그렇지 해당되는 모든 케이스에 대해서 잘 정리만 해준다면 일반적인 dfs처럼 좌표의 1번부터 수행하면 된다.

개인적으로 이렇게 좌표탐색을 할 때는 row, col을 넘겨주는거보다 하나의 숫자를 넘겨 row,col 을 계산하는 것이 훨씬 깔끔하다. 예전 스도쿠 문제 풀면서 다른 사람 풀이 중에 이런 부분이 있어 바로 차용해서 이제는 저렇게 숫자로 넘기는 게 더 편하다.

 

이번주는 생각보다 문제를 많이 풀지 못했다. 아침에 가면 문의 답변 해야 할게 생각보다 있어 오전시간에는 거의 풀지 못했다.

 

다만 지난주보다 마음에 들었던 점은 해당 두 문제에 대해서 바로 한 번에 통과해서 기분이 무척 좋았다.

 

'PS > Boj' 카테고리의 다른 글

백준-완탐 연습(Brute-Force)  (0) 2023.08.14
백준 1251 단어나누기[Java]  (0) 2022.06.26
백준 1120 문자열[Java]  (0) 2022.06.26
백준 10448 유레카[Java]  (0) 2022.06.26

쉬는 날이기에 근 1~2주 동안 일찍 출근해서 풀었던 문제와 못 푼 문제에 대해 정리해보고자 한다.
물론 다시풀어보면서 작성해보고자 한다.

1. 백준 일곱 난쟁이 [2309]

아홉 개의 숫자를 주고 그중에서 일곱 난쟁이를 찾는 문제이다. 이중 일곱 난쟁이의 키의 합은 100이라는 전제조건이 붙으며 정답이 여러 개 존재할 경우 아무거나 하나출력하면 된다.

func solution() {
	reader := bufio.NewReader(os.Stdin)

	arr := make([]int, 9)
	for i := range arr {
		var a int
		fmt.Fscanln(reader, &a)
		arr[i] = a
	}

	sort.Ints(arr)
	btc := func(arr, rs []int, idx, sum int) {}

	btc = func(arr, rs []int, idx, sum int) {
		if len(rs) > 7 || sum >= 100 || idx >= len(arr) {
			if sum == 100 {
				for _, v := range rs {
					fmt.Println(v)
				}
				os.Exit(0)
			}
		} else {
			btc(arr, append(rs, arr[idx]), idx+1, sum+arr[idx])
			btc(arr, rs, idx+1, sum)
		}
	}

	btc(arr, []int{}, 0, 0)
}

백트리킹 방식을 사용했다. 난쟁이를 선택한 경우와 그렇지 않은 경우

최초 정렬을 수행하고 다음 항목을 진행하는데 정답의 출력이 오름차순 출력이기에 최초 입력값 자체를 정렬해 주었다.

 

이 방법 외에도 간단하게 이중 포문을 돌려서 하나씩 제거하는 방법도 존재한다.

func solution() {
	reader := bufio.NewReader(os.Stdin)

	arr := make([]int, 9)
	var (
		total, x, y int
	)
	for i := range arr {
		var a int
		fmt.Fscanln(reader, &a)
		arr[i] = a
		total += a
	}

	sort.Ints(arr)
Loop:
	for i := 0; i < len(arr); i++ {
		for j := i + 1; j < len(arr); j++ {
			if total-(arr[i]+arr[j]) == 100 {
				x, y = i, j
				break Loop
			}
		}
	}

	for i := range arr {
		if i != x && i != y {
			fmt.Println(arr[i])
		}
	}
}

이렇게 수를 받아오면서 전체 수의 값을 계산하고 이중포문으로 하나씩 날려주면 된다. 

 

2. 백준 로또 [6603]
1~49의 숫자 중 6개를 고르는 것이다 단 집합 S를 만들고 해당 수의 번호만 선택하는 전략을 가져간다.

해당 부분집합은 총 6~13 개의 숫자로 되어있으며, 입력마지막 줄에는 0이 하나 주어진다.

 

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
)

func main() {
	solution()
}

func solution() {
	reader := bufio.NewReader(os.Stdin)
	answer := strings.Builder{}

	bitmask := func(arr, rs []string, flag, start int) {}
	bitmask = func(arr, rs []string, flag, start int) {
		if len(rs) >= 6 {
			ans := ""
			for _, v := range rs {
				ans += v + " "
			}
			answer.WriteString(ans + "\n")
			return
		} else {
			for i := start; i < len(arr); i++ {
				if flag&(1<<i) != 0 {
					continue
				}
				bitmask(arr, append(rs, arr[i]), flag|(1<<i), i+1)
			}
		}
	}

	for {
		line, _ := reader.ReadString('\n')
		if line[0] == '0' {
			break
		}
		input := strings.TrimSpace(line)
		target := strings.Split(input, " ")[1:]
		var rs []string
		bitmask(target, rs, 0, 0)
	}
	fmt.Println(answer.String())
}

해당 문제는 비트마스크를 이용해 해결했다. 여기서 비트마스크는 & | 연산을 활용하는 방법이다. 

최초 0의 플래그를 가지고 이동하며 플래그 즉 이진수의 각 자릿수는 배열의 방문을 표기한다. 

 

예를 들어 0의 플래그가 있고 최초방문 0의 자리를 방문한다고 했을 때 0 | (1<<0) 1을 0만큼 쉬프트 한다.  즉 1과 0을 or 연산을 하게 되고플 래그는 0000001의 값을 가진다. 이룰 활용한 방법이다. 

나의 맥북은 64비트 메모리를 사용하고 있으니 int는 int64 고되고 이는 총 64개의 방문배열을 처리할 수 있음을 의미하니 마음 놓고 사용해도 된다.

 

지난번도 그렇고 이번에 다시 풀 때도 그렇고 input을 읽어올 때 "\n" 읽어오고 trimspace를 안 해 자꾸 출력값이 이상해서 오답을 받았다.

ㅎㅎ....

 

3. 부분수열의 합 [1182]

총 20 개의 숫자와 하나의 숫자가 주어지고 몇몇의 숫자들을 선택해 합을 구해 주어진 하나의 숫자와 같은 경우의 개수를 출력해야 한다.

여기서 부분수열의 합이라서 연속된 배열의 부분수열의 합이라고 생각할 수 있다. 그게 아닌 특정 숫자들을 선택해 더하는 것이다.

func solution() {
	reader := bufio.NewReader(os.Stdin)

	var (
		n, answer, sum int
	)

	fmt.Fscanln(reader, &n, &sum)

	arr := make([]int, n)
	for i := range arr {
		var a int
		fmt.Fscan(reader, &a)
		arr[i] = a
	}

	find := func(arr []int, total, idx, flag int) {}
	find = func(arr []int, total, idx, flag int) {
		if idx >= len(arr) {
			if total == sum && flag > 0 {
				answer++
			}
			return
		} else {
			find(arr, total+arr[idx], idx+1, flag|(1<<idx))
			find(arr, total, idx+1, flag)
		}

	}
	find(arr, 0, 0, 0)
	fmt.Println(answer)
}

1번 백설공주  와 마찬가지로 숫자를 선택 혹은 넘기는 쪽을 선택했다. flag를 넣어준 이유는 총합이 0 이 되는 경우 answer는 0으로 초기화되기 때문에 최소 고른 횟수를 넣어주기 위해 위와 같이 넣어주었다.

 

4. 차이를 최대로 [10819] 

범위 또한 3~8로 순열 완탐에 최적화된 전형적인 순열 문제이다. 각 배열을 바꿔서 차이를 최대로 만들면 되기 때문에 순열을 이용해서 재배치 후 계산을 해주면 된다. 

func solution() {
	reader := bufio.NewReader(os.Stdin)
	abs := func(a int) int {
		if a < 0 {
			return -a
		}
		return a
	}
	cal := func(arr []int) (total int) {
		for i := 1; i < len(arr); i++ {
			total += abs(arr[i] - arr[i-1])
		}
		return total
	}

	var (
		n, answer int
	)

	fmt.Fscanln(reader, &n)

	arr := make([]int, n)
	for i := range arr {
		var a int
		fmt.Fscan(reader, &a)
		arr[i] = a
	}
	answer = cal(arr)

	recur := func(arr, rs []int, depth, flag int) {}
	recur = func(arr, rs []int, depth, flag int) {
		if depth >= len(arr) {
			sum := cal(rs)
			if answer < sum {
				answer = sum
			}
			return
		} else {
			for i := 0; i < len(arr); i++ {
				if flag&(1<<i) == 0 {
					recur(arr, append(rs, arr[i]), depth+1, flag|(1<<i))
				}
			}
		}
	}
	recur(arr, []int{}, 0, 0)
	fmt.Println(answer)
}

단순히 비트마스크를 이용해서 순열을 조회해서 다음으로 넘겨준다. 이게 전부다.

 

5. 스도쿠 [2580] 

예전에 풀어봤던 문제이다. 확실히 한번 풀어봐서인지 나름 손쉽게 풀 수 있었다.
(https://guiwoo.tistory.com/3 - 자바풀이)

백트리킹의 조건과 패스 될 수 있는 수의 경우만 명확하게 해 주면 된다.

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	solution()
}

func solution() {
	reader := bufio.NewReader(os.Stdin)
	arr := make([][]int, 9)
	for i := range arr {
		sub := make([]int, 9)
		for j := range sub {
			var a int
			fmt.Fscan(reader, &a)
			sub[j] = a
		}
		arr[i] = sub
	}
	isPossible := func(arr [][]int, num, row, col int) bool {
		//가로줄 체크
		for i := 0; i < 9; i++ {
			if arr[row][i] == num {
				return false
			}
		}

		//세로줄 체크
		for i := 0; i < 9; i++ {
			if arr[i][col] == num {
				return false
			}
		}

		// 3*3 체크
		row = (row / 3) * 3
		col = (col / 3) * 3
		for i := row; i < row+3; i++ {
			for j := col; j < col+3; j++ {
				if arr[i][j] == num {
					return false
				}
			}
		}
		return true
	}

	dfs := func(arr [][]int, cal int) {}
	dfs = func(arr [][]int, cal int) {
		if cal >= 81 {
			for i := range arr {
				for j := range arr[i] {
					fmt.Printf("%d ", arr[i][j])
				}
				fmt.Println()
			}
			os.Exit(0)
		}
		row := cal / 9
		col := cal % 9
		if arr[row][col] != 0 {
			dfs(arr, cal+1)
		} else {
			for i := 1; i <= 9; i++ {
				if isPossible(arr, i, row, col) {
					arr[row][col] = i
					dfs(arr, cal+1)
					arr[row][col] = 0
				}
			}
		}
	}
	dfs(arr, 0)
}

이문제는 row, col을 하나의 숫자로 넘겨서 계산해 주는 게 보다 손쉽게 계산할 수 있다. 

 

6. 단어나누기 [1251]

알파벳 소문자 가 주어지고 단어를 임의의 두 부분을 골라서 단어를 쪼개고 나눠진 단어들을 뒤집어서 다시 재조합해 사전에서 찾을 수 있는 가장 앞에 단어를 출력해야 한다.

단 나눠진 단어의 길이는 1 이상인 단어여야만 한다.

 

포인트는 임의의 두 부분을 골라서 단어를 나눈다는 것이다.

바로 슬라이스를 사용하면 된다. 예를 들어보자 "abcdefg"와 같은 단어가 있다면? 

func Test_StringSplit(t *testing.T) {
	a := "abcdefg"
	fmt.Println(a[:1]) // a
	fmt.Println(a[1:3]) // bc
	fmt.Println(a[3:]) // defg
	fmt.Println(a[7:]) // 
}

 

단어의 특정 부분을 고르는 것이 아닌 모든 부분을 사용해야 하기 때문에 딱 2개의 숫자만 고르면 된다. 단 길이가 7이라면 6까지 골라야 한다. 위에서 언급한 나눠진 단어는 길이가 최소 1 이상이어야 하기 때문에

func solution() {
	reader := bufio.NewReader(os.Stdin)
	reverse := func(s string) (ans string) {
		for i := len(s) - 1; i >= 0; i-- {
			ans += string(s[i])
		}
		return ans
	}
	strCompare := func(a string, b string, n []int) string {
		b1 := reverse(b[:n[0]])
		b2 := reverse(b[n[0]:n[1]])
		b3 := reverse(b[n[1]:])
		if a > (b1 + b2 + b3) {
			return b1 + b2 + b3
		}
		return a
	}

	var input string
	answer := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
	fmt.Fscanln(reader, &input)

	recur := func(n int, rs []int, start, flag int) {}
	recur = func(n int, rs []int, start, flag int) {
		if len(rs) == 2 {
			answer = strCompare(answer, input, rs)
			return
		} else {
			for i := start; i < n; i++ {
				if flag&(1<<i) == 0 {
					recur(n, append(rs, i), i+1, flag|(1<<i))
				}
			}
		}
	}
	recur(len(input), []int{}, 1, 0)
	fmt.Println(answer)
}

string을 reverse 해주는 함수가 없어 따로 작성했다(그립다 java stringBuilder) answer 같은 경우는 최대 50 개의 단어가 주어지기 때문에 위와 같이 초기화시켜주었으며 조합을 응용해서 작성했다.

 

7. 부등호 [2529]

<,>  두 종류 의 부등호가 주어지고 주어진 부등호의 관계를 만족시켜야 한다는 조건이 붙는다.

부등호 앞뒤로 넣을 수 있는 숫자는 0~9까지이며 숫자의 선택은 한 번씩 만 가능하다.

k의 범위가 2~9까지 이기 때문에 나올 수 있는 숫자는 총 10자리이며 0 또한 표기해주어야 하기 때문에 스트링으로 처음부터 표기하면서 넘어가자.

func solution() {
	reader := bufio.NewReader(os.Stdin)
	var a int
	fmt.Fscanln(reader, &a)
	arr := make([]string, a)
	for i := range arr {
		var b string
		fmt.Fscan(reader, &b)
		arr[i] = b
	}

	min, max := "9999999999", "0"

	recur := func(rs []string, depth, flag int) {}
	recur = func(rs []string, depth, flag int) {
		if len(rs) >= a+1 {
			result := strings.Join(rs, "")
			if min > result {
				min = result
			}
			if max < result {
				max = result
			}
			return
		} else {
			for i := 0; i <= 9; i++ {
				if depth == 0 {
					recur(append(rs, fmt.Sprintf("%d", i)), depth+1, flag|(1<<i))
				} else {
					if flag&(1<<i) == 0 {
						next := fmt.Sprintf("%d", i)
						switch arr[depth-1] {
						case "<":
							if rs[depth-1] < next {
								recur(append(rs, next), depth+1, flag|(1<<i))
							}
						default:
							if rs[depth-1] > next {
								recur(append(rs, next), depth+1, flag|(1<<i))
							}
						}
					}
				}
			}
		}
	}
	recur([]string{}, 0, 0)
	fmt.Printf("%s\n%s", max, min)
}

최소 최댓값을 선정을 최초 그냥 스트링으로 지정하였다. 이후 기존 백트래킹 방식과 유사하게 작성하였다. 

else 구문에는 switch 구문을 이요해 케이스의 분기를 나누어 주었다. 요즘 select 구문을 자주 사용하다 보니 switch 문이 조금 더 편한 기분이기도 하다. 

 

8. 연산자 끼워넣기 [14888]

숫자 2~11개 가 주어지고 연산자는 주어진 숫자-1 만큼 주어진다. 이에 따라 숫자의 최댓값, 최솟값을 구하라는 문제이다. 

자릿수가 최대 여도 11자리 부호연산자는 10개 총 필요한 연산자는 10! 이 될듯하기 때문에 완탐으로 때려도 된다.

 

위의 대부분의 완탐의 경우에는 주어지는 숫자에 대해 재귀를 돌았다면 이번에는 정해진 operation에 의해 재귀를 돌면 된다.

func solution() {
	reader := bufio.NewReader(os.Stdin)

	var a int
	fmt.Fscanln(reader, &a)

	arr := make([]int, a)
	for i := range arr {
		fmt.Fscan(reader, &a)
		arr[i] = a
	}
	oper := make([]int, 4)
	for i := range oper {
		fmt.Fscan(reader, &a)
		oper[i] = a
	}
	min, max := 1<<31, -(1 << 31)

	recur := func(idx, sum int) {}
	recur = func(idx, sum int) {
		if idx == len(arr) {
			if min > sum {
				min = sum
			}
			if max < sum {
				max = sum
			}
			return
		} else if idx == 0 {
			recur(idx+1, arr[idx])
		} else {
			for i := 0; i < len(oper); i++ {
				if oper[i] > 0 {
					switch i {
					case 0:
						oper[i]--
						recur(idx+1, sum+arr[idx])
						oper[i]++
					case 1:
						oper[i]--
						recur(idx+1, sum-arr[idx])
						oper[i]++
					case 2:
						oper[i]--
						recur(idx+1, sum*arr[idx])
						oper[i]++
					default:
						oper[i]--
						recur(idx+1, sum/arr[idx])
						oper[i]++
					}
				}
			}
		}
	}
	recur(0, 0)
	fmt.Printf("%d\n%d", max, min)
}

연산자의 개수가 0 이상일 때만 switch 문의 재귀를 타도록 만들었으며 재귀호출 이후에는 다시 ++ 를 해주어 모든 경우에 대한 탐색 을 하고 마지막 수의 값을 min, max에 따라 가져간다.

 

9. 라그랑주의 네 제곱수 정리 [3933]

못 푼 문제이다.ㅠ  시간초과 의 벽을 어쩌지 못하고 주어지는 숫자 n에 대하여 최대 4개의 자연수의 제곱의 합이 주어지는 숫자 n과 일치하는 여부를 찾는 문제이다.

기존방식과 동일하게 백트래킹 을 사용했으나 실패했다. 시간초과로.... 그래서 방문한 부분에 대해 처리를 해주려고 

visit [][][] 이렇게 사용했다가 메모리 플로우로 터져버렸다.

풀이를 참조해 보니 그냥 4중 for 문 돌리던가 아니면 dp를 사용한다.

func solution() {
	reader := bufio.NewReader(os.Stdin)
	ans := strings.Builder{}

	getAnswer := func(n int) int {
		var cnt int
		for i := 1; i*i <= n; i++ {
			if i*i == n {
				cnt++
				continue
			} else if i*i > n {
				break
			}
			for j := i; i*i+j*j <= n; j++ {
				if i*i+j*j == n {
					cnt++
					continue
				} else if i*i+j*j > n {
					break
				}
				for k := j; i*i+j*j+k*k <= n; k++ {
					if i*i+j*j+k*k == n {
						cnt++
						continue
					} else if i*i+j*j+k*k > n {
						break
					}
					for l := k; i*i+j*j+k*k+l*l <= n; l++ {
						if i*i+j*j+k*k+l*l == n {
							cnt++
							continue
						} else if i*i+j*j+k*k+l*l > n {
							break
						}
					}
				}
			}

		}
		return cnt
	}
	for {
		var n int
		fmt.Fscanln(reader, &n)
		if n == 0 {
			break
		}
		ans.WriteString(strconv.Itoa(getAnswer(n)) + "\n")
	}

	fmt.Println(ans.String())
}

디피로 푸는 방법도 있다.

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	//fmt.Println(1 << 15)
	solution()
}

func solution() {
	reader := bufio.NewReader(os.Stdin)
	ans := strings.Builder{}
	var dp [32768][5]int
	for i := 1; i*i < 32768; i++ {
		dp[i*i][1] = 1
		for j := i * i; j < 32768; j++ {
			dp[j][2] += dp[j-i*i][1]
			dp[j][3] += dp[j-i*i][2]
			dp[j][4] += dp[j-i*i][3]
		}
	}

	getAnswer := func(n int) int {

		var cnt int
		for i := 0; i < 5; i++ {
			cnt += dp[n][i]
		}
		return cnt
	}
	for {
		var n int
		fmt.Fscanln(reader, &n)
		if n == 0 {
			break
		}
		ans.WriteString(strconv.Itoa(getAnswer(n)) + "\n")
	}

	fmt.Println(ans.String())
}

이 테이블 dp [i][j]은 “수 i를 j개의 제곱수의 합으로 표현하는 방법의 수”를 저장한다.

초기에 i*i (제곱수 자체)는 i*i를 1개의 제곱수로 표현하는 방법이므로 dp [i*i][1]=1로 설정되고.
dp를 채우는 이 반복문에서, j는 새로운 합을 나타내는 인덱스이고 i는 제곱수를 나타냅니다. j에 대해 반복할 때마다 i의 제곱값을 현재의 j에 뺀 값 (j-i*i)에 대한 DP 값을 가지고 온 후, j*(i*i)에 해당하는 값을 경신합니다. 이렇게 하면, 각 j에 대해 최대 j 개의 제곱수로 수를 표현하는 방법의 수를 누적하여 저장할 수 있다.
즉, dp [j][2]는 j를 2개의 제곱수의 합으로 표현하는 경우의 수를 나타내며, dp [j][3]과 dp [j][4] 역시 동일한 방식으로 계산된다.

 

현실적으로 이 방법에 대해 어렴풋이 느낌만 있었지 구현하라 그러면 못하지 않았을까 싶다... 

 

10. 스타트와 링크 [14889]

arr로 각  번호들의 능력치를 입력 값으로 주고 arr [i][j] + arr [j][i]는 같은 팀이 된다면 앞에와 같은 힘을 낸다 따라서 조합으로 팀원을 2개의 팀으로 구분 짓고 각각의 팀의 능력치의 최소 값을 찾아주면 된다. 

한 명의 플레이어가 선택된다면 그건 가로 세로 다 선수의 능력치를 사용할 수 없다 다시 말해 저 2차원 배열은 1차원 배열로 다시 나열될 수 있다.

위 그림에서 해당 인원들의 능력치를 다시 1차원 배열로 변경한다면 

[6 15 10 12] [14 6 12 11]  이렇게 변경될 수 있다. 이  모든 수의 합은 43이 되고 이는 모두 중복되는 값이다. 여기서 1,4를 선택한다고 하면 1(6+14), 4(12+11) 이 들어가고 total에서 빼주면 반대편 값이 나온다.

이해가 잘 안 된다면 풀어서 작성해 보자 중복된 값들의 덧셈이 존재해 이것이 가능하다.

 

1번 (6(1+2+3) + 14(4+7+3)) 4번 (12(3+4+5) + 11(3+6+2))

2번 (15(4+5+6) + 6(1+1+4)) 3번 (10(7+1+2) + 12(2+5+5))

 

위에 보이는 것처럼 중복된 값들이 계산되기 때문에 모두 더한 후 total에서 빼주게 되면 값이 일치하게 된다.

 

func solution() {
	reader := bufio.NewReader(os.Stdin)
	abs := func(a int) int {
		if a < 0 {
			return -a
		}
		return a
	}

	var a int
	fmt.Fscanln(reader, &a)

	arr := make([][]int, a)
	row := make([]int, a)
	col := make([]int, a)
	total := 0
	for i := range arr {
		sub := make([]int, a)
		for j := range sub {
			var b int
			fmt.Fscan(reader, &b)
			sub[j] = b
			total += b
			row[i] += b
			col[j] += b
		}
		arr[i] = sub
	}
	min := 1 << 31
	recur := func(start, depth, sum, flag int) {}
	recur = func(start, depth, sum, flag int) {
		if depth == a/2 {
			target := abs(total - sum)
			if target < min {
				min = target
			}
			return
		} else {
			for i := start; i < a; i++ {
				if flag&(1<<i) == 0 {
					recur(i+1, depth+1, sum+row[i]+col[i], flag|(1<<i))
				}
			}
		}
	}
	recur(0, 0, 0, 0)
	fmt.Println(min)
}

사실 해당 문제의 다른 버전을 친구에게 제공받아 풀었지만 실패하고 이 문제를 찾아서 풀게 되었다.

 

11. 링크와 스타트 [15661]

해당 문제는 위에서 언급한 것처럼 해결하지 못했다.

위와 동일한 조건이지만 단 팀이 무조건 공평한 숫자가 아닌 최소 한 명 이상만 있으면 팀의 조건을 만족하게 된다.

위에서 제시한 조건을 그대로 이용해서 사용한다면?

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	//fmt.Println(1 << 15)
	solution()
}

func solution() {
	reader := bufio.NewReader(os.Stdin)
	abs := func(a int) int {
		if a < 0 {
			return -a
		}
		return a
	}
	min := func(a, b int) int {
		if a < b {
			return a
		}
		return b
	}

	var a int
	fmt.Fscanln(reader, &a)

	arr := make([][]int, a)
	row := make([]int, a)
	col := make([]int, a)
	total := 0
	for i := range arr {
		sub := make([]int, a)
		for j := range sub {
			var b int
			fmt.Fscan(reader, &b)
			total += b
			row[i] += b
			col[j] += b
		}
	}

	res := 1 << 31
	recur := func(idx, cur int) {}
	recur = func(idx, cur int) {
		res = min(res, abs(cur))
		for i := idx + 1; i < a; i++ {
			recur(i, cur-row[i]-col[i])
		}
	}

	recur(0, total)
	fmt.Println(res)
}

계산된 total 값을 이용해 한 칸씩 없애주면 된다. 오셀로 하듯이

 

나는 완탐을 잘못하기 때문에 이렇게 하루 쉬는날을 이용해서 풀었던 완탐을 정리해 봤다.

또 하루 날 잡고 완탐에 대해 다시 풀어보고자 한다.

완벽하게 완탕이 될 때까지 해보고 싶다.

 

'PS > Boj' 카테고리의 다른 글

백준-완탐 연습(Brute-Force)-2  (0) 2023.08.20
백준 1251 단어나누기[Java]  (0) 2022.06.26
백준 1120 문자열[Java]  (0) 2022.06.26
백준 10448 유레카[Java]  (0) 2022.06.26

144. Binary Tree Preorder Traversal

 

비교적 쉬운 문제이다. 트리노드 의 트리 형태의 자료구조 가 주어지면 이걸 리스트에 담아 리턴하면 된다. 

순회의 방법은 전위순회 중위순회 후위순회가 있고 이 문제에서 요구하는 바는 전위순회 이다.

예전 easy 101 할때 풀었던 문제인데 그당시 에는 bfs 를 이용해 계층 별 탐색을 했으니 이번에는 재귀로 풀어보자.

 

class Solution {
    List<Integer> list = new ArrayList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        helper(root);
        return list;
    }
    public void helper(TreeNode root){
        if(root == null) return;
        list.add(root.val);
        helper(root.left);
        helper(root.right);
    }
}

별다를 바 없는 코드이다 현재 들어온 루트 를 기점으로 다음재귀 로 넘어갈때 마다 list 에 담아준다. 

list.add 의 위치가 한칸 아래 내려오면 중위순회이고 맨아래 들어가면 후위순회가 된다. 

LeetCode BinaryTree 설명

 

Account Login - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

100. Same Tree

위의 문제와 다를 바 없다 동일한 순회방법을 정해 태우면서 같은 값이 나오지 않는다면 false 를 바로바로 리턴해주면 된다.

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return helper(p,q);
    }
    public boolean helper(TreeNode p,TreeNode q){
        if(p == null || q == null)
            return  p==q;
        if(p.val != q.val) return false;
        return helper(p.left,q.left) && helper(p.right,q.right);
    }
}

좌우 노드를 이용해서 돌렸다. if 부분 의 return 값 이 의아할텐데 하나가 null 이고 하나가 null 이 아닌경우 에 대해 해결 해주기 위해 위와 같이 작성하엿다.

여기서 중점적으로 봐야 할 부분은 여기다 null == null 은 트루가 나온다(Objects.equals(null,null)도 트루이다.) 

null 이란 특정 원시타입 혹은 객체 타입이 아닌 그냥 변수이다.

이 null 의 메모리 어드레스가 0 이라고들 말하는데 메모리 한칸을 쓰고 있는거 아니겠는가 그래서 null 값이 나오면 저 하나의 포인트 로 가기 때문에 true 값이 나온다고 이해된다.

직접 확인 해보기가 어려워서 스택오버 플로 말을 믿자..(C 나 C++ 은 0을 포인팅 한다고 한다.) 위의 이유에서 null == null 이 성립한다. 

 

1443. Minimum Time to Collect All Apples in a Tree

생각보다 시간 소모를 많이한 문제이다.(문제를 이상하게 읽어서...) 어쩃든 보면 0 에서 시작해서 모든 사과를 수거해서 0 으로 돌아오면 된다. 

사과를 가지고 올 최선의 길은 ? 항상 동일하다 같던 방향 그대로 돌아오면 된다. 단 여기서 1번 노드 는 4,5 를 1번 분기를 기준으로 다녀오면 된다. 

분기를 나누는 재귀가 마구마구 떠오른다.

솔루션 의 다른 풀이 확인을 하다보니 좋은 그림이 있어 첨부한다.

 

가장 아래에서 부터 올라가면 된다. 사과 를 가지고 위 분기로 올라갈떄는 2를 더한다 왜 ? 갔다 와야하니깐

 

코드를 보자.

class Solution {
    private List<List<Integer>> graph = new ArrayList<>();
    private void init(int n,int[][] edges){
        for (int i = 0; i < n; i++) {
            graph.add(new ArrayList());
        }
        for (int i = 0; i < edges.length; i++) {
            int from = edges[i][0],to = edges[i][1];

            graph.get(from).add(to);
            graph.get(to).add(from);
        }
    }
    public int minTime(int n, int[][] edges, List<Boolean> hasApple) {
        init(n,edges);
        boolean[] visit = new boolean[n];
        return helper(0, hasApple, visit);
    }
    public int helper(int cur,List<Boolean> hasApple,boolean[] visit){
        visit[cur]  = true;
        int move = 0;

        for(int next : graph.get(cur)){
            if(visit[next]) continue;
            move += helper(next,hasApple,visit);
        }

        if(cur != 0 && (hasApple.get(cur) || move > 0)) move +=2;
        return move;
    }
}

init 을 이용해 그래프 를 그려 주었다 예전 다익스트라 알고리즘 에서 할떄 이런 방법 으로 그래프 그렸던 기억이 있어 위와 같이 그래프를 만들었고. 중복 방문을 처리하기 위해 visit 배열을 하나 생성 했다. 다른 부분은 이상하지 않으나 저기 += 2 해주는 부분을 보자.

0 이 아니라면 왜 ? 0부터 시작하니깐 사과를 집은 시점부터 이동 거리가 0 이상이라면 계속 더해주면서 가야한다

그길은 거쳐야 하니깐 계속 2 씩 증가시켜주면서 다음으로 넘어가 준다.

'PS > LeetcCode' 카테고리의 다른 글

LeetCode Daily 문제풀기  (0) 2023.01.06
LeetCode Daily 문제풀기  (0) 2023.01.05
1월4일 문제 풀기  (2) 2023.01.04
Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
Binary Tree Maximum Path Sum  (0) 2022.12.11

1833. Maximum Ice Cream Bars

주어진 배열 이 있고 거기서 최대 아이스크림을 살만큼 사면되는 문제이다. 비교적 어제 보다 쉬운 문제이다. 왜냐하면 노트에 표기된 순서에 상관없는 구매가 가능하다는 부분이 이문제를 직관적으로 만든다.

만약 저 노트 부분이 없다면 sliding window 를 이용해 범위를 넓혀나가면서 계산해야지 싶다.

최대범위 10k 이기 떄문에 부담 없이 for loop 돌려도 된다.

정렬을 우선 해주고 o(nlogn), while을 이용해서 작은 가격의 아이스크림부터 우선적으로 챙긴다.

더보기
    class Solution {
        public int maxIceCream(int[] costs, int coins) {
            int answer = 0;
            Arrays.sort(costs);

            if(costs[0] > coins) return answer;

            int idx = 0;
            while(coins > 0 && idx < costs.length){
                if(costs[idx] <= coins){
                    answer++;
                    coins -= costs[idx++];
                }else{
                    break;
                }
            }
            return answer;
        }
    }

 

별다를 게 없는 코드이다. 다만 index 의 변수 라던지 answer의 변수를 줄일 수 있을 거 같아 여러 번 고민했다.

class Solution {
    public int maxIceCream(int[] costs, int coins) {
        Arrays.sort(costs);
        int len = costs.length;

        for(int i=0;i<len;i++){
            if(costs[i] > coins) return i;
            coins -= costs[i];
        }

        return len;
    }
}

이렇게 압축을 시켜줄수 있다. for 문안에서 리턴되는 경우라면? 배열 끝까지 못 사는 경우 

마지막에 리턴이 된다면 ? 플렉스

이거는 미디엄 레벨 이 아니라 이지 레벨로 측정되어야 하지 않는가 싶다.

 

108. Convert Sorted Array to Binary Search Tree

정렬된 배열 을 BST로 바꾸는 로직을 작성하는 문제이다. 높이가 균등한 BST 이기 때문에 한쪽으로 편향된 경우를 제외하자.

 

여기서 BST 가 생소할 수 있다. BST 란 노드 하나 기준으로 외쪽은 노드 본인보다 작고 오른쪽은 본인보다 큰 경우 이다.

즉 다시 말해 위의 답이 2가지 가 될 수 있는 이유이다. 또한 높이가 균등한 BST라고 되어 있기 때문에 왼쪽 높이가 3이라면 오른쪽 높이는 최대 4 최소 2 즉 1의 차이를 가진 트리 그래프를 그려야 한다.

** 여기서 중요한 것은 BST를 구성할 때 노드 좌 우는 본인보다 작고 커야 한다 즉 중간 값이라는 이야기다

 

바로 구현 가보자

 

더보기
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return helper(nums,0,nums.length-1);
    }
    public TreeNode helper(int[] arr,int left,int right){
        if(left >right) return null;
        int mid = (left+right)/2;
        TreeNode node = new TreeNode(arr[mid]);
        node.left = helper(arr,left,mid-1);
        node.right = helper(arr,mid+1,right);
        return node;
    }
}

재귀를 이용해서 좌우로 넣어주었다. mid 값 기준으로 좌우로 넣어주되 +1을 해야 하는 이유는 현재 mid 값은 검증된 값 이기 때문이다. 

추가적으로 검증을 할 이유가 없다 저렇게 안 한다면 아마 StackOverFlow를 마주하지 않을까 싶다.

 

110. Balanced Binary Tree

주어진 TreeNode 가 균등한 BST 인지 아닌지 판별하는 문제이다. 아까 말했다시피 높이를 판별해서 true false의 값을 반환해주어야 하는데...  푸는데 시간 좀 들였다.

더보기
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null) return true;
        
        int left = helper(root.left,0);
        int right = helper(root.right,0);

        return Math.abs(right-left) > 1 ? false : true;
    }
    public int helper(TreeNode node,int depth){
        if(node == null) return depth;
        int left = helper(node.left,depth+1);
        int right = helper(node.right,depth+1);
        return Math.max(left,right);
    }
}

단순 트리의 깊이를 이용해 좌우측 깊이 판정을 통해 비교하는 값을 했더니만 233번인가 203번 케이스에서 걸린다.

좌우측편향 트리인 경우 true의 값을 리턴하는 문제였다.  / \ 이런 식으로 된 트리구조라면 나의 답은 true로 나오기 때문이다 왜? 좌우측 그냥 가장 깊은 트리의 높이를 반환하니깐

 

재귀 중간중간 높이 체크할 변수가 필요하다. 방법을 찾지 못해 디스커스에 가서 몇몇 해설을 읽었다.

더보기
class Solution {
    public boolean isBalanced(TreeNode root) {
        return helper(root) != -1 ;
    }
    public int helper(TreeNode node){
        if(node == null) return 0;

        int left = helper(node.left);
        int right = helper(node.right);

        if(left == -1 || right == -1) return -1;
        if(Math.abs(right-left) > 1) return -1;

        return 1 + Math.max(left,right);
    }
}

이렇게 되면 편향 트리인 경우 2번째 높이에서 1 보다 큰 차이가 있기에 두 번째 If에서 -1 로직을 타면서 false를 리턴한다.

 

오히려 Medium 난이도 인 데일리 문제보다 이 BST 문제가 더 어려웠다.
Easy 난이도인데도 이런 트리문제가 나오면 너무 약하다. 만약 라이브 코딩 을 한다면 이런 트리문제는 흐.... 트리문제 위주로 더 풀어봐야겠다.

 

'PS > LeetcCode' 카테고리의 다른 글

LeetCode 1월9 ~ 1월 11일  (2) 2023.01.11
LeetCode Daily 문제풀기  (0) 2023.01.05
1월4일 문제 풀기  (2) 2023.01.04
Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
Binary Tree Maximum Path Sum  (0) 2022.12.11

452. Minimum Number of Arrows to Burst Balloons

풍선은 Y 축에 관계없이 주어진 배열에 따라 x 축으로 길게 형성되어있다. 최소한의 화살을 쏴서 풍선을 터트려야 하는데.. 음 어떻게 풀지 감이 잘 안 온다. 뭔가 그리디 문제 같은데..

첫 번째 접근방법

좌우 차이를 이용해 가장 큰 것부터 제거하면서 그 범위를 기록해 놓는 것이다.  (x)

-> 1-5  1-2, 4-5 이렇게 주어진다면 가장 큰 1-5 가 범위가 기록되고 1-2, 4-5 모두 한 번에 1-5 라인에 소거되기에 탈락

두 번째 접근방법

좌우 차이를 이용해 가장 작은 것부터 제거하면서 그 범위를 기록해 놓는 것

-> 1-5, 1-2, 4-5 음? 뭔가 될 거 같다. 바로 PriorityQueue를 이용해 정렬 후 뽑아가면서 계산을 해보자.

더보기
class Solution {
    public int findMinArrowShots(int[][] points) {
        PriorityQueue<int[]> q
                    = new PriorityQueue<>((a,b) ->(a[1]-a[0])-(b[1]-b[0]));

        for(int[] arr : points){
            q.offer(arr);
        }

        List<int[]> list = new ArrayList<>();
        while(!q.isEmpty()){
            int[] poll = q.poll();
            boolean range = false;
            for(int[] arr : list){
                boolean a = poll[0] <= arr[0] && arr[0] <= poll[1];
                boolean b = poll[0] <= arr[1] && arr[1] <= poll[1];
                if(a || b){
                    range = true;
                    break;
                }
            }
            if(!range) list.add(poll);
        }
        return list.size();
    }
}

36 번째 테스트 케이스에서 깨진다. 왜? 3-5, 5- 7,1-4 이런 경우 3-5가 범위가 찍히고 5-7 이 찍힌다. 이러면 화살은 5번을 쏴야 하지만 1-4 또한 소거된다. 3을 기준으로 분류되기 때문에 이렇게 범위를 이용하는 것이 아닌 하나의 점을 찍어서 구현을 해야 할 것 같은데...

사실 위 방법 말고도 다양한 방법으로 구현해봤으나 다 실패했다.

하.. 멘털 갈린다. 설루션 가서 접근법을 먼저 살펴보고 오자. 

한 번에 많은 화살로 최대한 많은 풍선을 터트려야 한다. 그렇다면 가장 많은 풍선을 터트릴 방법 중 하나는 주어진 범위 최소 혹은 최대 값을 이용하면 판별하기 쉽다. 그러나 모든 가지의 경우의 수를 계산할 필요는 없다 왜? 

정렬을 이용해 오른쪽 숫자별로 기준으로 정해서 그 포인트 별로 화살을 날려주면 되기 때문이다.

balloons = [[7,10], [1,5], [3,6], [2,4], [1,4]]
정렬 이후 
balloons = [[2,4], [1,4], [1,5], [3,6], [7,10]]

하... 이 쉬운 걸 파악 못해서 1시간을 저러고 있으니 참 그렇다.

더보기
class Solution {
    public int findMinArrowShots(int[][] points) {
        PriorityQueue<int[]> q = new PriorityQueue<>(
                    (a,b) -> a[1]-b[1]
            );
            Arrays.stream(points).forEach(q::offer);

            int cnt = 0;

            while(!q.isEmpty()){
                int[] cur = q.poll();
                while(!q.isEmpty() && q.peek()[0] <= cur[1] &&  cur[1] <= q.peek()[1]){
                    q.poll();
                }
                cnt++;
            }
            return cnt;
    }
}

항상 화살은 endPoint 기준으로 날려준다는 로직을 세우고 

우선순위 큐를 활용해 정렬을 했고, 큐를 돌면서 다음 범위를 확인한 후 지워줄지 말지를 정하고 날려준다.

 

이것 외에 배열 정렬을 통해 해결하는 방법도 있다.

 

더보기
class Solution {
    public int findMinArrowShots(int[][] points) {
        Arrays.sort(points, (a,b) -> Integer.compare(a[1], b[1]));
        int cnt = 1;
        int current = points[0][1];
        for(int i=1;i<points.length;i++){
            if(current < points[i][0]){
                cnt++;
                current = points[i][1];
            }
        }
        return cnt;
    }
}

current 가 현재 화살을 소는 타깃 즉 endPoint 가 된다. 

 

Simillar Question 찍어서 비슷한 문제 더 풀어야겠다 화나서 안 되겠다.

 

56. Merge Intervals

이번에는 합쳐주는 방식이다. 양 좌우측 사이에 겹치는 오버래핑되는 수가 있다면 합쳐서 늘려주면 된다.

위에서는 pq를 이용했으니 이번에는 배열방식으로 풀어보자.

더보기
class Solution {
    public int[][] merge(int[][] intervals) {
        Arrays.sort(intervals,(a,b)->a[0]-b[0]);
            
        List<int[]> answer = new ArrayList<>();

        int min = intervals[0][0];
        int max = intervals[0][1];

        for (int i = 1; i < intervals.length; i++) {
            if(min <= intervals[i][0] && intervals[i][0] <= max){
                max = Math.max(intervals[i][1],max);
            }else{
                answer.add(new int[]{min,max});
                min = intervals[i][0];
                max = intervals[i][1];
            }
        }
        answer.add(new int[]{min,max});
        return answer.toArray(new int[answer.size()][]);
    }
}

 

 

이번에는 합치는 로직이다 보니 위에 와는 달리 작은 값이 중요하다 현재 값 사이에  정렬된 배열의 다음 왼쪽값이  포함되느냐 가 주된 로직이다.

forLoop 가 끝나고 한 번 더 추가하는데 이는 마지막까지 모든 수를 업데이트하고 나서 추가가 안 되는 경우를 위해 이렇게 따로 추가한다.

사실 틀리기 전까지 몰랐다. 그래서 추가적으로 위와 같이 작성했다.

 

435. Non-overlapping Intervals

이번에는 겹치는 부분을 제거해주는 로직을 구현하면 된다.

위와 동일하게 좌측 기준으로 정렬 을 하고 오른쪽 값을 업데이트해나가면서 오른쪽 값과 새로 들어온 왼쪽 값을 비교하면 된다.

더보기
class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        Arrays.sort(intervals,(a,b)->Integer.compare(a[0],b[0]));

        int cur = intervals[0][1];
        int cnt = 0;

        for(int i=1;i<intervals.length;i++){
            if(cur > intervals[i][0]){
                cnt++;
                cur = Math.min(intervals[i][1],cur);
            }else{
                cur = intervals[i][1];
            }
        }
        return cnt;
    }
}

어으 사실 이 문제도 여러 번 틀렸다. 접근방법은 비슷했으나. 어떻게 다음으로 걸러 주는지 분기를 나누는 부분에서 버벅 거렸다..

 

결론 그리디 문제 너무 싫다.

'PS > LeetcCode' 카테고리의 다른 글

LeetCode 1월9 ~ 1월 11일  (2) 2023.01.11
LeetCode Daily 문제풀기  (0) 2023.01.06
1월4일 문제 풀기  (2) 2023.01.04
Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
Binary Tree Maximum Path Sum  (0) 2022.12.11

2244. Minimum Rounds to Complete All Tasks

0번 부터 시작하는 인덱스가 주어지고 한번의 테스크는 2,3 개 만 이루어진다. 동일한 레벨 만 동시에 작업할수 있다.

최소 라운드 를 구해라 만약 없다면 -1 을 리턴 하라는 문제이다.

 

최대 갯수가 10000 개 밖에 되지않는다.

 

Map 을 이용해 저 숫자들 을 분류하고, bfs 를 이용해 최소 라운드 를 찾아갈 생각을 했다.

 

더보기
class Solution {
    public int minimumRounds(int[] tasks) {
        Map<Integer,Integer> map = new HashMap();
            for(int a:tasks){
                Integer b = map.getOrDefault(a,0);
                map.put(a,b+1);
            }

            int round = 0;
            for(Map.Entry<Integer,Integer> entry : map.entrySet()){
                Integer value = entry.getValue();
                if(value < 2) return -1;
                int calculate = calculate(value);
                if(calculate < 0) return -1;
                round += calculate;
            }

            return round;
    }

    private int calculate(Integer value) {
            int round =0;
            boolean found = false;

            Queue<Integer> q = new ArrayDeque<>();
            Set<Integer> set = new HashSet();
            q.add(0);

            while(!q.isEmpty()){
                int size = q.size();
                for(int i =0;i<size;i++){
                    int poll = q.poll();

                    int a = poll + 2;
                    if( a == value) return round+1;
                    int b = poll + 3;
                    if( b == value) return round+1;

                    if(a < value && set.add(a)) q.offer(a);
                    if(b < value && set.add(b)) q.offer(b);
                }
                round++;
            }

            return found ? round : -1;
        }
}

이렇게 작성했더니 무려 126ms 나 나와버렸더 무진장 느리다 ...  아무래도 저 bfs 부분이 문제인거 같은데 나름 최적화 가 되어있다고 생각하는데 이렇게 느린걸 보면 다른 규칙이 있는것 같다.

1번   2,3
2번  4,5,6
3번 7,8,9
4번 10,11,12

연산 을 한번 두번 .... 했을때 중복 값을 제외하고 나올 경우의 수를 작성해 보면 위와 같다. 파란색으로 마킹된 부분을 살펴보자. 

모두 3의 배수이다. 2,3 을 이용한다면 모든수 를 작성할수 있다 단 1 인 경우를 제외한다면 이다. caculate 함수 부분을 변경하자.

    private int calculate(Integer value) {
        if(value%3 == 0) return value/3;
        return (value/3)+1;
    }

좋다 이렇게 변경하니 속도가 개선 되었으나 여전히 36ms 로 상위 19% 이다. 많이 느리다. 어떤 부분을 놓친걸까...

 

더보기
class Solution {
    public int minimumRounds(int[] tasks) {
        Arrays.sort(tasks);
        int round = 0;
        int cnt = 1;
        if(tasks.length < 2) return -1;

        for(int i=1;i<tasks.length;i++){
            if(tasks[i-1] != tasks[i]){
                if(cnt < 2) return -1;
                round += calculate(cnt);
                cnt =1;
            }else{
                cnt++;
            }
        }
        
        if(cnt < 2) return -1;
        else round += calculate(cnt);

        return round;
    }

    private int calculate(Integer value) {
        if(value%3 == 0) return value/3;
        return (value/3)+1;
    }
}

 

로직을 변경해 배열을 정렬하고 for loop 으로 로직 변경 => 10ms  98.8% 까지 나왔다.

 

음 ? 다른사람의 풀이도 별반 다르지 않다... 다 이런 2가지 방식으로 풀었다.

 

83. Remove Duplicates from Sorted List

연결 노드상 에서 중복된 값을 지워주는 로직을 구현하면 된다. 재귀 를 노드 의 마지막 까지 이동을 하고

현재 헤드 기준으로 헤드 의 다음을 계속 업데이트 해주면서 날려주는 로직을 재귀 안에 구성하면 될것같다.

 

더보기
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        helper(head);
        return head;
    }
    public void helper(ListNode head){
        if(head == null || head.next == null) return;
        while(head.next != null && head.val == head.next.val){
            ListNode next = head.next;
            if(next == null){
                head.next = null;
                break;
            }
            head.next = next.next;
        }
        helper(head.next);
    }
}

null 체크 를 제대로 하지않아서 nullPointer exception 을 여러번 맞았다 ㅎㅎㅎ... while 을 next 의 null 까지 돌리지 않는다면 

1,1,1,1,1,1,1 케이스 에서 1,1 로 반환되는 불상사가 발생한다. 주의 하자

Solution 중에 가독성 좋은 코드가 있어서 가져와 봤다.

더보기
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode list = head;
         
         while(list != null) {
        	 if (list.next == null) {
        		 break;
        	 }
        	 if (list.val == list.next.val) {
        		 list.next = list.next.next;
        	 } else {
        		 list = list.next;
        	 }
         }
         
         return head;
    }
}

while 을 이용해서 loop 를 돌리는게 확실히 재귀보다 직관적 이다.

 

1619. Mean of Array After Removing Some Elements

정렬하고 5% 계산해서 평균 구해주면 되는 Easy 난이도 에 맞는 문제이다.

더보기
class Solution {
    public double trimMean(int[] arr) {
        Arrays.sort(arr);
        int cut = (int) ((double) arr.length * 0.05);
        double sum = 0;
        for(int i = cut;i<arr.length-cut;i++){
            sum+=arr[i];
        }
        return sum/(arr.length-(cut*2));
    }
}

 

깔끔한 코드 20 개 의 배수가 주어진다는 문제의 요구사항에 따라 미리 20 을 나눠서 계산하는 모습이 인상적이다.

    public double trimMean(int[] arr) {
        int n = arr.length;
        double sum = 0d;
        Arrays.sort(arr);
        for (int i = n / 20; i < n - n / 20; ++i) {
            sum += arr[i];
        }
        return sum / (n * 9 / 10);
    }

 

'PS > LeetcCode' 카테고리의 다른 글

LeetCode Daily 문제풀기  (0) 2023.01.06
LeetCode Daily 문제풀기  (0) 2023.01.05
Easy 문제 풀기 Day2(83/618)  (0) 2023.01.03
Binary Tree Maximum Path Sum  (0) 2022.12.11
1339. Maximum Product of Splitted Binary Tree  (0) 2022.12.10

+ Recent posts