Lexi's Leetcode solutions

Archive for October 20th, 2013

Hands down,这是我leetcode做的最难的题top 5。别的都是可以用算法说得通的,这题就是纯数学。我这辈子最怕的就是数学,要死要活。

几个要点:

  1. 找没用过的里面的第(k – 1) / (n – 1)!个数字,放第一位上
  2. k = (k – 1) % (n – 1)!,第一个数字确定了,剩下这些里面重新找第k个。
  3. n每次-1,比如第一位确定后有(n-1)!种后面数字的排法,第二位也确定了后面的数字排法就又少了一位(n – 1 – 1)!

还是不是很清楚,今晚睡觉接着想。

public String getPermutation(int n, int k) {
    boolean[] used = new boolean[n];
    // used[i] means the digit i + 1 is used, e.g. used[0] means digit '1' is used
    StringBuilder sb = new StringBuilder();
    int factorial = getFactorial(n - 1);
    int originalN = n;
    while (k > 0) {
        int index = (k - 1) / factorial;
        int count = 0;
        for (int i = 0; i < originalN; i++) {
            if (used[i] == false)
                count++;
            if (index == count - 1) {
                sb.append(i + 1);
                used[i] = true;
                break;
            }
        }
        n--;
        k = (k - 1) % factorial + 1;
        if (n == 0)
            break;
        factorial /= n;
    }
    return sb.toString();
}
private int getFactorial(int n) {
    if (n == 1 || n == 0)
        return 1;
    return n * getFactorial(n - 1);
}
Tags: ,

非常非常难,用dfs完全过不了。即使把每个substring是不是palindrome全算好了放在cache里也会挂。必须dp了。

算法:

  1. 首先把每个substring是不是palindrome先二维dp的做出来,参见上一题的分析。其实这一步可以合在下一步里,但是下一步本身的logic就够难想了,所以这里分开来写。
  2. 一维dp(为神马是一维啊!!头一次看这样的一维啊!!一维dp却是两层循环啊卧槽!!!),d[i]表示str[i..n]最少切几刀能保证substring全是palindrome。
  3. 把d[i]先赋成最大值。
  4. i从后往前,然后j在每个i处从前往后。这样做就能保证在i处,i + 1…j – 1的所有两两combination(中间的小节substring)都已经算出来了,就能用了。如果str[i, j]是个palindrome,那么可以在j 和j + 1中间切一刀,这样的#就是d[j + 1] + 1([i..j], 一刀, [j + 1..本来有几刀])。这样切法可能就比之前算的d[i]小了,于是update。
public int minCut(String s) {
    int n = s.length();
    boolean[][] isPalin = new boolean[n][n];
    for (int i = n - 1; i >= 0; i--) { // i is always <= j
        for (int j = i; j < n; j++) {
            boolean isPalindrome = s.charAt(i) == s.charAt(j) 
                && (j - i < 2 || isPalin[i + 1][j - 1]);
            isPalin[i][j] = isPalindrome;
        }
    }
    int[] d = new int[n];
    // initialize as cut between every letter
    for (int i = 0; i < n; i++) {
        d[i] = n - 1 - i;
    }
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i; j < n; j++) {
            if (isPalin[i][j]) {
                if (j == n - 1)
                    d[i] = 0;
                else
                    d[i] = Math.min(d[i], d[j + 1] + 1);
            }
         }
    }
    return d[0];
}

这个题第二遍做code就写的挺完美的,高兴~~几个要点:

  • 直接乘会溢出,所以每次都要两个single digit相乘,最大81,不会溢出。
  • 比如385 * 97, 就是个位=5 * 7,十位=8 * 7 + 5 * 9 ,百位=3 * 7 + 8 * 9 …
    可以每一位用一个Int表示,存在一个int[]里面。
  • 这个数组最大长度是num1.len + num2.len,比如99 * 99,最大不会超过10000,所以4位就够了。
  • 这种个位在后面的,不好做(10的0次方,可惜对应位的数组index不是0而是n-1),
    所以干脆先把string reverse了代码就清晰好多。
  • 最后结果前面的0要清掉。
public String multiply(String num1, String num2) {
    num1 = new StringBuilder(num1).reverse().toString();
    num2 = new StringBuilder(num2).reverse().toString();
    // even 99 * 99 is < 10000, so maximaly 4 digits
    int[] d = new int[num1.length() + num2.length()];
    for (int i = 0; i < num1.length(); i++) {
        int a = num1.charAt(i) - '0';
        for (int j = 0; j < num2.length(); j++) {
            int b = num2.charAt(j) - '0';
            d[i + j] += a * b;
        }
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < d.length; i++) {
        int digit = d[i] % 10;
        int carry = d[i] / 10;
        sb.insert(0, digit);
        if (i < d.length - 1)
            d[i + 1] += carry;
        }
    //trim starting zeros
    while (sb.length() > 0 && sb.charAt(0) == '0') {
        sb.deleteCharAt(0);
    }
    return sb.length() == 0 ? "0" : sb.toString();
}
Tags: