Highest "Valued" Palindrome - java

So I was at a programming interview a few months ago and this problem tripped me up for some reason. There are a couple of solutions I can think of but most of them seem extremely inefficient. Though I've been programming in some capacity for years, I'm currently in college for a CS degree so my point of reference may be incomplete. I was hoping someone here might offer up some possible solutions:
"Given a set of strings and associated numerical 'values,' assemble a palindrome from these string whose value (defined by the sum of the strings used to create it) is the highest possible."
There were no limits to how many strings could be provided, some strings may not be used.
Example:
"asd" - 3
"dsa" - 5
"appa" - 1
Result would be "asdappadsa" with a value of 9.
My thought would be to try all strings in all orders, then drop off one, starting with the lowest valued one, but that solution is O(N!) and I'd assume that's not ok.
(Preferred languages are C and Java, but whatever works)
EDIT: Clarification. Each string provided can only be used once, and has to be used exactly as provided, though you may choose to not use any of the strings in your palindrome. You can not use substrings of provided strings, nor can you reverse the string.

Replace "all strings" with "all palindromes" and the problem space becomes much smaller.
Divide the strings into 26 subsets.
Strings beginning with x (for 'a' <= x <= 'z')
[or whatever the set of "letters" is]
Now divide them into another 26 subsets
Strings ending with y ( for 'a' <= y <= 'z')
Note each string appears in a "begins with" set and an "ends with" set.
Use these sets to guide creation of all possible palindromes.
Start with two empty strings: prefix and suffix
for each string in the original set
assign it to the prefix
call recursiveFunction(prefix, suffix)
def recursiveFunction(prefix, suffix):
if prefix + <anything> + suffix cannot form a palindrome return
if prefix + suffix is a palindrome, remember it
while you have unused strings
if the suffix is shorter than the prefix
Look at the first unmatched character in the prefix
for each unused string that ends in that character
call recursiveFunction(prefix, string + suffix)
else if prefix is shorter than suffix
look at the last unmatched character in the suffix
for each unused string that ends with that character
call recursiveFunction(prefix + string, suffix)
else // suffix and prefix have equal lenghths
for each unused string
call recursiveFunction(prefix + string, suffix)
Be sure to mark the string used in both begins with and ends when you use it.
And be sure to consider the impact of recursion on the "used" marker.
Then pick the palindrome with the best score.

With palindrones, you can split the string into 3 substrings, ABC. There is potential within the set to find string reversals, so when you get a hit you can keep track of these strings for useage in substring A and C. If you don't get a reversal hit within the entire set, you just need to find the largest string (or string with the highest value) that is a reflection of itself. Not the best solution, I'm sure, but I did it for fun anyways so I may as well share. Also, I made no efforts to make this code "clean", so its a bit ugly, but it works.
public class Main {
private static String[] set = new String[] { "asdf" , "kjdij", "skjdihi", "ddidid" , "ididid", "iddid", "oihu", "uhio", "fdsa", "ajsja" };
public static void main(String[] args){
//Used for a couple of for loops...
int count;
Map<String, String> map = new HashMap<String,String>(); //Hold the strings and their reversals in a hashmap for quick lookups.
/* Think of the palindrome as 3 substrings, A, B, and C. */
ArrayList<String> A = new ArrayList<String>();
ArrayList<String> B = new ArrayList<String>();
ArrayList<String> C = new ArrayList<String>();
count = set.length;
String[] reverse_set = new String[count];
for(int x = 0; x < count; x++){
String reverse = new StringBuilder(set[x]).reverse().toString();
/* Check strings that would work for section B, since it's a set, we know that if it could work for B,
* it couldn't work for A or C - since a set contains no duplicates. */
if(reverse.equals(set[x])){
B.add(set[x]);
} else {
/* Otherwise, we'll have to check to see if it would work for A and C */
reverse_set[x] = reverse;
map.put(set[x], reverse);
}
}
/* Check the map for reverse string hits, we know that if another string exists in set S such that S(X) == R(X) it can work for substring
* A and C. */
int map_size = map.size();
int hit_count = 0;
for(int i = 0; i < map_size; i++){
String s = map.get(reverse_set[i]);
if(s != null){
hit_count++;
A.add(set[i]);
C.add(reverse_set[i]);
map.remove(set[i]); //remove the reflection, so we don't get duplicates
}
}
/* Find the longest string in B, since we'll have to do this even if was had any hits in the previous loop. */
String b = null;
if(B.size() > 0){
int length = 0;
for(String x : B){
int mLength = x.length();
if(mLength > length){
b = x;
length = mLength;
}
}
}
/* Build the palinedrome */
String palineString = "";
if(hit_count == 0 && b != null){
palineString = b;
} else {
for(String x : A){
palineString += x;
}
if(b != null){
palineString += b;
}
count = C.size();
for(int y = (count - 1); y >= 0; y--){
palineString += C.get(y);
}
}
if(checkPalindrome(palineString)){
System.out.print("The Palindrone is: " + palineString + " Score: " + Integer.toString(palineString.length()));
} else {
System.out.print("Oops...");
}
}
private static boolean checkPalindrome(String x){
if(x.equals(new StringBuilder(x).reverse().toString())){
return true;
} else {
return false;
}
}
}
Output:
The Palindrone is: asdfoihuajsjauhiofdsa Score: 21
EDIT:
Note: for the time being, I used the string's length as the "assigned value", I'll make an edit later to account for assigning random values to strings, but it won't make too much of a difference, since it only adds a few extra use cases to check for.

