[25.03.19] TIL(달리기반 문제)

설민우·2025년 3월 19일

<1. 홀수출력>

코드 확인
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
    static void Main(string[] args)
    {
        int i;
        for (i =1; i <=100; i++)
            CheckOddNum(i);

        i = 0; // 초기화
        while (i < 100)
        {
            i++;
            CheckOddNum(i);
        }

        i = 0;
        do
        {
            i++;
            CheckOddNum(i);
        } while (i < 100);
    }
    public static void CheckOddNum(int i)
    {
        if (i % 2 != 0)
            Console.WriteLine(i);
    }
}

달리기반 첫번째 문제로 for, while, do-While의 사용을 체크하는 문제입니다. while문과 do-while문의 차이는 내부의 실행문을 먼저 진행하느냐, 조건문을 먼저 확인하느냐로 갈리게 됩니다. 그것 이외에는 초기화만 잘 해 주면 어렵지 않게 문제를 풀이할 수 있습니다.

<2. 배열을 사용한 합계 및 평균 계산>

코드 확인
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
    static void Main(string[] args)
    {
        //linq를 사용해 받은 입력을 바로 string -> int로 변환 후 배열로 정리
        int[] input = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
        CheckSumAverage(input);
    }
    public static void CheckSumAverage(int[] arr)
    {
        int sum = 0;

        foreach (int i in arr)
            sum += i;
        Console.WriteLine($"Sum: {sum}");
        Console.WriteLine($"Average: {sum / arr.Length}");
    }
}

배열 순회를 통해서 내부 int 값의 합과 평균을 계산하는 문제입니다. 최초에는 입력을 받을 때 string 배열로 Split 합수를 이용해 받고 다시 for 문을 돌면서 int.Parse 를 해 주었으나, Linq를 사용해서 좀더 간결하게 한줄로 입력을 받도록 수정했습니다. Linq는 이 외에도 사용빈도가 높으니 많은 관심이 필요할 것으로 생각됩니다.

입력을 받고 나면 그 이후에는 크게 어려움 없이 답을 낼 수 있으나, 제출해야 하는 평균이 정수값인지 실수값인지에 따라 세심한 확인이 필요할 것으로 보입니다.

+) 추가적인 자주 사용되는 Linq 함수들

OrderBy : 컬렉션을 오름차순(혹은 내림차순) 정렬합니다.
ThenBy : 앞선 정렬 이후 추가적인 정렬 조건을 설정합니다.
GroupBy : 특정 기준에 따라 그뤂으로 묶습니다.
Select : 컬렉션의 각요소를 다른 형태로 변환합니다.
Where : 컬렉션에서 조건에 맞는 요소들을 필터링 합니다.
To~~ : ~~에 해당하는 형태로 변환합니다. (딕셔너리, 배열, 리스트)
Skip : 컬렉션에서 특정 구간의요소를 건너뛰어야 할때 사용 가능합니다.


<3. 팩토리얼 계산>

코드 확인
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
    static void Main(string[] args)
    {
        int input = int.Parse(Console.ReadLine());
        Factorial(input);
    }

    public static void Factorial(int num)
    {
        int ans = 1;
        for(int i = 1; i <= num; i++)
            ans *= i;

        Console.WriteLine($"Factorial of {num} is {ans}");
    }
}


가장 간단한 방식으로 for문을 이용해서 구현한 팩토리얼 함수 이것 이외에도 여러가지 방법이 있는데, 정리하자면 아래와 같습니다.

반복문 이용 : 단순하고 오버플로우 걱정이 없으나 O(n)의 시간복잡도

DP를 이용:
이전 값을 저장해서 사용하면 중복 연산을 줄일 수 있으나, 메모리를 추가로 사용

재귀 :
스택 오버플로우 문제가 발생할 수 있습니다.

