Skip to content
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
61 changes: 55 additions & 6 deletions Exercise_1.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,68 @@
#include <stdio.h>

// TC: O(log n), since we reduce the search space by half each time. So for an array of size 8, we would call the function 4 times in the worst
// case: 8 -> 4 -> 2 -> 1. log(8) with base 2 = 3 (roughly equal to 4). Therefore, the TC is O(log n).
// SC: O(log n), since there are log n recursive calls, the recursion stack will have size O(log n). We can optimize this to O(1) space complexity
// by using an iterative approach.
// Explanation in Sample.java

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

// A recursive binary search function. It returns
// location of x in given array arr[l..r] is present,
// otherwise -1
int binarySearch(int arr[], int l, int r, int x)
{
//Your Code here
if(l>r) return -1;
int mid = l + (r-l)/2; // to avoid integer overflow
if(arr[mid] == x) return mid; // found the search element
else if(arr[mid] > x) return binarySearch(arr, l, mid-1, x); // element possibly exists in the left half
else return binarySearch(arr, mid+1, r, x); // element possibly exists in the right half
}


// iterative approach
int binarySearchIter(int arr[], int l, int r, int x) {
while(l<=r) {
int mid = l + (r-l)/2;
if(arr[mid] == x){
return mid;
} else if(arr[mid] < x) {
l = mid + 1;
} else{
r = mid - 1;
}
}
return -1;
}

bool isSorted(int A[], int size) {
for(int i=1; i<size; i++){
if(A[i-1] > A[i]) return false;
}
return true;
}