Related

Java: Determining if a word contains letters that can be found in another word?

For example:
If you have a String "magikarp", and you tested it against "karma", this would be true, because all of the letters that make up "karma" can be found in "magikarp".
"kipp" would return false, because there is only one "p" in "magikarp."
This is the attempt that I have right now, but I don't think it's very efficient, and it doesn't return correctly for cases when there are multiple occurrences of one letter.
private boolean containsHelper(String word, String word2){
for (int i = 0; i < word2.length(); i ++){
if (!word.contains(String.valueOf(word2.charAt(i)))){
return false;
}
}
return true;
}
I don't write the program here, but let you know how to do. There are 2 ways to do this considering complexity:
1) If you are sure that you would be getting only a-z/A-Z characters in the string, then take a array of size 26. Loop thorough the first string and place the count of the character appeared in the respective index. Say for example you have String "aabcc". Now array would look like [2,1,2,0,...0]. Now loop through the second String, and at each character, subtract the 1 from the array at the respective character position and check the resultant value. If value is less than 0, then return false. For example you have "aacd". When you are at d, you would be doing (0-1), resulting -1 which is less than 0, hence return false.
2) Sort the characters in the each String, and then compare.
Since you are only checking for characters, it would be more efficient to use indexOf and to check if it return -1 as contains itself call indexOf but with some other trims...
However, I think it would be simplier to convert the String to an array of char and to remove them if they are found which would also handle the case of multiple occurences.
So you're algorithm woud look something like this :
private final boolean containsHelper(final String word, final String word2)
{
char[] secondWordAsCharArray = word2.toCharArray();
char[] firstWordAsCharArray = word.toCharArray();
Arrays.sort(firstWordAsCharArray);//Make sure to sort so we can use binary search.
int index = 0;
for(int i = 0; i++ < secondWordAsCharArray.length;)
{
index = Arrays.binarySearch(firstWordAsCharArray, secondWordAsCharArray[i]);//Binary search is a very performant search algorithm
if(index == -1)
return false;
else
firstWordAsCharArray[index] = ''; //A SENTINEL value, set the char a value you are sure that will never be in word2.
}
}
Basically, what I do is :
Convert both word to char array to make it easier.
Sort the char array of the word we inspect so we can use binary search.
Loop over all characters of the second word.
retrieve the index using the binary search algorithm (a very performant algorithm on char, the best from my knowledge).
if index is -1, it was not found so we can return false.
else make sure we unset the character.
You need to ensure that for any character c that appears in the second string, the number of times that c appears in the second string is no greater than the number of times that c appears in the first string.
One efficient way to tackle this is to use a hashmap to store the count of the characters of the first string, and then loop through the characters in the second string to check whether their total count is no greater than that in the first string. The time and space complexity are O(n) in the worst case, where n is the length of the input strings.
Here is the sample code for your reference:
import java.util.HashMap;
import java.util.Map;
public class HashExample {
public static void main(String[] args) {
System.out.println(containsHelper("magikarp", "karma")); // true
System.out.println(containsHelper("magikarp", "kipp")); // false
}
private static boolean containsHelper(String word, String word2) {
Map<Character, Integer> hm = new HashMap<>();
for (int i = 0; i < word.length(); i++) {
Character key = word.charAt(i);
int count = 0;
if (hm.containsKey(key)) {
count = hm.get(key);
}
hm.put(key, ++count);
}
for (int i = 0; i < word2.length(); i++) {
Character key = word2.charAt(i);
if (hm.containsKey(key)) {
int count = hm.get(key);
if (count > 0) {
hm.put(key, --count);
} else {
return false;
}
} else {
return false;
}
}
return true;
}
}
On possible algorithm is to remove all letters from your second word that don't occur in the first, sort both and then compare them.
Here is a reasonable way to achieve that in Java 8:
List<Integer> wordChars = word.chars().sorted().collect(Collectors.toList());
List<Integer> searchChars = search.chars()
.filter(wordChars::contains).sorted().collect(Collectors.toList());
return wordChars.equals(searchChars);

