4

Given a string, I need to find the longest palindrome that can be constructed by removing or shuffling characters from the string. If more than one palindrome exists of same length then I need to make ensure that lexicographically smallest one is been given as output. Example : "adskassda" Output expected is : "adsasda"

I am able to find largest palindrome, but how to ensure in case of multiple of same maximum length lexicographically smallest one is been given as output ?

Any palindromic string can be divided into three parts – beg, mid and end. For palindromic string of odd length say 2n + 1, ‘beg’ consists of first n characters of the string, ‘mid’ will consist of only 1 character i.e. (n + 1)th character and ‘end’ will consists of last n characters of the palindromic string. For palindromic string of even length 2n, ‘mid’ will always be empty. It should be noted that ‘end’ will be reverse of ‘beg’ in order for string to be palindrome. I have used same logic for this too.

#include <bits/stdc++.h>
using namespace std;

string longestPalindrome(string str){
    map<char,int> frequencyChar;
    for(int i=0;i<str.length();i++){
        frequencyChar[str[i]]++;
    }
    char middle_character;
    string leftStr;
    for(auto it: frequencyChar){
        char currentChar=it.first;
        int frequencyCurrentChr = it.second;
        if(frequencyCurrentChr%2!=0){
            middle_character=currentChar;
        }
        leftStr.append(frequencyCurrentChr/2,currentChar);
    }
    string rightStr(leftStr.rbegin(),leftStr.rend());
    return leftStr + middle_character + rightStr;
}
int main() {
    string str = "adskassda";
    cout<<longestPalindrome(str);
}

I am getting "adsssda" but expected is "adsasda"

Gaurav Gupta
  • 99
  • 1
  • 2
  • 5
  • When you determine the `middle_character`, you could/should apply a `std::min()` to the prior candidate and the new one. Furthermore, you should initialize `middle_character` (e.g. to `'\0'`) to recognize whether there is already a candidate. This would also be useful if no candidate can be found (i.e. all characters have even frequency). – Scheff's Cat Jun 24 '19 at 14:47
  • @Scheff finding a min with middle character may not work with current code approach. Refer this for ref : https://ideone.com/sCCdcm – Gaurav Gupta Jun 24 '19 at 14:51
  • 1
    It does: [**Live Demo on coliru**](http://coliru.stacked-crooked.com/a/9809654bb8c3cb2b). ;-) – Scheff's Cat Jun 24 '19 at 14:53
  • 2
    Just a comment on style, unless you are developing libstdc++ you should not include `` or anything else within the `bits` sub-directory. These are internal files used by the library implementation, use the include files defined by the standard instead. – Johan Jun 24 '19 at 14:54
  • Please, note, I initialized `middle_char` with 127 to eliminate the need for the extra check before `min()`. You didn't do the extra check, hence you will end up always with `middle_char` == 0. Before `return` the check is needed as well to insert (or not) the `middle_char` only if it hasn't anymore the init. value. – Scheff's Cat Jun 24 '19 at 14:57
  • the problem is updating the middle character must be only done once, the ones found later would have a higher lexicographical order. That, and there's the case when all frequencies are even. After fixing that the code seems to work. – Ameer Jewdaki Jun 24 '19 at 16:28

3 Answers3

0

This seemed to work for me, although my testing was far from extensive:

#include <iostream>
#include <string>
#include <map>
#include <algorithm>
using namespace std;

int main()
{
    string in("adskassda");
    map<char, int> chars;
    string out;

    for (auto c : in)
    {
        ++chars[c];
    }

    string middle;
    for (auto e : chars)
    {
        if (e.second >= 2)
        {
            out.append(e.second/2, e.first);
            e.second = e.second%2;
        }

        if (e.second && middle.empty())
            middle = e.first;
    }

    string tail(out);
    reverse(tail.begin(), tail.end());
    out = out + middle + tail;

    cout << in << endl;
    cout << out << endl;
}
Steve
  • 1,760
  • 10
  • 18
  • Does this return the longest lexocographically smallest palindrome? – David G Jun 24 '19 at 16:07
  • @0x499602D2: Longest but lexicographically smallest was my intent. As I said though, I only superficially tested it. The logic seems sound to me though. – Steve Jun 24 '19 at 16:09
0

You only have a simple error. When you want to chose the middle character, the first time you see one with and odd frequency, you should choose it and never update it again, because that'll be the one with the lowest lexicographical order. That's why I've added the boolean variable mid_char_chosen and once it's set to true it will not be updated again. There's another corner case you haven't considered: if all frequencies are even then there'll be no midle character and the result will have even number of characters. So the output should ommit middle character. With these minor modifications, I think the code runs:

#include <bits/stdc++.h>
using namespace std;

string longestPalindrome(string str){
    map<char,int> frequencyChar;
    for(int i=0;i<str.length();i++){
        frequencyChar[str[i]]++;
    }
    char middle_character;
    string leftStr;
    bool mid_char_chosen = false;
    for(auto it: frequencyChar){
        char currentChar=it.first;
        int frequencyCurrentChr = it.second;
        if(!mid_char_chosen and frequencyCurrentChr%2!=0){
            middle_character=currentChar;
            mid_char_chosen = true;
        }
        leftStr.append(1*(frequencyCurrentChr/2),currentChar);
    }
    string rightStr(leftStr.rbegin(),leftStr.rend());
    if (mid_char_chosen)
        return leftStr + middle_character + rightStr;
    else
        return leftStr +  rightStr;
}
int main() {
    string str = "adskassda";
    cout<<longestPalindrome(str) << endl;
}
Ameer Jewdaki
  • 1,758
  • 4
  • 21
  • 36
0

I added a small change in the code - Sort lexicographically as soon as we get the left part. This may be required in Java. When I wrote the above code in java, I got "asdadsa" instead of "adsasda"

Here is the code in Java:

import java.util.*;

public class Solution {

    public static String longPalindrome(String a) {
        Map<Character, Integer> map = new HashMap<>();
        for(int i=0; i<a.length(); i++) {
            map.put(a.charAt(i), map.getOrDefault(a.charAt(i), 0) + 1);
        }
        char mid = 0;
        boolean mid_chosen = false;
        StringBuilder left = new StringBuilder();
        for(Map.Entry<Character, Integer> entry : map.entrySet()) {
            if(!mid_chosen && entry.getValue() % 2 != 0) { //odd
                mid_chosen = true;
                mid = entry.getKey();
            }
            //Adding elements to left
            for(int k=0; k<entry.getValue()/2; k++) {
                left.append(entry.getKey());
            }
        }
        //New Step added to sort it lexicographically 
        char[] leftChArr = left.toString().toCharArray();
        Arrays.sort(leftChArr);
        
        StringBuilder leftC = new StringBuilder(new String(leftChArr));
        StringBuilder right = new StringBuilder();
        //adding reverse elements to left
        for(int j=leftC.length()-1; j>=0; j--) {
            right.append(leftC.charAt(j));
        }
        if(mid_chosen == true) {
            leftC.append(mid).append(right);
        } else {
            leftC.append(right);
        }
        return leftC.toString();
    }
    
    public static void main(String[] args) {
        String str = "adskassda";
        System.out.println(longPalindrome(str));
    }
}

There may be some refactoring required, example using existing lib in Java to add repeat characters in StringBuilder. But the above code provides the solution.

Sdembla
  • 1,629
  • 13
  • 13