Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completed Design 2 #2046

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions DesignHashMap Linear Chaining Question
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
706. Design HashMap

Design a HashMap without using any built-in hash table libraries.

Implement the MyHashMap class:

MyHashMap() initializes the object with an empty map.
void put(int key, int value) inserts a (key, value) pair into the HashMap. If the key already exists in the map, update the corresponding value.
int get(int key) returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key.
void remove(key) removes the key and its corresponding value if the map contains the mapping for the key.


Example 1:

Input
["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"]
[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]]
Output
[null, null, null, 1, -1, null, 1, null, -1]

Explanation
MyHashMap myHashMap = new MyHashMap();
myHashMap.put(1, 1); // The map is now [[1,1]]
myHashMap.put(2, 2); // The map is now [[1,1], [2,2]]
myHashMap.get(1); // return 1, The map is now [[1,1], [2,2]]
myHashMap.get(3); // return -1 (i.e., not found), The map is now [[1,1], [2,2]]
myHashMap.put(2, 1); // The map is now [[1,1], [2,1]] (i.e., update the existing value)
myHashMap.get(2); // return 1, The map is now [[1,1], [2,1]]
myHashMap.remove(2); // remove the mapping for 2, The map is now [[1,1]]
myHashMap.get(2); // return -1 (i.e., not found), The map is now [[1,1]]


Constraints:

0 <= key, value <= 106
At most 104 calls will be made to put, get, and remove.
64 changes: 64 additions & 0 deletions HashMapDoubleHashing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
HashSet using Double Hashing is already done in Design 1 Assignment

Time Complexity: O(1) for put, get, and remove.
Space Complexity: O(1) for operations; O(n) for storage due to the 2D array.
*/
public class HashMapDoubleHashing {

int primaryArraySize;
int secondaryArraySize;
int[][] storage;

public HashMapDoubleHashing() {
this.primaryArraySize = 1001;
this.secondaryArraySize = 1000;
// Using -1 to indicate an empty slot since key and value are non-negative integers
this.storage = new int[primaryArraySize][];
}

private int getPrimaryHashValue(int key) {
return key / primaryArraySize;
}

private int getSecondaryHashValue(int key) {
return key % secondaryArraySize;
}

public void put(int key, int value) {
int primaryArrayIndex = getPrimaryHashValue(key);

if (storage[primaryArrayIndex] == null) {
// Initialize a new array if it doesn't exist, using -1 as the default value
this.storage[primaryArrayIndex] = new int[secondaryArraySize];
for (int i = 0; i < secondaryArraySize; i++) {
this.storage[primaryArrayIndex][i] = -1;
}
}

int secondaryArrayIndex = getSecondaryHashValue(key);
storage[primaryArrayIndex][secondaryArrayIndex] = value;
}

public int get(int key) {
int primaryArrayIndex = getPrimaryHashValue(key);

if (storage[primaryArrayIndex] == null) {
return -1; // Key doesn't exist
}

int secondaryArrayIndex = getSecondaryHashValue(key);
return storage[primaryArrayIndex][secondaryArrayIndex];
}

public void remove(int key) {
int primaryArrayIndex = getPrimaryHashValue(key);

if (storage[primaryArrayIndex] == null) {
return; // Nothing to remove as value doesn't exist
}

int secondaryArrayIndex = getSecondaryHashValue(key);
storage[primaryArrayIndex][secondaryArrayIndex] = -1; // Set to -1 to indicate removal
}
}
150 changes: 150 additions & 0 deletions HashMapLinearChainingHW.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
Time Complexity : {Time Complexity is analyzed based on => Whenever we are receiving some input, with the increase in our input, how our
algorithm performs}
In order to optimize performance we are increasing the primary data structure size and decreasing the secondary data
structure size to have fewer collisions. If our time complexity is not increasing after a particular point,and that point
is a very small number, we can tell our average time complexity is O(1), here the small number for us is 100 in
secondary data structure.

Note: If interviewer is asking that 100 size of secondary bucket is also large number, and it is affecting performance, then the answer can
be I can check in production and based on performance if time complexity is not O(1) or more, I can increase the size of primary data structure.

Note: It is also means increase of size of primary data structure and decrease of secondary data structure is trade off between
performance and space.

Space Complexity : For user oriented functions, all the functions put(), get(), remove() will be having amortized complexity O(1). For constructor,
we are creating storage so it will have O(n) but that is not what we be looking at. We only look at what we created for user.

Did this code successfully run on Leetcode : Yes

HASH MAP USING LINEAR CHAINING {HOMEWORK}

Here the primary dataset will be a Node Array and secondary will be linkedlist, in java hash map by default uses Linear chaining to
handle collisions

null, __ , null, null {primary buckets}
-1, -1 {linked list}
2001, 3
3001, 5