scramble some letter in ArrayList assignment

This assignment involves reasoning about strings made up of uppercase letters. You will implement several static methods that appear in the same class (not shown). Here are the details.
1. The first method takes a single string parameter and returns a scrambled version of that string. The scrambling process begins at the first letter of the word and continues from left to right. If two consecutive letters consist of an "A" followed by a letter that is not an "A", then the two letters are swapped in the resulting string. Once the letters in two adjacent positions have been swapped, neither of those two positions can be involved in a future swap.
public static String scrambleWord(String word)
The method takes a given word (an empty string or a string containing only upper case letters) and returns a string that contains a scrambled version of the word according to the rules given above. The following table shows several examples of words and their scrambled versions.
Original word After scrambling
"TAN" "TNA"
"ABRACADABRA" "BARCADABARA"
"WHOA" "WHOA"
"AARDVARK" "ARADVRAK"
"EGGS" "EGGS"
"A" "A"
"" ""
the code i used but it dose not work is
public class ScrambleWord {
public static void main(String[] args) {
List<String> strList = new ArrayList<String>();
strList.add("TAN");
strList.add("ABRACADABRA");
strList.add("WHOA");
strList.add("EGGS");
strList.add("A");
strList.add("");
System.out.prentln(MainMethod.scrambleWordMeth(strList));
}
class MainMethod {
public static void scrambleWordMeth(List<String> strList) {
int curr = 0;
String res = "";
while (curr < strList.size()) {
String currentString = strList.get(curr);
for(int i = 0; i < currentString.length(); i++){
if (currentString.charAt(i) == 'A' && !(currentString.charAt(i + 1) == 'A')) {
res = res + currentString.substring(curr + 1, curr + 2);
res = res + 'A';
curr = curr + 2;
}
else {
res = res + currentString.substring(curr, curr + 1);
curr++;
}
}
if (curr < strList.size()) {
res = res + currentString.charAt(curr);
//res=res + strList.substring(curr);
}
}
return res;
}
}
}
Here is template for how to setup the methods such that the algorithm can be worked on in a more clear and isolated manner (note how the task states for "several methods"). This will prevent some issues in the posted code such as the incorrect usage of curr (which did not related to characters at all) in the inner loop. The usage of the array for the letters makes the task itself more logical to focus on without needing to perform slicing and concatenation.
static void scrambleAllWords(List<String> words) {
// Iterate through the list of word applying the scramble
// function and replacing the original item with the result.
for (int i = 0; i < words.size(); i++) {
String scrambled = scrambleWord(words.get(i));
words.set(i, scrambled);
}
}
static String scrambleWord(String word) {
// Get the letters that make up the word
char[] letters = word.toCharArray();
// Perform the algorithm on the letters
// for (int i = 0; i < ..
// Create a new string from the now-scrambled letters
return new String(letters);
}
The algorithm itself is rather simple and can be read as the following pseudo-code, which should be trivial to apply to letters as it is now an array clearly separated from the other cruft.
for i in the range [0, the number of letters in the word - 1)
if the letter at i is an 'A' and the following letter at i+1 is not an 'A' then
swap the letter at i with the letter at i+1 and
skip the next letter by advancing i by 1
(so that the current-now-next 'A' letter cannot be swapped again)
otherwise
do nothing

