Practice Final 2 Solutions


Q1) Unique Grid Distance Puzzle (15 points)

bool isUniqueSolution(Vector<GridLocation> circles) {
    Set<double> distances;
    for (int i = 0; i < circles.size(); i++) {
        for (int j = i + 1; j < circles.size(); j++) {
            double distance = euclidianDistance(circles[i], circles[j]);
            if (distances.contains(distance)) {
                return false;
            }
        distances.add(distance);
        }
    }
    return true;
}

void findUniqueDistancesHelper(const int n, Vector<GridLocation> &circles, Set<Vector<GridLocation>> &solutions, int row, int col) {
    // n is the number of circles we need to place on a grid of n x n locations
    // base cases
    if (!isUniqueSolution(circles)) {
        // not a solution
        return;
    }
    if (circles.size() == n) {
        solutions.add(circles);
        return;
    }
    if (row == n) {
        return;
    }
    int newRow = row;
    int newCol = col + 1;
    if (newCol == n) {
        newRow++;
        newCol = 0;
    }
    // recursive cases
    // don't add a new circle
    findUniqueDistancesHelper(n, circles, solutions, newRow, newCol);

    // add a new circle (choose)
    circles.add({row, col});
    findUniqueDistancesHelper(n, circles, solutions, newRow, newCol);

    // unchoose
    circles.remove(circles.size() - 1);
}

Q2) Classes and Dynamic Memory (15 Points)

// Section.h

#pragma once
#include "vector.h"
using namespace std;

struct Student {
    string name;
    int suid;
    Vector<string> preferences;
};

class Section {

public:
    Section(int capacity, string time);
    ~Section();
    bool tryAddStudent(Student s);
    Student getStudent(int index);
    void swap(int index1, int index2);

private:
    Student *_students;
    int _capacity;
    int _numEnrolled;
    string _time;
    bool checkStudent(Student s);
    int thisSectionPreference(int index);
};
// Section.cpp

#include "Section.h"

Section::Section(int capacity, string time) {
    _students = new Student[capacity];
    _capacity = capacity;
    _numEnrolled = 0;
    _time = time;
}

Section::~Section() {
    delete[] _students;
}

bool Section::checkStudent(Student s) {
    // Condition 1: section is full 
    if (_numEnrolled == _capacity) return false;

    // Condition 2: this section's time is not in the top 3 of student preferences.
    for (int i = 0; i < s.preferences.size() && i < 3; i++) {
        if (s.preferences[i] == _time) {
            return true;
        }
    }

    return false;
}

int Section::thisSectionPreference(int index) {
    Vector<string> studentPref = _students[index].preferences;
    for (int i = 0; i < studentPref.size(); i++) {
        if (studentPref[i] == _time) {
            return i;
        }
    }
    return -1;
}

bool Section::tryAddStudent(Student s) {
    if (!checkStudent(s)) {
        return false;
    }

    _students[_numEnrolled] = s;

    // Student can be added. Now let's insert in sorted order by preference.
    for (int i = _numEnrolled; i > 0; i--) {
        // Check if student at index i's preference for this section 
        // is less than student at index i - 1's preference for this
        // section.
        if (thisSectionPreference(i) < thisSectionPreference(i - 1)) {
            swap(i, i - 1);
        }
    }

    _numEnrolled++;
    return true;
}

Student Section::getStudent(int index) {
    if (index < 0 || index >= _numEnrolled) {
        error("Out of bounds!");
    }

    return _students[index];
}

Q3) Linked Lists (15 Points)

ListNode* addToEndOfList(ListNode* node, ListNode*& front, ListNode*& end) {
    if (front == nullptr) {
        front = node;
        end = node;
    } else {
        end->next = node;
        end = node;
    }
    ListNode* next = node->next;
    // make sure that the new node is in fact the end of our list node->next = nullptr;
    node->next = nullptr;
    return next;
}

ListNode* listDivisibleBy(ListNode*& front, int divisor) {
    ListNode* divisibleFront = nullptr;
    ListNode* divisibleEnd = nullptr;
    ListNode* indivisibleFront = nullptr;
    ListNode* indivisibleEnd = nullptr;
    ListNode* current = front;
    while (current != nullptr) {
        if (current->data % divisor == 0) {
            current = addToEndOfList(current, divisibleFront, divisibleEnd);
        } else {
            current = addToEndOfList(current, indivisibleFront, indivisibleEnd);
        }
    }
    front = indivisibleFront;
    return divisibleFront;
}

Q4) Trees (20 Points)

Part A: Difference of Max and Min in a BST

int diffMaxMinBst(TreeNode *root) {
    // Solution should be O(log n)
    // assumes at least one node in tree
    int min, max;
    // find min
    TreeNode *temp = root;
    while (temp->left != nullptr) {
        temp = temp->left;
    }
    min = temp->data;

    // find max
    temp = root;
    while (temp->right != nullptr) {
        temp = temp->right;
    }
    max = temp->data;

    return max - min;
}

Part B: Difference of Max and Min in a non-BST

void diffMaxMinNonBstHelper(TreeNode *root, int &min, int &max) {
    // base case
    if (root != nullptr) {
        // pre-order traversal
        if (root->data < min) {
            min = root->data;
        }
        if (root->data > max) {
            max = root->data;
        }
        diffMaxMinNonBstHelper(root->left, min, max);
        diffMaxMinNonBstHelper(root->right, min, max);
    }
}

int diffMaxMinNonBst(TreeNode *root) {
    // Solution should be O(n)
    // assumes at least one node in tree
    int min = root->data;
    int max = root->data;
    diffMaxMinNonBstHelper(root, min, max);
    return max - min;
}

Part C: Create a complete, balanced BST from a sorted vector

TreeNode *balancedBstFromSortedVec(Vector sortedVec) {
    int vecSize = sortedVec.size();
    // base cases
    if (vecSize == 0) {
        return nullptr;
    }
    return new TreeNode(sortedVec[vecSize / 2], balancedBstFromSortedVec(sortedVec.subList(0, vecSize / 2)), balancedBstFromSortedVec(sortedVec.subList(vecSize / 2 + 1)));
}

Q5) Hashing (9 Points)

Question A: Hashing – multiple choice, choose all answers that are correct

A good hash function suitable for a hash table should:

  • A. Be deterministic
  • C. Create a good distribution

  • B is not correct because given a hash, you cannot determine the original input

Question B: Hashing – multiple choice, choose the best answer

What does it mean for a hash table to use chaining?

  • A. Each hash bucket holds a linked list for elements that collide when hashed.

Q6) BFS, Dijkstra's Algorithm, and A* Algorithm (9 points)

Question A: Breadth First Search – multiple choice, choose the best answer.

If a breadth first search (BFS) was run to determine the shortest path from node A to node B in the graph, what would the algorithm return?

  • A. A->B

(BFS does not take into account)

Question B: Dijkstra's Algorithm – multiple choice, choose the best answer.

If Dijkstra's Algorothm was run to determine the shortest path from node A to node B in the graph, what would the algorithm return?

  • C. A->C->B

(Dijkstra’s always finds shortest path with weights)

Question C: A* Algorithm – multiple choice, choose the best answer.

If an A* Algorithm was run to determine the shortest path from node A to node B in the graph, what would the algorithm return?

  • C. A->C->B

(A* always finds shortest path with weights)