Every computer program is a solution (not the solution) to some problem. Before we begin writing code, we must understand the problem we are attempting to solve and know how to solve it. Simple problems are often solved directly with basic programming operations, but solutions for more complex problems require a sequence of operations. Together, these operations form an algorithm or recipe for solving a specific problem. Some well-known problems have equally well-known algorithmic solutions, but computer scientists must often create algorithms for smaller, more unique problems. The palindrome-number problem is a puzzle that has three parts or sub-problems. Two parts are simple, but solving the third part will require us to develop a new algorithm.
Problem:
Find the smallest positive integer that, when squared, produces a palindrome of at least six digits in length:abccba
orabcdcba
(wherea
is not 0).
Solution Outline:
We can decompose the palindrome-number problem as follows:
- Generate a consistent list of candidate numbers that might solve the problem. To avoid skipping a potential solution, count sequentially. End the search when a candidate satisfies all the requirements.
- Set the first candidate number to 1
- Increment the candidate number: number = number + 1
- Square the number.
- Convert the squared number into a string (e.g., convert the integer 123 into the string "123"), and use string operations to identify the first candidate to satisfy the three problem requirements.
- Organize the requirements tests from the easiest/fastest to the hardest/slowest, and stop testing a candidate whenever it fails one of the tests:
- The squared number is at least six digits long.
- The squared number does not begin or end with a '0'.
- The digits of the squared number form a palindrome - identifying a palindrome requires us to develop a new algorithm.
Digits and characters are significant when determining if a string is a palindrome. However, the determination does not consider spaces, punctuation, and character-case (upper or lower). Although the potential palindromes arising in the palindrome-number problem only contain digits, we can design a general palindrome-checking algorithm without increasing its complexity by imposing three "reasonable" preconditions on the potential palindrome:
The programming examples will include a more general program that deals with the spaces, punctuation, and case, but the following algorithms focus on the simple case. How we think about a problem impacts how we design the solution. Two different solutions, taking two different perspectives, demonstrate this observation. Each perspective leads to a different algorithm. Although the two algorithms seem different, the programs derived from them will contain remarkably similar code fragments.
Imagine you will solve the problem without a computer - how would you do it, especially if the string is long? I imagine I could write the string on a whiteboard, then point at and compare the characters at each end. I would keep moving my fingers together as long as the characters matched. The string is a palindrome if I reach the middle without any mismatches. The middle character is irrelevant and skipped if the string has an odd number of characters. Seeing a picture helps to make this clear.
Our algorithm must also identify when a string is not a palindrome.
If we change our perspective by imagining that the program stores the candidate palindrome in computer memory as a string (either a C-string or an instance of the string class), we can develop a different solution. The very definition of a palindrome is a string that reads the same forwards and backward. Another solution, based on the definition, creates and compares two strings. We copy our candidate string, reverse it, and then compare it to the original string. If the two strings are the same (i.e., equal), then the candidate string is a palindrome; if the two strings are not the same, then the candidate is not a palindrome.
Again, it is essential that the algorithm correctly identifies a non-palindrome.
If we view the data as too large to manipulate easily, we develop an algorithm that examines the data in small parts. This situation often occurs when the data is too large to fit in main memory. Our "finger" algorithm demonstrates this view. Although the palindrome string does fit in memory, we imagined it as written on a whiteboard, simulating the difficulty of manipulating the whole string. Alternatively, if the data is small enough to fit in main memory, we can develop algorithms that reorganize the data. Our "reverse" algorithm demonstrates this approach.
Our next step is to design an overall solution for the palindrome-number problem. Forming a palindrome is only one of three constraints the problem imposes on the solution. Any algorithm correctly identifying a palindrome and rejecting a non-palindrome will complete this constraint. A sequence of tests ensures that a candidate number satisfies each constraint or reports a failure and skips the remaining tests. We can only declare that we have found a solution number when all tests are satisfied.
We organize the constraint tests based on a common problem-solving technique called case analysis. So, our approach will systematically generate candidate numbers, square them, convert the numbers to strings, and test each string to determine whether it is a palindrome. The adjacent figure and the palindrome algorithms developed above outline the solution's logic but stop short of implementing a program. One advantage of solving a problem before you begin writing code is that the solution leaves you free to choose the language and the implementation details. Even after choosing C++ as our implementation language, we can still choose which string representation to use. We base our first solution on C-strings.