Find shortest text containing all combinations of a given string

Given a string, find another string which contains all the combinations of the input string.
Example:
If input string = "23", then its combinations would be ["22", "23", "32", "33"],
One of the strings to contain all the above combinations would be "22233233", but that would not be the shortest. The shortest would be "22332".
Algorithm should be generic enough to work for input string of any size. (Assume that input is not too large and the output will remain under normal int/string/jvm etc. sizes. Also assume that input string will have alphanumeric characters from English language only)
I have tried the following algorithm but it does not seem to be working:
1) Find all combinations of the string = ["22", "23", "32", "33"]
2) Build a prefix map [2: {22, 23}, 3: {32, 33}]
3) Start from any combination and lookup suffix in the prefix map.
Example: Start with 22, its suffix is 2
From the prefix-map, values corresponding 2 are 22 and 23.
Pick one of the words here which is not the current picked word, so it will give 23
4) Add picked word's suffix to current string (This gives 223)
5) Repeat.
So I will get 223's suffix = 3
From prefix map, 3: {32, 33}
Choose any one, say 32
Append to current string to get 2232
6) If nothing else matches, append to current string. This gives 223233
However, the answer should be 22332 as that is the shortest.
Here is the full code I have written till now:
public class TextContainingAllPermutations
{
static String input = "ABC";
public static void main (String args[])
{
int suffixLen = input.length()-1;
Set<String> combinations = getCombinations();
while (suffixLen > 0 && combinations.size() > 1)
{
Map<String, List<String>> suffixToWords = getPrefixMap(combinations, suffixLen);
String someWordsString = combinations.iterator().next();
combinations.remove(someWordsString);
Set<String> combinations2 = new HashSet<String>();
while (combinations.size() > 0)
{
String suffix = someWordsString.substring(someWordsString.length()-suffixLen);
List<String> words = suffixToWords.get(suffix);
if (words == null || words.size()==0)
{
combinations2.add(someWordsString);
System.out.println (someWordsString);
if (combinations.size() == 0)
break;
someWordsString = combinations.iterator().next();
combinations.remove(someWordsString);
}
else
{
String w = words.get(words.size()-1);
words.remove(words.size()-1);
combinations.remove(w);
if (someWordsString.indexOf(w) == -1)
someWordsString += w.charAt(w.length()-1); // append last char
}
}
combinations2.add(someWordsString);
System.out.println (someWordsString);
combinations = combinations2;
suffixLen--;
}
}
private static Map<String, List<String>> getPrefixMap(Set<String> combinations, int suffixLen)
{
Map<String, List<String>> suffixToWords = new HashMap<String, List<String>>();
for (String s: combinations)
{
String suffix = s.substring(0,suffixLen);
if (!suffixToWords.containsKey(suffix))
{
suffixToWords.put(suffix, new ArrayList<String>());
}
suffixToWords.get(suffix).add(s);
}
return suffixToWords;
}
static Set<String> getCombinations()
{
char[] inputChars = input.toCharArray();
int N = (int)Math.pow(input.length(), input.length());
Set<String> combinations = new HashSet<String>(N);
for (int i=0; i<N; i++)
{
char[] binary = padZeroes(Integer.toString(i, input.length())).toCharArray();
String combination = "";
for (int j=0; j<inputChars.length; j++)
{
char c = binary[j];
int index = c - '0';
char inputChar = inputChars[index];
combination = inputChar + combination;
}
System.out.println (new String(binary) + " = " + combination);
combinations.add(combination);
}
return combinations;
}
private static String padZeroes(String s)
{
int j = input.length()-s.length();
for (int i=0; i<j; i++)
s = '0' + s;
return s;
}
}
This is not a homework problem.
What you are looking for is basically a De Bruijn sequence. A De Bruijn sequence B(k,n) is a cyclic sequence which contains all the possible subsequences of length n from a set of k symbols, each appearing exactly once. The length of the sequence is precisely kn.
The minimal non-cyclic sequence can be obtained by breaking the cycle at any point and then copying the first n-1 symbols to the end, producing a sequence of length kn + n - 1, which is obviously minimal.
There are a variety of techniques to generate a De Bruijn sequence. The simplest technique to describe is that the de Bruijn sequence consists of the concatenation of all the Lyndon words over the alphabet whose length divides n, in lexicographic order. (A Lyndon word is a sequence which is lexicographically previous to any rotation of itself. This implies that the Lyndon word is aperiodic.)
There is a simple algorithm to generate Lyndon words of maximum length n over an alphabet in lexicographic order:
Start with the length consisting of only the lexicographically first character in the set.
As long as possible, form the next word by cyclically repeating the previous word up to length n (discarding the extra symbols from the last repetition, if necessary) and then "incrementing" the word by:
As long as the last symbol in the word is the lexicographically greatest symbol in the alphabet, remove it.
If there is still a symbol in the word, change the last symbol to the lexicographical successor. If there are no symbols left in the word, the production is finished.
To make the De Bruijn sequence of order n, we produce the above sequence of Lyndon words, but we only keep the ones whose length divides n. Since "almost all" of the Lyndon words of maximum length n are actually of length n, the algorithm can be considered O(1) per symbol, or O(kn) for the full sequence.
In the specific case requested by the question, k == n.

