I've included a picture of the problem below that explains it in more detail. The goal is to just find the k highest occurrences in a dictionary of words. My approach is getting the frequency in a HashMap and then using a Priority Queue to store the max k elements. I then add the max k elements to my return list and return it.
For the given input in the picture, my code returns to correct output -
["i","love"]. The problem is for inputs like the one below:
input: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"]
output: ["day","sunny","is","the"]
expected: ["the","is","sunny","day"]
The correct answer would just be a reverse of my current string, however if I reverse the string before returning the original input (the one in the picture) no longer works.
I think this had something to do with how the values are being store in the priority queue when their frequency is the same...but I'm not sure of to check for that.
Any thoughts on how I could fix this?
class Solution {
public List<String> topKFrequent(String[] words, int k) {
HashMap<String, Integer> map = new HashMap<>();
List<String> mostFrequent = new ArrayList<>();
for(int i = 0; i < words.length; i++) {
if(map.containsKey(words[i])) {
map.put(words[i], map.get(words[i]) + 1);
}
else {
map.put(words[i], 1);
}
}
PriorityQueue<String> pq = new PriorityQueue<String>((a,b) -> map.get(a) - map.get(b));
for(String s : map.keySet()) {
pq.add(s);
if(pq.size() > k) {
pq.remove();
}
}
for(String s : pq) {
mostFrequent.add(s);
}
//Collections.reverse(mostFrequent);
return mostFrequent;
}
}
One way to achieve is by changing your code like below,
class Solution {
public static List<String> topKFrequent(String[] words, int k) {
HashMap<String, Integer> map = new HashMap<>();
List<String> mostFrequent = new ArrayList<>();
for(int i = 0; i < words.length; i++) {
if(map.containsKey(words[i])) {
map.put(words[i], map.get(words[i]) + 1);
}
else {
map.put(words[i], 1);
}
}
//Below I am sorting map based on asked condition and storing it into the list.
List<Map.Entry<String,Integer>> sorted = new ArrayList<>(map.entrySet());
Collections.sort(sorted,(Map.Entry<String,Integer> x,Map.Entry<String,Integer> y) -> x.getValue().compareTo(y.getValue()) == 0? x.getKey().compareTo(y.getKey()):x.getValue().compareTo(y.getValue()) > 0 ? -1 : 1 );
for(Map.Entry<String,Integer> e : sorted) {
mostFrequent.add(e.getKey());
}
return mostFrequent;
}
Here, after creating the frequency map I am sorting them based on frequency and creating one new ArrayList.
You almost did it. But you have a few bugs.
At first, your solution is not full. In original task they asked not only about frequency but additional, if frequency is equal - output elements in alphabetical order.
To achieve that you can use following comparator for PriorityQueue:
PriorityQueue<String> pq = new PriorityQueue<String>((a, b) -> {
int countComparison = Integer.compare(map.get(a), map.get(b));
if (countComparison == 0)
return b.compareTo(a);
return countComparison;
});
And, the next mistake with your solution is iterating over PriorityQueue. PriorityQueue iterator does not guarantee any particular order. From the Javadocs
The Iterator provided in method iterator() is not guaranteed to traverse the elements of the priority queue in any particular order.
Because of it you need to poll elements from the queue. And part responsible for it:
while(!pq.isEmpty()) {
String s = pq.poll();
mostFrequent.add(s);
}
And final part - because of the order of elements in queue(from the lowest to the highest) you need to reverse output array:
Collections.reverse(mostFrequent);
The final solution will look like this:
public List<String> topKFrequent(String[] words, int k) {
HashMap<String, Integer> map = new HashMap<>();
List<String> mostFrequent = new ArrayList<>();
for(int i = 0; i < words.length; i++) {
if(map.containsKey(words[i])) {
map.put(words[i], map.get(words[i]) + 1);
}
else {
map.put(words[i], 1);
}
}
PriorityQueue<String> pq = new PriorityQueue<String>((a, b) -> {
int countComparison = Integer.compare(map.get(a), map.get(b));
if (countComparison == 0)
return b.compareTo(a);
return countComparison;
});
for(String s : map.keySet()) {
pq.add(s);
if(pq.size() > k) {
pq.remove();
}
}
while(!pq.isEmpty()) {
String s = pq.poll();
mostFrequent.add(s);
}
Collections.reverse(mostFrequent);
return mostFrequent;
}
Related
I'm trying to use hashmaps to detect any duplicates in a given list, and if there is, I want to add "1" to that String to indicate its duplication. If it occurs 3 times, the third one would add "3" after that string.
I can't seem to figure that out, keeping track of the number of duplicates. It only adds 1 to the duplicates, no matter if it's the 2nd or 3rd or 4th,..etc duplicate.
This is what I have:
public static List<String> duplicates(List<String> given) {
List<String> result = new ArrayList<String>();
HashMap<String, Integer> hashmap = new HashMap<String, Integer>();
for (int i=0; i<given.size(); i++) {
String current = given.get(i);
if (hashmap.containsKey(current)) {
result.add(current+"1");
} else {
hashmap.put(current,i);
result.add(current);
}
}
return result;
}
I want to include the values that only occur once as well, as is (no concatenation).
Sample Input: ["mixer", "toaster", "mixer", "mixer", "bowl"]
Sample Output: ["mixer", "toaster", "mixer1", "mixer2", "bowl"]
public static List<String> duplicates(List<String> given) {
final Map<String, Integer> count = new HashMap<>();
return given.stream().map(s -> {
int n = count.merge(s, 1, Integer::sum) - 1;
return s + (n < 1 ? "" : n);
}).collect(toList());
}
I renamed final to output as the first one is a keyword that cannot be used as a variable name.
if (hashmap.containsKey(current)) {
output.add(current + hashmap.get(current)); // append the counter to the string
hashmap.put(current, hashmap.get(current)+1); // increment the counter for this item
} else {
hashmap.put(current,1); // set a counter of 1 for this item in the hashmap
output.add(current);
}
You always add the hard-coded string "1" instead of using the count saved in the map:
public static List<String> duplicates(List<String> given) {
List<String> result = new ArrayList<>(given.size());
Map<String, Integer> hashmap = new HashMap<>();
for (String current : given) {
if (hashmap.containsKey(current)) {
int count = hashmap.get(current) + 1;
result.add(current + count);
hashmap.put(current, count);
} else {
hashmap.put(current, 0);
result.add(current);
}
}
return result;
}
ArrayList finallist = new ArrayList<String>();
for (int i=0; i<given.size(); i++) {
String current = given.get(i);
if (hashmap.containsKey(current)) {
hashmap.put(current,hashmap.get(current)+1);
} else {
hashmap.put(current,1);
}
String num = hashmap.get(current) == 1 ? "" :Integer.toString(hashmap.get(current));
finallist.add(current+num);
}
System.out.println(finallist);
Below is the code I have implemented. My doubt here is: when I am trying to print the first biggest and second Biggest values in the string, the output I get is in the order of [second biggest, first biggest].
Here is the output of what I got for the below code:
The output of the map is: real--6
The output of the map is: to--2
The output of the map is: world--1
The output of the map is: hello--0
The list after insertion is: [to, real]
The list inserted as [biggest,secondBiggest] after calling main is: [to, real]
......
but, I want The list after insertion to be: [real, to].
public class ReadString {
static String input = "This is a real project with real code to do real things to solve real problems in real world real";
public static void main(String[] args) {
List<String> lst = ReadString.RepeatedString("This is a real project with real "
+ "code to do real things to solve real " + "problems in real world real");
System.out.println("The list inserted as [biggest,secondBiggest] after calling main is: " + lst);
}
public static List<String> RepeatedString(String s) {
String[] s2 = input.split(" ");
String[] key = { "real", "to", "world", "hello" };
int count = 0;
Integer biggest = 0;
Integer secondBiggest = 1;
Map<String, Integer> map = new HashMap<String, Integer>();
for (int j = 0; j < key.length; j++) {
count = 0;
for (int i = 0; i < s2.length; i++) {
if (s2[i].equals(key[j])) {
count++;
}
}
map.put(key[j], count);
System.out.println("The output of the map is: " +key[j] + "--" + count);
}
/*
* To find the top two most repeated values.
*/
List<Integer> values = new ArrayList<Integer>(map.values());
Collections.sort(values);
for (int n : map.values()) {
if (biggest < n) {
secondBiggest = biggest;
biggest = n;
} else if (secondBiggest < n)
secondBiggest = n;
}
/* To get the top most repeated strings. */
List<String> list = new ArrayList<String>();
for (String s1 : map.keySet()) {
if (map.get(s1).equals(biggest))
list.add(s1);
else if (map.get(s1).equals(secondBiggest))
list.add(s1);
}
System.out.println("The list after insertion is: " +list);
return list;
}
}
The problem appears to be when you are adding items to the list. As you are iterating through the map.keySet(), there is no guarantee that you will get the biggest item first. The smallest change I would make would be to add the biggest item first in the list.
for (String s1 : map.keySet()) {
if (map.get(s1).equals(biggest))
list.add(0, s1);
else if (map.get(s1).equals(secondBiggest))
list.add(s1);
}
This way, if secondBiggest is added first, biggest will be at the top of the list.
We can simplify your approach quite a bit if we extract the word and count into a simple POJO. Something like,
static class WordCount implements Comparable<WordCount> {
String word;
int count;
WordCount(String word, int count) {
this.word = word;
this.count = count;
}
#Override
public int compareTo(WordCount o) {
return Integer.compare(count, o.count);
}
}
Then we can use that in repeatedString. First, count the words in the String; then build a List of WordCount(s). Sort it (since it's Comparable it has natural ordering). Then build the List to return by iterating the sorted List of WordCount(s) in reverse (for two items). Like,
static List<String> repeatedString(String s) {
Map<String, Integer> map = new HashMap<>();
for (String word : s.split("\\s+")) {
map.put(word, !map.containsKey(word) ? 1 : 1 + map.get(word));
}
List<WordCount> al = new ArrayList<>();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
al.add(new WordCount(entry.getKey(), entry.getValue()));
}
Collections.sort(al);
List<String> ret = new ArrayList<>();
for (int i = al.size() - 1; i >= al.size() - 2; i--) {
ret.add(al.get(i).word);
}
return ret;
}
Finally, your main method should use your static input (or static input should be removed)
static String input = "This is a real project with real code to do "
+ "real things to solve real problems in real world real";
public static void main(String[] args) {
List<String> lst = repeatedString(input);
System.out.println("The list inserted as [biggest,"
+ "secondBiggest] after calling main is: " + lst);
}
And I get (as requested)
The list inserted as [biggest,secondBiggest] after calling main is: [real, to]
If you are only concerned about biggest and secondbiggest,
you can refer to the code below.
Instead of creating the list directly, I created an array, added required elements on specified positions. (This way it becomes more readable)
and finally convert the array to a list.
/* To get the top most repeated strings. */
String[] resultArray = new String[2];
for (String s1 : map.keySet()) {
if (map.get(s1).equals(biggest))
resultArray[0]=s1;
else if (map.get(s1).equals(secondBiggest))
resultArray[1]=s1;
}
List<String> list = Arrays.asList(resultArray);
I am trying to find the number of Strings that only appear exactly once in an ArrayList.
How many I achieve this (preferably with the best possible time complexity)?
Below is my method:
public static int countNonRepeats(WordStream words) {
ArrayList<String> list = new ArrayList<String>();
for (String i : words) {
list.add(i);
}
Collections.sort(list);
for (int i = 1; i < list.size(); i++) {
if (list.get(i).equals(list.get(i - 1))) {
list.remove(list.get(i));
list.remove(list.get(i - 1));
}
}
System.out.println(list);
return list.size();
}
Why doesn't it remove the String at list.get(i) and list.get(i-1)?
There is no need for sorting.
A better approach would be to use two HashSet One for maintaining repeating and one for non-repeating words. Since HashSet internally uses HashMap, Ideally contains, get, put operation has o(1) complexity. So the overall complexity of this approach would be o(n).
public static int countNonRepeats(List<String> words) {
Set<String> nonRepeating = new HashSet<String>();
Set<String> repeating = new HashSet<String>();
for (String i : words) {
if(!repeating.contains(i)) {
if(nonRepeating.contains(i)){
repeating.add(i);
nonRepeating.remove(i);
}else {
nonRepeating.add(i);
}
}
}
System.out.println(nonRepeating.size());
return nonRepeating.size();
}
Here's one simple suggestion:
First, sort your array by alphanumerical order
Iterate through with a loop, if( !list.get(i).equals(list.get(i+1)) ) → unique
If you find duplicates, increment i until you reach a different string
This will have the complexity of the sorting algorithm, since step 2+3 should be O(n)
Is there any specific need of using an ArrayList? You can do it easily by using a HashSet.
Here is the code snippet:
public static void main (String[] args) {
String[] words = {"foo","bar","foo","fo","of","bar","of","ba","of","ab"};
Set<String> set = new HashSet<>();
Set<String> common = new HashSet<>();
for (String i : words) {
if(!set.add(i)) {
common.add(i);
}
}
System.out.println(set.size() - common.size());
}
Output:
3
Here is the modified code:
public static int countNonRepeats(WordStream words) {
Set<String> set = new HashSet<>();
Set<String> common = new HashSet<>();
for (String i : words) {
if(!set.add(i)) {
common.add(i);
}
}
return (set.size() - common.size());
}
You can use hashmap to achieve this.With this approach we can count the occurrence of all the words, If we are interested in only unique words then access the element having count = 1.
HashMap<String,Integer> - key represents the String from arraylist and Integer represents the count of occurrence.
ArrayList<String> list = new ArrayList<String>();
HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
for (int i = 0; i < list.size(); i++) {
String key = list.get(i);
if (hashMap.get(key) != null) {
int value = hashMap.get(key);
value++;
hashMap.put(key, value);
} else {
hashMap.put(key, 1);
}
}
int uniqueCount = 0;
Iterator it = hashMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
if ((int) pair.getValue() == 1)
uniqueCount++;
}
System.out.println(uniqueCount);
I have an Array which is structured like this :
String Array = {"1","2","3","41","56","41","72","72","72","78","99"}
and I want to partition this array into a number of arrays which values are not duplicates... like this :
String Array1 = {"1","2","3","41","56","72","78","99"}
String Array2 = {"41","72"}
String Array3 = {"72"}
is there any straight way to do this in Java or I have to do this with ugly loops (Just kidding !) ?
Thanks !
UPDATE
I'm gonna make the question a bit harder... now I have a Map which structure is like below :
Map<String,String> map = new HashMap(){{
put("1##96","10");
put("2##100","5");
put("3##23","100");
put("41##34","14");
put("56##22","25");
put("41##12","100");
put("72##10","100");
put("72##100","120");
put("72##21","0");
put("78##22","7");
}}
note that the values are not important BUT the keys are important...
what can I do to partition this map to submaps which are like :
Map map1 = {"1##96" => "10"
"2##100" => "5"
"3##23" => "100"
"41##34" => "14"
"56##22" => "25"
"72##10" => "100"
"78##22" => "7"
}
Map map2 = {
"41##12" => "100"
"72##100" => "120"
}
Map map3 = {
"72##100" => "120"
}
like before the first part of the map (before '##') is the ID which I want the uniqueness be based upon... this is just like the Array Example but a bit harder and more complex...
Sorry for changing the question midway...
Probably nothing in libs (seems not generic enough) but some ideas:
O(n) time and O(n) space complexity. Here you just count how many times each number occurs and then put them in that many resulting arrays.
#Edit: as #mpkorstanje pointed out if you change the input from numbers to strings or any other objects in the worst-worst case this will degrade to O(n^2). But in that case you should revise your hashing imho for the data on which you're working as it's not well distributed.
public List<List<Integer>> split(int[] input) {
Map<Integer, Integer> occurrences = new HashMap<>();
int maxOcc = 0;
for (int val : input) {
int occ = 0;
if (occurrences.containsKey(val)) {
occ = occurrences.get(val);
}
if (occ + 1 > maxOcc) {
maxOcc = occ + 1;
}
occurrences.put(val, occ + 1);
}
List<List<Integer>> result = new ArrayList<>(maxOcc);
for (int i = 0; i < maxOcc; i++) {
result.add(new LinkedList<>());
}
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
for (int i = 0; i < entry.getValue(); i++) {
result.get(i).add(entry.getKey());
}
}
return result;
}
O(nlogn) time and O(1) space complexity (not counting the resulting arrays) but doesn't retain order and "destroys" the input array. Here you utilize the fact that the array is already sorted so you can just go over it and keep adding the element to an appropriate resulting list depending on whether you're looking at a duplicate or a "new" entry.
public List<List<Integer>> split(int[] input) {
Arrays.sort(input);
int maxDup = getMaxDuplicateNumber(input);
List<List<Integer>> result = new ArrayList<>(maxDup);
for(int i = 0; i < maxDup; i++) {
result.add(new LinkedList<>());
}
int count = 0;
result.get(0).add(input[0]);
for(int i = 1; i < input.length; i++) {
if(input[i] == input[i-1]) {
count++;
} else {
count = 0;
}
result.get(count).add(input[i]);
}
return result;
}
private int getMaxDuplicateNumber(int[] input) {
int maxDups = 1;
int currentDupCount = 1;
for(int i = 1; i < input.length; i++) {
if(input[i] == input[i - 1]) {
currentDupCount++;
} else {
currentDupCount = 1;
}
if(currentDupCount > maxDups) {
maxDups = currentDupCount;
}
}
return maxDups;
}
You can't do this without loops. But you can use a set to remove some loops. You can add data structure trappings to your own liking.
I'm assuming here that the order of elements in the bins must be consistent with the order of the elements in the input array. If not this can be done more efficiently.
public static void main(String[] args) {
String[] array = { "1", "2", "3", "41", "56", "41", "72", "72", "72",
"78", "99" };
List<Set<String>> bins = new ArrayList<>();
for (String s : array) {
findOrCreateBin(bins, s).add(s);
}
System.out.println(bins); // Prints [[1, 2, 3, 41, 56, 72, 78, 99], [41, 72], [72]]
}
private static Set<String> findOrCreateBin(List<Set<String>> bins, String s) {
for (Set<String> bin : bins) {
if (!bin.contains(s)) {
return bin;
}
}
Set<String> bin = new LinkedHashSet<>();
bins.add(bin);
return bin;
}
If I have an ArrayList of these strings...
"String1, String1, String1, String2, String2, String2, String3, String3"
How can I find the most appearing string in this list? If there are any duplicates, I would want to put both into the same list and deal with that accordingly. How could I do this, assuming that the list of strings could be of any size.
This is about as far as I've gotten:
public String getVotedMap() {
int[] votedMaps = new int[getAvailibleMaps().size()];
ArrayList<String> mostVoted = new ArrayList<String>();
int best = 0;
for(int i = 0; i < votedMaps.length; i++) {
if(best > i) {
best = i;
} else if(best == i) {
} else {
}
}
}
getAvailibleMaps() is a list of Maps that I would choose from (again, can get any size)
Thanks!
Use HashMap
The basic Idea is loading all the values into a hashtable. Hash tables are nice because you can assign a value to a unique key. As we add all the keys to the hashtable we are checking to see if it already exists. If it does then we increment the value inside.
After we have inserted all the elements into the hashtable then we go through the hashtable one by one and find the string with the largest value. This whole process is O(n)
Map<String, Integer> map = new HashMap<String, Integer>();
for(int i = 0; i < array.length(); i++){
if(map.get(array[i]) == null){
map.put(array[i],1);
}else{
map.put(array[i], map.get(array[i]) + 1);
}
}
int largest = 0;
String stringOfLargest;
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
if( value > largest){
largest = value;
stringOfLargest = key;
}
}
if you want multiple largest strings then instead of just finding the largest and being done. You can add all the largest to a mutable list.
for example:
int largest = 0;
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
if( value > largest){
largest = value;
}
}
ArrayList<Object> arr = new ArrayList<Object>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
int value = entry.getValue();
if( value == largest){
arr.add(key);
}
}
arr now stores all the most frequently appearing strings.
This process is still O(n)
Just iterate through the list and keep a HashMap<String,Integer> that counts how many times the string has appeared, eg.
Map<String,Integer> counts = new HashMap<String,Integer>();
for(String s : mostVoted) {
if(counts.containsKey(s)) {
counts.put(s,counts.get(s)+1);
} else {
counts.put(s,1);
}
}
// get the biggest element from the list
Map.Entry<String,Integer> e = Collections.max(counts.entrySet(),new Comparator<Map.Entry<String,Integer>>() {
#Override
public int compare(Map.Entry<String,Integer> o1, Map.Entry<String,Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
System.out.printf("%s appeared most frequently, %d times%n",e.getKey(),e.getValue());
If you can use java 8, this will give you a list of most frequent strings or throw an exception if the list is empty.
List<String> winners = strings.stream()
.collect(groupingBy(x->x, counting())) // make a map of string to count
.entrySet().stream()
.collect(groupingBy( // make it into a sorted map
Map.Entry::getValue, // of count to list of strings
TreeMap::new,
mapping(Map.Entry::getKey, toList()))
).lastEntry().getValue();