트리와 힙 문제 풀이 문서의 문제 1 풀이 코드다.
/* 트리와 힙 문제 1: 좋은 노드 세기 (LeetCode 1448. Count Good Nodes in Binary Tree)
*
* 문서: 트리와 힙 문제 풀이(연습 30)의 p1
* traversal을 goodNodes보다 먼저 둔다. 아직 선언되지 않은 함수를 부르면 컴파일 오류가 난다.
* 실행: cc -std=c11 -Wall -Wextra 30_week9-inclass_p1.c -o p1 && ./p1
*
* 문제: 뿌리에서 노드 X까지 가는 길에 X보다 큰 값이 없으면 X는 좋은 노드다. 좋은 노드의 수를 구한다.
*
* 확인하는 것
* 1. 문서 예 1의 트리 3(1, 4) -> 2.
* 2. LeetCode 문제의 예: [3,1,4,3,null,1,5] -> 4, [3,3,null,4,2] -> 3, [1] -> 1. 빈 트리 -> 0.
* 3. 무작위 트리 3,000개에서 느린 풀이(노드마다 뿌리부터의 길을 다시 걸어 최댓값을 구한다)와 같은 답.
* 4. 문서의 변형 문제: "길 위에 나보다 작은 값이 없는 노드"는 두 예에서 2개와 3개, 좋은 노드의 합은 15.
*/
#include <stdio.h>
#include <stdlib.h>
#define CHECK(cond, msg) \
do { if (!(cond)) { fprintf(stderr, "[FAIL] %s (line %d)\n", msg, __LINE__); exit(1); } } while (0)
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
/* prevMax: 뿌리에서 이 노드의 부모까지 오는 길에서 본 가장 큰 값 */
int traversal(TreeNode *root, int prevMax)
{
if (root == NULL) return 0;
int good = (root->val >= prevMax) ? 1 : 0; /* 길 위에 나보다 큰 값이 없었다 */
int maxVal = good ? root->val : prevMax; /* 아래로 넘길 최댓값 */
return good + traversal(root->left, maxVal) + traversal(root->right, maxVal);
}
int goodNodes(TreeNode *root)
{
if (root == NULL) return 0;
return traversal(root, root->val); /* 뿌리는 늘 좋은 노드다 */
}
/* 변형 (가): 길 위에 나보다 작은 값이 없는 노드 세기. 부등호와 넘기는 값만 바뀐다 */
int countNoSmaller(TreeNode *root, int prevMin)
{
if (root == NULL) return 0;
int good = (root->val <= prevMin) ? 1 : 0;
int minVal = good ? root->val : prevMin;
return good + countNoSmaller(root->left, minVal) + countNoSmaller(root->right, minVal);
}
/* 변형 (나): 좋은 노드의 값을 모두 더하기 */
long sumGood(TreeNode *root, int prevMax)
{
if (root == NULL) return 0;
int good = (root->val >= prevMax);
int maxVal = good ? root->val : prevMax;
return (good ? root->val : 0) + sumGood(root->left, maxVal) + sumGood(root->right, maxVal);
}
/* ---- 테스트 ---- */
/* 느린 풀이: 노드마다 뿌리에서 그 노드까지의 길을 따로 구해 최댓값과 견준다 */
static int pathMaxTo(TreeNode *root, TreeNode *target, int best, int *found)
{
if (root == NULL || *found) return best;
int here = (root->val > best) ? root->val : best;
if (root == target) { *found = 1; return here; }
int l = pathMaxTo(root->left, target, here, found);
if (*found) return l;
return pathMaxTo(root->right, target, here, found);
}
static int slowCount(TreeNode *root, TreeNode *t)
{
if (t == NULL) return 0;
int found = 0;
int m = pathMaxTo(root, t, t->val, &found); /* 길 위의 최댓값(자기 포함) */
return (m == t->val) + slowCount(root, t->left) + slowCount(root, t->right);
}
static unsigned long long seed = 20261012ULL;
static unsigned rnd(void)
{
seed = seed * 6364136223846793005ULL + 1442695040888963407ULL;
return (unsigned)(seed >> 33);
}
static TreeNode pool[64];
static int used;
static TreeNode *randomTree(int budget)
{
if (budget <= 0 || used >= 64 || rnd() % 4 == 0) return NULL;
TreeNode *t = &pool[used++];
t->val = (int)(rnd() % 21) - 10; /* -10 ~ 10: 같은 값과 음수도 나온다 */
t->left = randomTree(budget - 1);
t->right = randomTree(budget - 1);
return t;
}
int main(void)
{
/* 1. 문서 예 1의 트리 */
TreeNode n1 = {1, NULL, NULL};
TreeNode n4 = {4, NULL, NULL};
TreeNode n3 = {3, &n1, &n4};
CHECK(goodNodes(&n3) == 2, "3(1, 4): 좋은 노드는 3과 4, 둘");
printf("[OK] 예 1의 트리 3(1, 4): %d\n", goodNodes(&n3));
/* 2. LeetCode 문제의 예 */
TreeNode a3 = {3, NULL, NULL}, a1 = {1, &a3, NULL};
TreeNode b1 = {1, NULL, NULL}, b5 = {5, NULL, NULL}, a4 = {4, &b1, &b5};
TreeNode ra = {3, &a1, &a4}; /* [3,1,4,3,null,1,5] */
CHECK(goodNodes(&ra) == 4, "[3,1,4,3,null,1,5] -> 4");
TreeNode c4 = {4, NULL, NULL}, c2 = {2, NULL, NULL}, c3 = {3, &c4, &c2};
TreeNode rb = {3, &c3, NULL}; /* [3,3,null,4,2] */
CHECK(goodNodes(&rb) == 3, "[3,3,null,4,2] -> 3");
TreeNode rc = {1, NULL, NULL};
CHECK(goodNodes(&rc) == 1, "[1] -> 1");
CHECK(goodNodes(NULL) == 0, "빈 트리 -> 0");
printf("[OK] LeetCode의 예 세 개: 4, 3, 1. 빈 트리는 0\n");
/* 변형 문제의 답 */
CHECK(countNoSmaller(&n3, n3.val) == 2, "변형 (가): 3(1, 4)에서 3과 1, 둘");
CHECK(countNoSmaller(&ra, ra.val) == 3, "변형 (가): [3,1,4,3,null,1,5]에서 뿌리 3, 왼쪽 1, 4 아래의 1, 셋");
CHECK(sumGood(&ra, ra.val) == 3 + 3 + 4 + 5, "변형 (나): 좋은 노드 3, 3, 4, 5의 합 15");
printf("[OK] 변형: 작은 값 판 2와 3, 좋은 노드의 합 15\n");
/* 3. 무작위 트리 */
for (int trial = 0; trial < 3000; trial++) {
used = 0;
TreeNode *root = randomTree(6);
CHECK(goodNodes(root) == slowCount(root, root), "느린 풀이와 같은 답");
}
printf("[OK] 무작위 트리 3000개: 느린 풀이와 같은 답\n");
printf("ALL CHECKS PASSED\n");
return 0;
}