Tuesday, May 28, 2019

Leetcode 355. Design Twitter

Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and is able to see the 10 most recent tweets in the user's news feed. Your design should support the following methods:
  1. postTweet(userId, tweetId): Compose a new tweet.
  2. getNewsFeed(userId): Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
  3. follow(followerId, followeeId): Follower follows a followee.
  4. unfollow(followerId, followeeId): Follower unfollows a followee.
Example:
Twitter twitter = new Twitter();

// User 1 posts a new tweet (id = 5).
twitter.postTweet(1, 5);

// User 1's news feed should return a list with 1 tweet id -> [5].
twitter.getNewsFeed(1);

// User 1 follows user 2.
twitter.follow(1, 2);

// User 2 posts a new tweet (id = 6).
twitter.postTweet(2, 6);

// User 1's news feed should return a list with 2 tweet ids -> [6, 5].
// Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5.
twitter.getNewsFeed(1);

// User 1 unfollows user 2.
twitter.unfollow(1, 2);

// User 1's news feed should return a list with 1 tweet id -> [5],
// since user 1 is no longer following user 2.
twitter.getNewsFeed(1);
Code (Java):
class Twitter {
    Map<Integer, Set<Integer>> friendMap;
    Map<Integer, PriorityQueue<Tweet>> tweetMap;
    SeqTime seqTime;

    /** Initialize your data structure here. */
    public Twitter() {
        friendMap = new HashMap<>();
        tweetMap = new HashMap<>();
        seqTime = new SeqTime();
    }
    
    /** Compose a new tweet. */
    public void postTweet(int userId, int tweetId) {
        // init the friend map
        Tweet tweet = new Tweet(userId, tweetId, seqTime.getTime());
        Set<Integer> friends = friendMap.getOrDefault(userId, new HashSet<>());
        friends.add(userId);
        friendMap.put(userId, friends);
        
        // save the tweet into the tweetmap
        PriorityQueue<Tweet> tweets = tweetMap.getOrDefault(userId, new PriorityQueue<>(new MyTweetComparator()));
        tweets.offer(tweet);
        tweetMap.put(userId, tweets);
    }
    
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    public List<Integer> getNewsFeed(int userId) {
        Set<Integer> friends = friendMap.get(userId);
        
        if (friends == null || friends.isEmpty()) {
            return new ArrayList<>();
        }
        
        
        Map<Integer, PriorityQueue<Tweet>> tweetList = new HashMap<>();
        for (Integer friend : friends) {
            PriorityQueue<Tweet> tweets = tweetMap.get(friend);
            PriorityQueue<Tweet> top10List = getTop10Tweets(tweets);
            
            Iterator<Tweet> it = top10List.iterator();
            if (!top10List.isEmpty()) {
                tweetList.put(friend, top10List);
            }
        }
        
        return mergeTweets(tweetList);
    }
    
    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    public void follow(int followerId, int followeeId) {
        Set<Integer> friends = friendMap.getOrDefault(followerId, new HashSet<>());
        friends.add(followerId);
        friends.add(followeeId);
        friendMap.put(followerId, friends);
    }
    
    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    public void unfollow(int followerId, int followeeId) {
        if (followerId == followeeId) {
            return;
        }
        Set<Integer> friends = friendMap.getOrDefault(followerId, new HashSet<>());
        friends.remove(followeeId);
        friendMap.put(followerId, friends);
    }
    
    private PriorityQueue<Tweet> getTop10Tweets(PriorityQueue<Tweet> tweets) {
        PriorityQueue<Tweet> top10Tweets = new PriorityQueue<>(new MyTweetComparator());
        for (int i = 0; i < 10; i++) {
            if (tweets == null || tweets.isEmpty()) {
                break;
            }
            
            top10Tweets.offer(tweets.poll());
        }
        
        // push back
        Iterator it = top10Tweets.iterator();
        while (it.hasNext()) {
            tweets.offer((Tweet)it.next());
        }
        
        return top10Tweets;
    }
    
    private List<Integer> mergeTweets(Map<Integer, PriorityQueue<Tweet>> tweetLists) {
        List<Integer> ans = new ArrayList<>();
        PriorityQueue<Tweet> finalPQ = new PriorityQueue<>(new MyTweetComparator());
        for (Integer userId : tweetLists.keySet()) {
            PriorityQueue<Tweet> tweets = tweetLists.get(userId);
            Tweet top = tweets.poll();
            //tweetLists.put(userId, tweets);
            finalPQ.offer(top);
        }
        
        int count = 0;
        while (count < 10 && !tweetLists.isEmpty()) {
            Tweet curr = finalPQ.poll();
            ans.add(curr.tid);
            PriorityQueue<Tweet> nextTweetList = tweetLists.get(curr.uid);
            
            if (!nextTweetList.isEmpty()) {
                finalPQ.offer(nextTweetList.poll());
            } else {
                tweetLists.remove(curr.uid);
            }
            count += 1;
        }
        
        return ans;
    }
}

