This is a very common problem in which we would have to find the longest substring which is also a palindrome substring for the given input string.
Now there are multiple possible approaches to this and I am aware about Dynamic programming solution, expand from middle etc. All these solutions should be used for any practical usecase.
I was experimenting with using recursion to solve this problem and trying to implement the simple idea.
Let us assume that s is the given input string and i and j represent any valid character indexes of input string. So if s[i] == s[j], my longest substring would be:
s.charAt(i) + longestSubstring(s, i + 1, j - 1) + s.charAt(j)
And if these two characters are not equal then:
max of longestSubstring(s, i + 1, j) or longestSubstring(s, i, j - 1)
I tried to implement this solution below:
// end is inclusive
private static String longestPalindromeHelper(String s, int start, int end) {
if (start > end) {
return "";
} else if (start == end) {
return s.substring(start, end + 1);
}
// if the character at start is equal to end
if (s.charAt(start) == s.charAt(end)) {
// I can concatenate the start and end characters to my result string
// plus I can concatenate the longest palindrome in start + 1 to end - 1
// now logically this makes sense to me, but this would fail in the case
// for ex: a a c a b d k a c a a (space added for visualization)
// when start = 3 (a character)
// end = 7 (again end character)
// it will go in recursion with start = 4 and end = 6 from now onwards
// there is no palindrome substrings apart from the single character
// substring (which are palindrome by itself) so recursion tree for
// start = 3 and end = 7 would return any single character from b d k
// let's say it returns b so result would be a a c a b a c a a
// this would be correct answer for longest palindrome subsequence but
// not substring because for sub strings I need to have consecutive
// characters
return s.charAt(start)
+ longestPalindromeHelper(s, start + 1, end - 1) + s.charAt(end);
} else {
// characters are not equal, increment start
String s1 = longestPalindromeHelper(s, start + 1, end);
String s2 = longestPalindromeHelper(s, start, end - 1);
return s1.length() > s2.length() ? s1 : s2;
}
}
public static String longestPalindrome(String s) {
return longestPalindromeHelper(s, 0, s.length() - 1);
}
public static void main(String[] args) throws Exception {
String ans = longestPalindrome("aacabdkacaa");
System.out.println("Answer => " + ans);
}
For a moment let us forgot about time complexity or runtime. I am focused towards making it work for simple case above.
As you can see in the comments I got the idea why this is failing but I tried hard to rectify the problem following the exactly same approach. I don't want to use loops here.
What could be the possible fix for this following same approach?
Note: I am interested in the actual string as answer and not the length. FYI I had a look at all the other questions and it seems no one is following this approach for correctness so I am trying.
Once you have a call wherein s[i] == s[j], you could flip a boolean flag or switch to a modified method that communicates to child calls that they can no longer use the "don't match, try i + 1 and j - 1" branch (else condition). This ensures you're looking at substrings, not subsequences, for the remainder of the recursion.
Secondly, for the substring variant, even if s[i] == s[j], you should also try i + 1 and j - 1 as if these characters didn't match, because one or both of these characters might not be part of the final best substring between i and j. In the subsequence version, there's never any reason not to add any matching characters to the current palindromic subsequence for the range i to j, but that's not always the case with substrings.
For example, given input "aabcbda" and we're at a call frame where i = 1 and j = length - 1, we need to maximize over three possibilities:
The best substring includes both 'a' characters. Call the subroutine with the flag that says we have to consume from both ends on down and can no longer try skipping characters.
The best substring might still include s[i] but not s[j], try j - 1.
The best substring might still include s[j] but not s[i], try i + 1.
Another observation: it might make more sense to pass best indices up the helper call chain, then grab the longest palindromic substring based on these indices at the very end in the wrapper function.
On a similar note, if you're struggling, you might simplify the problem and return the longest palindromic substring length using your recursive method, then switch to getting the actual substring itself. This makes it easier to focus on the subsequence logic without the return value complicating things as much.
It is much easier to use loops here, rather than recursion, something like this:
public static void main(String[] args) {
System.out.println(longestPalindrome("abbqa")); // bb
System.out.println(longestPalindrome("aacabdkacaa")); // aca
System.out.println(longestPalindrome("aacabdkaccaa")); // acca
}
public static String longestPalindrome(String str) {
String palindrome = "";
for (int i = 0; i < str.length(); i++) {
for (int j = i; j < str.length(); j++) {
String substring = str.substring(i, j);
if (isPalindrome(substring)
&& substring.length() > palindrome.length()) {
palindrome = substring;
}
}
}
return palindrome;
}
public static boolean isPalindrome(String str) {
for (int i = 0; i < str.length() / 2; i++) {
if (str.charAt(i) != str.charAt(str.length() - i - 1)) {
return false;
}
}
return true;
}
So basically i have to verify how many times words are counted within a file on an array.
Edit#2 ( i will run this program through cmd after)
This program takes a number of command line arguments:
1) The file to open
2+) The words to search for and count
If you don’t give it any words to search for, it defaults to these words: "doctor",
"frankenstein", "the", "monster", "igor", "student", "college", "lightning",
"electricity", "blood", and "soul".
You may want to run it with fewer or different words, eg:
$ java ReadSearchAndSort frankenstein.txt frankenstein doctor igor monster
Edit/Update*:
If we consider the simple program:
public class CopyCat{
public static void main(String[] arguments){
for (int ii = 0; ii < arguments.length; ii++){
System.out.println("Argument "+ ii + " = " +
arguments[ii]);
}
}
}
Then if we run it as such:
$ java CopyCat a b c
we will get the following output
Argument 0 = a
Argument 1 = b
Argument 2 = c
Using this as your basis:
Get the first argument and store it in a String named filename
● Get the rest of the arguments and place them in a String array named
queryWords (this should be of the correct size to hold all of the words to count,
and there should be no maximum size — ignoring memory demands of your
system)
Loop through the scanner while things remain in the file ( i.e. hasNext() is true)
and get the next word in the file (i.e. call next())
● Put the word in an array — note, you will need to constantly resize the array —
○ So first instantiate an empty array, outside of the for loop
String[] words = new String[0]();
○ Then, when you read a word, resize the array using Arrays.copyof, i.e.
words = Arrays.copyOf(words, words.length+1)
○ Then place the word in the last spot of the array
■ … but first, we want to make it lower case
word = word.toLowerCase();
■ and remove all of the non-letters
word = word.replaceAll("[^a-zA-Z ]", "");
For each word in our words list, we need to go through the array of words we have read
in, and count each instance. To do this we will make a helper function:
Implementing the function countWordsInUnsorted
Create a counter variable to record how many times we have seen a word. Create a
loop that iterates over every word in the array. Each time a word matches the query word, increment the counter. We need to use the equals method, since we care about whether two strings have the same characters in them, not that they are the exact same
object.
Once you’ve completed the two tasks above, you can run your code. Verify that
"frankenstein" appears 26 times
Overall im having trouble understanding the directions and how i should order my code so its understandable. If you can help in anyway i would appreciate it. I know its quite long, but this isnt even whole assignment so its shortened.
EDIT 9/7/18 updates instructions
Timing things in Java
How long did your code take to run?
In this assignment, we will see some search algorithms run faster than others. To seehow much faster, we will record how long the code takes to run.
Here is an example of timing code:
// Look at the clock when we start
long t0 = (new Date()).getTime();
for (var i = 0; i < 100000; i++) {
// Do something that takes time
}
// Look at the clock when we are finished
long t1 = (new Date()).getTime();
long elapsed = t1 - t0;
System.out.println("My code took " + elapsed + "milliseconds to
run.")
This is already set up for you in main and will output how long your code took to run.
Your job is to implement and call your functions, and make sure that they are working
correctly. If you look in the starter code, you’ll see that we’re running your two different
search and count methods 100 times, so as to get a good average value for how long it
takes. We’re only do the sort once, as sorting the array of 75289 words already takes
awhile.
4. Sorting an array of words with Merge Sort
(35 points)
● Implement mergeSort (Do not use Java's built-in Array.sort for this assignment)
● Call your new method in main
● Checking if you are right: the words (after SORTED in the output) will be in
alphabetical order.
Implementing mergeSort
Implement a mergeSort method that sorts an array of strings. The signature for this
method should be:
public static void mergeSort(String[] arrayToSort, String[]
tempArray, int first, int last)
The first argument is the String[] to sort, the second argument is an empty temporary
array that should be the same size as the array to sort, the third argument is the starting
index for the portion of the array you want to sort, and the fourth argument is the ending
index for the portion of the array you want to sort.
Note that the version of mergeSort we’ll be implementing sorts arrayToSort in place.
This means that you pass an unsorted array in to mergeSort and after the call that
same array is now sorted.
Note that mergeSort involves comparing to values to see which is larger. With numbers
you can just use < or > to compare them.
if you have two Strings s1 and s2, then:
● s1.compareTo(s2) == 0 if s1 and s2 contain the same string.
● s1.compareTo(s2) < 0 if s1 would be alphabetically ordered before s2.
● s1.compareTo(s2) > 0 if s1 would be alphabetically ordered after s2.
Calling mergeSort in main
To call mergeSort in main, you first need to create a temporary string array that is the same length as allWords.
We want to sort the whole array, so the third argument should be the index of the first word in allWords and the fourth argument should be the
index of the last word in allWords. The four arguments to call mergeSort with are then:
● The array we’re sorting, which is allWords.
● Our new temporary array we’ve created that’s the same length as allWords.
● The index of the first word in allWords (hint: what’s always the index of the very first element of an array?).
● The index of the last word in allWords (hint: if you know the length of an array, you can easily compute the index of the last element).
Checking you are right
Did this sort the array of words? The program prints every 500th word. Do they look
sorted?
5. Counting words (with Binary Search) and timing it
(35 points)
● Implement
public static int binarySearch(String[] sortedWords,
String query, int startIndex, int endIndex)
● Implement
public static int getSmallestIndex(String[] words,
String query, int startIndex, int endIndex)
● Implement
public static int getLargestIndex(String[] words, String
query, int startIndex, int endIndex)
● Call getSmallestIndex and getLargestIndex in main to get the smallest and
largest indices at which the word you’re looking for appears in the sorted array.
Use these two values to compute how many times that word appears.
● Checking if you are right: you will get the same values as in the first search section, but much faster.
Implementing binarySearch
The arguments to binarySearch are:
● The sorted array of words to search in.
● The word to search for.
● The index in the array at which to start searching.
● The index in the array at which to stop searching.
Binary search returns the array index where it found the word. If the word only appears once in the array, then this index will be where it occurs.
But if the word appears multiple times in the sorted array (so all the instances of the word will be next to eachother in the array), then this index will be to one of the words in the middle of the group.
The binary search algorithm doesn’t guarantee that this will be the first element in the group or the last element in the group.
So we need to implement some other methods to do this.
Implementing getSmallestIndex
The method getSmallestIndex will be a recursive method that uses the
binarySearch method to find the smallest index for which a word is found in the array.
The outline for this method is:
● Use binarySearch to find an index to the word. If the index binarySearch
returns is -1, then the word wasn’t found and getSmallestIndex should just
return -1. This is the base case.
● If binarySearch did find the word, then recursively call getSmallestIndex on
the portion of the array before where the word was found. This is from index 0 up
to (but not including) the index where the word was found. If this returns -1 then
we know we already had the smallest index, otherwise the recursive call to
getSmallestIndex found the smallest index and we should return that. This is
the recursive case.
Implementing getLargestIndex
The method getLargestIndex will be a recursive method that uses the binarySearch
method to find the largest index for which a word is found in the array. The outline for
this method is very similar to the outline above for getSmallestIndex, except that the
recursive call should search the portion of the array starting after where binarySearch
has found the word.
Using getSmallestIndex and getLargestIndex in main to count words
Since the array that you’re working with has been sorted by this point, all the same
words appear next to each other (e.g. all 26 appearances of the word "frankenstein"
are next to each other in the array). So if you’ve found the smallest and largest index for
a word, then you can just subtract the two indices to count the words! But it’s not quite
word appears only once (so the first and last index are the same) and the case where
the word doesn’t appear at all.
Checking if you are right
Check that you’re getting the same answers as the naive approach that iterates through
the whole array. Also, look to see how much faster this approach is to the naive
approach!
Example arguments and output
$ java ReadSearchAndSort frankenstein.txt student college frankenstein blood the
Arguments: use ''student,college,frankenstein,blood,the'' words, time 100 iterations, search for words:
student,college,frankenstein,blood,the
NAIVE SEARCH:
student:2
college:3
frankenstein:26
blood:19
the:4194
96 ms for 500 searches, 0.192000 ms per search
SORTING:
38 ms to sort 75097 words
SORTED (every 498 word): a a aboard affection all although ancient and and and and and angel approached
as asked attended be been believe boat but but by cause clerval comprehensive continued country dante
decay desire died do duvillard end escape exception eyes favourite few flourishing for frankenstein from
girl grief had happiness have he heart her his histories however i i i i i ice impossible in in
inhabitants investigating it its know leaves limbs love man me me might mixture most my my my my nature
no not oatmeal of of of of of old on or over passed philosophy possession profoundly rain regular
resolve room saved seem shape should smiles some spirit straw sun tavernier that that the the the the
the the the the the then thick those time to to to to town uncle upon vessels was was was were when
which while will with with would you you
BINARY SEARCH:
student:2
college:3
frankenstein:26
blood:19
the:4194
6 ms for 500 searches, 0.012000 ms per search
Of course the actual timing for running the searches and sort will vary on your computer,
depending on how fast your computer is and how many other processes are running on it.
EDIT 9/7/18
import java.io.*;
import java.util.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Scanner;
public class ReadSearchAndSort {
public static void main(String[] args) throws FileNotFoundException {
String filename = args[0];
int n = args.length - 1;
String[] queryWords = null;
String[] allWords = readWords("frankenstein.txt");
String[] tempAllWords = new String[allWords.length];
if (n > 0) { //if user provides the querywords
queryWords = new String[n];
for (int i = 1; i < args.length; i++) {
queryWords[i - 1] = args[i];
}
} else { //if user doesn't provide querywords
queryWords = new String[]{"doctor", "frankenstein", "the", "monster", "igor", "student", "college", "lightning", "electricity", "blood", "soul"};
}
ReadSearchAndSort.readWords("C:/Users/Jordles/IdeaProjects/TimeConversionToSecond.java/out/production/GettingStarted/cs141/frankenstein.txt");
int timingCount = 100;
System.out.println("\nArguments: use '" + String.join(",", queryWords) + "' words, time " + timingCount + " iterations, search for words: " + String.join(",", queryWords) + "\n");
System.out.println("NAIVE SEARCH:");
// Record the current time
long t0 = new Date().getTime();
// Time how long it takes to run timingCount loops
// for countWordsInUnsorted
for (int j = 0; j < timingCount; j++) {
for (int i = 0; i < queryWords.length; i++) {
int count = countWordsInUnsorted(allWords, queryWords[i]);
if (j == 0) {
System.out.println(queryWords[i] + ":" + count);
}
}
}
long t1 = (new Date()).getTime();
long timeToSearchNaive = t1 - t0;
int searchCount = timingCount * queryWords.length;
// Output how long the searches took, for how many searches
// (remember: searches = timingcount * the number of words searched)
int searches = timingCount * 500;
System.out.printf("%d ms for %d searches, %f ms per search\n", timeToSearchNaive, searchCount, timeToSearchNaive * 1.0f / searchCount);
// Sort the list of words
System.out.println("\nSORTING: ");
mergeSort(allWords, tempAllWords, 0, allWords.length);
long t2 = (new Date()).getTime();
// Output how long the sorting took
long timeToSort = t2 - t1;
System.out.printf("%d ms to sort %d words\n", timeToSort, allWords.length);
// Output every 1000th word of your sorted wordlist
int step = (int) (allWords.length * .00663 + 1);
System.out.print("\nSORTED (every " + step + " word): ");
for (int i = 0; i < allWords.length; i++) {
if (i % step == 0)
System.out.print(allWords[i] + " ");
}
System.out.println("\n");
System.out.println("BINARY SEARCH:");
// Run timingCount loops for countWordsInSorted
// for the first loop, output the count for each word
for (int j = 0; j < timingCount; j++) {
for (int i = 0; i < queryWords.length; i++) {
int count = countWordsInUnsorted(allWords, queryWords[i]);
if (j == 0) {
System.out.println(queryWords[i] + ":" + count);
}
}
}
long t3 = (new Date()).getTime();
long timeToSearchBinary = t3 - t2;
System.out.printf("%d ms for %d searches, %f ms per search\n", timeToSearchBinary, searchCount, timeToSearchBinary*1.0f/searchCount);
}
public static String[] readWords(String fileName) throws FileNotFoundException{
String[] words = new String[0];
int i = 0;
File file = new File(fileName);
Scanner input = new Scanner(file, "UTF-8");
input.useDelimiter("\\s+|\\-");
while(input.hasNext()){
String word = input.next();
word = word.toLowerCase();
word = word.replaceAll("[^a-zA-Z ]", "");
words = Arrays.copyOf(words, words.length + 1);
words[i++] = word;
}
return null;
}
public static int countWordsInUnsorted(String[] WordsToCount, String countedWord){
if ((countedWord == null) || (WordsToCount == null)){
return 0;
}
int counter = 0;
for( String word : WordsToCount){
if (word.equals(countedWord)){
counter++;
}
}
return counter;
}
public static void mergeSort(String[] arrayToSort, String[] tempArray, int first, int last){
if (first == last){
return;
}
int mid = (first + last)/2;
mergeSort(arrayToSort, tempArray, first, mid);
mergeSort(arrayToSort, tempArray, mid + 1, last);
merge(arrayToSort, tempArray, first, mid, mid + 1, last);
}
public static void merge(String[] arrayToSort, String[] tempArray, int first, int mid, int mid1, int last){
int j = first;
int k = mid1;
int l = first;
do{
if (tempArray[j].compareTo(tempArray[k]) < 0){
arrayToSort[l] = tempArray[j];
j++;
}
else{
arrayToSort[l] = tempArray[k];
k++;
}
}
while (j <= mid && k <= last){
}
}
public static int binarySearch(String[] sortedWords, String query, int startIndex, int endIndex){
if (startIndex > endIndex){
}
int mid = (startIndex + endIndex)/2;
if(query.compareTo(sortedWords[mid]) == 0){
return;
}
if (query.compareTo(sortedWords[mid]) < 0){
binarySearch(sortedWords, query, startIndex, mid -1 );
}
if (query.compareTo(sortedWords[mid]) > 0){
binarySearch(sortedWords, query, mid + 1, endIndex);
}
return -1;
}
public static int getSmallestIndex(String[] words, String query, int startIndex, int endIndex){
return -1;
}
public static int getLargestIndex(String[] words, String query, int startIndex, int endIndex){
return -1;
}
public static int countWordsInSorted(String[] wordsTocount, String countedWord){
return 0;
}
}
In the readWords method you should call .toLowerCase() and .replaceAll() only after reading the word form the file.
Edit: The return null; must not be the only return of the method, otherwise you're just throwing away everything the method did. You could use it to handle error, but I strongly suggest to return an empty array.
If you return an empty array (or collection), you avoid surprising consumers (whoever call your method) of this method with an unintentional NullPointerException, so so they don't have to guard against it.
Take also a look at the tutorial for try-with-resources statement I've used.
public static String[] readWords(final String filename) {
// Check on input
if (filename == null) {
return null;
}
String[] words = new String[0];
int i = 0; // index of the first empty position in the array
final File inputFile = new File(filename);
try (Scanner in = new Scanner(inputFile)) {
in.useDelimiter("\\s+|\\-");
while (in.hasNext()) {
String word = in.next().toLowerCase();
word = word.replaceAll("[^a-z ]", ""); // No need of A-Z since we called .toLowerCase()
words = Arrays.copyOf(words, words.length + 1);
words[i++] = word; // Put the current word at the bottom of the array, then increment the index
}
} catch (final FileNotFoundException e) {
System.err.println("The file " + filename + " does not exist");
return null;
}
return words;
}
The method is very simple, I suggest using the enhanced for loop.
Please also consider Java naming conventions: variable name should start with lowercase letter.
Edit: same here about the return 0;. It must not be the only return of the method.
public static int countWordsInUnsorted(final String searchedWord, final String[] words) {
// Check on input
if ((searchedWord == null) || (words == null)) {
return 0;
}
int count = 0;
for (final String word : words) {
if (word.equals(searchedWord)) {
count++;
}
}
return count;
}
Edit4:
This program takes one command line argument: the words to search for and count.
java SearchAndSort "frankenstein,doctor,igor,monster"
Since the assignement uses a comma as separator, you can use String.split() to get the array of strings.
Due to the need of calculating the elapsed time, you cannot search and print the words: the print operation is very time expensive, so it would distort the result. That's why I used an array of int to store the count of every word.
int[] wordsCounter = new int[queryWords.length];
In order to calculate the time per search, you need a double, but if you do an arithmetic operation between long, you'll always get a long. That's why you need to cast at least one operand to double.
double timePerSearch = ((double) elapsedTime) / totalSearches;
Refactored main method:
public static void main(final String[] args) {
// Default words to search for
String queryWordsString = "doctor,frankenstein,the,monster,igor,student,college,lightning,electricity,blood,soul";
if (args.length > 0) { // If the user provided query words
queryWordsString = args[0];
}
final String[] queryWords = queryWordsString.split(","); // split the string into an array
System.out.println("SEARCH AND SORT");
System.out.println();
System.out.println("Searching and counting the words " + queryWordsString); // print the words to search for
System.out.println();
// Just the name of the file if it's in the same directory of program
// The absolute path if they are in different directory
final String filename = "frankenstein.txt";
// Read words from file
final String[] words = SearchAndSort.readWords(filename);
if (words == null) {
return;
}
final int[] wordsCounter = new int[queryWords.length];
// Store the starting time
final long startTime = System.currentTimeMillis();
// Search the words and store their count
for (int i = 0; i < queryWords.length; i++) {
final int count = SearchAndSort.countWordsInUnsorted(queryWords[i], words);
wordsCounter[i] = count;
}
final long elapsedTime = System.currentTimeMillis() - startTime;
System.out.println("NAIVE SEARCH:");
// Print how many time each word appears
for (int i = 0; i < queryWords.length; i++) {
System.out.println(queryWords[i] + ": " + wordsCounter[i]);
}
final int totalSearches = queryWords.length * words.length;
final double timePerSearch = ((double) elapsedTime) / totalSearches;
// Print the elapsed time in ms
System.out.println(elapsedTime + " ms for " + totalSearches + " searches, " + timePerSearch + " ms per search");
}
Edit4:
long t0 = ( new Date()).getTime();
for (var i = 0 ; i < 100000 ; i++) {
// Do something that takes time
}
In this example the 100000 isn't a new variable, it's the standard upper limit of the for loop you're going to use. It's queryWords.length for NAIVE SEARCH: block and words.length for SORTING: block.
Edit:
// Record the current time
long t0 = (new Date()).getTime();
Don't do this. If you want to measure elapsed time, you should use long t0 = System.nanoTime(). From the documentation
Returns the current value of the running Java Virtual Machine's high-resolution time source, in nanoseconds.
This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time. The value returned represents nanoseconds since some fixed but arbitrary origin time (perhaps in the future, so values may be negative). The same origin is used by all invocations of this method in an instance of a Java virtual machine; other virtual machine instances are likely to use a different origin.
This method provides nanosecond precision, but not necessarily nanosecond resolution (that is, how frequently the value changes) - no guarantees are made except that the resolution is at least as good as that of currentTimeMillis().
[...]
The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
For example, to measure how long some code takes to execute:
Probably you would read System.currentTimeMillis() documentation too.
So I'm working on some Java exercises, and one that has caught my attention recently is trying to produce all permutations of a String using iteration. There are plenty of examples online - however, a lot of them seem very complex and I'm not able to follow.
I have tried using my own method, which when tested with a string of length 3 it works fine. The method is to (for each letter) keep moving a letter along the string, swapping it with whatever letter is in front of it. E.g.
index: 012
string: abc
(iteration 1) swap 'a' (index 0) with letter after it 'b' (index 0+1) : bac
(iteration 2) swap 'a' (index 1) with letter after it 'c' (index 1+1) : bca
(iteration 3) swap 'a' (index 2) with letter after it 'b' (index 0) : acb
current permutations: abc (original), bac, bca, acb
(iteration 3) swap 'b' (index 1) with letter after it 'c' (index 1+1) : acb
(iteration 4) swap 'b' (index 2) with letter after it 'a' (index 0) : bca
(iteration 5) swap 'b' (index 0) with letter after it 'c' (index 1) : cba
current permutations: abc (original), bac, bca, acb, acb, cba
...
This is how I implemented it in Java:
String str = "abc"; // string to permute
char[] letters = str.toCharArray(); // split string into char array
int setLength = factorial(letters.length); // amount of permutations = n!
HashSet<String> permutations = new HashSet<String>(); // store permutations in Set to avoid duplicates
permutations.add(str); // add original string to set
// algorithm as described above
for (int i = 0; i < setLength; i++) {
for (int j = 0; j < letters.length; j++) {
int k;
if (j == letters.length - 1) {
k = 0;
} else {
k = j + 1;
}
letters = swap(letters, j, k);
String perm = new String(letters);
permutations.add(perm);
}
}
The problem is if I input a string of length 4, I only end up with 12 permutations (4x3) - if I input a string of length 5, I only end up with 20 permutations (5x4).
Is there a simple modification I could make to this algorithm to get every possible permutation? Or does this particular method only work for strings of length 3?
Appreciate any feedback!
Suppose the input is "abcd". This is how your algorithm will work
bacd
bacd
bcad
bcda
If you observe carefully, "a" was getting positioned at all indexes and the following consecutive letter was getting replaced with "a". However, after your algorithm has produced "bacd" - it should be followed by "badc" also, which will be missing from your output.
For string of length 4, When you calculated the number of permutations as factorial, you understand that the first position can be occupied by 4 characters, followed by 3, 2 and 1. However, in your case when the first two positions are occupied by "ba" there are two possibilities for 3rd position, i.e. c and d. While your algorithm correctly finds "cd", it fails to find "dc" - because, the loop does not break the problem into further subproblems, i.e. "cd" has two permutations, respectively "cd" and "dc".
Thus, the difference in count of your permutations and actual answer will increase as the length of string increases.
To easily break problems into sub-problem and solve it, many algorithm uses recursion.
However, you could look into Generate list of all possible permutations of a string for good iterative answers.
Also, as the length of string grows, calculating number of permutation is not advisable.
While I do not know of a way to expand upon your current method of switching places (I've attempted this before to no luck), I do know of a fairly straightforward method of going about it
//simple method to set up permutate
private static void permutations(String s)
{
permutate(s, "");
}
//takes the string of chars to swap around (s) and the base of the string to add to
private static void permutate(String s, String base)
{
//nothing left to swap, just print out
if(s.length() <= 1)
System.out.println(base + s);
else
//loop through the string of chars to flip around
for(int i = 0; i < s.length(); i++)
//call with a smaller string of chars to flip (not including selected char), add selected char to base
permutate(s.substring(0, i) + s.substring(i + 1), base + s.charAt(i));
}
The goal with this recursion is to delegate as much processing as possible to something else, breaking the problem down bit by bit. It's easy to break down this problem by choosing a char to be first, then telling a function to figure out the rest. This can then be done for each char until they've all been chosen once
Consider the following code to permute a string containing unique characters from McDowell:
This code is based on a recursion and adds to each result by placing a character to every single spot. If the string contains n characters, then there are n+1 such places.
Since for every string (there are n! possible strings of length n), you add n+1 characters, the complexity should be 1! + 2! + ... + (n+1)!
The author says that the complexity is O(n!). Is this the same as O(1!+...+(n+1)!)? If so, why?
public static ArrayList<String> getPerms(String str){
if (str == null){
return null;
}
ArrayList<String> permutations = new ArrayList<String>();
if (str.length() == 0){
permutations.add("");
return permutations;
}
char first = str.charAt(0);
String remainder = str.substring(1);
ArrayList<String> words = getPerms(remainder);
for (String word: words){
for (int j = 0; j <= word.length(); j++){
String s = insertCharAt(word, first, j);
permutations.add(s);
}
}
return permutations;
}
public static String insertCharAt(String s, char c, int j){
String start = s.substring(0, j);
String end = s.substring(j+1);
return start + c + end;
}
Let's define F(n) as the length of the list returned by getPerms when you pass it a string of length n.
By inspection of the base case, we see that F(0) == 1.
In the recursive case, with a string of length n > 0, getPerms calls itself recursively on a string of length n-1. It gets back a list of length F(n-1) (that is the definition of F) and stores the list in words. For each element of words, it adds n elements to permutations. Thus permutations will have n * F(n-1) elements when it is returned.
Therefore, F(n) == n * (n-1) * (n-2) * ... * 1 == n!. The getPerms function returns a list of length n! given a string of length n.
Now, what is the complexity of getPerms? I'll assume we're talking about time complexity. Well, it performs ∑i=0ni! list appends in total (n! for the outermost call, plus (n-1)! for the first recursive call, plus (n-2)! for the second recursive call, etc.). So you could say the complexity is O(∑ni!) list appends.
But list appends aren't the only computation here. We copy a lot of characters around when we join strings in Java.
Let's say G(n) is the number of character copies performed by a call to getPerms when you pass a string of length n.
By inspection of the base case, we see that G(0) == 0.
In the recursive case, getPerms calls itself on a string of length n-1, which performs G(n-1) character copies and returns a list of F(n-1) strings, each of length n-1. Then getPerms copies each of those strings n times, inserting another character into each copy, so F(n-1) * n * n == n * n! characters copied directly in the getPerms call, in addition to the G(n-1) from the recursion.
Thus the number of characters copied by a call to getPerms is O(∑i=0ni * i!).