티스토리 뷰

코드카타

 

타겟 넘버

 

문제

 

해당 문제는 n개의 수로 타겟 넘버를 만들 수 있는 모든 경우의 수를 탐색해야 하며,

주어진 숫자의 개수가 그렇게 많지 않기에 완전탐색을 이용하여 풀 수 있다.

 

가능한 모든 덧셈 및 뺄셈 조합을 탐색해야 하기 때문에 DFS를 써야한다.

 

 

입출력 예시

 

 

풀이

class Solution {
    fun solution(numbers: IntArray, target: Int): Int {
        
        // i는 현재 숫자, sum은 현재까지의 합에 해당한다.
        fun dfs(i: Int, sum: Int): Int {
            
            // 모든 숫자를 사용한 경우, sum이 target과 같으면 방법 수를 1추가
            if (i == numbers.size) return if (sum == target) 1 else 0
            
            // 현재 숫자를 더하거나 빼는 두 가지 경우를 재귀로 탐색
            val add = dfs(i + 1, sum + numbers[i])
            val subtract = dfs(i + 1, sum - numbers[i])
            
            // 두 경우의 합 반환
            return add + subtract
        }
        
        // 첫번째 숫자부터 탐색을 시작한다.
        return dfs(0, 0)
    }
}

주어진 숫자 배열에서 모든 가능한 덧셈, 뺄셈 조합을 탐색해

타겟 넘버를 만들 수 있는 방법의 수를 계산하는 로직이다.

 

각 숫자는 더하거나 뺄 수 있는 두 가지 선택지가 있으므로,

각 숫자마다 2번의 재귀 호출이 발생시킨다.

 

 

dfs()의 호출 횟수

 

 

numbers의 사이즈가 n일 때, dfs()는 2^(n+1) − 1 호출된다.

 

레벨 0: 1번 호출
dfs(0, 0)

레벨 1: 2번 호출
dfs(1, 1), dfs(1, -1)

레벨 2: 4번 호출
dfs(2, 2), dfs(2, 0), dfs(2, 0), dfs(2, -2)

레벨 3: 8번 호출
dfs(3, 3), dfs(3, 1), dfs(3, 1), dfs(3, -1),
dfs(3, 1), dfs(3, -1), dfs(3, 1), dfs(3, -3)

n이 2일 때 dfs 호출이 어떤식으로 이루어지는지 단계별로 나열하였다.

 

dfs()는 레벨 n에서 2^n번의 호출되는데 레벨 0에서는

즉, 함수가 처음 호출될 때는 1번만 실행되기에 위와 같은 호출 수를 가진다.

 

 

재귀함수의 반환값

재귀 함수는 함수가 종료되는 시점에서 호출된 모든

함수의 반환값을 더한 값이 최종적으로 반환된다.

 

dfs(0, 0)을 호출하면 numbers 배열의 모든 숫자를 사용하여

만들 수 있는 모든 덧셈과 뺄셈 조합을 탐색하게 된다.

 

결과적으로, 만든 조합의 합이 타겟과 일치한다면 1을 반환하는 로직은

모든 수를 사용하여 만든 합이 타겟과 일치하는 경우의 수를 카운트하는 것에 해당한다.

 

 

회고

dfs의 동작방식을 이해하려면 재귀함수의 개념을 제데로

이해할 필요가 있었기에 재귀함수에 대해 따로 정리를 하였다.

 

재귀함수의 반환값을 알아내려면 수를 하나하나 대입하는 방법보다는

해당 함수가 어떤 규칙을 가지고 몇 번이 실행되는지를 파악하는 방법이 효율적이었다.

 

완전탐색은 모든 경우의 수를 탐색하는 무식한 방법이기에,

최악의 경우일 때도 시간 복잡도가 그리 크지 않을 때 사용할 수 있다.

 

그런데 만약 모든 경우의 수를 탐색하는 방식으로 풀어야되는 문제에서