How to change a random element of an attribute?

I'm trying to select a random element in an attribute, and then reverse the value of that randomly selected element between 0 and n, and then reprint the whole attribute again, with the reversed element shown. The reverse value for 0 is 1, and for 1 is 0 in this case.
Here is what I've attempted:
String whatever = "11111";
int n = whatever.length();
//the UI class generates a random integer number between 0 and n
int p = CS2004.UI(0,n);
if (whatever.charAt(p)=='0') {
+= 1;
} else {
+= 0;
}
I'm lost on the adding the opposite number bit.
I should clarify that this is homework!
Strings are immutable in Java, but you could do
StringBuilder sb = new StringBuilder(whatever);
if (whatever.charAt(p)=='0') {
sb.setCharAt(p, '1');
} else {
sb.setCharAt(p, '0');
}
whatever = sb.toString();
OK, lets give this a try.
Firstly, you will need to change the name of your variable 5 - I don't think you're allowed to start variables with a number (see here)
I'm assuming that by 'attribute' you mean the length of the String? This is probably supposed to be the value of your 5 variable.
So, lets look at some modified code...
String whatever = "11111";
int n = whatever.length();
//the UI class generates a random integer number between 0 and n
int p = CS2004.UI(0,n);
StringBuilder sb = new StringBuilder(whatever);
if (whatever.charAt(p)=='0') {
sb.setCharAt(p, '1');
} else {
sb.setCharAt(p, '0');
}
whatever = sb.toString();
This code will change the value of the character in position p, and then convert it back to a String.
Basic StringBuilder usage with a ternary operator produces what you're looking for:
StringBuilder result = new StringBuilder();
result.append(whatever.substring(0, p));
result.append(whatever.charAt(p) == '0' ? "1" : "0");
result.append(whatever.substring(p + 1, whatever.length()));
System.out.println("Result = " + result.toString());
As others have noted, using the StringBuilder.setCharAt() method produces a more concise answer (with the ternary operator added, this is the shortest answer so far):
StringBuilder result = new StringBuilder(whatever);
result.setCharAt(p, whatever.charAt(p) == '0' ? '1' : '0');
System.out.println("Result = " + result.toString());
Strings in Java are immutable so you need to first convert them to some mutable structure before modifying them.
If you are trying to flip the characters at some position in a code then this code might be of help :
String whatever = "11111";
char[] charArray = whatever.toCharArray();
int n = whatever.length();
//the UI class generates a random integer number between 0 and n
int p = CS2004.UI(0,n);
if (charArray[p]=='0') {
charArray[p] = 1;
} else {
charArray[p] = 0;
}
Then you can modify whatever characters you wish to. Once you are done you can reconstruct a String from the charArray as follows :
String result = new String(charArray);
This can also be achieved using StringBuilder.

