Month: June 2016

LeetCode 343. Integer Break

LC address: https://leetcode.com/problems/integer-break/

Given a positive integer n, break it into the sum of at least two positive integers and maximize the product of those integers. Return the maximum product you can get.

For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).

Note: you may assume that n is not less than 2.

Analysis:

又是比较简单的数学题,有3拆3,余下1的话就组个4,余下2直接乘,证明从略,数学归纳法就可以了。记得考虑edge cases 2和3。

Solution:

public class Solution {
    public int integerBreak(int n) {
        if (n == 2) {
            return 1;
        }
        if (n == 3) {
            return 2;
        }
        int times = n / 3;
        int remains = n % 3;
        int p = (int) Math.pow(3, times);
        if (remains == 0) {
            return p;
        }
        if (remains == 1) {
            return p / 3 * 4;
        }
        return p * 2;
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 357. Count Numbers with Unique Digits

LC address: https://leetcode.com/problems/count-numbers-with-unique-digits/

Given a non-negative integer n, count all numbers with unique digits, x, where 0 ≤ x < 10n.

Example:
Given n = 2, return 91. (The answer should be the total numbers in the range of 0 ≤ x < 100, excluding [11,22,33,44,55,66,77,88,99])

Analysis:

数学题,用下草稿纸就可以了,O(1)时间和空间。

Solution:

public class Solution {
    public int countNumbersWithUniqueDigits(int n) {
        int[] count = new int[11];
        count[0] = 1;
        count[1] = 10;
        int max = (n > 10) ? 10 : n;
        int temp = 9;
        for (int i = 2; i <= max; i++) {
            temp = temp * (11 - i);
            count[i] = count[i - 1] + temp;
        }
        return count[max];
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 369. Plus One Linked List

LC address: https://leetcode.com/problems/plus-one-linked-list/

Given a non-negative number represented as a singly linked list of digits, plus one to the number.

The digits are stored such that the most significant digit is at the head of the list.

Example:

Input:
1->2->3

Output:
1->2->4

Analysis:

一道比较简单的M题 ,小技巧就是先加一个newhead在原来的head之前,最后return的时候根据情况return head或者newhead。题目不难,因为是单向linked list,所以用recursion比较合理。不过可以有follow up比如可以有负数该怎么做之类的。

Solution:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public class Solution {
    public ListNode plusOne(ListNode head) {
        ListNode newhead = new ListNode(1);
        newhead.next = head;
        Stack stack = new Stack();
        ListNode cur = newhead;
        if (operate(head)) {
            return newhead;
        } else {
            return head;
        }
    }
    
    private boolean operate(ListNode cur) {
        if (cur.next == null) {
            cur.val = (cur.val + 1) % 10;
            return cur.val == 0;
        } else {
            if (!operate(cur.next)) {
                return false;
            } else {
                cur.val = (cur.val + 1) % 10;
                return cur.val == 0;
            }
        }
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 294. Flip Game II

LC address: https://leetcode.com/problems/flip-game-ii/

You are playing the following Flip Game with your friend: Given a string that contains only these two characters: + and -, you and your friend take turns to flip two consecutive "++" into "--". The game ends when a person can no longer make a move and therefore the other person will be the winner.

Write a function to determine if the starting player can guarantee a win.

For example, given s = "++++", return true. The starting player can guarantee a win by flipping the middle "++" to become "+--+".

Follow up:
Derive your algorithm’s runtime complexity.

Analysis:

这一题虽然LC是放在了M,但是如果想给出比较完美的解法(多项式复杂度),必须了解SG函数,在不知道这个函数的情况下,应该是SH(Super Hard)的。

Medium

可以利用recursion的做法从原始状态(包含若干段任意长度“+”的String)迭代到基础状态(不含有连续超过一个“+”的String),复杂度为O(n!)。

Super Hard

虽然不一定知道那个神秘的SG函数,但是依旧可以有一些思路。如果希望减少复杂度,我们就想从基础状态开始慢慢积累到复杂状态,把中间经历过的状态和结果记录,这样我们就可以省去很多迭代的过程。根据这个思路,首先可以想到是利用DP算法。

接下来,我们需要知道一个事实:这个游戏,在任意状态,下一个行动的人要么有必胜策略,要么对手有必胜策略(可以使当前次行动者必败),因为这是一个类Nim的游戏(Nim Wiki)。至于这个结论的推导,有兴趣的朋友可以看一下知乎上的这篇:为什么说多数棋类游戏先手有必胜/必不败策略? – 晓生的文章 – 知乎专栏。

相信到这里目标已经很清晰了,就是如何在某一状态下,利用DP高效地找出“当前行动者”是“必胜”还是“必败”。这时候就需要请出这个神秘的SG(Sprague–Grundy)函数了(SG Wiki)。考虑最简单的情况,就是对于连续k个“+”,当前行动者是否有必胜策略?显然k = 0, 1的时候答案否定,k = 2, 3的时候答案肯定,那么对于更大的k呢?这不是一个简单的问题,我三两句话也说不清楚,还不如谨言慎行,给大家指个路,让高手来解释。来,上车了:

在这些理论的指导下,我写了关于本题的解答,仅为抛砖引玉,复杂度应该是O(n^2)。

Solution:

public class Solution {
    public boolean canWin(String s) {

        // split the strings
        String[] ss = s.split("--*");

        // abstract the length of substrings and find the maxLen
        int maxLen = 0;
        for (int i = 0; i < ss.length; i++) {
            if (maxLen  lose; win otherwise
        List gs = new ArrayList();
        gs.add(0); // len = 0
        gs.add(0); // len = 1
        gs.add(1); // len = 2

        // update GS number
        for (int i = 3; i <= maxLen; i++) {
            buildGS(gs, i);
        }

        int result = 0;
        for (String cur : ss) {
            result ^= gs.get(cur.length());
        }

        return result != 0;
    }

    private void buildGS(List gs, int len) {
        Set set = new HashSet();
        for (int i = 0; i < len / 2; i++) {
            set.add(gs.get(i) ^ gs.get(len - i - 2));
        }
        for (int i = 0; i < len; i++) {
            if (!set.contains(i)) {
                gs.add(i);
                return;
            }
        }
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 347. Top K Frequent Elements

LC address: https://leetcode.com/problems/top-k-frequent-elements/

Given a non-empty array of integers, return the k most frequent elements.

For example,
Given [1,1,1,2,2,3] and k = 2, return [1,2].

Note:

  • You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
  • Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size.

Analysis:

这道题基本的思路是统计词频,根据词频排序,取得前k个元素。思路大同小异,但是具体做法上会有很多变化,统计词频基本是用HashMap或者Hashtable完成,时间空间都是O(n)。排序取前k个元素这个方面,有一下几种方式:

  • 完全排序,使用quicksort或者mergesort等传统排序,时间O(nlogn),空间O(n)。
  • 完全排序,但是观察到frequency一定是正整数,所以可以使用radix sort,时间O(n),空间O(d),其中d是最大出现的数字(或者如果array中有负数,d = max – min)。
  • 非完全排序,利用heap的性质去完成,时间O(n + klogn),空间O(n)。
  • 非完全排序,利用k th-select sort的方法去做(算法原型是quicksort),时间O(n),空间O(n)。

通过以上比较可以看出,radix sort 和 k th-select sort 最高效,但是考虑到d可以是很大的数字,这样会使用更多的内存,所以k th-select是最好的选择。所以我的solution应用了k th-select,但是导致了数据结构比较复杂,代码比较丑陋,各位将就看一下(sort的时候用tuple代替map entry会更轻便一点)。

Solution:

import java.util.*;

public class Solution {
    public List topKFrequent(int[] nums, int k) {
        // count the number frequency
        Map map = new HashMap();
        for (int i = 0; i < nums.length; i++) {
            map.putIfAbsent(nums[i], 0);
            map.put(nums[i], map.get(nums[i]) + 1);
        }
        // move the map entry to a list
        List<Map.Entry> list = new ArrayList();
        for (Map.Entry entry : map.entrySet()) {
            list.add(entry);
        }
        // quicksort (k th select sort)
        quicksort(list, k, 0, list.size() - 1);
        List result = new ArrayList();
        for (int i = 0; i < k; i++) {
            result.add(list.get(i).getKey());
        }
        return result;
    }
    
    // quicksort kth select
    private void quicksort(List<Map.Entry> list, int k, int start, int end) {
        int pivot = list.get(start).getValue();
        int pointer = start + 1;
        for (int i = start + 1; i  pivot) {
                Map.Entry temp = list.get(i);
                list.set(i, list.get(pointer));
                list.set(pointer, temp);
                pointer += 1;
            }
        }
        Map.Entry temp = list.get(pointer - 1);
        list.set(pointer - 1, list.get(start));
        list.set(start, temp);

        if (pointer == k) {
            return;
        } else if (pointer > k) {
            end = pointer;
        } else {
            start = pointer;
        }
        quicksort(list, k, start, end);
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 366. Find Leaves of Binary Tree

LC address: https://leetcode.com/problems/find-leaves-of-binary-tree/

Given a binary tree, find all leaves and then remove those leaves. Then repeat the previous steps until the tree is empty.

Example:
Given binary tree

          1
         / \
        2   3
       / \     
      4   5    

Returns [4, 5, 3], [2], [1].

Explanation:

1. Remove the leaves [4, 5, 3] from the tree

          1
         / 
        2          

2. Remove the leaf [2] from the tree

          1          

3. Remove the leaf [1] from the tree

          []         

Returns [4, 5, 3], [2], [1].

Analysis:

这题考察的是recursion的熟练运用,可以one pass完成,在统计level结束的时候储存val到对应的List中就可以了。时间O(n),空间O(n),如果不算输出所用的空间,那么运行空间是O(level)。

Solution:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    
    public List<List<Integer>> findLeaves(TreeNode root) {
        List<List<Integer>> leavesList = new ArrayList<>();
        TreeNode cur = root;
        findDepth(cur, leavesList);
        return leavesList;
    }
    
    private int findDepth(TreeNode cur, List<List<Integer>> leavesList) {
        if (cur == null) {
            return 0;
        }
        int level = Math.max(findDepth(cur.left, leavesList), findDepth(cur.right, leavesList));
        if (leavesList.size() <= level) {
            leavesList.add(new ArrayList<Integer>());
        }
        leavesList.get(level).add(cur.val);
        return level + 1;
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 122. Best Time to Buy and Sell Stock II

LC address: https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

Analysis:

太简单了一点,感觉放在easy会比较合适。

Solution:

public class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        if (prices == null || prices.length == 0) {
            return profit;
        }
        int temp = prices[0];
        for (int i = 1; i  temp) {
                profit += cur - temp;
            }
            temp = cur;
        }
        return profit;
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 256. Paint House

LC address: https://leetcode.com/problems/paint-house/

There are a row of n houses, each house can be painted with one of the three colors: red, blue or green. The cost of painting each house with a certain color is different. You have to paint all the houses such that no two adjacent houses have the same color.

The cost of painting each house with a certain color is represented by a n x 3 cost matrix. For example, costs[0][0] is the cost of painting house 0 with color red; costs[1][2] is the cost of painting house 1 with color green, and so on… Find the minimum cost to paint all houses.

Note:
All costs are positive integers.

Analysis:

简单的DP就可以做了,just as straight as the code tells.

Solution:

public class Solution {
    public int minCost(int[][] costs) {
        int[] prevSum = {0, 0, 0};
        int[] curSum = {0, 0, 0};
        if (costs == null || costs.length == 0) {
            return 0;
        }
        for (int[] cost : costs) {
            curSum[0] = Math.min(prevSum[1], prevSum[2]) + cost[0];
            curSum[1] = Math.min(prevSum[0], prevSum[2]) + cost[1];
            curSum[2] = Math.min(prevSum[0], prevSum[1]) + cost[2];
            prevSum[0] = curSum[0];
            prevSum[1] = curSum[1];
            prevSum[2] = curSum[2];
        }
        return Math.min(Math.min(curSum[0], curSum[1]), curSum[2]);
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 323. Number of Connected Components in an Undirected Graph

LC address: https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/

Number of Connected Components in an Undirected Graph | LeetCode OJ

Given n nodes labeled from 0 to n - 1 and a list of undirected edges (each edge is a pair of nodes), write a function to find the number of connected components in an undirected graph.

Example 1:

     0          3
     |          |
     1 --- 2    4

Given n = 5 and edges = [[0, 1], [1, 2], [3, 4]], return 2.

Example 2:

     0           4
     |           |
     1 --- 2 --- 3

Given n = 5 and edges = [[0, 1], [1, 2], [2, 3], [3, 4]], return 1.

Note:
You can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0, 1] is the same as [1, 0] and thus will not appear together in edges.

Analysis:

这道题可以用Union-Find的思路完成,使用一个int array,复杂度最坏情况是O(n^2)。我尝试修改我的方案,理论上用Hashtable<Integer, HashSet<Integer>>的数据结构可以优化时间复杂度(O(nlogn))。然并卵,实际跑LC的时候效果还不如int array,所以我就不贴那个版本的了。

Solution:

public class Solution {
    public int countComponents(int n, int[][] edges) {
        int count = n;
        int[] idx = new int[n];
        for (int i = 0; i &lt; n ; i++) {
            idx[i] = i;
        }
        for (int[] edge : edges) {
            int a = edge[0];
            int b = edge[1];
            int fa = idx[a];
            int fb = idx[b];
            if (fa != fb) {
                for (int i = 0; i &lt; n; i++) {
                    if (idx[i] == fb) {
                        idx[i] = fa;
                    }
                }
                count -= 1;
            }
        }
        return count;
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git

LeetCode 51. N-Queens I and II

LC address:N-Queens I and N-Queens II

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.

Given an integer n, return all distinct solutions to the n-queens puzzle.

Each solution contains a distinct board configuration of the n-queens’ placement, where 'Q' and '.' both indicate a queen and an empty space respectively.

For example,
There exist two distinct solutions to the 4-queens puzzle:

[
 [".Q..",  // Solution 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // Solution 2
  "Q...",
  "...Q",
  ".Q.."]
]

Analysis:

这是一道特别经典的CSP(Constraint Satisfaction Problem)问题,其根本是一种searching问题,可以用DFS,BFS等等,基本都是要利用回溯进行遍历搜索从而得到所有答案。

由于这道题的特殊性,我们可以简单的算一下这道题目的time complexity,假设我们使用最朴素的DFS,一行一行填充Queens,每一行从左到右检查可能可以投放Queen的位置,然后记录成表,以便回溯。一开始,第一行的Q可以放任意一个位置,n种选择,不妨假设是在(0, 0)这个位置;接下来考虑第二行,显然(1, 0)以及(1, 1)这两个位置已经不能再放置Q,所以第二行有n-2个可能的位置,依次往下考虑。假设第n行的选择可能是f(n),那么我们可知,通常有 f(n) – 3 <= f(n+1) <= f(n),而且一定存在f(n) <= n。所以大致上来说,复杂度会在O(n!)左右。(其实这个只是一个上界,因为通常f(n) < n)

其实我们也发现了,根据之前的定义,f(n+1) = f(n) – k,这个k通常是一个非负整数,而k的值越大,对搜索效率越有利。基于这个想法,我们想尽可能的剪枝,并且对于“下一步”的选择更加慎重,因为我们想尽量从选项少的“那一行”开始我们的下一步,同时又希望每一步的选择可以为我们更好地排除余下的选项。为了说明清楚,我决定用个例子来表示这种策略。

e.g. A = {a1, a2, a3}, B = {b1, b2} 假设在某一时刻,我们要从每个集合中选取某一个元素来代表这个集合。而我们又知道,元素下标奇偶性相同的是冲突选项,比如我选了a1代表A,那么我之后就不能选b1代表B而只能选b2代表B。在这个规则下,a1, b2就是一种组合。根据我们的策略,面对这种情况,我们优先从B中选取元素,因为B的选择性更小;而在B中我们又会选择b1作为预选,因为b1可以帮助我们删除a1和a3两个选项,优于b2的效果。

回到这道题目上来说,因为我们是要找到所有的组合,而不是找到一个,所以从选项少的“那一行”开始我们的下一步比较重要,而另一个原则不那么重要因为那一行所有的情况我们都需要遍历。

我的solution比较冗长,而且从LeetCode的考评上是35ms,只超过了2%的人;而另一个比较简单的DFS暴搜,不考虑CSP的剪枝优先,反而只需要8ms。但是这并不说明那个DFS的暴搜比较好,因为测试所用的n非常小,而我自己做的测试,在n=10的时候,我的用时是另一个solution的5倍,当n=15的时候,我与另一个solution的用时是一样的;若n更大,显然我的算法的复杂度会更低。这里贴上DFS暴搜的算法的link,也是值得学习的,因为这个思路更加容易懂,更加容易想到,更轻简,也更容易实施。

Solution:

public class Solution {

    public List<List<String>> solveNQueens(int n) {

        // the list of the grids
        List<List<String>> result = new ArrayList<>();

        // chosen positions for Queens
        List<Integer> chosenRows = new ArrayList<>();
        List<Integer> chosenCols = new ArrayList<>();

        // the original possible positions for searching
        List<Set<Integer>> positions = initializePos(n);

        // rowCounter counts the number of posible positions on each row
        // rowCounter = [n, n, ..., n] (before searching)
        int[] rowCounter = initializeCounter(n);

        // sb = "........" n dots
        StringBuilder dotsSB = initializeSB(n);

        search(positions, rowCounter, chosenRows, chosenCols, result, dotsSB);

        return result;
    }


    // search
    private void search(List<Set<Integer>> positions, int[] rowCounter,
                        List<Integer> chosenRows, List<Integer> chosenCols,
                        List<List<String>> result, StringBuilder dotsSB) {
        int minRow = findMin(rowCounter);
        int numOfPositions = rowCounter[minRow];

        // dead end
        if (numOfPositions == 0) {
            return;
        }

        // N Quees are settled
        if (numOfPositions == Integer.MAX_VALUE) {
            addGrid(result, dotsSB, chosenRows, chosenCols);
            return;
        }

        // need further seaching
        rowCounter[minRow] = Integer.MAX_VALUE;
        Set<Integer> colSet = positions.get(minRow);
        positions.set(minRow, new HashSet<>());
        chosenRows.add(minRow);
        
        for (Integer col : colSet) {
            //update chosenCol
            chosenCols.add(col);

            // get deleted positions
            List<Integer> deletedPos = checkConflict(positions, minRow, col);

            // update rowCounter
            for (int i = 0; i < deletedPos.size(); i += 2) {
                rowCounter[deletedPos.get(i)] -= 1;
            }

            // recursively call search
            search(positions, rowCounter, chosenRows, chosenCols, result, dotsSB);

            // restore deletedPos
            for (int i = 0; i < deletedPos.size(); i += 2) {
                positions.get(deletedPos.get(i)).add(deletedPos.get(i + 1));
            }

            // restore rowCounter
            for (int i = 0; i < deletedPos.size(); i += 2) {
                rowCounter[deletedPos.get(i)] += 1;
            }

            // restore chosenCol
            chosenCols.remove(chosenCols.size() - 1);
        }


        // restore other variables
        rowCounter[minRow] = numOfPositions;
        chosenRows.remove(chosenRows.size() - 1);
        positions.set(minRow, colSet);
    }

    // find the row with min number of possible positions
    private int findMin(int[] rowCounter) {
        int idx = 0;
        int temp = rowCounter[0];
        for (int i = 1; i < rowCounter.length; i++) {
            if (rowCounter[i] < temp) {
                temp = rowCounter[i];
                idx = i;
            }
        }
        return idx;
    }


    // add new N-Queens grid to the result
    private void addGrid(List<List<String>> result, StringBuilder dotsSB,
                         List<Integer> chosenRows, List<Integer> chosenCols) {
        List<String> grid = new ArrayList<>();
        for (int i = 0; i < chosenRows.size(); i++) {
            grid.add(null);
        }
        for (int i = 0; i < chosenRows.size(); i++) {
            dotsSB.replace(chosenCols.get(i), chosenCols.get(i) + 1, "Q");
            String temp = dotsSB.toString();
            grid.set(chosenRows.get(i), temp);
            dotsSB.replace(chosenCols.get(i), chosenCols.get(i) + 1, ".");
        }
        result.add(grid);
    }

    // check the conflict when Queen is settled at selected position, return
    // the deleted positions
    private List<Integer> checkConflict(List<Set<Integer>> positions, 
                                             int row, int col) {

        List<Integer> deletedPos = new ArrayList<>();

        for (int i = 0; i < positions.size(); i++) {
            Set<Integer> rowPos = positions.get(i);
            if (rowPos.size() > 0) {
                List<Integer> tempList = new ArrayList<>();
                for (Integer cur_col : rowPos) {
                    if (cur_col == col || Math.abs(cur_col - col) == Math.abs(i - row)) {
                        deletedPos.add(i);
                        deletedPos.add(cur_col);
                        tempList.add(cur_col);
                    }
                }
                rowPos.removeAll(tempList);
            }
        }
        return deletedPos;
    }

    // initialize the original possible positions
    private List<Set<Integer>> initializePos(int n) {
        List<Set<Integer>> pos = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            Set<Integer> row = new HashSet<>();
            for (int j = 0; j < n; j++) {
                row.add(j);
            }
            pos.add(row);
        }
        return pos;
    }

    // initialize rowCounter
    private int[] initializeCounter(int n) {
        int[] counter = new int[n];
        for (int i = 0; i < n; i++) {
            counter[i] = n;
        }
        return counter;
    }

    // initialize stringbuilder
    private StringBuilder initializeSB(int n) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append('.');
        }
        return sb;
    }
}

Solution code can also be found here: https://github.com/all4win/LeetCode.git