최악의 경우 시간복잡도가 터무니없이 높을 때는 어떤 방법을 써야할까라는 의문점이 들었다.

 


 

앱개발 입문 개인 과제 필수 구현 사항

 

Lv1

 

구현 사항

- 새 프로젝트를 만들고 MainActivity의 이름을 SignInActivity로 바꿔주세요.
- 로고 이미지는 원하는 이미지로 넣어주세요.
- 아이디, 비밀번호를 입력 받는 EditText를 넣어주세요.(미리보기 글씨(플레이스 홀더) 포함)
- 비밀번호 EditText는 입력 내용이 가려져야 합니다.(●●● 처리)
- 로그인 버튼을 누르면 HomeActivity가 실행되도록 구현합니다. (Extra로 아이디를 넘겨줍니다.)
- 아이디/비밀번호 모두 입력 되어야만 로그인 버튼이 눌리도록 구현합니다.
  (“로그인 성공”이라는  토스트 메세지 출력하도록 구현)
- 아이디/비밀번호 중 하나라도 비어 있다면
  (“아이디/비밀번호를 확인해주세요” 라는 토스트 메세지가 출력되도록 구현합니다.)
- 회원가입 버튼을 누르면 SignUpActivity가 실행되도록 구현합니다.

 

 

액티비티 파일이름 변경

 

MainActivy 우클릭 => Refactor => Rename => 원하는 파일명으로 변경

 

 

로그인 액티비티 레이아웃 구현

 

ImageView, editTextId, Button 이용해서 로그인 화면을 구현한다.

 

텍스트를 제외한 모든 뷰의 양쪽 Constraints값을 0으로 줘서 중앙정렬을 진행하고,

텍스트뷰는 텍스트필드를 기준으로 좌측정렬시킨다.

 

다양한 디바이스에서 UI의 일관성을 유지하기 위해

뷰들의 여백은 4의 배수로 지정한다.

 

 

사용자 터치영역 크기

 

클릭 이벤트가 있는 뷰의 높이를 48dp 이상으로 지정해주지 않으면 오류가 난다.

(48dp가 구글이 권장하는 사용자 터치 영역 크기이다.)

 

 

화면 크기 기준으로 뷰 너비주기

android:layout_width="0dp"
app:layout_constraintWidth_percent="0.8"

ConstraintLayout내에서 부모 뷰를 기준으로 자식뷰의 크기를 설정하는 구문이다.

텍스트필드와 버튼의 너비는 디바이스 너비의 80퍼센트에 해당한다.

 

 

이미지 추가

화면에 이미지를 불러오고 싶다면 png파일을 res/drawable폴더 내에 추가하면 되는데

드래그 드롭이 아닌 복사 붙여넣기 형식으로 추가해주는게 좋다.

 

드래그 드롭으로 이미지를 추가하면 이미지 파일 자체가 drawable폴더로 이동한다.

 

 

앱 빌드 시 이미지가 보이지 않을 때

tools:srcCompat="@tools:sample/avatars"

tools:srcCompat으로 이미지를 불러오면 디자인 화면에서는

잘보이지만 앱 빌드 시 보이지 않게 되는 문제가 발생한다.

 

android:src="@drawable/lion"

그래서 android:src로 이미지 경로를 지정해줘야한다.

 

 

액티비티 파일

class SignInActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sign_in)

        val editTextId = findViewById<EditText>(R.id.editTextId)
        val editTextPw = findViewById<EditText>(R.id.editTextPw)

        val loginButton = findViewById<Button>(R.id.loginButton)
        val signUpButton = findViewById<Button>(R.id.signUpButton)

        loginButton.setOnClickListener {
            if (editTextId.text.isBlank() || editTextPw.text.isBlank()) {
                Toast.makeText(this, "아이디/비밀번호를 확인해주세요.", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "로그인 성공", Toast.LENGTH_SHORT).show()
                val intent = Intent(this, HomeActivity::class.java)
                intent.putExtra("id", editTextId.text.toString())
                startActivity(intent)
            }
        }

        signUpButton.setOnClickListener {
            val intent = Intent(this, SignUpActivity::class.java)
            startActivity(intent)
        }
    }
}

