-2
\$\begingroup\$

Input: a word (2-100 characters)

Convert this word to a palindrome:

  • delete character - 13 points
  • add character - 12 points
  • increase character - 5 points ('d' > 'e')
  • decrease character - 4 points ('n' > 'm')
  • switch 2 characters - 7 points

What is the minimal points needed to make the word palindrome? (C++/C#)

Output: Minimal points needed to convert the word

Winning criteria: fastest algorithm

\$\endgroup\$
12
  • 4
    \$\begingroup\$ Why is it restricted to C and C++? \$\endgroup\$ Commented Jan 24, 2012 at 15:06
  • \$\begingroup\$ @userunknown so I can understand the algorithm :) \$\endgroup\$ Commented Jan 24, 2012 at 15:13
  • \$\begingroup\$ And what is the objective winning criteria? \$\endgroup\$ Commented Jan 24, 2012 at 15:30
  • 4
    \$\begingroup\$ Meh. Graph search. \$\endgroup\$ Commented Jan 24, 2012 at 16:16
  • 2
    \$\begingroup\$ 1) There is a meta page to prepare questions, and the chat, and the FAQ. 2) You're free to restrict solutions to C and C#, but this will cost you much audience. 3) A code-challenge may still have some guidelines, and should have a date, when the decision is made. \$\endgroup\$ Commented Jan 24, 2012 at 16:24

2 Answers 2

2
\$\begingroup\$

Spent about an hour and got a working solution that I believe should give an optimal result:

public class PalindromeFinder : IComparer<Tuple<int,string>> { public PalindromeFinder() { heap = new IntervalHeap<Tuple<int, string>>(this); handles = new Dictionary<string, IPriorityQueueHandle<Tuple<int, string>>>(); } private readonly IntervalHeap<Tuple<int, string>> heap; private readonly Dictionary<string, IPriorityQueueHandle<Tuple<int, string>>> handles; public int FindMinValue(string startingWord) { AddWord(startingWord,0); while(!heap.IsEmpty) { var item = heap.DeleteMin(); var word = item.Item2; var value = item.Item1; handles.Remove(word); if(IsPalindrome(word)) { return value; } for(int i = 0; i<word.Length; i++) { //decrease var decreased = word.ToCharArray(); if (decreased[i] > 'a') { decreased[i]--; AddWord(new string(decreased), value + 4); } //switch for(int j = i+1; j<word.Length; j++) { if (word.Count(x => x == word[i]) >= 2 || word.Count(x => x == word[j]) >= 2) { var swap = word.ToCharArray(); var tmp = swap[i]; swap[i] = swap[j]; swap[j] = tmp; AddWord(new string(swap), value + 7); } } //add character before (only add characters already present) foreach (char c in word.Distinct()) { var added = word.Insert(i, c.ToString()); AddWord(added, value+12); } } //add character at end of word foreach (char c in word.Distinct()) { var added = word + c.ToString(); AddWord(added, value + 12); } } return int.MaxValue; } private void AddWord(string word, int value) { var min = int.MaxValue; if(handles.ContainsKey(word)) { var tuple = heap.Delete(handles[word]); min = tuple.Item1; handles.Remove(word); } IPriorityQueueHandle<Tuple<int, string>> handle = null; heap.Add(ref handle,new Tuple<int, string>(Math.Min(min, value), word)); handles[word] = handle; } private bool IsPalindrome(string word) { int left = 0; int right = word.Length - 1; while(left<right) { if (word[left] != word[right]) return false; left++; right--; } return true; } public int Compare(Tuple<int, string> x, Tuple<int, string> y) { return x.Item1 - y.Item1; } } 

Uses priority queue implementation from C5 collections. This is basically the brute force best-first search solution to the problem. It runs out of memory pretty fast, and is less efficient than is probably possible. I could fix that by implementing a better data structure like a DAWG, but out of time for now. Maybe later.It does work well for short words, and even for long ones that are within a few steps of a palindrome already.

Edit:

Made some improvements on Peter's suggestion, but the complexity is still too high. On input: "superman", runs for about a minute until it runs out of memory with over 11 million items in heap and a max value of 42 reached. Still a bit off from being a viable general solution.

\$\endgroup\$
5
  • \$\begingroup\$ You've got a copy-paste bug in the decrease case. Although it would be "better" to reuse the char[] and just do increased[i]-=2;. \$\endgroup\$ Commented Jan 25, 2012 at 16:57
  • \$\begingroup\$ Also, you can limit the memory required to some extent by setting a bound. Anything which takes more than 13*(startingWord.Length-1) can be discarded in AddWord. \$\endgroup\$ Commented Jan 25, 2012 at 17:00
  • \$\begingroup\$ Good catch on the bug. edited in. I think that optimization would not help very much though. There are a ton of possibilities with scores far lower than 13N. For "superman", it runs out of memory when the maximum value in the heap is only 38, with 11 million entries. I think to improve this I need to do some sort of estimation and apply A* to the search. Having trouble deciding on a consistent heuristic though. \$\endgroup\$ Commented Jan 25, 2012 at 17:12
  • 3
    \$\begingroup\$ On further thought, incrementing a character is never going to be optimal (decrementing the one it matches is better), and deleting a character is never going to be optimal (inserting one to match it is better), and there's no point doing a swap unless one of the characters you swap is present in multiple copies. Those should reduce the branching factor considerably. \$\endgroup\$ Commented Jan 25, 2012 at 18:59
  • \$\begingroup\$ I hadn't considered those. I figured maybe a swap-increment sequence might lead to something, but there would indeed be a corresponding decrement-swap sequence. \$\endgroup\$ Commented Jan 25, 2012 at 22:38
0
\$\begingroup\$

C++

Made this under the assumption that simply changing the character costs the same as switching two characters(7 points) & that comparing costs 0 points. A pretty basic & dumb solution, but it works.

int main(int argc, char *argv[]) { int c, l, p; p = 0; if(argc != 2) { cout<<"Incorrect arguments\n"; return 1; } else if(strlen(argv[1])<2 || strlen(argv[1])>100) { cout<<"Not a valid string(between 2 & 100 characters)\n"; return 1; } l = strlen(argv[1]); c = l/2; for(int i=0;i<c;i++) { if(argv[1][i] != argv[1][l-i-1]) { argv[1][i] = argv[1][l-i-1]; p+=7; } } cout<<argv[1]<<endl<<p<<endl; return 0; } 
\$\endgroup\$
2
  • \$\begingroup\$ if the string is "abcddba", the cost is 4 \$\endgroup\$ Commented Jan 25, 2012 at 7:22
  • \$\begingroup\$ @e-MEE yeah that's the minimum required. My algorithm is not that elegant. Will have to do a complete rewrite to get that kind of efficiency \$\endgroup\$ Commented Jan 25, 2012 at 7:25

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.