Monday, August 31, 2015

Leetcode: Kth Largest Element in an Array

Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4] and k = 2, return 5.
Note: 
You may assume k is always valid, 1 ≤ k ≤ array's length.
Credits:
Special thanks to @mithmatt for adding this problem and creating all test cases.
A max-heap solution:
First of all, put all elements into the heap. Then poll the top k elements from the max heap then we get the result. The time complexity for max heap is  O(n) + O(k * logn). The space complexity is O(n).

Code (Java):
public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(nums.length, Collections.reverseOrder());
        
        for (int num : nums) {
            maxHeap.offer(num);
        }
        
        int result = 0;
        for (int i = 0; i < k; i++) {
            result = maxHeap.poll();
        }
        
        return result;
    }
}

A min-heap solution:
 -- Step 1: put the first k elements into the heap. Time complexity is O(k). 
 -- Step 2: Start from elements k + 1 to n, compare each one with heap.peek(). If greater than the peek, replace with the element. The time complexity is O((n - k) logk).
  -- Step 3: After we iterate the array, the heap stores the top k elements, and the kth largest element is the minimum element of the heap, which is peek.

The space complexity is O(k). 

Code (Java):
public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k);
        
        for (int num : nums) {
            if (maxHeap.size() < k) {
                maxHeap.offer(num);
            } else {
                if (num > maxHeap.peek()) {
                    maxHeap.poll();
                    maxHeap.offer(num);
                }
            }
        }
        
        return maxHeap.peek();
    }
}


A quick-selection algorithm:
The quick selection algorithm is very similar to quick sort. Instead of sorting, it only needs the partition step, where it finds the pivot and compare the pivot with k. 

There is post for the quick sort. 
http://buttercola.blogspot.com/2014/12/data-structure-algorithms-sorting.html

Code (Java):
public class Solution {
    public int findKthLargest(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        
        return findKthLargestHelper(nums, 0, nums.length - 1, nums.length - k);
    }
    
    private int findKthLargestHelper(int[] nums, int lo, int hi, int k) {
        if (lo >= hi) {
            return nums[lo];
        }
        
        int pivot = partition(nums, lo, hi);
        if (pivot == k) {
            return nums[k];
        }
        
        if (pivot > k) {
            return findKthLargestHelper(nums, lo, pivot - 1, k);
        } else {
            return findKthLargestHelper(nums, pivot + 1, hi, k);
        }
    }
    
    private int partition(int[] nums, int lo, int hi) {
        int pivot = nums[lo];
        int i = lo + 1;
        int j = hi;
        
        while (i <= j) {
            while (i <= j && nums[i] < pivot) {
                i++;
            }
            
            while (i <= j && nums[j] >= pivot) {
                j--;
            }
            
            if (i <= j) {
                swap(nums, i, j);
            }
        }
        
        swap(nums, lo, j);
        
        return j;
    }
    
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}


Since it only uses the partition algorithm, the average time complexity is O(n). However, the worst case complexity is O(n^2). 

In which case the quick selection has time complexity of O(n^2)?
Let's say you have a sorted array, 1 2 3 4 5, and k = 1, means we select the largest element which is 5. For each element in the array, we need to run the partition algorithm and discard the pivot only, which has the time complexity of O(n). For the n elements in the array, the overall time complexity is O(n^2). Therefore, for either quick sort and quick select algorithm, we usually need to well suffle the array for the reason each time the pivot is chosen in the half.

Monday, August 11, 2014

Leetcode: Partition List

Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x.
You should preserve the original relative order of the nodes in each of the two partitions.
For example,
Given 1->4->3->2->5->2 and x = 3,
return 1->2->2->4->3->5.

Understand the problem:
The problem asks for partition a linked list according to a given value x such that all nodes less than x come before nodes greater than or equal to x. 
Also note that the problem asks for the stable partitioning, i.e, preserve the original relative order of the nodes in each of the two partitions. 

We can take several examples to illustrate the problem. 
Given 1 -> 4 -> 3 -> 2 ->5 ->2, and x = 3,
return 1 -> 2 -> 2 -> 3 -> 4 -> 5 (WRONG) See the analysis later

Given 1 -> 2 -> 6 -> 3 -> 2 -> 1, and x = 3
return 1- > 2 -> 2-> 1-> 3 -> 6 (WRONG) See the analysis later