onCreate 내에서 필요한 뷰들을 불러와 각 버튼의 동작을 정의한다.

 

or연산을 활용해 모든 텍스트필드에 값을 입력해야만

홈 액티비티로 이동이 가능하도록 해준다.

 

TextView의 text는 String형이므로 id는 String형으로 넘겨준다.

 

 

isBlank()

isBlank()는 isEmpty()와 다르게 문자열에 공백만이 존재해도 true를 반환한다.

이 함수를 사용하면 사용자가 공백만 입력할 때 아무것도 입력하지 않은 것으로 간주할 수 있다.

 

 

 

Lv2

 

구현 사항

- SignpActivity를 생성해 주세요.
- 타이틀 이미지는 원하는 이미지로 넣어주세요.
- 이름, 아이디, 비밀번호 모두 입력 되었을 때만 회원가입 버튼이 눌리도록 구현합니다.
  (셋 중 하나라도 비어있으면 “입력되지 않은 정보가 있습니다” 라는 토스트 메세지를 출력)
- 비밀번호 EditText는 입력 내용이 가려져야 합니다.(●●● 처리)
- 회원가입 버튼이 눌리면 SignInActivity로 이동하도록 구현합니다. (finish 활용)

 

 

액티비티 생성

 

"src/main/java/com.example.패키지" 위치에 있는 폴더 우클릭

> New > Activity > Empty Views Activity

 

 

회원가입 액티비티 레이아웃 구현

 

로그인 화면 구현한 것처럼 똑같이 구현해주면 된다.

 

 

액티비티 파일

class SignUpActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sign_up)

        val editTextName = findViewById<EditText>(R.id.editTextName)
        val editTextId = findViewById<EditText>(R.id.editTextId)
        val editTextPw = findViewById<EditText>(R.id.editTextPw)

        val signUpButton = findViewById<Button>(R.id.signUpButton)

        signUpButton.setOnClickListener {
            if(editTextName.text.isBlank() || editTextId.text.isBlank() || editTextPw.text.isBlank()){
                Toast.makeText(this, "입력되지 않은 정보가 있습니다.", Toast.LENGTH_SHORT).show()
            }else{
                finish()
            }
        }
    }
}

finish()는 현재 액티비티를 종료하고 스택에서 제거하는 동작을 한다.

 

회원가입 화면에서 finish()를 호출하면 회원가입 화면은 종료되고,

스택에 남은 화면인 로그인 화면으로 이동하게 된다.

 

 

Lv3

 

구현 사항

- HomeActivity를 생성해 주세요.
- SignInActivity에서 받은 extra data(아이디)를 화면에 표시합니다.
- ImageView, TextView 외에 각종 Widget을 활용해 자유롭게 화면을 디자인 해주세요.
- 이름, 나이, MBTI 등 자기소개등이 들어가는 위젯을 자유롭게 디자인해주세요.
- 종료 버튼이 눌리면 SignInActivity로 이동하도록 구현합니다. (finish 활용)

 

 

홈 화면 레이아웃 구현

 

넘겨받은 id를 출력하기 위해 공백의 텍스트뷰를 

"아이디 -" 우측에 추가한다. (여백은 4dp)

 

 

가로 배치된 복수 위젯 중앙정렬

 

중앙정렬할 복수 위젯을 선택하고 Horizontal Chain을 생성한다.

그리고 생성한 체인의 스타일을 packed로 지정한다.

 

 

액티비티 파일

class HomeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        val textViewId = findViewById<TextView>(R.id.textViewId)
        textViewId.text = intent.getStringExtra("id")

        val finishButton = findViewById<Button>(R.id.finishButton)

        finishButton.setOnClickListener {
            finish()
        }
    }
}

intent.getStringExtra로 아이디를 받아온다.

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함