2075. Decode the Slanted Ciphertext
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:
-
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. -
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
' '
-
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 messagerows
: an integer representing the number of rows used in encoding
Output:
- Return the decoded original string
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:
- First calculate the number of columns:
cols = len(encodedText) / rows
- Treat the encoded text as if it's already arranged in the matrix (since it was read row by row)
- 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 columny
by 1 - Continue until we either reach the bottom row or the rightmost column
- The character at matrix position
(x, y)
corresponds to indexx * 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:
-
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
- We know the number of
-
Iterate through each diagonal:
- Use a loop where
j
goes from0
tocols - 1
- Each
j
represents the starting column of a diagonal
- Use a loop where
-
Traverse each diagonal:
- For each starting column
j
, initialize position(x, y) = (0, j)
- Move diagonally down-right by incrementing both
x
andy
by 1 - Continue while
x < rows
andy < cols
(stay within matrix bounds)
- For each starting column
-
Extract characters:
- At each position
(x, y)
in the matrix, the corresponding character inencodedText
is at indexx * cols + y
- This formula works because
encodedText
was created by reading the matrix row by row - Append each character to our answer list
- At each position
-
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 EvaluatorExample 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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!