어떤 동기가 "어떻게 해야 문제 풀이 방법을 떠올려낼 수 있냐"고 물어봤다.
내 코가 석자지만 일단 생각나는대로 대답했는데,
스스로도 생각을 더 정리해보면 좋을만한 질문일 것 같아 몇 줄 적어본다.
1.
코딩 테스트용 문제를 푸는 것을 포함하여,
어떤 코드를 작성하더라도 이 두 가지는 필수적으로 알아야 한다.
이 두 가지를 잘 알아내려면 또 다시 두 가지가 필요하다.
2.
사고력은 단기간에 늘리기도 어렵고, 재능의 차이도 큰 영역이라고 생각한다.
다만 한 가지 확실한 것은, 정확한 방법으로 꾸준하게 노력한다면
조금씩이나마 늘기는 한다는 것이다. 마치 물방울이 바위를 뚫는 것처럼.
쉽고 편안한 문제들만 풀려고 하지 말고,
어렵고 불편한 문제들에서 어떻게든 단서를 찾아내어보려는 연습을 꾸준히 하다보면
사고력은 분명 성장한다.
어려운 책을 읽고, 어려운 문제를 풀고, 이해하기 위해 애쓰고,
똑똑한 사람들의 학습 방식을 모방하고, 스스로를 피드백하고,
하루종일 어떤 문제를 붙잡고 고민하다보면 어느새 성장한 자신을 만날 수 있다.
다만 그 성장의 폭이 자신이 기대하는 정도보다 작을 수 있고,
언제나 자신보다 적은 노력(혹은 높은 확률로, 더 열심히)으로
저 멀리 앞서가는 사람들이 존재한다는 것은 알고 있어야 하겠지만.
3.
경험은 사고력을 쌓아가는 과정에서 자연스럽게 따라온다.
인간의 뇌는 조금 변태같아서 힘들게 배울수록 기억을 잘 하기 때문이다.
기억을 잘 하면 다음에 비슷한 문제를 풀 때
풀이를 응용하거나 실마리를 잡을 수 있다.
무엇을 기억하느냐도 중요하다.
더 깊은 생각, 더 원론적인 생각을 할 수록
더 많은 문제에 적용할 수 있는 경험이 생긴다.
A -> Z로 결론이 났으니 끝. 이 아니라
A를 분해해보니 B가 되었고 C가 떠올랐는데 D를 시도해봤더니...
라는 식으로 사고 과정을 쪼개어
건질만한 것이 있는지, 틀린 점은 없었는지 점검해보아야 한다.
단순하게 '전부 쪼개어서 분석해본다' 정도로 생각하고 실천해보아도 좋을 것이다.
무엇을 어떻게 복기할지 생각하는 것마저도 재능의 영역이긴 하지만,
경험만큼은 방법론과 근성이 조금이나마 힘을 쓸 수 있다.
시간은 누구에게나 똑같이 주어지기 때문이다.
그마저도 재능 있는 사람들은 남들 5배속, 10배속으로 보긴 하지만.
4.
너무 재능 재능거리면서 암울하게 굴긴 했는데,
명백한 사실을 없는 것마냥 취급하고 싶진 않았다.
그래도 어쩌겠는가, 삶이 아무리 불공평하고 아니꼬와도
하고 싶은게 있으면 하는거고 해야 하는게 있으면 하는거지.
그리고 원래 인생의 모든게 그렇듯이 남 일을 말하기엔 쉽고
내 삶에 적용하긴 어려운 법인 것 같다.
나도 사고력이랑 경험 부족해서 더 열심히 해야되는데 안 함.
저번에 유튜브 링크 줬던거 오늘 보고 필기
: 이건 ~한 이유로 ~식으로 바꾸는게 좋습니다. (+근거 레퍼런스까지 첨부)
for (; root; root = root->left)
output[i++] = node;
연산이 이루어진 이후에 i가 증가하게 할 수 있다.
int maxHeight(BTNode *node)
{
// NULL이 아니라면 그냥 넘어간다
// 꼬리 노드의 자식이라면 return -1
// 모든 maxheight는 여기서 시작한다
if(node==NULL)
return -1;
// l과 r에서 maxheight 받아오기
int l = maxHeight(node->left);
int r = maxHeight(node->right);
// l과 r 중 최댓값 반환
// 자식이 있는 노드는 여기서 return한다
if (l>r){
return l + 1;
} else {
return r + 1;
}
}
옆자리 잘하는 동기 코드 본 다음 외워서 풀었다.
사고 과정이 어땠는지 물어봤는데 결국 경험인듯.
재귀 함수 많이 안 풀어보긴 했다.
다 풀면 재귀 함수 연습해봐야할듯.
int countOneChildNodes(BTNode *node)
{
if(node==NULL){
return 0;
}
int l=countOneChildNodes(node->left);
int r=countOneChildNodes(node->right);
return ((node->left != NULL) != (node->right != NULL)) ? l+r+1 : l+r;
}
이전 코드 살짝만 고침.
return ((node->left) ^ (node->right)) ? l+r+1 : l+r;
라고 해서 오류가 났었다.
코파일럿한테 고쳐달라고 요청해서
return ((node->left != NULL) != (node->right != NULL)) ? l+r+1 : l+r;
이렇게 바꿈.
node->left
는 포인터라서 그 자체로 비교하면 안된다고 한다.
int sumOfOddNodes(BTNode *node)
{
if(node==NULL){
return 0;
}
int l=sumOfOddNodes(node->left);
int r=sumOfOddNodes(node->right);
return (node->item % 2 != 0) ? l+r+(node->item) : l+r;
}
나머지도 다 비슷한 느낌.
void mirrorTree(BTNode *node)
{
if(node==NULL){
return;
}
BTNode *temp = node->left;
node->left = node->right;
node->right = temp;
mirrorTree(node->left);
mirrorTree(node->right);
return;
}
이 문제는 잠깐 헤매긴 했다.
void printSmallerValues(BTNode *node, int m)
{
if(node==NULL){
return;
}
if(node->item < m){
printf("%d ", node->item);
}
printSmallerValues(node->left,m);
printSmallerValues(node->right,m);
return;
}
int smallestValue(BTNode *node)
{
if(node==NULL){
return INT_MAX;
}
int l=smallestValue(node->left);
int r=smallestValue(node->right);
int n=node->item;
int min;
min = (l < r) ? l : r;
min = (min < n) ? min : n;
return min;
}
#include <limits.h>
사용함
int hasGreatGrandchild(BTNode *node)
{
// NULL이 아니라면 그냥 넘어간다
// 꼬리 노드의 자식이라면 return 0
// 모든 maxheight는 여기서 시작한다
if(node==NULL)
return 0;
// l과 r에서 maxheight 받아오기
int l = hasGreatGrandchild(node->left);
int r = hasGreatGrandchild(node->right);
// l과 r 중 최댓값 반환
// 자식이 있는 노드는 여기서 return한다
int max = (l>r) ? l+1 : r+1;
if(max >= 4) {
printf("%d", node->item);
}
return max;
}
void levelOrderTraversal(BSTNode* root)
{
// 루트 노드 큐에 넣는다
// while문 : 큐가 빌 때까지
// 노드를 뺀다
// 노드를 출력한다
// 노드 왼쪽 자식 오른쪽 자식 큐에 넣는다
Queue *Qptr = (Queue*)calloc(1,sizeof(Queue));
enqueue(&(Qptr->head), &(Qptr->tail), root);
while(!(isEmpty(Qptr->head))){
BSTNode *newPtr = dequeue(&(Qptr->head), &(Qptr->tail));
printf("%d ", newPtr->item);
if(newPtr->left != NULL)
enqueue(&(Qptr->head), &(Qptr->tail), newPtr->left);
if(newPtr->right != NULL)
enqueue(&(Qptr->head), &(Qptr->tail), newPtr->right);
}
free(Qptr);
}
미리 정의해둘 수 있으면 포인터 찍으면 되는데,
연산 결과를 더블 포인터에 넣으려고 할 땐 &를 붙여주면 된다.
void inOrderTraversal(BSTNode *root)
{
/* add your code here */
Stack *s;
BSTNode *node = root;
while(!(isEmpty(s)) || node != NULL){
if(node != NULL){
push(s, node);
node = node->left;
}
else {
node = pop(s);
printf("%d", node->item);
node = node->right;
}
}
return;
}
처음에 짠 오답 코드.
슈도 코드를 보고 거의 그대로 짰다.
왼쪽으로 무지성으로 내려가다가
NULL을 만나면 NULL의 부모 노드 꺼내고 오른쪽 형제 노드 탐색한다.
오른쪽 형제가 NULL이 아니면 또 무지성으로 내려가는데
오른쪽 형제가 NULL이면 스택에 들어있던 부모 노드의 부모 노드 꺼낸다.
이거 반복하면 중위 순회.
그런데 node->right에 값이 없을 때 segmentation fault가 일어났다.
방지하려고 if문을 넣었더니 while문이 제대로 돌아가질 않았다.
답답한 마음에 코파일럿으로 고쳐봤다.
void inOrderTraversal(BSTNode *root)
{
/* add your code here */
Stack *s;
BSTNode *node = root;
s = malloc(sizeof(Stack)); // Initialize the stack
s->top = NULL; // Set the top of the stack to NULL
while(!(isEmpty(s)) || node != NULL){
if(node != NULL){
push(s, node);
node = node->left;
}
else {
node = pop(s);
printf("%d ", node->item);
node = node->right;
}
}
free(s); // Free the memory allocated for the stack
return;
}
슈도 코드를 보고 짰던 로직 자체가 c에서는 안 통하는건가 생각했는데,
의외로 node->right의 문제가 아니라 애초에 스택에 malloc 하고 초기화 해줘야했다.
void preOrderIterative(BSTNode *root)
{
Stack *s;
BSTNode *node = root;
s = malloc(sizeof(Stack));
s->top = NULL;
push(s, node);
while(!(isEmpty(s))){
node = pop(s);
if(node != NULL){
push(s, node->right);
push(s, node->left);
}
}
free(s);
return;
}
이번엔 다른 거 안 보고 해봤는데 의외로 금방 구현이 됐다.
왜 한 번에 됨? 전위라서 바로 프린트하면 되니까 쉬웠는듯.
(아니면 테케만 통과 가능하고 나머지는 안되는 코드인 걸수도 있음.
다른 블로그 코드들 봤는데 다들 NULL if문 쓴거 좀 신경쓰임)
BSTNode* p = NULL;
Stack *s;
s = malloc(sizeof(Stack));
s->top = NULL;
while (1) {
for (; root; root = root->left) { // 왼쪽 아래까지 이동
push(s,root);
}
while (1) {
root = pop(s);
if (!root){
free(s);
return;
}
if (!root->right || p == root->right) {
printf("[%d] ", root->item);
p = root;
}
else {
push(s,root);
root = root->right;
break;
}
}
}