2018年9月28日 星期五

[Java] Doubly Linked List

Reference: https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html

The time complexity of remove() method of java.util.LinkedList is O(n) because Java standard LinkedList class does not expose the internal Node details for clients.

Therefore, we implement a custom DoublyLinkedList class for competitive programming. In this implementation, both time complexity of add() and of remove() are O(1).
    
    public class DoublyLinkedList<V> {
        private int size;
        private Node<V> head;
        private Node<V> tail;

        public DoublyLinkedList() {
            this.size = 0;
            this.head = new Node<V>();
            this.tail = new Node<V>();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }
        
        public void addFirst(Node<V> node) {
            this.size++;
            node.next = this.head.next;
            node.next.prev = node;
            node.prev = this.head;
            node.prev.next = node;
        }
        
        public void addLast(Node<V> node) {
            this.size++;
            node.prev = this.tail.prev;
            node.prev.next = node;
            node.next = this.tail;
            node.next.prev = node;
        }
        
        public void remove(Node<V> node) {
            if (node.prev != null && node.next != null) {
                this.size--;
                node.next.prev = node.prev;
                node.prev.next = node.next;
                node.prev = node.next = null;
            }
        }
        
        public Node<V> getFirst() {
            return isEmpty() ? null : this.head.next;
        }

        public Node<V> getLast() {
            return isEmpty() ? null : this.tail.prev;
        }
        
        public int size() {
            return this.size;    
        }
        
        public boolean isEmpty() {
            return this.size == 0;
        }
        
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            Node<V> curr = this.head.next;
            while (curr != this.tail) {
                builder.append(curr.getValue());
                builder.append(" -> ");
                curr = curr.next;
            }
            builder.append("END \n");
            return builder.toString();
        }
    }

    public class Node<V> {
        private V value;
        private Node prev;
        private Node next;

        public Node(V value) {
            this.value = value;
        }

        public Node() {
            this.value = null;
        }
        
        public V getValue() {
            return this.value;
        }
        
        @Override
        public String toString() {
            return this.value == null ? null : this.value.toString();
        }
    }

[Java] Map Implementations

Documents: https://docs.oracle.com/javase/tutorial/collections/implementations/map.html


General-Purpose Map Implementations:
  • HashMap
  • TreeMap
  • LinkedHashMap

Special-Purpose Map Implementations:
  • EnumMap
  • WeakHashMap
  • IdentityHashMap

Concurrent Map Implementations:
  • The java.util.concurrent package contains the ConcurrentMap interface, which extends Map with atomic putIfAbsent, remove, and replace methods, and the ConcurrentHashMap implementation of that interface.


Example: [LeetCode] 146. LRU Cache

public class LRUCache {
    private int capacity;
    private Map<Integer, Node> cache;
    private DoublyLinkedList doublyLinkedList;
    
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.cache = new HashMap<>();
        this.doublyLinkedList = new DoublyLinkedList();
    }

    public int get(int key) {
        if (!cache.containsKey(key)) {
            return -1;
        }
        Node node = cache.get(key);
        doublyLinkedList.moveToTail(node);
        return node.value;
    }

    public void put(int key, int value) {
        if (cache.containsKey(key)) {
            Node node = cache.get(key);
            doublyLinkedList.moveToTail(node);
            node.value = value;
            return;
        }

        if (cache.size() == capacity) {
            cache.remove(doublyLinkedList.first().key);
            doublyLinkedList.remove(doublyLinkedList.first());
        }

        Node insert = new Node(key, value);
        cache.put(key, insert);
        doublyLinkedList.addToTail(insert);
    }
    
    public static class DoublyLinkedList {
        private Node head;
        private Node tail;

        public DoublyLinkedList() {
            this.head = new Node();
            this.tail = new Node();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }
        
        public Node first() {
            return head.next;
        }
        
        public void remove(Node node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }

        public void addToTail(Node node) {
            node.prev = tail.prev;
            tail.prev = node;
            node.prev.next = node;
            node.next = tail;
        }
        
        public void moveToTail(Node node) {
            remove(node);
            addToTail(node);
        }
    }
    
    public static class Node {
        public int key;
        public int value;
        public Node prev;
        public Node next;
        
        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
        
        public Node() {
            this.key = -1;
            this.value = -1;
        }
    }
}



