๐ ๊ฐ์
์ฝ๋ฉ ํ ์คํธ๋ฅผ ์ํด ๊ณต๋ถํ ์๊ณ ๋ฆฌ์ฆ ์ง์๋ค์ ๊ฐ๋ฐ ์ค๋ฌด์์ ์ฌ์ฉํ ์ผ์ด ์์๊น?
๊ฐ๋ฐ ์์ญ, ๊ฐ๋ฐ์์ ์ญ๋, ๊ฐ๋ฐ ํ๊ฒฝ๋ง๋ค ๋ค๋ฅผ ๊ฒ์ด๋ค.
์ด์ ๋ณธ์ธ ์๊ธฐ๋ฅผ ํ์๋ฉด ์น ํ๋ซํผ ์๋น์ค์ FE ๊ฐ๋ฐ์์ด๋ฉฐ ์ฃผ๋ก ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ง๋ ํ๋ฉด์ ๊ฐ๋ฐํ๋ค.
๋ณดํต ํ๋ฉด ๊ฐ๋ฐ์ด๋ผ ํจ์
- ์ฌ์ฉ์ ์ธํฐํ์ด์ค, ์ธํฐ๋ ์ ๊ฐ๋ฐ
- ์๋ฒ API ์์ฒญ/์๋ต ์ฒ๋ฆฌ
์ผ ๊ฒ์ด๋ค.
๋ฐฉ๋ํ ๋ฐ์ดํฐ๋ฅผ ์ง์ ์กฐํํ๋ ๊ฒฝ์ฐ๋ ์๊ณ , ์ด๋์ ๋ ๊ฐ๊ณต๋์ด ์๋ต๋ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ฐ์์ ์ฌ์ฉํ๋ค๋ณด๋
๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ํ๊ณ ์ฒ๋ฆฌํ๋ ค๋ ๊ณ ๋ฏผ๋ณด๋ค๋ ์ฌ์ฉ์ ์ธํฐํ์ด์ค์ ์ฌ์ฉ์ ๊ฒฝํ ์ต์ ํ์ ๋ํ ๊ณ ๋ฏผ์ ์ฃผ๋ก ํ์๋ค.
์ต๊ทผ์๋ ํ์๋ณด๋ค ๋ฐ์ดํฐ์ ํจ๊ณผ์ ์ธ ํ์ ๋ฐ ์ฒ๋ฆฌ์ ๋ํ ์๊ณ ๋ฆฌ์ฆ์ ์ธ ์ ๊ทผ์ ์๊ตฌํ๋ ์ํฉ์ด ๋ฐ์ํ๊ณ
์ด๋ฅผ ์ ๋ฆฌํ์ฌ ๊ธฐ๋กํด๋๊ณ ์ ํ๋ค.
๐ฅธ ๋ฌธ์
์ด์๋ ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋งํ๋ ๊ณผ์ ์์ ์์๋ค.
ํ์ฌ ์ฝ๋์ ๋ฐ์ดํฐ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ๊ณต๊ฐํ ์ ์๊ธฐ ๋๋ฌธ์ ์ต๋ํ ์ถ์ํํ์ฌ ๋ฌธ์ ๋ฅผ ์ ์ํด ๋ณด์๋ฉด,
// ๊ตฌ๋งคํ๋ ค๋ ์ํ๋ค๊ณผ ๊ฐ ์ํ๋ณ ์ ์ฉ๊ฐ๋ฅํ ์ฟ ํฐ ๋ฐฐ์ด์ด ์ฃผ์ด์ก์ ๋,
// ์๋ ์กฐ๊ฑด๋ค์ ๋ง์กฑํ๋ ์ด ์ต๋ ํ ์ธ ๊ธ์ก์ ๊ณ์ฐํ๋ค.
// ๊ฐ์ ์ฟ ํฐ์ ๊ฐ์ id๋ฅผ ๊ฐ์ง๋ค.
// ์ฟ ํฐ์ ๊ธ์ก ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ๋์ด ์๋ค.
// ๊ฐ ์ํ์ ํ๋์ ์ฟ ํฐ๋ง ์ ์ฉ ๊ฐ๋ฅํ๋ค.
// ์ฟ ํฐ์ ์ฌ๊ณ ๊ฐ ์์ด์ผ ์ ์ฉ ๊ฐ๋ฅํ๋ค.
// ์ํ์ ์ต๋ ๊ฐ์๋ 20์ด๋ค. (orderItems.length <= 20)
// ์ํ ํ์
type OrderItem = {
coupons: {
id: number
price: number // ํ ์ธ ๊ธ์ก
remain: number // ์์ฌ ๊ฐ์
}[]
}
// ์๋ orderItems ๋ฐฐ์ด์ด ์ฃผ์ด์ก์ ๋, id:1, id:2 ์ด ๊ฐ๊ฐ ์ ์ฉ๋์ด 3000 ์ด ์๋ต๋์ด์ผ ํ๋ค.
orderItems = [
{coupons: [{id:1, price: 2000, remain: 1}]},
{coupons:
[
{id:1, price: 2000, remain: 1},
{id:2, price: 1000, remain: 2}
]
}
]
์ ๋๊ฐ ๋ ๊ฒ์ด๋ค.
Greedy
๊ธฐ์กด ์ฝ๋๋ ์ด๋ฅผ ๊ทธ๋ฆฌ๋(Greedy) ํ ๋ฐฉ์์ผ๋ก ๊ณ์ฐํ๊ณ ์์์ ๋ฐ๊ฒฌํ๋ค.
๊ฐ๋จํ๊ฒ ์ฝ๋๋ก ์ฎ๊ฒจ๋ณด์๋ฉด ์๋์ ๊ฐ์ ๋ฐฉ์์ด๋ค.
// ์ฟ ํฐ์ ์ฌ์ฉ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ์ถ์ ํ๋ ๊ฐ์ฒด
let couponUsage = {};
// ์ด ํ ์ธ ๊ธ์ก์ ๊ณ์ฐ
let totalDiscount = 0;
// ๊ฐ ์ํ๋ณ๋ก ์ต๋ ํ ์ธ ์ ์ฉ
orderItems.forEach(item => {
let maxDiscount = 0;
let appliedCouponId = null;
item.coupons.forEach(coupon => {
// ํด๋น ์ฟ ํฐ์ด ์์ง ์ฌ์ฉ ๊ฐ๋ฅํ๊ณ , ์ต๋ ํ ์ธ ๊ธ์ก๋ณด๋ค ํฐ ๊ฒฝ์ฐ
if (couponUsage[coupon.id] > 0 && coupon.price > maxDiscount) {
maxDiscount = coupon.price;
appliedCouponId = coupon.id;
}
});
// ์ฟ ํฐ ์ ์ฉ
if (appliedCouponId !== null) {
totalDiscount += maxDiscount;
couponUsage[appliedCouponId]--; // ์ฌ์ฉ๋ ์ฟ ํฐ์ ์ฌ๊ณ ๊ฐ์
}
});
orderItems๊ฐ ์์ ์์์ธ ๊ฒฝ์ฐ ์ฝ๋๋ฅผ ์คํ์์ผ๋ณด๋ฉด totalDiscount์ 3000์ด ํ ๋น๋ ๊ฒ์ด๋ค.
orderItems = [
{coupons: [{id:1, price: 2000, remain: 1}]},
{coupons:
[
{id:1, price: 2000, remain: 1},
{id:2, price: 1000, remain: 2}
]
}
]
ํ์ง๋ง ๋ฌธ์ ๋ ์๋์ ๊ฐ์ด orderItems ์์์ ์์๊ฐ ๋ฐ๋๊ฒ ๋๋ค๋ฉด ๋ค๋ฅธ ๊ฐ์ด ํ ๋น๋๋ค๋ ๊ฒ์ด๋ค. (id:1 ๋ง ์ ์ฉ๋์ด 2000 ํ ๋น)
orderItems = [
{coupons:
[
{id:1, price: 2000, remain: 1},
{id:2, price: 1000, remain: 2}
]
},
{coupons: [{id:1, price: 2000, remain: 1}]}
]
์ต๋ ํ ์ธ ๊ธ์ก์ orderItems ์์์ ์์์ ์๊ด์์ด ์ผ์ ํด์ผ ํ๋ค.
ํด๋น ๋ฌธ์ ๋ฅผ ๊ณ์ ๊ทธ๋ฆฌ๋ํ๊ฒ ์ ๊ทผํ์๋ฉด, orderItems๋ฅผ ํน์ ์กฐ๊ฑด์ผ๋ก ์ ๋ ฌํ ๋ค ์ต๋ ํ ์ธ ๊ธ์ก์ ๊ณ์ฐํ๋ ๋ฐฉ๋ฒ์ด ์์ ๊ฒ์ด๋ค.
ํ์ง๋ง ๊ฒฝํ์ ๊ทธ๋ฆฌ๋ํ๊ฒ ์ ๊ทผํ๋ ๋ฐฉ์์ ํ๊ณ๊ฐ ์๋ค. ์ดํ์ ์๋ก์ด ์ฟ ํฐ ์ฌ์ฉ ๊ท์น์ด ์ถ๊ฐ๋์์ ๋ ํ์ฅ์ฑ์ ์ทจ์ฝํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ฒฝํ์ ๊ทธ๋ฆฌ๋ํ๊ฒ ์ ๊ทผํ์ฌ ์ต์ ์ ํฉ์ ๋ผ ์ ์์ ๋ brute force ๋ฐฉ์์ผ๋ก ์ ๊ทผํ๋ฉด ๋์์ด ๋๋ค.
Brute Force (recursively)
๋ฐ๋ผ์ ์ฃผ์ด์ง orderItems ์ ์ ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ์ฟ ํฐ ํ ์ธ ๊ธ์ก์ ์ฌ๊ท์ ์ผ๋ก ๊ณ์ฐํด์ ๋น๊ตํด๋ณด๊ณ ์ ํ๋ค.
const orderItems = [
{coupons: [{id: 1, price: 2000, remain: 1}, {id: 2, price: 1000, remain: 2}]},
{coupons: [{id: 1, price: 2000, remain: 1}]}
];
// ์ฟ ํฐ ์ฌ์ฉ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฐ์ฒด
let couponUsage = {};
function getMaxDiscount(orderItems, index = 0, usedCoupons = {}, currentDiscount = 0) {
if (index >= orderItems.length) {
// ๋ชจ๋ ์ํ์ ๋ํ ์ฟ ํฐ ์ ์ฉ์ ์๋ํ ๊ฒฝ์ฐ, ํ์ฌ๊น์ง์ ํ ์ธ ๊ธ์ก์ ๋ฐํ
return currentDiscount;
}
let maxDiscount = currentDiscount; // ํ์ฌ๊น์ง์ ์ต๋ ํ ์ธ ๊ธ์ก์ ์ด๊ธฐ๊ฐ์ผ๋ก ์ค์
// ํ์ฌ ์ํ์ ์ ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ์ฟ ํฐ์ ๋ํด ๋ฐ๋ณต
for (let coupon of orderItems[index].coupons) {
const couponId = coupon.id;
if (!usedCoupons[couponId] || usedCoupons[couponId] < coupon.remain) {
// ์ฟ ํฐ์ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ
const newUsedCoupons = {...usedCoupons};
newUsedCoupons[couponId] = (newUsedCoupons[couponId] || 0) + 1;
// ํ์ฌ ์ฟ ํฐ์ ์ ์ฉํ๊ณ ๋ค์ ์ํ์ผ๋ก ๋์ด๊ฐ ์ต๋ ํ ์ธ ๊ธ์ก์ ๊ณ์ฐ
const newDiscount = getMaxDiscount(orderItems, index + 1, newUsedCoupons, currentDiscount + coupon.price);
maxDiscount = Math.max(maxDiscount, newDiscount);
}
}
// ํ์ฌ ์ํ์ ์ด๋ค ์ฟ ํฐ๋ ์ ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๋ ๊ณ ๋ ค
const noCouponDiscount = getMaxDiscount(orderItems, index + 1, usedCoupons, currentDiscount);
maxDiscount = Math.max(maxDiscount, noCouponDiscount);
return maxDiscount;
}
const totalMaxDiscount = getMaxDiscount(orderItems);
console.log(totalMaxDiscount); // 3000
์ด์ ์ด๋ค ์์์ orderItems๊ฐ ๋์ด์๋ ์ ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ์ฟ ํฐ ํ ์ธ ๊ธ์ก์ ๊ณ์ฐํ๊ธฐ ๋๋ฌธ์ ํญ์ ์ต์ ์ ํด๋ฅผ ๋ผ ์ ์๊ฒ ๋์๋ค.
...๊ณผ์ฐ ์ด์ ๋ ์ด์ ๋ฌธ์ ๊ฐ ์์๊น? ๐ค
์ฝ๋ฉ ํ ์คํธ๋ฅผ ๊ณต๋ถํ๋ฉฐ ํญ์ ๊ฒฝํํ๋ ๊ฒ์ด์ง๋ง, ๋ค๋ฅธ ์ผ์ด์ค๋ ๋ค pass ํ๋๋ฐ ํญ์ ์๊ฐ ๋ณต์ก๋์์ ์คํจํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค.
๊ทธ๋ฆฌ๊ณ ๋ณดํต brute force ๋ฐฉ์์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ ๋ ์ด๋ฐ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๋ค.
์ค์ ๋ก ์ด ์ฃผ๋ฌธํ ์ํ์ ๊ฐ์๋ 20๊ฐ๊น์ง ๊ฐ๋ฅํ๊ณ (orderItems.length <= 20)
๊ฐ ์ํ์ ์ฟ ํฐ์ด 2๊ฐ์ฉ๋ง ์๋ค๊ณ ๊ฐ์ ํด๋ 2^20 ๋ฒ ์ํ์ ํ๊ฒ ๋๋ค.
์ด๋ฅผ jest๋ก ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ํ ์คํธ ํด๋ดค์ ๋ ๊ต์ฅํ ๊ธด ์๊ฐ๋์ ์คํ์ด ๋์๊ณ , (macbook pro m3, ram 36gb 5min ์ด์)
JS ๋ ์ฑ๊ธ ์ค๋ ๋ ์ธ์ด์ด๊ธฐ ๋๋ฌธ์ ์ ํจ์๊ฐ ์๋ฃ๋ ๋๊น์ง ์ดํ๋ฆฌ์ผ์ด์ ์ด ๋ฉ์ถฐ์์ ๊ฒ์ด๋ค. ๐ฑ
์ด๋ ์ ๋ ํ์ฉ ๋ถ๊ฐ ์์น์ด๋ฉฐ ์ด๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ชจ์ํด์ผํ๋ค.
Backtracking
๋ฐฑํธ๋ํน์ ๋ธ๋ฃจํธ ํฌ์ค(Brute Force) ํ์์ ๊ธฐ๋ฐ์ผ๋ก ํ๋, ํด๊ฐ ๋ ๊ฐ๋ฅ์ฑ์ด ์๋ ๊ฒฝ์ฐ ๊ทธ ๊ฒฝ๋ก๋ฅผ ์กฐ๊ธฐ์ ํฌ๊ธฐ(Pruning)ํ๊ณ ๋ค๋ฅธ ๊ฒฝ๋ก๋ฅผ ์๋ํ๋ "๊ฐ์ง์น๊ธฐ" ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ํจ์จ์ฑ์ ๋์ด๋ ๋ฐฉ์์ด๋ค.
orderItem์ ์ฌ๊ท์ ์ผ๋ก ์ํํ๋ฉด์, ์์ผ๋ก ๋จ์ orderItem๋ค์ '๊ฐ๋ฅํ ์ต๋์ ์ฟ ํฐ ํ ์ธ ๊ธ์ก' ํฉ์ ๊ณ์ฐํ๋ค.
๋ง์ฝ 'ํ์ฌ ์ฟ ํฐ ํ ์ธ ๊ธ์ก (currentDiscount)' + '๊ฐ๋ฅํ ์ต๋์ ์ฟ ํฐ ํ ์ธ (possibleMaxDiscount)' ์ด
๊ธฐ์กด '์ต๋ ํ ์ธ ๊ธ์ก (maxDiscount)' ๋ณด๋ค ๋ฎ๋ค๋ฉด, ํด๋น ๊ฒฝ๋ก๋ ๋ ์ด์ ํ์ํ ํ์๊ฐ ์๋ค.
๋ค์์ backtracking ๋ฐฉ์์ ์ถ๊ฐํ ์ฝ๋์ด๋ค.
const getMaxDiscount = (orderItems) => {
let maxDiscount = 0 // ์ต๋ ํ ์ธ์ก์ ์ ์ฅํ๋ ๋ณ์
const couponStocks = {} // ๊ฐ ์ฟ ํฐ์ ์ฌ๊ณ ๋ฅผ ๊ด๋ฆฌํ๋ ๊ฐ์ฒด
// ๊ฐ orderItem์ ์ต๋ ํ ์ธ์ก์ ์ ์ฅ
const maxDiscountPerOrderItems = orderItems.map(orderItem => orderItem.coupons[0].price)
const getPossibleMaxDiscountOfRestOrderItems = (currentIndex: number) => {
return maxDiscountPerOrderItems.slice(currentIndex).reduce((acc, cur) => acc + cur, 0)
}
// ๋ฐฑํธ๋ํน ํจ์ ์ ์
function backtrack(currentIndex: number, currentDiscount: number) {
// ๋ชจ๋ orderItem ๋ฅผ ์ฒ๋ฆฌํ ๊ฒฝ์ฐ, ์ต๋ ํ ์ธ์ก์ ์
๋ฐ์ดํธ
if (currentIndex === orderItems.length) {
maxDiscount = Math.max(maxDiscount, currentDiscount)
return
}
// ํ์ฌ ํ ์ธ ๊ธ์ก๊ณผ ๋๋จธ์ง orderItems ์ ์ต๋ ํ ์ธ ๊ธ์ก์ ํฉ์ด ๊ธฐ์กด ์ต๋ ํ ์ธ ๊ธ์ก๋ณด๋ค ์์ ๊ฒฝ์ฐ, ํ์ ์ค๋จ
if (currentDiscount + getPossibleMaxDiscountOfRestOrderItems(currentIndex) <= maxDiscount) {
return
}
// ํ์ฌ property ์ ๋ชจ๋ ์ฟ ํฐ์ ์ํ
for (const coupon of orderItems[currentIndex].coupons) {
// ์ฟ ํฐ์ ์ฌ๊ณ ๊ฐ ์๋ ๊ฒฝ์ฐ๋ง ์ฒ๋ฆฌ
if (coupon.remain <= 0) continue
if (!couponStocks[coupon.id] || couponStocks[coupon.id] < coupon.remain) {
// ์ฟ ํฐ ์ ์ฉ: ์ฌ๊ณ ๊ฐ์ ๋ฐ ํ ์ธ์ก ์ฆ๊ฐ
couponStocks[coupon.id] = (couponStocks[coupon.id] || 0) + 1
backtrack(currentIndex + 1, currentDiscount + coupon.price)
// ๋ฐฑํธ๋ํน: ์ฟ ํฐ ์ ์ฉ ์ทจ์
couponStocks[coupon.id] -= 1
}
}
// ํ์ฌ property ์์ ์ฟ ํฐ์ ์ ์ฉํ์ง ์๋ ๊ฒฝ์ฐ๋ ํ์
backtrack(currentIndex + 1, currentDiscount)
}
// ๋ฐฑํธ๋ํน ์์
backtrack(0, 0)
return maxDiscount
}
์ฟ ํฐ ๋ฐฐ์ด๋ค์ ์ด๋ฏธ ๊ฐ๊ฒฉ ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ ๋ ฌ ๋์ด์๊ธฐ ๋๋ฌธ์ ๋๋ถ๋ถ์ ์ต์ ์ธ ์ต๋ํ ์ธ ๊ธ์ก๋ค์ด ๋จผ์ ๊ฒฐ์ ์ด ๋์ด ๋ง์ ๊ฒฝ๋ก๋ฅผ '๊ฐ์ง์น๊ธฐ' ํ ์ ์์ ๊ฒ์ผ๋ก ์์๋๋ค.
๐ ๋ง์น๋ฉฐ
์ค์ ์ด์๋๊ณ ์๋ ์ฝ๋์ ์ํ, ์ฟ ํฐ ํ์ ์ ์๋ ํจ์ฌ ๋ ๋ณต์กํ๋ค.
์ด์ ์ฝ๋๊ฐ ๊ทธ๋ฆฌ๋ํ๊ฒ ์์ฑ๋์ด ์๋ค๊ณ ํ์ง๋ง, ๊ทธ๋ฆฌ๋ํ๊ฒ ํด๊ฒฐํ๊ธฐ ์ํด ๊ต์ฅํ ๋ง์ ๋น์ง๋์ค ๋ก์ง์ด ๊ตฌํ๋์ด ์๋ค.
์ฒซ ๊ฐ๋ฐ ์ดํ ๋ง์ ๊ธฐ๋ฅ๊ณผ ์ ์ฑ ๋ค์ด ์ถ๊ฐ๋ ๊ฒ์ผ๋ก ๋ณด์ด๊ณ ์ด๋ฅผ ๊ธฐ์กด ์ฝ๋์์ ํด๊ฒฐํ๋ค๋ณด๋ ๋ณต์ก๋๊ฐ ์ฌ๋ผ๊ฐ๊ณ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ด ๋จ์ด์ง๊ฒ ๋์๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๊ธฐ์กด ์ฝ๋๋ฅผ ์ต๋ํ ์ถ์ํํ๋ ๊ณผ์ ์์, ๋ฌธ์ ๋ฅผ ๊ต์ฅํ ๋จ์ํํ ์ ์์๋ค.
๋ํ, ์ด ๊ณผ์ ์ ํตํด ๊ธฐ์กด ์ฝ๋์ ํฌํจ๋์ด ์๋ ์ผ๋ถ ๋ถํ์ํ ๋น์ฆ๋์ค ๋ก์ง์ ๋ฐ๊ฒฌํ๊ณ ์ ๊ฑฐํจ์ผ๋ก์จ,
์ต์ ์ ํ์ ๋ฐฉ๋ฒ์ ์ฐพ์๋ผ ์ ์์๋ค. ( ๋ ์ข์ ๋ฐฉ๋ฒ์ด ์์ ์๋...? )
์๊ณ ๋ฆฌ์ฆ ์ง์์ ํ์ฉํด ์ค์ ์ด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ด๋ฒ ๊ฒฝํ์ ๋งค์ฐ ์์คํ์ผ๋ฉฐ,
์ด๋ฅผ ํตํด ์ป์ ํต์ฐฐ๋ ฅ์ ๋ฐํ์ผ๋ก ์์ผ๋ก ๋ค์ํ ๋์ ๋ค์ ์ฑ๊ณต์ ์ผ๋ก ๊ทน๋ณตํด๋๊ฐ ์ ์์ ๊ฒ์ด๋ค.
'Trouble Shooting' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋ด ๋ธ๋ก๊ทธ๊ฐ ๊ฒ์๋์ง ์๋๋ค (5) | 2022.11.07 |
---|---|
๋กค๋ง ์ ๋ฐ์ดํธ health check fail issue (0) | 2022.07.24 |