Facebook Pixel

2075. Decode the Slanted Ciphertext

MediumStringSimulation
Leetcode Link

Problem Description

You are given an encoded string encodedText that was created using a slanted transposition cipher with a fixed number of rows. Your task is to decode it back to the original string.

How the encoding works:

  1. The original text is placed into a matrix with a fixed number of rows. The number of columns is automatically determined so that the rightmost column won't be empty after placing all characters.

  2. Characters are placed in a diagonal pattern starting from the top-left corner:

    • Start at position (0, 0)
    • Move diagonally down-right: (0, 0) → (1, 1) → (2, 2)...
    • When you reach the bottom row or run out of characters, move to the next starting position in the first row: (0, 1), then (0, 2), and so on
    • Continue this diagonal filling pattern until all characters are placed
    • Any remaining empty cells in the matrix are filled with spaces ' '
  3. The encoded text is then created by reading the matrix row by row, from left to right.

Example: If originalText = "cipher" and rows = 3:

  • The matrix would be filled diagonally as:
    c   i   e
      h   p   r
  • Reading row by row gives: "c i e h p r""ch ie pr" (removing extra spaces between characters)

Given encodedText and rows, you need to reverse this process and return the original string. The original string does not have any trailing spaces.

Input:

  • encodedText: a string containing the encoded message
  • rows: an integer representing the number of rows used in encoding

Output:

  • Return the decoded original string
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To decode the message, we need to understand how the original text was placed in the matrix. The key observation is that the encoding placed characters diagonally, and then read them row by row. To reverse this, we need to reconstruct where each character in the original text would have been placed.

Let's think about the pattern of diagonal placement:

  • The first diagonal starts at (0, 0) and goes to (1, 1), (2, 2), etc.
  • The second diagonal starts at (0, 1) and goes to (1, 2), (2, 3), etc.
  • The third diagonal starts at (0, 2) and goes to (1, 3), (2, 4), etc.

Each diagonal starts from the first row at column j and moves down-right by incrementing both row and column by 1 each time.

Since we know the encoded text was created by reading the matrix row by row, we can:

  1. First calculate the number of columns: cols = len(encodedText) / rows
  2. Treat the encoded text as if it's already arranged in the matrix (since it was read row by row)
  3. To get the original text back, we need to read the diagonals in order

For each starting column j from 0 to cols-1:

  • Start at position (0, j)
  • Move diagonally: increment both row x and column y by 1
  • Continue until we either reach the bottom row or the rightmost column
  • The character at matrix position (x, y) corresponds to index x * cols + y in the encoded text

By reading all diagonals in order (starting from column 0, then column 1, etc.), we reconstruct the original text. The trailing spaces need to be removed since the original text didn't have them.

Solution Approach

Following the intuition, we implement the solution by simulating the diagonal reading process:

  1. Calculate the matrix dimensions:

    • We know the number of rows from the input
    • Calculate cols = len(encodedText) // rows
    • This gives us the complete matrix dimensions
  2. Iterate through each diagonal:

    • Use a loop where j goes from 0 to cols - 1
    • Each j represents the starting column of a diagonal
  3. Traverse each diagonal:

    • For each starting column j, initialize position (x, y) = (0, j)
    • Move diagonally down-right by incrementing both x and y by 1
    • Continue while x < rows and y < cols (stay within matrix bounds)
  4. Extract characters:

    • At each position (x, y) in the matrix, the corresponding character in encodedText is at index x * cols + y
    • This formula works because encodedText was created by reading the matrix row by row
    • Append each character to our answer list
  5. Clean up the result:

    • Join all characters in the answer list to form a string
    • Use rstrip() to remove trailing spaces, since the original text didn't have them

Implementation walkthrough:

ans = []  # Store decoded characters
cols = len(encodedText) // rows  # Calculate number of columns

for j in range(cols):  # For each starting column
    x, y = 0, j  # Start at first row, column j
    while x < rows and y < cols:  # Stay within bounds
        ans.append(encodedText[x * cols + y])  # Get character at (x,y)
        x, y = x + 1, y + 1  # Move diagonally down-right

