Two types of improvements can be made:
- Prune the search space
- optimize the checking step for each item in the search space
First type is a first order improvement that can lead to exponential improvement in the runtime and any reduction of this step will also remove the need to do the checking step.
Second type is a second order improvement which at most will lead to a linear improvement in the runtime.
For first type improvement, I'd do the following which makes sure I only loop through numbers which are larger than the palindrome I've already found (therefore no checking is needed to see if new palindrome is larger.. note that if you have this check, by the time you get to it, you have already lost the exponential speedup potential of pruning the search space and this check only provides at most a linear speedup.. the below approach actually prunes the search space):
find_largest_pali():
largest_pali = 0;
for x from 999 downto 100:
for y from 999 downto max(x, largest_pali/x):
largest_pali = return_larger_pali(x*y, largest_pali);
I am not going to focus on any number theoretic improvements (like knowing certain palindromes or what palindromes factor into) since this is a programming assignment not a math problem.
Now for the second order type improvement:
Arithmetic operations are much much much faster than any string manipulation. So stick to math operations (divide, mod, compare, e
tc.)
A small helper function to extract a digit:
digit(number, position)
return (number/position) mod 10;
Now the palindrome checking function: note that compare is abandoned if any digit fails the checking. With the string approach, yo
u need to make full string translatioons and reversing to decide if a palindrome is found)
return_larger_pali(candidate_pali, prev_pali)
if (candidate_pali between 10000 and 99999)
if( digit(candidate_pali, 1) != digit(candidate_pali, 10000)) return prev_pali;
if( digit(candidate_pali, 10) != digit(candidate_pali, 1000 )) return prev_pali;
// no need to check digit in position 100
else
if (candidate_pali between 100000 and 999999)
if( digit(candidate_pali, 1) != digit(candidate_pali, 100000)) return prev_pali;
if( digit(candidate_pali, 10) != digit(candidate_pali, 10000 )) return prev_pali;
if( digit(candidate_pali, 100) != digit(candidate_pali, 1000 )) return prev_pali;
end if
return candidate_pali
Another approach which will probably be more suitable for larger range of numbers is to precompute the digit-reverse (e,g, digit-reverse of 123 is 321) for numbers less than 1000 and use a lookup method to check if the upper half digits of a number is equal to the digit-reverse of its lower half (hence a palindrome). This approach uses arrays and memory ops whose overhead may waste any gains made by the lookup method so some benchmarking is needed before one can decide between the two approaches.
Here's an outline for a function using a lookup table:
digit(number, position)
return (number/position) mod 10;
find_largest_pali_using_lookup():
// prcompute digit-reverse
lookup_arr = array[1000];
for i from 999 downto 0
lookup_arr[i] = 100*digit(i,1)+10*digit(i,10)+digit(i,100);
largest_pali = 0;
for x from 999 downto 100:
for y from 999 downto max(x, largest_pali/x):
largest_pali = return_larger_pali_using_lookup(x*y, largest_pali, &lookup_arr);
return_larger_pali_using_lookup(candidate_pali, prev_pali, lookup_arr)
if (candidate_pali between 10000 and 99999)
if( (candidate_pali/100) != lookup_arr[candidate_pali mod 1000] ) return prev_pali;
else
if (candidate_pali between 100000 and 999999)
if( (candidate_pali/1000) != lookup_arr[candidate_pali mod 1000] ) return prev_pali;
end if
return candidate_pali;
Notes for above outline:
- I am passing a pointer to the array to the check function (passing arrays around is very expensive)
- For the case where candidate_pali is less than 100000 above, I am comparing digits 0 through 2 against digits 2 through 4 (e.g., for 12345 I am comparing 123 to digit reverse of 345). This trick allows me to use the same lookup table for all cases. let me know if the trick is not obvious.
Note that I have used a verbose approach in above outline to make my intention clear. You could compress the programming and rewrite it in the following more compact form once the intention is understood:
return_larger_pali_using_lookup(cand, prev_pali, lookup_arr)
return
( (cand/((cand<100000)?100:1000) = lookup_arr[cand mod 1000])? cand : prev_pali);
And ultimately if you really want to do everything in one function:
find_largest_pali_using_lookup():
//init
lookup_arr = array[1000];
largest_pali = 0;
// prcompute digit-reverse
for i from 999 downto 0
lookup_arr[i] = (100*(i mod 10))+(10*((i/10) mod 10))+(i/100);
// find
for x from 999 downto 100:
for xy from 999*x downto max(x*x,largest_pali) decrement by x:
if( ((xy)/(((xy)<100000)?100:1000) = lookup_arr[(xy) mod 1000]))
largest_pali = xy;
sb1
andsb2
gives the impression they are both the same type, which they are not. \$\endgroup\$