Naive Solution:
Since the problem does not require a in-place partition, one native solution is to scan the linked list once and picked up the nodes less than the pivot. Then append the pivot to the end of the linked list. Finally, scan the list again and append the nodes greater or equal to x.
For this solution, the time complexity is O(n) + O(n) = O(n). The space complexity is O(n) since it is out-of-place partition. 

A better solution:
In the naive solution, we scan the linked list twice and takes additional O(n) space. Can we just scan the list once and take less space? The answer is yes. We scan the list, when the node is less than x, move on and check next. If greater, construct a linked list only storing the greater nodes. If equal, we only need to count the number of nodes that are equal to the target. After we scan the linked list. We only need to append the equal nodes and greater nodes. 

Wrong Code (Java):
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode partition(ListNode head, int x) {
        if (head == null || head.next == null) return head;
        
        // create a helper node to avoid the complexities in updating the head node
        ListNode helper = new ListNode(Integer.MIN_VALUE);
        helper.next = head;
        head = helper;
        
        ListNode p = head;
        
        ListNode head2 = new ListNode(0);
        ListNode p2 = head2;
        int count = 0;
        
        while (p.next != null) {
            if (p.next.val < x) p = p.next;
            else if (p.next.val > x) {
                ListNode temp = p.next;
                p2.next = temp;
                p2 = p2.next;
                p.next = temp.next;
            } else {
                ListNode temp = p.next;
                p.next = temp.next;
                count++;
            }
        }
        p2.next = null;
        
        // append the equal nodes
        for (int i = 0; i < count; i++) {
            ListNode temp = new ListNode(x);
            p.next = temp;
            p = p.next;
        }
        
        // append the greater nodes
        p.next = head2.next;
        
        return head.next;
    }
}

Discussion:
The OJ returned the wrong answer:
Input:{2,1}, 1
Output:{1,2}
Expected:{2,1}
But why? That is because we did not understand the problem correctly. The problem asks for partitioning the list such that all node less than x come before nodes greater OR EQUAL than x. For the example at beginning of the post, 

Given 1 -> 4 -> 3 -> 2 ->5 ->2, and x = 3,
return 1 -> 2 -> 2 -> 3 -> 4 -> 5 (WRONG) 
return 1 -> 2 -> 2 -> 4 -> 3 -> 5 (CORRECT)

Given 1 -> 2 -> 6 -> 3 -> 2 -> 1, and x = 3
return 1- > 2 -> 2-> 1-> 3 -> 6 (WRONG)
return 1 ->2 ->2 -> 1 -> 6 -> 3 (CORRECT)

In the code implementation, we don't need to handle the case where nodes are equal to x, and append the nodes before the nodes greater than x. Then the correct Java code is:

Correct Code (Java):
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode partition(ListNode head, int x) {
        if (head == null || head.next == null) return head;
        
        // create a helper node to avoid the complexities in updating the head node
        ListNode helper = new ListNode(Integer.MIN_VALUE);
        helper.next = head;
        head = helper;
        
        ListNode p = head;
        
        ListNode head2 = new ListNode(0);
        ListNode p2 = head2;
        int count = 0;
        
        while (p.next != null) {
            if (p.next.val < x) p = p.next;
            else {
                ListNode temp = p.next;
                p2.next = temp;
                p2 = p2.next;
                p.next = temp.next;
            }
        }
        p2.next = null;
        
        // append the greater or equal nodes
        p.next = head2.next;
        
        return head.next;
    }
}

Discussion:
Note that in the Line 36 above we must terminate the second list. That is because if not, it may cause a cyclic list. Take an example {3, 1} and x = 2. You will find the cycle. So make sure you understand exactly why we need to terminate the second list. 

This solution has time complexity of O(n) since we only iterate the list once. The space complexity is O(1) as we don't allocate new list to store the second linked list. Note that if we store the second list into another new linked list, there will be no cycle in the linked list and we don't need to handle this problem. But the space complexity becomes O(n) in the worst case. 

Summary:
This problem looks very like the first step of the quick sort. But quick sort is not a stable sorting algorithm. This problem, however, requires a stable partitioning algorithm. Make sure you understand well both the wrong solution and correct solution, as well as the cyclic linked list problem.