return ''.join(ans).rstrip()  # Join and remove trailing spaces

The time complexity is O(n) where n is the length of encodedText, as we visit each character exactly once. The space complexity is O(n) for storing the answer.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through decoding encodedText = "ch ie pr" with rows = 3.

Step 1: Calculate matrix dimensions

  • rows = 3 (given)
  • cols = len("ch ie pr") // 3 = 9 // 3 = 3
  • So we have a 3×3 matrix

Step 2: Visualize the encoded text as a matrix Since the encoded text was read row by row, we can arrange it back into the matrix:

Position: (0,0) (0,1) (0,2)
Row 0:    'c'   'h'   ' '

Position: (1,0) (1,1) (1,2)  
Row 1:    'i'   'e'   ' '

Position: (2,0) (2,1) (2,2)
Row 2:    'p'   'r'   ' '

Step 3: Read diagonals to decode

Now we read the diagonals starting from each column in the first row:

Diagonal starting at column 0 (j=0):

  • Start at (0, 0): encodedText[0*3 + 0] = encodedText[0] = 'c'
  • Move to (1, 1): encodedText[1*3 + 1] = encodedText[4] = 'e'
  • Move to (2, 2): encodedText[2*3 + 2] = encodedText[8] = ' '
  • Collected: ['c', 'e', ' ']

Diagonal starting at column 1 (j=1):

  • Start at (0, 1): encodedText[0*3 + 1] = encodedText[1] = 'h'
  • Move to (1, 2): encodedText[1*3 + 2] = encodedText[5] = ' '
  • Move to (2, 3): Out of bounds (y=3 >= cols=3), stop
  • Collected: ['h', ' ']

Diagonal starting at column 2 (j=2):

  • Start at (0, 2): encodedText[0*3 + 2] = encodedText[2] = ' '
  • Move to (1, 3): Out of bounds (y=3 >= cols=3), stop
  • Collected: [' ']

Step 4: Combine and clean

  • All collected characters: ['c', 'e', ' ', 'h', ' ', ' ']
  • Join: "ce h "
  • Remove trailing spaces: "ce h"

Wait, this doesn't match our expected output. Let me reconsider the problem...

Actually, looking at the original encoding example more carefully:

c   i   e
  h   p   r

The encoded text should be "c ie hpr" when reading row by row (with spaces preserved).

Let me redo with encodedText = "c ie hpr" and rows = 2:

Step 1: Calculate dimensions

  • rows = 2
  • cols = 8 // 2 = 4

Step 2: Matrix representation

Row 0: 'c' ' ' 'i' 'e'
Row 1: ' ' 'h' 'p' 'r'

Step 3: Read diagonals

Diagonal from column 0:

  • (0,0): encodedText[0] = 'c'
  • (1,1): encodedText[5] = 'h'
  • Collected: ['c', 'h']

Diagonal from column 1:

  • (0,1): encodedText[1] = ' '
  • (1,2): encodedText[6] = 'p'
  • Collected: [' ', 'p']

Diagonal from column 2:

  • (0,2): encodedText[2] = 'i'
  • (1,3): encodedText[7] = 'r'
  • Collected: ['i', 'r']

Diagonal from column 3:

  • (0,3): encodedText[3] = 'e'
  • (1,4): Out of bounds
  • Collected: ['e']

Step 4: Combine

  • All characters: ['c', 'h', ' ', 'p', 'i', 'r', 'e']
  • Join: "ch pire"
  • Remove trailing spaces: "ch pire"

Hmm, still not quite right. Let me reconsider with the correct understanding that we need "cipher" as output.

Actually, rereading the problem, the spaces in the encoded text are part of the encoding. Let me use a cleaner example:

Example: encodedText = "ci ehr p" with rows = 2

Step 1: cols = 8 // 2 = 4

Step 2: Matrix:

Row 0: 'c' 'i' ' ' 'e'
Row 1: 'h' 'r' ' ' 'p'

Step 3: Read diagonals:

  • Column 0: (0,0)='c', (1,1)='r' → ['c','r']
  • Column 1: (0,1)='i', (1,2)=' ' → ['i',' ']
  • Column 2: (0,2)=' ', (1,3)='p' → [' ','p']
  • Column 3: (0,3)='e' → ['e']

Step 4: Join: "cri pe" → after rstrip: "cri pe" (only trailing spaces removed)

Let me try once more with a clearer example:

Example: encodedText = "hloel" with rows = 2

This encoded text represents "hello" encoded with 2 rows.

Step 1: cols = 5 // 2 = 2 (integer division)

Wait, that can't be right. Let me recalculate: if we have 5 characters and 2 rows, we need 3 columns (ceiling of 5/2).

Actually, the problem states the encoded text already includes padding spaces. So let's say encodedText = "hlo el" (6 characters) with rows = 2.

Step 1: cols = 6 // 2 = 3

Step 2: Matrix:

Row 0: 'h' 'l' 'o'
Row 1: ' ' 'e' 'l'

Step 3: Read diagonals:

  • Column 0: (0,0)='h', (1,1)='e' → ['h','e']
  • Column 1: (0,1)='l', (1,2)='l' → ['l','l']
  • Column 2: (0,2)='o' → ['o']

Step 4: Join: "hello" → after rstrip: "hello"

This gives us the correct decoded result!

Solution Implementation

1class Solution:
2    def decodeCiphertext(self, encodedText: str, rows: int) -> str:
3        # Initialize list to store decoded characters
4        result = []
5      
6        # Calculate number of columns in the matrix
7        cols = len(encodedText) // rows
8      
9        # Iterate through each starting column position
10        for start_col in range(cols):
11            # Initialize position for diagonal traversal
12            row_pos = 0
13            col_pos = start_col
14          
15            # Traverse diagonally from current starting column
16            while row_pos < rows and col_pos < cols:
17                # Convert 2D position to 1D index and append character
18                index = row_pos * cols + col_pos
19                result.append(encodedText[index])
20              
21                # Move diagonally down-right
22                row_pos += 1
23                col_pos += 1
24      
25        # Join characters and remove trailing spaces
26        return ''.join(result).rstrip()
27
1class Solution {
2    public String decodeCiphertext(String encodedText, int rows) {
3        // Initialize result string builder
4        StringBuilder result = new StringBuilder();
5      
6        // Calculate number of columns in the matrix
7        int columns = encodedText.length() / rows;
8      
9        // Iterate through each diagonal starting from the first row
10        // Each diagonal starts at position (0, j) where j is the column index
11        for (int startColumn = 0; startColumn < columns; ++startColumn) {
12            // Traverse the diagonal from (0, startColumn) going down-right
13            // row increments by 1, column increments by 1
14            for (int row = 0, column = startColumn; 
15                 row < rows && column < columns; 
16                 ++row, ++column) {
17                // Calculate the position in the original string
18                // Position = row * columns + column (row-major order)
19                int charPosition = row * columns + column;
20                result.append(encodedText.charAt(charPosition));
21            }
22        }
23      
24        // Remove trailing spaces from the decoded text
25        while (result.length() > 0 && result.charAt(result.length() - 1) == ' ') {
26            result.deleteCharAt(result.length() - 1);
27        }
28      
29        // Return the final decoded string
30        return result.toString();
31    }
32}
33
1class Solution {
2public:
3    string decodeCiphertext(string encodedText, int rows) {
4        string result;
5      
6        // Calculate number of columns in the matrix
7        int columns = encodedText.size() / rows;
8      
9        // Traverse each diagonal starting from the first row
10        // Each diagonal starts at position (0, j) where j is the starting column
11        for (int startCol = 0; startCol < columns; ++startCol) {
12            // Traverse the diagonal from (0, startCol) going down-right
13            // row increases by 1, column increases by 1 for each step
14            int row = 0;
15            int col = startCol;
16          
17            while (row < rows && col < columns) {
18                // Convert 2D coordinates (row, col) to 1D index in the string
19                // Index formula: row * columns + col
20                int index = row * columns + col;
21                result += encodedText[index];
22              
23                // Move to next position in the diagonal
24                row++;
25                col++;
26            }
27        }
28      
29        // Remove trailing spaces from the decoded message
30        while (!result.empty() && result.back() == ' ') {
31            result.pop_back();
32        }
33      
34        return result;
35    }
36};
37
1/**
2 * Decodes a ciphertext that was encoded in a diagonal pattern across a matrix
3 * @param encodedText - The encoded string to decode
4 * @param rows - Number of rows in the encoding matrix
5 * @returns The decoded plaintext string with trailing spaces removed
6 */
7function decodeCiphertext(encodedText: string, rows: number): string {
8    // Calculate the number of columns in the matrix
9    const columns: number = Math.ceil(encodedText.length / rows);
10  
11    // Array to store decoded characters
12    const decodedCharacters: string[] = [];
13  
14    // Iterate through each diagonal starting from top row
15    // k represents the starting column for each diagonal
16    for (let startColumn: number = 0; startColumn <= columns; startColumn++) {
17        // Traverse the diagonal from (0, startColumn) moving down-right
18        let row: number = 0;
19        let column: number = startColumn;
20      
21        while (row < rows && column < columns) {
22            // Calculate the index in the original encoded string
23            const characterIndex: number = row * columns + column;
24          
25            // Add the character at this position to the result
26            decodedCharacters.push(encodedText[characterIndex]);
27          
28            // Move to the next position in the diagonal (down and right)
29            row++;
30            column++;
31        }
32    }
33  
34    // Join all characters and remove trailing whitespace
35    return decodedCharacters.join('').trimEnd();
36}
37

Time and Space Complexity

The time complexity is O(n), where n is the length of the string encodedText.

The algorithm iterates through each column (there are cols columns), and for each column, it traverses diagonally. Each diagonal traversal visits at most min(rows, cols - j) characters for column j. The total number of characters visited across all diagonals is exactly n (the length of encodedText), as each character is visited at most once. Additionally, the rstrip() operation at the end takes O(n) time in the worst case. Therefore, the overall time complexity is O(n).

The space complexity is O(n), where n is the length of the string encodedText.

The ans list stores all the characters from the diagonal traversal, which in the worst case contains all n characters from encodedText. The final join operation creates a new string of length at most n. Thus, the total space complexity is O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle Edge Cases with Empty Strings or Single Row

A common mistake is not properly handling edge cases where encodedText is empty or when rows = 1.

Pitfall Example:

# This could fail if encodedText is empty
cols = len(encodedText) // rows  # Division by zero if rows = 0

Solution: Add edge case checks at the beginning:

if not encodedText or rows == 0:
    return ""
if rows == 1:
    return encodedText.rstrip()

2. Incorrect Index Calculation for 2D to 1D Mapping

Many developers mistakenly use the wrong formula to convert 2D coordinates to 1D index.

Pitfall Example:

# Wrong formulas that might seem intuitive
index = row_pos + col_pos * rows  # Incorrect - treats it column-major
index = row_pos * rows + col_pos  # Incorrect - uses rows instead of cols

Solution: The correct formula for row-major order is:

index = row_pos * cols + col_pos  # Correct formula

3. Not Removing Trailing Spaces from Final Result

The problem explicitly states that the original string doesn't have trailing spaces, but the matrix might have been padded with spaces during encoding.

Pitfall Example:

# Forgetting to strip trailing spaces
return ''.join(result)  # May include trailing spaces from padding

Solution: Always use rstrip() to remove trailing spaces:

return ''.join(result).rstrip()

4. Misunderstanding the Diagonal Pattern Direction

Some might implement the wrong diagonal direction or pattern.

Pitfall Example:

# Moving in wrong direction (up-right instead of down-right)
row_pos -= 1  # Wrong direction
col_pos += 1

# Or starting from wrong positions
for start_row in range(rows):  # Wrong - should start from columns
    row_pos = start_row
    col_pos = 0

Solution: Ensure the diagonal pattern starts from the first row and moves down-right:

for start_col in range(cols):  # Start from first row, different columns
    row_pos = 0
    col_pos = start_col
    while row_pos < rows and col_pos < cols:
        # Process character
        row_pos += 1  # Move down
        col_pos += 1  # Move right
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More