int main(void)
{
int arr[] = { 2, 3, 4, 10, 40 };
/* TEST CASES */
int arr[] = { 2, 3, 4, 10, 40 }; int x = 15; // not present
// int arr[] = {5}; int x = 4; // single element
// int arr[] = {1, 3, 2, 5, 0}; int x = 3; // unsorted
// int arr[] = {}; int x = 5; // empty
// int arr[] = {2, 2, 2, 2, 2}; int x = 2; // all duplicates
// int arr[] = {-10, -3, 0, 1, 8}; int x = -3; // negative
// int arr[] = {INT_MIN, -1, 0, 1, INT_MAX}; int x = INT_MIN; // extreme values

int n = sizeof(arr) / sizeof(arr[0]);
int x = 10;
int result = binarySearch(arr, 0, n - 1, x);
if(n <= 0) {
cerr<<"Invalid array"<<endl;
return 0;
}
if(!isSorted(arr, n)){
cerr<<"Binary search requires sorted array!"<<endl;
return 0;
}
int result = binarySearchIter(arr, 0, n - 1, x);
(result == -1) ? printf("Element is not present in array")
: printf("Element is present at index %d",
result);
Expand Down
77 changes: 73 additions & 4 deletions Exercise_2.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
/*
TC: Best/Average Case -> O(nlogn), Worst Case -> O(n^2)
In the best case, the pivot selection will be such that the array is split into halves each time. So if we split into half at each level, the
recursion tree will have a depth of log n. At each step the work done is n. Since we scan/swap over the entire array at each level. Even if we split
the arrays into smaller subarrays, the total work done for all subarrays on the same level = n. Therefore the best case TC is O(nlogn).
For average case, we might not get a clean half split, but we can still expect a reasonably balanced/non-skewed split. The TC for this case still
ends up about O(nlogn).

The worst case happens when the pivot selection is bad, either the smallest or the largest element in the subarray is picked as pivot. What this does
is that the partitioned subarrays have lengths [n-1] & 0. This leads to a very skewed recursion tree (n -> n-1 -> n-2 -> n-3 ...). The depth of this
tree will be n. The work done at each level still is n. Therefore, the worst case TC becomes O(n^2).

SC: Best/Average case: O(logn) -> There is no extra space needed as we swap in place in the same array. The space consumed is due to the recursion
stack, which is log n in the best/average case as explained above.
For the worst case, since the recursion tree is skewed and resembles a linkedlist/chain type structure, the depth = n. So the SC becomes O(n).

Explanation in Sample.java
*/

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

// A utility function to swap two elements
void swap(int* a, int* b)
{
//Your Code here
void swap(int* a, int* b)
{
if(*a == *b) return;
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}

/* This function takes last element as pivot, places
Expand All @@ -15,6 +37,22 @@ of pivot */
int partition (int arr[], int low, int high)
{
//Your Code here
int pivot = arr[high];
int i = low; // iterator for the left partition
int j = high; // iterator for the right partition
while(i < j){ // to ensure that the left & right iterators don't cross each other
while(i<=high && arr[i] < pivot){ // find first element in the left partition which does not satisfy our constraints
i++;
}
while(j>=low && arr[j] >= pivot) { // find first element in the right partition which does not satisfy our constraints
j--;
}
if(i < j){ // to make sure that the swap is still valid (we don't want to accidentally swap elements which are in the correct partition)
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i], &arr[high]); // the index where the left iterator spills into the right partition will be the correct position of the pivot
return i; // return index of pivot
}

/* The main function that implements QuickSort
Expand All @@ -24,6 +62,11 @@ high --> Ending index */
void quickSort(int arr[], int low, int high)
{
//Your Code here
if(low < high) { // atleast 2 elements need to be present to continue, 1 element by itself is considered sorted
int pIndex = partition(arr, low, high);
quickSort(arr, low, pIndex-1);
quickSort(arr, pIndex+1, high);
}
}

/* Function to print an array */
Expand All @@ -34,12 +77,38 @@ void printArray(int arr[], int size)
cout << arr[i] << " ";
cout << endl;
}

bool isSorted(int A[], int size) {
for(int i=1; i<size; i++){
if(A[i-1] > A[i]) return false;
}
return true;
}

// Driver Code
int main()
{
int arr[] = {10, 7, 8, 9, 1, 5};
/* TEST CASES */
int arr[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
// int arr[] = {}; // empty array
// int arr[] = {1}; // array with only one element (already sorted)
// int arr[] = {1, 2, 3, 4}; // already sorted array
// int arr[] = {1, 3, 2, 1, 2, 3, 4, 1, 4, 2, 3}; // array with duplicates
// int arr[] = {5,-1,3,0,-2,4,-3}; // negatives
// int arr[] = {INT_MIN, 0, INT_MIN, INT_MAX}; // extreme values

int n = sizeof(arr) / sizeof(arr[0]);

if(n == 0) {
cerr<<"Invalid array"<<endl;
return 1;
}

if(n == 1 || isSorted(arr, n)){
cout<<"Array is already sorted."<<endl;
return 0;
}

quickSort(arr, 0, n - 1);
cout << "Sorted array: \n";
printArray(arr, n);
Expand Down
167 changes: 129 additions & 38 deletions Exercise_3.cpp
Original file line number Diff line number Diff line change
@@ -1,50 +1,141 @@
// TC: printMiddle() -> O(n)
// SC: O(1) (printMiddle() requires constant memory)
// Explanation in Sample.java
#include<bits/stdc++.h>
using namespace std;


class LinkedList {
private:
// Struct
struct Node
{
int data;
struct Node* next;
};
Node* head;
public:
LinkedList() {
head = nullptr;
}
~LinkedList() {
while(head){
Node* temp = head;
head = head->next;
delete temp;
}
}
/* Brute force to find middle of the linked list */
void printMiddleBruteForce() {
if(!head){
// throw exception
cerr<<"Linked list is empty!"<<endl;
return;
}
if(!head->next){
cout<<"Middle element: "<<head->data<<endl;
return;
}
Node* temp = head;
int len = 0;
while(temp){ // keep going till we hit nullptr
temp = temp->next;
len++;
}
temp = head;
for(int i=0; i<len/2; i++) { // iterate till len/2 to end up at the middle of the list
temp = temp->next;
}
cout<<"Middle element: "<< temp->data<<endl;
}

/* Function to get the middle of the linked list*/
void printMiddle()
{
//YourCode here
if(!head){
// throw exception
cerr<<"Linked list is empty!"<<endl;
return;
}
if(!head->next){
cout<<"Middle element: "<<head->data<<endl;
return;
}
//Use fast and slow pointer technique
Node* fast = head;
Node* slow = head;
while(fast != nullptr && fast->next != nullptr){
slow = slow->next;
fast = fast->next->next;
}
cout<<"Middle element: "<< slow->data<<endl;
}

// Struct
struct Node
{
int data;
struct Node* next;
};

/* Function to get the middle of the linked list*/
void printMiddle(struct Node *head)
{
//YourCode here
//Use fast and slow pointer technique
}

// Function to add a new node
void push(struct Node** head_ref, int new_data)
{
struct Node* new_node = new Node;
new_node->data = new_data;
new_node->next = (*head_ref);
(*head_ref) = new_node;
}

// A utility function to print a given linked list
void printList(struct Node *ptr)
{
while (ptr != NULL)
// Function to add a new node
void push(int new_data)
{
Node* new_node = new Node;
new_node->data = new_data;
new_node->next = head;
head = new_node;
}

// A utility function to print a given linked list
void printList()
{
printf("%d->", ptr->data);
ptr = ptr->next;
Node* ptr = head;
while (ptr != NULL)
{
printf("%d->", ptr->data);
ptr = ptr->next;
}
printf("NULL\n");
}
printf("NULL\n");
}
};



// Driver Code
int main()
{
struct Node* head = NULL;
{ /* TEST CASES */
// LinkedList list; // empty list
// list.printList();
// list.printMiddle();

LinkedList list; // normal list
for (int i=15; i>0; i--)
{
push(&head, i);
printList(head);
printMiddle(head);
list.push(i);
}

list.printList(); // moved printList and printMiddle out of the loop to print only when the list is completely created
list.printMiddle();

// LinkedList list; // single element
// list.push(12);
// list.printList();
// list.printMiddle();

// LinkedList list; // odd length
// for (int i=5; i>=1; i--) {
// list.push(i);
// }
// list.printList();
// list.printMiddle();

// LinkedList list; // even length
// for (int i=6; i>=1; i--) {
// list.push(i);
// }
// list.printList();
// list.printMiddle();

// LinkedList list;
// for (int i=0; i<5; i++) {
// list.push(5);
// }
// list.printList();
// list.printMiddle();

return 0;
}
}
Loading