class Tweet {
    int tid;
    int uid;
    int time;
    public Tweet(int uid, int tid, int time) {
        this.uid = uid;
        this.tid = tid;
        this.time = time;
    }
}

class SeqTime {
    int time;
    public SeqTime() {
        time = 0;
    }
    
    public int getTime() {
        int curTime = time;
        time += 1;
        return curTime;
    }
}

class MyTweetComparator implements Comparator<Tweet> {
    @Override 
    public int compare(Tweet a, Tweet b) {
        return b.time - a.time;
    }
}

/**
 * Your Twitter object will be instantiated and called as such:
 * Twitter obj = new Twitter();
 * obj.postTweet(userId,tweetId);
 * List<Integer> param_2 = obj.getNewsFeed(userId);
 * obj.follow(followerId,followeeId);
 * obj.unfollow(followerId,followeeId);
 */

Leetcode 378. Kth Smallest Element in a Sorted Matrix

Given a n x n matrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix.
Note that it is the kth smallest element in the sorted order, not the kth distinct element.
Example:
matrix = [
   [ 1,  5,  9],
   [10, 11, 13],
   [12, 13, 15]
],
k = 8,

return 13.
Note: 
You may assume k is always valid, 1 ≤ k ≤ n2.
Code (Java):
/*
 * @lc app=leetcode id=378 lang=java
 *
 * [378] Kth Smallest Element in a Sorted Matrix
 */
class Solution {
    public int kthSmallest(int[][] matrix, int k) {
        if (matrix == null || matrix.length == 0 || k <= 0 || k > matrix.length * matrix.length) {
            return -1;
        }

        int n = matrix.length;

        PriorityQueue<Pair> minHeap = new PriorityQueue<>(new MyPairComparator());

        boolean[][] visited = new boolean[n][n];

        minHeap.offer(new Pair(0, 0, matrix[0][0]));
        visited[0][0] = true;

        Pair ans = null;
        int[][] dirs = {{0, 1}, {1, 0}};

        for (int i = 0; i < k; i++) {
            ans = minHeap.poll();
            for (int j = 0; j < 2; j++) {
                int nx = ans.x + dirs[j][0];
                int ny = ans.y + dirs[j][1];
                if (nx < n && ny < n && !visited[nx][ny]) {
                    minHeap.offer(new Pair(nx, ny, matrix[nx][ny]));
                    visited[nx][ny] = true;
                }
            }
        }

        return ans.val;
    }
}

class Pair {
    int x;
    int y;
    int val;
    public Pair(int x, int y, int val) {
        this.x = x;
        this.y = y;
        this.val = val;
    }
}

class MyPairComparator implements Comparator<Pair> {
    @Override
    public int compare(Pair a, Pair b) {
        return a.val - b.val;
    }
}


Friday, May 24, 2019

Leetcode 518: Coin Change 2