미리 계산한 값 사용 :
미리 배열에 팩토리얼 값을 저장하고 불러오기, O(1)의 시간 복잡도를 가지나, 숫자가 너무 크면 최초 계산에 시간이 많이 걸릴 수 있고, 메모리를 추가적으로 사용합니다.

추가적으로 숫자가 너무 큰 경우에는 "스털링 근사법" 을 사용해서 O(1)에 계산할수 있으나, 값이 완벽하게 정확하지는 않습니다.

정리하자면 아래와 같다.

단순한 경우 반복문(O(n)) 방식 추천
여러 번 계산해야 한다면 메모이제이션(O(n)) 사용
n이 매우 크다면 스털링 근사법(O(1)) 사용
매우 작은 범위(n ≤ 20)에서는 미리 계산한 값 사용(O(1)) 가능


<4. 숫자 맞추기 게임>

코드 확인
using System;
using System.Collections.Generic;
using System.Linq;

public class Solution
{
    static void Main(string[] args)
    {
        Random randInt = new Random();
        int ans = randInt.Next(1, 100);
        while (true)
        {
            Console.Write("Enter your guess (1-100):");
            int input = int.Parse(Console.ReadLine());
            if (input > ans)
                Console.WriteLine("Too high! Try again.");
            else if (input < ans)
                Console.WriteLine("Too low! Try again.");
            else 
            {
                Console.WriteLine("Congratulations! You guessed the number.");
                break;
            }
        }
    }
}

While문 과 if 문을 통해서 조건을 계속해서 확인하고, else문( == ) 에 해당하는 값이 나왔을때 break를 통해 while문을 탈출하도록 작성했습니다. 이때, 게임이 끝나도 계속해서 새로운 숫자로 플레이하고 싶다면, 별도의 함수로 구분해두고, 재귀 형식으로 구현하면 무한히 플레이할 수 있도록 수정할 수 있습니다.


<5. 이중 반복문을 이용한 구구단 출력>

코드 확인
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

public class Solution
{
    static void Main(string[] args)
    {
        Console.WriteLine("Select print mode : 0 = vertical, 1 = horizontal");
        PrintMultiplication(int.Parse(Console.ReadLine()));
    }

    static void PrintMultiplication(int mode)
    {
        StringBuilder sb = new StringBuilder();
        if (mode != 0 && mode != 1)
        {
            Console.WriteLine("Mode input is Not 0 or 1. Plz input correct num");
            PrintMultiplication(int.Parse(Console.ReadLine()));
        }
        else
        {
            // 반복되는 write를 제어하는 StringBuilder 사용
            for (int i = 2; i <= 9; i++)
            {
                for (int j = 1; j <= 9; j++)
                {
                    if (mode == 0 && j != 1)
                        sb.Append($"{j} x {i} = {i * j,-4}");
                    if (mode == 1)
                        sb.Append($"{i} x {j} = {i * j,-4}");
                }
                sb.Append("\n");
            }
        }
        Console.WriteLine(sb);
    }
}

먼저 0과 1로 세로로 출력할지 가로로 출력할지 정하고, 이에 해당하지 않는 숫자가 들어오면 재귀 형태로 다시 자신을 불러오도록 예외처리를 진행했습니다. 그 다음 이중 for문을 돌면서 stringbulider를 통해 해답을 추가해주고 마지막에 출력하는 방식을 선택했습니다. string 값에 +를 사용하여 값을 너무 많이 추가하면 "불변 문자열 연결 문제"가 발생할수 있기 때문입니다. string은 불변(Immutable) 객체이므로, + 연산을 사용할 때마다 새로운 문자열 인스턴스가 생성되는데 이는 새로운 메모리 할당을 불러오고 나중에 GC 부담을 키울 수 있습니다.(GC는 할당 해제를 말하고, 이를 진행할때 시스템 자체가 멈추기 떄문에 버벅이는 문제가 생길 수 있습니다)