Here, like in previous homework Design 1, we choose 1000 for primary bucket and 1000 for secondary bucket, we followed this formula
where we took square root of 10^6 so we got 1000 Primary bucket and 1000 secondary bucket, but here if we use that size, we will have 1000
collisions.
*/
class HashMapLinearChainingHW {

class Node
{
int key;
int value;
Node next;

public Node(int key, int value) {
this.key = key;
this.value = value;
}
}

Node[] storage; //Primary array
int primaryBucketSize;

public HashMapLinearChainingHW() {
/*
As the bucket size is 10000, there will be 100 collisions, due to less collisions, after 100 elements, the time to access
the element, basically search will become constant i.e. O(1)
*/
this.primaryBucketSize = 10000;
this.storage = new Node[primaryBucketSize];
}

private int getPrimaryHash(int key) {
return key%primaryBucketSize;
}

private Node getPrevNode(Node head, int key) {
Node prev = null;
Node curr = head;

//Traversing through the nodes to find previous
while(curr != null && curr.key!=key)
{
prev = curr;
curr = curr.next;
}

return prev;
}

public void put(int key, int value) {
int primaryIndex = getPrimaryHash(key);
if(storage[primaryIndex] == null){
storage[primaryIndex] = new Node(-1, -1);
}
/*
There will be two scenarios:
1) If the node exists, replace the value.
2) If he node doesn't exist, we will reach the end of linked list and the last node will point to null, so point the last node
to point to new node and new node will point to null.

Note: We will not need a tail pointer, as anyway due to point 1, we have to traverse through complete linked list to check if the
value exists, it doesn't then only we will add a new node in end, so tail pointer is of no use.
*/

Node prev = getPrevNode(storage[primaryIndex], key);
if(prev.next == null) //Point 2
{
prev.next = new Node(key, value);
}
else {
prev.next.value = value; //Point 1
}
}

public int get(int key) {

int primaryIndex = getPrimaryHash(key);
if(storage[primaryIndex] == null) { //Primary index location in array itself is null
return -1;
}

Node prev = getPrevNode(storage[primaryIndex], key);
if(prev.next == null) { //Point 2, element not found after traversal
return -1;
}

return prev.next.value;
}

public void remove(int key) {
int primaryIndex = getPrimaryHash(key);
if(storage[primaryIndex] == null) { //Primary index location in array itself is null
return;
}

Node prev = getPrevNode(storage[primaryIndex], key);
if(prev.next == null) { //Point 2, element not found after traversal
return;
}

/*
eg: remove 3 hashmap {100,2 ; 101,3 ; 102,5}
prev = {100}
prev.next = curr.next => old value of prev.next is 101, new value is 102
curr.next = null meaning 101 is made to point to null
*/
Node curr = prev.next;
prev.next = curr.next;
curr.next = null;
}
}

/**
* Your MyHashMap object will be instantiated and called as such:
* MyHashMap obj = new MyHashMap();
* obj.put(key,value);
* int param_2 = obj.get(key);
* obj.remove(key);
*/
90 changes: 90 additions & 0 deletions HashSetLinearChaining.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
Time Complexity : This implementation maintains the O(1) average time complexity for operations
due to the hashing, similar to hash map.
Space Complexity : Amortized Complexity for User Functions (add(), remove(), contains()): O(1)
Worst-Case Complexity for User Functions: O(m), where m is the number of elements in the set.
*/
class HashSetLinearChaining {

class Node {
int key;
Node next;

public Node(int key) {
this.key = key;
}
}

Node[] storage; // Primary array
int primaryBucketSize;

public HashSetLinearChaining() {
// Initialize with the same primary bucket size as the hash map
this.primaryBucketSize = 10000;
this.storage = new Node[primaryBucketSize];
}

private int getPrimaryHash(int key) {
return key % primaryBucketSize;
}

private Node getPrevNode(Node head, int key) {
Node prev = null;
Node curr = head;

// Traverse the nodes to find the previous node
while (curr != null && curr.key != key) {
prev = curr;
curr = curr.next;
}

return prev;
}

public void add(int key) {
int primaryIndex = getPrimaryHash(key);
if (storage[primaryIndex] == null) {
// Initialize the linked list with a dummy head node
storage[primaryIndex] = new Node(-1);
}

Node prev = getPrevNode(storage[primaryIndex], key);

// If the key does not exist, add it to the end of the list
if (prev.next == null) {
prev.next = new Node(key);
}
}

public void remove(int key) {
int primaryIndex = getPrimaryHash(key);
if (storage[primaryIndex] == null) {
// If the bucket is empty, do nothing
return;
}

Node prev = getPrevNode(storage[primaryIndex], key);
if (prev.next == null) {
// Key not found in the set
return;
}

// Remove the node
Node curr = prev.next;
prev.next = curr.next;
curr.next = null;
}

public boolean contains(int key) {
int primaryIndex = getPrimaryHash(key);
if (storage[primaryIndex] == null) {
// If the bucket is empty, the key is not present
return false;
}

Node prev = getPrevNode(storage[primaryIndex], key);
return prev.next != null; // Return true if the key is found
}
}


42 changes: 42 additions & 0 deletions QueueUsingStack Question
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
232. Implement Queue using Stacks {Easy}

Implement a first in first out (FIFO) queue using only two stacks. The implemented queue should support all the functions of a normal queue (push, peek, pop, and empty).

Implement the MyQueue class:

void push(int x) Pushes element x to the back of the queue.
int pop() Removes the element from the front of the queue and returns it.
int peek() Returns the element at the front of the queue.
boolean empty() Returns true if the queue is empty, false otherwise.
Notes:

You must use only standard operations of a stack, which means only push to top, peek/pop from top, size, and is empty operations are valid.
Depending on your language, the stack may not be supported natively. You may simulate a stack using a list or deque (double-ended queue) as long as you use only a stack's standard operations.


Example 1:

Input
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
Output
[null, null, null, 1, 1, false]

Explanation
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false


Constraints:

1 <= x <= 9
At most 100 calls will be made to push, pop, peek, and empty.
All the calls to pop and peek are valid.


Follow-up: Can you implement the queue such that each operation is amortized O(1) time complexity? In other words, performing n operations will take overall O(n) time even if one of those operations may take longer.

Loading