Example: [LeetCode] 460. LFU Cache
class LFUCache {
    private int capacity;
    private HashMap<Integer, Integer> valueHash;
    private Node head;
    private HashMap<Integer, Node> nodeHash;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.valueHash = new HashMap<>();
        this.nodeHash = new HashMap<>();
    }
    
    public int get(int key) {
        if (valueHash.containsKey(key)) {
            increaseFrequency(key);
            return valueHash.get(key);
        } else {
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if (capacity == 0) {
            return;
        }
        if (!valueHash.containsKey(key)) {
            if (valueHash.size() == capacity) {
                removeOldestKey();
            }
            addToHead(key);
        }
        valueHash.put(key, value);    
        increaseFrequency(key);
    }
    
    private void addToHead(int key) {
        if (head == null) {
            head = new Node(0);
        } else if (head.frequency > 0) {
            Node node = new Node(0);
            node.next = head;
            head.prev = node;
            head = node;
        }
        head.bucket.add(key);
        nodeHash.put(key, head);      
    }
    
    private void increaseFrequency(int key) {
        Node node = nodeHash.get(key);
        node.bucket.remove(key);
        
        if (node.next == null) {
            node.next = new Node(node.frequency + 1);
            node.next.prev = node;
        } else if (node.next.frequency > node.frequency + 1) {
            Node middle = new Node(node.frequency + 1);
            middle.prev = node;
            middle.next = node.next;
            node.next.prev = middle;
            node.next = middle;
        }
        node.next.bucket.add(key);
        nodeHash.put(key, node.next);
        if (node.bucket.isEmpty()) {
            removeNode(node);
        }
    }
    
    private void removeOldestKey() {
        if (head == null) { 
            return;
        }
        int oldestKey = getOldestKey();
        head.bucket.remove(oldestKey);
        if (head.bucket.isEmpty()) {
            removeNode(head);   
        }
        nodeHash.remove(oldestKey);
        valueHash.remove(oldestKey);
    }
    
    private int getOldestKey() {
        for (int n : head.bucket) {
            return n;
        }
        return 0;
    }
    
    private void removeNode(Node node) {
        if (node.prev == null) {
            head = node.next;
        } else {
            node.prev.next = node.next;
        } 
        if (node.next != null) {
            node.next.prev = node.prev;
        }        
    }
    
    public static class Node {
        public int frequency;
        public LinkedHashSet<Integer> bucket;
        public Node prev;
        public Node next;
        
        public Node(int frequency) {
            this.frequency = frequency;
            this.bucket = new LinkedHashSet<>();
        }
    }
}

[Java] Set Implementations

Documents: https://docs.oracle.com/javase/tutorial/collections/implementations/set.html


There are three general-purpose Set implementations:
  • HashSet
    • No ordering guarantees.
  • TreeSet
    • Implements SortedSet interface
  • LinkedHashSet
    • Differs from HashSet in that it maintains a doubly-linked list running through all of its entries. 
    • This linked list defines the iteration ordering, which is the order in which elements were inserted into the set (insertion-order).

There are two special-purpose Set implementations:
  • EnumSet
  • CopyOnWriteArraySet

[FWD] Solving the 2x2x2 Rubik's Cube

Document: http://lghttp.38568.nexcesscdn.net/8013252/pdf/uploads/general_content/Rubiks_Cube_2x2x2_solving_guide.pdf


Comments:
  • Step 1: Solving the first layer (white) as 3x3x3.
  • Step 2: Orienting the yellow face.
    • There are only seven possible cases.
      • Left Soon algorithm: L' U' L U' L' U2 L
      • Right Soon algorithm: R U R' U R U2 R'
  • Step 3: Permuting the second layer.
    • x R' U R' D2 R U' R' D2 R2 x'

2018年9月18日 星期二

[HackerRank] Practice Interview Preparation Kit: Dynamic Programming


Link: https://www.hackerrank.com/interview/interview-preparation-kit/dynamic-programming/challenges



1. Max Array Sum: https://www.hackerrank.com/challenges/max-array-sum/problem

Solution: Naive dynamic programming. Time = O(n), Space = O(1).
  • Define 
    • f(i): max subarray sum from 1 to i which contains the last element.
    • g(i): max subarray sum from 1 to i which does not contain the last element.
  • Answer = max(f(n), g(n)).
  • Recursive formula:
    • f(i) = g(i - 1) + a[i]
    • g(i) = max(f(i - 1), g(i - 1)).

#!/bin/python3

import math
import os
import sys

# Complete the maxSubsetSum function below.
def maxSubsetSum(arr):
    best_w_last, best_wo_last = 0, 0
    for a in arr:
        best_w_last, best_wo_last = best_wo_last + a, max(best_w_last, best_wo_last)
    return max(best_w_last, best_wo_last)
        
if __name__ == '__main__':
    fptr = open(os.environ['OUTPUT_PATH'], 'w')
    n = int(input())
    arr = list(map(int, input().rstrip().split()))
    res = maxSubsetSum(arr)
    fptr.write(str(res) + '\n')
    fptr.close()



2. Candies: https://www.hackerrank.com/challenges/candies/problem

Solution: Two pass greedy algorithm. Time = O(n), Space = O(n).

#!/bin/python3

import math
import os
import sys

# Complete the candies function below.
def candies(n, r):
    c = [1 for i in range(n)]
    for i in range(1, n):
        if r[i] > r[i - 1]:
            c[i] += c[i - 1];
    for i in range(n - 2, -1, -1):
        if r[i] > r[i + 1]:
            c[i] = max(c[i + 1] + 1, c[i])
    return sum(c)    