You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin.

    Example 1:
    Input: amount = 5, coins = [1, 2, 5]
    Output: 4
    Explanation: there are four ways to make up the amount:
    5=5
    5=2+2+1
    5=2+1+1+1
    5=1+1+1+1+1
    
    Example 2:
    Input: amount = 3, coins = [2]
    Output: 0
    Explanation: the amount of 3 cannot be made up just with coins of 2.
    
    Example 3:
    Input: amount = 10, coins = [10] 
    Output: 1
    

    Note:
    You can assume that
    • 0 <= amount <= 5000
    • 1 <= coin <= 5000
    • the number of coins is less than 500
    • the answer is guaranteed to fit into signed 32-bit integer

    Code (Java):
    class Solution {
        public int change(int amount, int[] coins) {
            if (amount < 0 || coins == null || coins.length == 0) {
                return amount == 0 ? 1 : 0;
            }
    
            int[][] dp = new int[coins.length + 1][amount + 1];
            for (int i = 0; i <= coins.length; i++) {
                dp[i][0] = 1;
            }
    
            for (int i = 1; i <= coins.length; i++) {
                for (int j = 1; j <= amount; j++) {
                    dp[i][j] = dp[i - 1][j];
    
                    if (j - coins[i - 1] >= 0) {
                        dp[i][j] += dp[i][j - coins[i - 1]];
                    }
                }
            }
    
            return dp[coins.length][amount];
    
        }
    }
    
    
    
    Space optimization:
    class Solution {
        public int change(int amount, int[] coins) {
            if (amount < 0 || coins == null || coins.length == 0) {
                return amount == 0 ? 1 : 0;
            }
    
            int[][] dp = new int[2][amount + 1];
            for (int i = 0; i <= coins.length; i++) {
                dp[0][0] = 1;
            }
    
            int old = 0;
            int cur = 0;
    
            for (int i = 1; i <= coins.length; i++) {
                old = cur;
                cur = 1 - cur;
                dp[cur][0] = 1;
                for (int j = 1; j <= amount; j++) {
                    dp[cur][j] = dp[old][j];
    
                    if (j - coins[i - 1] >= 0) {
                        dp[cur][j] += dp[cur][j - coins[i - 1]];
                    }
                }
            }
    
            return dp[cur][amount];
        }
    }
    

    Space Optimization 2:
    class Solution {
        public int change(int amount, int[] coins) {
            if (amount < 0 || coins == null || coins.length == 0) {
                return amount == 0 ? 1 : 0;
            }
    
            int[] dp = new int[amount + 1];
            for (int i = 0; i <= coins.length; i++) {
                dp[0] = 1;
            }
    
            for (int i = 1; i <= coins.length; i++) {
                dp[0] = 1;
                for (int j = 1; j <= amount; j++) {
                    if (j - coins[i - 1] >= 0) {
                        dp[j] += dp[j - coins[i - 1]];
                    }
                }
            }
    
            return dp[amount];
        }
    }
    

    Leetcode 609. Find Duplicate File in System

    Given a list of directory info including directory path, and all the files with contents in this directory, you need to find out all the groups of duplicate files in the file system in terms of their paths.
    A group of duplicate files consists of at least two files that have exactly the same content.
    A single directory info string in the input list has the following format:
    "root/d1/d2/.../dm f1.txt(f1_content) f2.txt(f2_content) ... fn.txt(fn_content)"
    It means there are n files (f1.txtf2.txt ... fn.txt with content f1_contentf2_content ... fn_content, respectively) in directory root/d1/d2/.../dm. Note that n >= 1 and m >= 0. If m = 0, it means the directory is just the root directory.
    The output is a list of group of duplicate file paths. For each group, it contains all the file paths of the files that have the same content. A file path is a string that has the following format:
    "directory_path/file_name.txt"
    Example 1:
    Input:
    ["root/a 1.txt(abcd) 2.txt(efgh)", "root/c 3.txt(abcd)", "root/c/d 4.txt(efgh)", "root 4.txt(efgh)"]
    Output:  
    [["root/a/2.txt","root/c/d/4.txt","root/4.txt"],["root/a/1.txt","root/c/3.txt"]]
    

    Note:
    1. No order is required for the final output.
    2. You may assume the directory name, file name and file content only has letters and digits, and the length of file content is in the range of [1,50].
    3. The number of files given is in the range of [1,20000].
    4. You may assume no files or directories share the same name in the same directory.
    5. You may assume each given directory info represents a unique directory. Directory path and file info are separated by a single blank space.

    Follow-up beyond contest:
    1. Imagine you are given a real file system, how will you search files? DFS or BFS?
    2. If the file content is very large (GB level), how will you modify your solution?
    3. If you can only read the file by 1kb each time, how will you modify your solution?
    4. What is the time complexity of your modified solution? What is the most time-consuming part and memory consuming part of it? How to optimize?
    5. How to make sure the duplicated files you find are not false positive?

    Code (Java):
    /*
     * @lc app=leetcode id=609 lang=java
     *
     * [609] Find Duplicate File in System
     */
    class Solution {
        public List<List<String>> findDuplicate(String[] paths) {
            List<List<String>> ans = new ArrayList<>();
            if (paths == null || paths.length == 0) {
                return ans;
            }
    
            Map<String, List<String>> contentToFileMap = new HashMap<>();
            for (String path : paths) {
                String delim = "[ ]+";
                String[] tokens = path.split(delim);
                String filePath = tokens[0];
                for (int i = 1; i < tokens.length; i++) {
                    String delim2 = "[()]";
                    String[] nameAndContent = tokens[i].split(delim2);
                    String fileName = nameAndContent[0];
                    String fileContent = nameAndContent[1];
    
                    List<String> pathList = contentToFileMap.getOrDefault(fileContent, new ArrayList<>());
                    pathList.add(filePath + '/' + fileName);
                    // Needed?
                    contentToFileMap.put(fileContent, pathList);
                }
            }
    
            // output the result
            for (String key : contentToFileMap.keySet()) {
                List<String> list = contentToFileMap.get(key);
                if (list.size() > 1) {
                    ans.add(list);
                }
            }
    
            return ans;
        }
    }
    

    Leetcode 635. Design Log Storage System

    You are given several logs that each log contains a unique id and timestamp. Timestamp is a string that has the following format: Year:Month:Day:Hour:Minute:Second, for example, 2017:01:01:23:59:59. All domains are zero-padded decimal numbers.
    Design a log storage system to implement the following functions:
    void Put(int id, string timestamp): Given a log's unique id and timestamp, store the log in your storage system.

    int[] Retrieve(String start, String end, String granularity): Return the id of logs whose timestamps are within the range from start to end. Start and end all have the same format as timestamp. However, granularity means the time level for consideration. For example, start = "2017:01:01:23:59:59", end = "2017:01:02:23:59:59", granularity = "Day", it means that we need to find the logs within the range from Jan. 1st 2017 to Jan. 2nd 2017.
    Example 1:
    put(1, "2017:01:01:23:59:59");
    put(2, "2017:01:01:22:59:59");
    put(3, "2016:01:01:00:00:00");
    retrieve("2016:01:01:01:01:01","2017:01:01:23:00:00","Year"); // return [1,2,3], because you need to return all logs within 2016 and 2017.
    retrieve("2016:01:01:01:01:01","2017:01:01:23:00:00","Hour"); // return [1,2], because you need to return all logs start from 2016:01:01:01 to 2017:01:01:23, where log 3 is left outside the range.
    
    Note:
    1. There will be at most 300 operations of Put or Retrieve.
    2. Year ranges from [2000,2017]. Hour ranges from [00,23].
    3. Output for Retrieve has no order required.

    Solution:
    Use a treemap to store the <timestamp,id> pair

    Code (Java):
    import java.util.NavigableMap;
    class LogSystem {
        TreeMap<String, List<Integer>> timeToIdMap;
        String minTimeStamp;
        String maxTimeStamp;
        Map<String, Integer> graMap;
    
        public LogSystem() {
            timeToIdMap = new TreeMap<>();
            minTimeStamp = "2000:01:01:00:00:00";
            maxTimeStamp = "2017:12:31:23:59:59";
            graMap = new HashMap<>();
            graMap.put("Year", 4);
            graMap.put("Month", 7);
            graMap.put("Day", 10);
            graMap.put("Hour", 13);
            graMap.put("Minute", 16);
            graMap.put("Second", 19);
        }
        
        public void put(int id, String timestamp) {
            List<Integer> idList = timeToIdMap.getOrDefault(timestamp, new ArrayList<>());
            idList.add(id);
            timeToIdMap.put(timestamp, idList);
        }
        
        public List<Integer> retrieve(String s, String e, String gra) {
            List<Integer> ans = new ArrayList<>();
            int graIdx = graMap.get(gra);
            String startTime = s.substring(0, graIdx) + minTimeStamp.substring(graIdx);
            String endTime = e.substring(0, graIdx) + maxTimeStamp.substring(graIdx);
    
            NavigableMap<String, List<Integer>> subMap = timeToIdMap.subMap(
                startTime, true, endTime, true);
    
            for (String timeStamp : subMap.keySet()) {
                ans.addAll(subMap.get(timeStamp));
            }
    
            return ans;
        }
    }
    
    /**
     * Your LogSystem object will be instantiated and called as such:
     * LogSystem obj = new LogSystem();
     * obj.put(id,timestamp);
     * List<Integer> param_2 = obj.retrieve(s,e,gra);
     */
    
    
    

    Leetcode 547. Friend Circles

    There are N students in a class. Some of them are friends, while some are not. Their friendship is transitive in nature. For example, if A is a directfriend of B, and B is a direct friend of C, then A is an indirect friend of C. And we defined a friend circle is a group of students who are direct or indirect friends.
    Given a N*N matrix M representing the friend relationship between students in the class. If M[i][j] = 1, then the ith and jth students are directfriends with each other, otherwise not. And you have to output the total number of friend circles among all the students.
    Example 1:
    Input: 
    [[1,1,0],
     [1,1,0],
     [0,0,1]]
    Output: 2
    Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. 
    The 2nd student himself is in a friend circle. So return 2.
    
    Example 2:
    Input: 
    [[1,1,0],
     [1,1,1],
     [0,1,1]]
    Output: 1
    Explanation:The 0th and 1st students are direct friends, the 1st and 2nd students are direct friends, 
    so the 0th and 2nd students are indirect friends. All of them are in the same friend circle, so return 1.
    
    Note:
    1. N is in range [1,200].
    2. M[i][i] = 1 for all students.
    3. If M[i][j] = 1, then M[j][i] = 1.
    Solution 1: BFS:
    /*
     * @lc app=leetcode id=547 lang=java
     *
     * [547] Friend Circles
     */
    class Solution {
        public int findCircleNum(int[][] M) {
            if (M == null || M.length == 0) {
                return 0;
            }
            
            int N = M.length;
    
            boolean[] visited = new boolean[N];
            int numCC = 0;
            
            for (int i = 0; i < N; i++) {
                if (!visited[i]) {
                    numCC += 1;
                    bfs(i, M, visited);
                }
            }
    
            return numCC;
        }
    
        private void bfs(int row, int[][] M, boolean[] visited) {
            Queue<Integer> queue = new LinkedList<>();
            int N = M.length;
            
            queue.offer(row);
            visited[row] = true;
    
            while (!queue.isEmpty()) {
                int currRow = queue.poll();
                for (int neighbor = 0; neighbor < N; neighbor++) {
                    if (M[currRow][neighbor] == 1 && !visited[neighbor]) {
                        queue.offer(neighbor);
                        visited[neighbor] = true;
                    }
                }
            }
        }
    }
    
    Solution 2: Union-Find
    class Solution {
        private int numCC;
        public int findCircleNum(int[][] M) {
            if (M == null || M.length == 0) {
                return 0;
            }
            
            int N = M.length;
            int[] parents = new int[N];
            this.numCC = N;
            
            for (int i = 0; i < N; i++) {
                parents[i] = i;
            }
            
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (i != j && M[i][j] == 1) {
                        union(parents, i, j);
                    }
                }
            }
            
            return numCC;
        }
        
        private void union(int[] parents, int a, int b) {
            int rootA = find(parents, a);
            int rootB = find(parents, b);
            
            if (rootA != rootB) {
                parents[rootA] = rootB;
                numCC -= 1;
            }
        }
        
        private int find(int[] parents, int a) {
            int root = a;
            while (root != parents[root]) {
                root = parents[root];
            }
            
            // path compression
            while (a != root) {
                int next = parents[a];
                parents[a] = root;
                a = next;
            }
            
            return root;
        }
    }
    

    Thursday, May 23, 2019

    Leetcode 453: Minimum Moves to Equal Array Elements

    Given a non-empty integer array of size n, find the minimum number of moves required to make all array elements equal, where a move is incrementing n - 1 elements by 1.
    Example:
    Input:
    [1,2,3]
    
    Output:
    3
    
    Explanation:
    Only three moves are needed (remember each move increments two elements):
    
    [1,2,3]  =>  [2,3,3]  =>  [3,4,3]  =>  [4,4,4]

    Solution:
    First, the method of decreasing 1 instead of adding 1 for n-1 elements is brilliant. But, when I was doing the contest, I was dumb, so dumb to think outside the box. And this is how I tackled it using just math logic.
    First, traverse the array, get the sum and the minimum value. If every element is equal, then min*(len) should equal to sum. This part is easy to understand. So, if they are not equal, what should we do? we should keep adding 1 to the array for k times until min*(len)==sum. Then we have:
    len*(min+k)=sum+k*(len-1). ==> k=sum-min*len;

    Code (Java):
    /*
     * @lc app=leetcode id=453 lang=java
     *
     * [453] Minimum Moves to Equal Array Elements
     */
    class Solution {
        public int minMoves(int[] nums) {
            if (nums == null || nums.length  <= 1) {
                return 0;
            }
    
            int min = nums[0];
            long sum = 0;
            for (int num : nums) {
                sum += num;
                min = Math.min(min, num);
            }
    
            return (int)(sum - nums.length * min);
        }
    }
    



    Leetcode 679. 24 Game

    You have 4 cards each containing a number from 1 to 9. You need to judge whether they could operated through */+-() to get the value of 24.
    Example 1:
    Input: [4, 1, 8, 7]
    Output: True
    Explanation: (8-4) * (7-1) = 24
    
    Example 2:
    Input: [1, 2, 1, 2]
    Output: False
    
    Note:
    1. The division operator / represents real division, not integer division. For example, 4 / (1 - 2/3) = 12.
    2. Every operation done is between two numbers. In particular, we cannot use - as a unary operator. For example, with [1, 1, 1, 1] as input, the expression -1 - 1 - 1 - 1 is not allowed.
    3. You cannot concatenate numbers together. For example, if the input is [1, 2, 1, 2], we cannot write this as 12 + 12.
    Solution:
    DFS solution. 枚举任意两个数,即i和j,然后进行运算,每次将当前的num[i]替换成两数运算后的新数字,并将数字规模减一。每次搜索完成后,需要将num[i]和num[j]还原。

    Code (Java):
    /*
     * @lc app=leetcode id=679 lang=java
     *
     * [679] 24 Game
     */
    class Solution {
        private double EPS = 1e-2;
        public boolean judgePoint24(int[] nums) {
            if (nums == null || nums.length != 4) {
                return false;
            } 
    
            double[] copy = new double[4];
            for (int i = 0; i < 4; i++) {
                copy[i] = (double)nums[i];
            }
    
            return judgePoint24Helper(copy, 4);
        }
    
        private boolean judgePoint24Helper(double[] nums, int n) {
            if (n == 1) {
                return Math.abs(nums[0] - 24.0) <= EPS;
            }
    
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    double num1 = nums[i];
                    double num2 = nums[j];
    
                    List<Double> ans = new ArrayList<>();
                    ans.add(num1 + num2);
                    ans.add(num1 - num2);
                    ans.add(num2 - num1);
                    ans.add(num1 * num2);
                    ans.add(num1 / num2);
                    ans.add(num2 / num1);
    
                    for (Double num : ans) {
                        nums[i] = num;
                        nums[j] = nums[n - 1];
    
                        if (judgePoint24Helper(nums, n - 1) == true) {
                            return true;
                        }
    
                        nums[i] = num1;
                        nums[j] = num2;
                    }
                }
            }
    
            return false;
        }
    }
    
    
    

    Leetcode 692. Top K Frequent Words

    Given a non-empty list of words, return the k most frequent elements.
    Your answer should be sorted by frequency from highest to lowest. If two words have the same frequency, then the word with the lower alphabetical order comes first.
    Example 1:
    Input: ["i", "love", "leetcode", "i", "love", "coding"], k = 2
    Output: ["i", "love"]
    Explanation: "i" and "love" are the two most frequent words.
        Note that "i" comes before "love" due to a lower alphabetical order.
    
    Example 2:
    Input: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
    Output: ["the", "is", "sunny", "day"]
    Explanation: "the", "is", "sunny" and "day" are the four most frequent words,
        with the number of occurrence being 4, 3, 2 and 1 respectively.
    
    Note:
    1. You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
    2. Input words contain only lowercase letters.
    Follow up:
    1. Try to solve it in O(n log k) time and O(n) extra space.

    Code (Java)
    /*
     * @lc app=leetcode id=692 lang=java
     *
     * [692] Top K Frequent Words
     */
    class Solution {
        public List<String> topKFrequent(String[] words, int k) {
            List<String> ans = new ArrayList<>();
            if (words == null || words.length == 0 || k <= 0) {
                return ans;
            }
    
            Map<String, Integer> wordToCountMap = new HashMap<>();
            for (String word : words) {
                int count = wordToCountMap.getOrDefault(word, 0);
                count += 1;
                wordToCountMap.put(word, count);
            }
    
            PriorityQueue<Pair> minHeap = new PriorityQueue<>(new MyPairComparator());
            for (String key : wordToCountMap.keySet()) {
                int count = wordToCountMap.get(key);
                minHeap.offer(new Pair(count, key));
                if (minHeap.size() > k) {
                    minHeap.poll();
                }
            }
    
            String[] topK = new String[k];
            int i = topK.length - 1;
            while (!minHeap.isEmpty()) {
                topK[i] = minHeap.poll().word;
                i--;
            }
    
            return Arrays.asList(topK);
        }
    }
    
    class Pair {
        int count;
        String word;
    
        public Pair(int count, String word) {
            this.count = count;
            this.word = word;
        }
    }
    
    class MyPairComparator implements Comparator<Pair> {
        @Override
        public int compare(Pair a, Pair b) {
            if (a.count != b.count) {
                return a.count - b.count;
            }
    
            return b.word.compareTo(a.word);
        }
    }
    
    
    

    Leetcode 706. Design HashMap

    Design a HashMap without using any built-in hash table libraries.
    To be specific, your design should include these functions:
    • put(key, value) : Insert a (key, value) pair into the HashMap. If the value already exists in the HashMap, update the value.
    • get(key): Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key.
    • remove(key) : Remove the mapping for the value key if this map contains the mapping for the key.

    Example:
    MyHashMap hashMap = new MyHashMap();
    hashMap.put(1, 1);          
    hashMap.put(2, 2);         
    hashMap.get(1);            // returns 1
    hashMap.get(3);            // returns -1 (not found)
    hashMap.put(2, 1);          // update the existing value
    hashMap.get(2);            // returns 1 
    hashMap.remove(2);          // remove the mapping for 2
    hashMap.get(2);            // returns -1 (not found) 
    

    Note:
    • All keys and values will be in the range of [0, 1000000].
    • The number of operations will be in the range of [1, 10000].
    • Please do not use the built-in HashMap library.

    Code (Java):
    class MyHashMap {
        private DoublyLinkedList[] buckets;
        private int capacity;
    
        /** Initialize your data structure here. */
        public MyHashMap() {
            this.capacity = 10000;
            this.buckets = new DoublyLinkedList[capacity];
            for (int i = 0; i < capacity; i++) {
                buckets[i] = new DoublyLinkedList();
            }
        }
        
        /** value will always be non-negative. */
        public void put(int key, int value) {
            int bucketIdx = hashCode(key);
            buckets[bucketIdx].insertToHead(key, value);
        }
        
        /** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
        public int get(int key) {
            int bucketIdx = hashCode(key);
            return buckets[bucketIdx].get(key);
        }
        
        /** Removes the mapping of the specified value key if this map contains a mapping for the key */
        public void remove(int key) {
            int bucketIdx = hashCode(key);
            buckets[bucketIdx].remove(key);
        }
    
        private int hashCode(int key) {
            return Integer.hashCode(key) % capacity;
        }
    }
    
    class ListNode {
        ListNode next, prev;
        int key;
        int val;
    
        public ListNode(int key, int val) {
            next = prev = null;
            this.key = key;
            this.val = val;
        }
    }
    
    class DoublyLinkedList {
        ListNode dummyHead;
        ListNode dummyTail;
        public DoublyLinkedList() {
            dummyHead = new ListNode(-1, -1);
            dummyTail = new ListNode(-1, -1);
            dummyHead.next = dummyTail;
            dummyTail.prev = dummyHead;
        }
    
        public void insertToHead(int key, int val) {
            ListNode existNode = search(key);
            if (existNode != null) {
                existNode.val = val;
            } else {
                ListNode newNode = new ListNode(key, val);
                ListNode nextNode = dummyHead.next;
                dummyHead.next = newNode;
                newNode.next = nextNode;
                nextNode.prev = newNode;
                newNode.prev = dummyHead;
            }
        }
    
        private ListNode search(int key) {
            ListNode p = dummyHead.next;
            while (p != dummyTail) {
                if (p.key == key) {
                    return p;
                } 
    
                p = p.next;
            }
    
            return null;
        }
    
        public int get(int key) {
            ListNode node = search(key);
            
            return node == null ? -1 : node.val;
        }
    
        public void remove(int key) {
            ListNode currNode = search(key);
            if (currNode != null) {
                ListNode prevNode = currNode.prev;
                ListNode nextNode = currNode.next;
                prevNode.next = nextNode;
                nextNode.prev = prevNode;
            }
        }
    }
    
    /**
     * 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);
     */
    
    
    

    Wednesday, May 22, 2019

    Leetcode 814. Binary Tree Pruning

    We are given the head node root of a binary tree, where additionally every node's value is either a 0 or a 1.
    Return the same tree where every subtree (of the given tree) not containing a 1 has been removed.
    (Recall that the subtree of a node X is X, plus every node that is a descendant of X.)
    Example 1:
    Input: [1,null,0,0,1]
    Output: [1,null,0,null,1]
     
    Explanation: 
    Only the red nodes satisfy the property "every subtree not containing a 1".
    The diagram on the right represents the answer.
    
    
    
    Example 2:
    Input: [1,0,1,0,0,0,1]
    Output: [1,null,1,null,1]
    
    
    
    
    Example 3:
    Input: [1,1,0,1,1,0,1,0]
    Output: [1,1,0,1,1,null,1]
    
    
    
    
    Note:
    • The binary tree will have at most 100 nodes.
    • The value of each node will only be 0 or 1.
    Code (Java):
    /*
     * @lc app=leetcode id=814 lang=java
     *
     * [814] Binary Tree Pruning
     */
    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public TreeNode pruneTree(TreeNode root) {
            if (root == null) {
                return null;
            }
    
            ResultType ans = pruneTreeHelper(root);
    
            return ans.root;
        }
    
        private ResultType pruneTreeHelper(TreeNode root) {
            if (root == null) {
                return new ResultType(null, true);
            }
    
            ResultType left = pruneTreeHelper(root.left);
            ResultType right = pruneTreeHelper(root.right);
    
            if (left.isZeroTree) {
                root.left = null;
            }
    
            if (right.isZeroTree) {
                root.right = null;
            }
    
            boolean isZeroTree = left.isZeroTree && right.isZeroTree && root.val == 0;
            
            return new ResultType(root, isZeroTree);
        }
    }
    
    class ResultType {
        TreeNode root;
        boolean isZeroTree;
    
        public ResultType(TreeNode root, boolean isZeroTree) {
            this.root = root;
            this.isZeroTree = isZeroTree;
        }
    }
    

    Leetcode 891 Masking Personal Information

    We are given a personal information string S, which may represent either an email address or a phone number.
    We would like to mask this personal information according to the following rules:

    1. Email address:
    We define a name to be a string of length ≥ 2 consisting of only lowercase letters a-zor uppercase letters A-Z.
    An email address starts with a name, followed by the symbol '@', followed by a name, followed by the dot '.' and followed by a name. 
    All email addresses are guaranteed to be valid and in the format of "name1@name2.name3".
    To mask an email, all names must be converted to lowercase and all letters between the first and last letter of the first name must be replaced by 5 asterisks '*'.

    2. Phone number:
    A phone number is a string consisting of only the digits 0-9 or the characters from the set {'+', '-', '(', ')', ' '}. You may assume a phone number contains 10 to 13 digits.
    The last 10 digits make up the local number, while the digits before those make up the country code. Note that the country code is optional. We want to expose only the last 4 digits and mask all other digits.
    The local number should be formatted and masked as "***-***-1111", where represents the exposed digits.
    To mask a phone number with country code like "+111 111 111 1111", we write it in the form "+***-***-***-1111".  The '+' sign and the first '-' sign before the local number should only exist if there is a country code.  For example, a 12 digit phone number mask should start with "+**-".
    Note that extraneous characters like "(", ")", " ", as well as extra dashes or plus signs not part of the above formatting scheme should be removed.

    Return the correct "mask" of the information provided.

    Example 1:
    Input: "LeetCode@LeetCode.com"
    Output: "l*****e@leetcode.com"
    Explanation: All names are converted to lowercase, and the letters between the
                 first and last letter of the first name is replaced by 5 asterisks.
                 Therefore, "leetcode" -> "l*****e".
    Example 2:
    Input: "AB@qq.com"
    Output: "a*****b@qq.com"
    Explanation: There must be 5 asterisks between the first and last letter 
                 of the first name "ab". Therefore, "ab" -> "a*****b".
    Example 3:
    Input: "1(234)567-890"
    Output: "***-***-7890"
    Explanation: 10 digits in the phone number, which means all digits make up the local number.
    Example 4:
    Input: "86-(10)12345678"
    Output: "+**-***-***-5678"
    Explanation: 12 digits, 2 digits for country code and 10 digits for local number. 
    Notes:
    1. S.length <= 40.
    2. Emails have length at least 8.
    3. Phone numbers have length at least 10.
    Code (Java):
    /*
     * @lc app=leetcode id=831 lang=java
     *
     * [831] Masking Personal Information
     */
    class Solution {
        public String maskPII(String S) {
            if (S == null || S.length() == 0) {
                return "";
            }
    
            if (isEmailAddress(S)) {
                return maskEmailAddress(S);
            } else {
                return maskPhoneNumber(S);
            }
        }
    
        private boolean isEmailAddress(String s) {
            return s.contains("@");
        }
    
        private String maskEmailAddress(String s) {
            s = s.toLowerCase();
            String delim = "[@.]+";
            String[] tokens = s.split(delim);
            StringBuilder sb = new StringBuilder();
    
            String first = tokens[0].charAt(0) + "*****" + tokens[0].charAt(tokens[0].length() - 1);
            
            sb.append(first);
            sb.append('@');
            sb.append(tokens[1]);
            sb.append('.');
            sb.append(tokens[2]);
    
            return sb.toString();
        }
    
        private String maskPhoneNumber(String s) {
            String delim = "[^0-9]";
            s = s.replaceAll(delim, "");
            System.out.println(s);
    
            StringBuilder ans = new StringBuilder();
            if (s.length() > 10) {
                int countryCodeLen = s.length() - 10;
                ans.append('+');
                for (int i = 0; i < countryCodeLen; i++) {
                    ans.append('*');
                }
                ans.append('-');
            }
    
            ans.append("***-***-");
            ans.append(s.substring(s.length() - 4));
    
            return ans.toString();
        }
    
    
    }