Finding variants of a string (only deletion, no transposition)

Given a string, I want to find all variants without transposition, only deletion. For example, given the string:
helloo
The list of variants would be as follows (separated by white space).
helloo hello heloo helo
My solution so far is to move through each character, and then if the current character matches the next character, recursively try the original and the deleted character version, as follows.
// takes String with at most two consecutive characters of any character,
// and returns an Iterable of all possible variants (e.g. hheello -> heello, hhello, ...)
private static Iterable<String> findAllVariants(String word) {
StringBuilder variant = new StringBuilder(word);
Queue<String> q = new LinkedList<String>();
findAllVariants(word, variant, 0, q);
return q;
}
// helper method
private static void findAllVariants(String word, StringBuilder variant, int currIndex, Queue<String> q) {
if (currIndex == variant.length() - 1) q.add(variant.toString());
for (int i = currIndex; i < variant.length() - 1; i++) {
char thisChar = variant.charAt(i);
char nextChar = variant.charAt(i+1);
if (thisChar == nextChar) {
// get all variants with repeat character
findAllVariants(word, variant, i+1, q);
// get all variants without repeat character;
variant = variant.deleteCharAt(i);
findAllVariants(word, variant, i, q);
}
}
}
However, I end up getting a large number of copies of answers, and none of others. When I do my algorithm on paper, it seems correct. What am I doing wrong?
Something along the lines of the following code will enable you to get all possibilities (remember to add word itself if needed). The idea is to retreive all possibilities for removing one char (e.g. hello results in ello hllo helo hell). These results can in turn be used to get the possibilities for removing two chars (remove one char again). Resulting in llo elo ell for ello and so on...
List<String> getPossibilities(String word) {
int removeChars = word.length() - 1;
List<String> possibilities = new ArrayList();
List<String> options = Arrays.asList(word);
for(int i = 0; i <= removeChars; i++) {
List<String> results = new ArrayList();
for(String option : options) {
for(String result : removeOneChar(option)) {
if(!results.contains(result)) {
results.add(result);
}
}
}
possibilities.addAll(results);
options = results;
}
return possibilities;
}
private static List<String> removeOneChar(String word) {
List<String> results = new ArrayList();
for(int i = 0; i < word.length(); i++) {
int secondPart = i + 2;
if(secondPart <= word.length()) {
results.add(
word.substring(0, i)
+ word.substring(i + 1, word.length()));
}
else {
results.add(
word.substring(0, i));
}
}
return results;
}
Notice the if(!contains(result)) in order to prevent any duplicates.
Note I've used substring() to accomplish this, you're approach with removeCharAt() is a another good option. You could run some tests to see which performs better to decide which one to use. Notice using the latter could possibly remove the need of the if in the private method.
I'd use rather different algorithm: I'd find all repetitions (ll) (oo) (lll) (ooo) etc.., keep an array describing their positions in the text, and the count of characters per each repetition.
e.g Array A =
[l|2]
[o|2]
.
.
.
Then I'd say have second array with initial count zero and increase there the count and print out all permutations:
Array B =
[l|1]
[o|1]
==> prints helo
Step 2: (increment count)
B =
[l|2]
[o|1]
==> prints hello
Step 3:
B =
[l|3] ==> bigger than max,so reset it to 0, and increment the second cell now, so it becomes:
B =
[l|1]
[o|2]
==> prints heloo
Step 4: (increment first elem again)
[l|2] ==> not bigger than max, so no overflow, so keeping it that way
[o|2]
==> prints helloo

Categories

Resources