if __name__ == '__main__':
    fptr = open(os.environ['OUTPUT_PATH'], 'w')
    n = int(input())
    arr = []
    for _ in range(n):
        arr_item = int(input())
        arr.append(arr_item)
    result = candies(n, arr)
    fptr.write(str(result) + '\n')
    fptr.close()



3. Abbreviation: https://www.hackerrank.com/challenges/abbr/problem



4. Decibinary Numbers: https://www.hackerrank.com/challenges/decibinary-numbers/problem

2018年9月8日 星期六

[IOI 2002] Batch Scheduling


Category: DP + Convex Hull Trick

Problem statement: https://ioinformatics.org/files/ioi2002problem4.pdf

Convex Hull Trick:


[IOI 2014 Practice Contest 2] Guardians of the Lunatics


Category:
  • DP + Divide and Conquer Optimization
  • DP + Knuth Optimization

Problem statement: https://www.hackerrank.com/contests/ioi-2014-practice-contest-2/challenges/guardians-lunatics-ioi14


Solution: https://www.hackerrank.com/contests/ioi-2014-practice-contest-2/challenges/guardians-lunatics-ioi14/editorial


Knuth Optimization:
  • Paper: http://www.cs.ust.hk/mjg_lib/bibs/DPSu/DPSu.Files/p429-yao.pdf
  • DP formulation:
    • c[i, i] = 0
    • c[i, j] = w(i, j) + min_{i
  • Quadrangle Inequalities (QI): w(i, j) + w(i', j') ≤ w(i, j') + w(i', j) for all i ≤ i' ≤ j ≤ j'.
  • Monotone on the lattice of intervals: w(i, j') ≤ w(i', j) for all i ≤ i' ≤ j ≤ j'.
  • Theorem: QI + Monotone => Time = O(n^2).
    • Lemma 1: w(i, j) is QI + Monotone => c[i, j] is QI.
    • Lemma 2: 
      • Define c_k(i, j) = w(i, j) + c[i, k-1] + c[k, j].
      • Define K_c(i, j) = max{ k : c_k(i, j) = c[i, j] }.
      • c[i, j] is QI => K_c(i, j) ≤ K_c(i, j + 1) ≤ K_c(i + 1, j + 1).


2018年9月7日 星期五

[Dynamic Programming] Optimal binary search trees


Quote from Section 15.5 from CLRS, Introduction to Algorithms, 3rd.

Definitions:
  • \sum p_i + \sum q_j = 1.
  • E[search cost in T] = 1 + \sum depth(k_i) p_i + \sum depth(d_j) q_j.
  • e[i, j] = the expected cost of searching an optimal BST containing the keys k_i, ..., k_j.
    • When j = i - 1, e[i, j] = q_{i - 1} by definition. (When j = i - 1, there are no actual keys; we have just the dummy key d_{i - 1}.)
    • i from 1 to n + 1.
    • j from 0 to n.
  • w(i, j) = \sum_{l from i to j} p_l + \sum_{w from i - 1 to j} q_l.
    • i from 1 to n + 1.
    • j from 0 to n.
    • w[i, j] = w[i, j - 1] + p_j + q_j.
  • If k_r is the root of an optimal BST, we have
    • e[i, j] = p_r + (e[i, r - 1] + w(i, r - 1)) + (e[r + 1, j] + w(r + 1, j)).
    • e[i, j] = e[i, r - 1] + e[r +1, j] + w(i, j) 
      • since w(i, j) = w(i, r - 1) + p_r + w(r + 1, j).
  • e[i, j] =
    • q_{i - 1} if j = i - 1.
    • min_{r from i to j}{ e[i, r - 1] + e[r + 1, j] + w(i, j) }.
  • Define root[i, j] be the index r for which k_r is the root of an optimal BST containing key k_i, ..., k_j.

Naive algorithm, Time = O(n^3)
  • let e[1 .. n + 1, 0 .. n], w[1 .. n + 1, 0 .. n], and root[1 .. n, 1 .. n] be new tables
  • for i = 1 to n + 1
    • e[i, i - 1] = q_{i - 1}
    • w[i, i - 1] = q_{i - 1}
  • for l = 1 to n
    • for i = 1 to n - l + 1
      • j = i + l - 1
      • e[i, j] = infinity
      • w[i, j] = w[i, j - 1] + p_j + q_j
      • for r = i to j
        • t = e[i, r - 1] + e[r + 1, j] + w[i, j]
        • if t < e[i, j]
          • e[i, j] = t
          • root[i, j] = r
  • return e and root

Knuth Optimization, Time = O(n^2)
  • Fact: root[i, j - 1] ≤ root[i, j] ≤ root[i + 1, j].
  • Change the innermost for loop to for r = root[i, j - 1] to root[i + 1, j].