또한 출력시 미관을 위해서 포맷팅을 이용해 string 값의 위치를 정렬시켜주었습니다.

<6. 배열 요소의 최대값과 최소값 찾기>

코드 확인
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

public class Solution
{
    static int max = int.MinValue;
    static int min = int.MaxValue;
    static void Main(string[] args)
    {
        int[] inputArr = Console.ReadLine().Split(' ').Select(int.Parse).ToArray();
        CheckMaxMin(inputArr);
        Console.WriteLine($"Min : {min}, Max : {max}");
    }

    static void CheckMaxMin(int[] arr)
    {
        for(int i =0; i < arr.Length; i++)
        {
            if (arr[i] > max)
                max = arr[i];

            if (arr[i]< min)
                min = arr[i];
        }
    }
}

가장 기본적인 방법을 이용해서 해당 답을 구해보자면 for문을 모두 돌면서 최대 최소값을 계속해서 갱신해주는 방법이 있습니다. 이는 시간복잡도가 O(n)시간 걸리게 됩니다. 이것 이외의 방법으로는 아래와 같은 추가적인 방법과 장단점들이 존재합니다.

Array.Sort() 사용 :
배열을 정렬시켜서 첫번째 요소와 마지막 요소를 확인하면 됩니다. O(NLogN)의 시간복잡도를 가져 큰 배열에는 비효율적입니다.

Linq 사용:
numerable.Min() / Enumerable.Max() 사용하여 해답을 찾습니다. 이는 코드를 간결화할때 좋으나 내부에서 foreach문이 돌아가므로 시간 복잡도는 동일합니다.
또한 Aggregate 를 통해서도 정렬할수 있는데, 이는 깔끔하게 한줄로 모든 기능을 구현할 수 있으나 가독성이 떨어질 수 있습니다.

<7. 행맨 게임>

코드 확인
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

public class Solution
{
    static void Main(string[] args)
    {
        char[] secretWord = new char[] { 'h', 'a', 'n', 'g', 'm', 'a', 'n' };
        int len = secretWord.Length;
        char[] guessWord = new char[len];

        for (int i = 0; i < len; i++) // 초기화
            guessWord[i] = '_';

        int attempts = 6;
        bool wordGuessed =false;
        int leftWord = len;

        while (true)
        {
            if(attempts == 0)
            {
               Console.WriteLine( "패배하였습니다.");
                break;
            }

            StringBuilder sb = new StringBuilder();
            foreach(var value in guessWord)
                sb.Append(value + " ");

            Console.WriteLine($"현재 완성된 단어는 {sb} 입니다");
            Console.WriteLine($"남은 기회는 {attempts}회 입니다");
            Console.Write("확인하고 싶은 단어를 입력하세요 : ");

            char checkChar = char.Parse(Console.ReadLine());

            for(int i =0; i< len; i++)
            {
                if (secretWord[i] == checkChar)
                {
                    guessWord[i] = checkChar;
                    leftWord -= 1;
                    wordGuessed = true;
                }
            }

            if (!wordGuessed)
            {
                Console.WriteLine($"\n{checkChar}는 없는 문자입니다.\n");
                attempts--;
            }
            else
                Console.WriteLine($"\n{checkChar}는 있는 문자입니다.\n");

            wordGuessed = false;

            if (leftWord == 0)
            {
                Console.WriteLine($"축하합니다!");
                break;
            }
        }
    }
}

코드는 길지만 방법 자체는 간단하게 풀이 할 수 있습니다. for문을 통해서 제가 체크한 문자값이 존재하는 지 확인하고 이를 출력해서 보여줍니다. 여기에 추가적으로 예외처리 해줄 부분이 있다면 이미 찾았던 문자열에 대해서 재 입력시에 이미 찾은 문자열이라는 것을 list를 이용해서 구현 할 수 있을것으로 보입니다.
profile
클라이언트 개발자를 지망하고 있습니다.

0개의 댓글