Algorithm

[Algorithm] Modular Arithmetic - Caesar Cipher

montmer27 2026. 2. 23. 13:40

Problem

Caesar Cipher

Problem Description

A Caesar cipher is an encryption method where each letter in a given string is shifted by a fixed distance to produce a different letter. For example, shifting "AB" by 1 produces "BC", and shifting by 3 produces "DE". Shifting "z" by 1 wraps around to produce "a".

Given a string s and an integer n, complete the solution function to return the encrypted string by shifting each letter in s by n positions.


Constraints

  • Spaces remain as spaces regardless of the shift amount.
  • s consists of uppercase letters, lowercase letters, and spaces only.
  • The length of s is at most 8,000.
  • n is a natural number between 1 and 25, inclusive.

My Solution (Key Idea: manual boundary checks)

class Solution {
    public String solution(String s, int n) {
        
        char[] charArray = s.toCharArray();
        boolean isCapital;
        boolean isLowerCase;
        int displacement;
        
        for(int i = 0; i < charArray.length; i++) {
            
            isCapital = ((charArray[i] >= 65) && (charArray[i] <= 90));
            isLowerCase = ((charArray[i] >= 97) && charArray[i] <= 122);
            displacement = charArray[i] + n;
            
            if(charArray[i] == 32) continue;
            if((isCapital && displacement > 90) || (isLowerCase && displacement > 122)) {
                charArray[i] += n - 26;
                continue;
            }
            
            charArray[i] += n;
        }
        
        String answer = new String(charArray);
        return answer;
    }
}

/*
* First_attempt
* capitals first (65 ~ 90)
* smallcase later (97~122)
* if(position_after_push > 90), add(6) capitals become smallcase in case of overflow?
* do smallcases become capitals after overflow?
* then, if(position_after_push > 122), subtract(58)
* if(space_32), ignore
*/

/*
* Second_attempt
* Capitals remain capitals, so do lowercases.
* if(space_32), ignore
* if (isCapital && position_after_push > 90) OR
* if(isLowerCase && position_after_push > 122) char += n - 26
* char += n;
*/

 

Verdict: Correct but relies on raw ASCII values and manual boundary checks where standard library tools and modular arithmetic would be cleaner.

 

Strengths: 

* Logic is easy to read

 

* toCharArray() with in-place modification is memory-efficient and avoids repeated string concatenation.

Limitations (or Trade-offs):

* Too much reliance on magic numbers(65, 90, 122, etc.) -> hard to read, hard to maintain

 

* isCapital(), isLowerCase checks could come after the space check

 

* Lacking in idiomatic Java standard utility methods: could instead use Character.isUpperCase() and Character.isLowerCase()

 


Example (Key Idea: modular arithmetic)

class Caesar {
    String caesar(String s, int n) {
        String result = "";
        n = n % 26;
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (Character.isLowerCase(ch)) {
                ch = (char) ((ch - 'a' + n) % 26 + 'a');
            } else if (Character.isUpperCase(ch)) {
                ch = (char) ((ch - 'A' + n) % 26 + 'A');
            }
            result += ch;
        }
        return result;
    }
}

 

Verdict: Best core logic - clean, idiomatic, and generalizable. The StringBuilder issue is a clear and fixable gap.

Strengths:

* n % 26 upfront is a smart defensive move — it correctly handles cases where n >= 26 without any additional logic.

 

* Modular arithmetic (ch - 'a' + n) % 26 + 'a' is clean, elegant, and the standard approach for Caesar cipher wrap-around. No manual boundary checks needed.

 

* Character.isLowerCase() / Character.isUpperCase() are idiomatic and readable.


* Space handling is implicit — characters that are neither upper nor lowercase are passed through unchanged, which is clean.

Limitations (or Trade-offs):

* result += ch; in a loop is a significant performance issue. Each += creates a new String object, resulting in O(n2) memory allocations. StringBuilder should always be used for string construction inside loops.

 


Alternative Approaches (Key Idea: modular arithmetic + lambda)

class Caesar {
    public String caesar(String s, int _n) {
        return s.chars().map(c -> {
            int n = _n % 26;
            if (c >= 'a' && c <= 'z') {
                return 'a' + (c - 'a' + n) % 26;
            } else if (c >= 'A' && c <= 'Z') {
                return 'A' + (c - 'A' + n) % 26;
            } else {
                return c;
            }
        }).mapToObj(c -> String.valueOf((char)c))
          .reduce((a, b) -> a + b).orElse("");
    }
}

 

Verdict: Shows awareness of modern Java but has inefficiencies and style issues that suggest the functional approach was applied without full consideration of its tradeoffs.

Strengths:

* Functional stream-based approach is modern and expressive.

 

Limitations (or Trade-offs):

* _n % 26 computed inside the lambda means it is recalculated on every character iteration. This should be extracted once before the stream.

 

* _n as a parameter name is non-standard and unprofessional in Java. The underscore prefix convention does not belong in Java (it originates from other languages like Python or C++).

 

* Using raw character comparisons (c >= 'a') instead of Character utility methods reduces readability slightly.

 

* .reduce(...).orElse("") works but Collectors.joining() on a Stream<String> is the idiomatic replacement.

 

Lessons Learned

Should've understood the underlying mathmatical structure for the problem.
My logic compares each values against boundary values manually, but this only works when the value of n is smaller than 26. 
In other words, my code is not defensive enough.
Modular arithmetic here is not only effective in alphabet circulation but also in building defensive, generalizable code.

Next time, try finding the core of the problem and abstract the solution.
Furthermore, review if my code is defensive enough and generalizable.

 

My Github Repository for Solution Code

 

[level 1] Title: 시저 암호, Time: 0.68 ms, Memory: 69.9 MB -BaekjoonHub · ginsengcandy/Coding-Test-Practice@c93fde8

+ <p>어떤 문장의 각 알파벳을 일정한 거리만큼 밀어서 다른 알파벳으로 바꾸는 암호화 방식을 시저 암호라고 합니다. 예를 들어 "AB"는 1만큼 밀면 "BC"가 되고, 3만큼 밀면 "DE"가 됩니다. "z"는 1만

github.com

Problem Source

 

프로그래머스

SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr