Lexi's Leetcode solutions

Archive for July 2013

题目:给一个string,找这个str里面最长的中心对称substring

这个题上来就想一维dp,然后挣扎半天做不出来(找不到一维的d[i]和d[i + 1]的直接关系)。然后上网查答案,原来用二维dp做,而且速度是O(n^2)。其实这题上来就应该先想暴力,然后知道暴力的速度是n^3,然后想办法降一维,别上来就强迫自己O(n)!

二维dp:

  1. d[i][j]是个boolean,表示(i, j)这个substring是不是中心对称。
  2. d[i][j]
    • 正常情况下,d[i][j] = true when 1.A[i]==A[j] 2. d[i + 1][j – 1] == true (中间那部分已经中心对称了)
    • 处理i+1, j-1出界和左边反而大于右边的情况:
      • 当i == j时,d[i][j] = 1(一个字母肯定是中心对称)
      • 当i == j – 1时,说明两个字符是连着的,看A[i]是否==A[j]就行
  3. 这个题的坑爹之处在于正常i从0~last, j从0~last那么循环不行。因为d[i][j]用到了d[i + 1][j – 1],说明i要从后往前,j则要从前往后。导致二层循环是这种顺序:
    for (int i = last; i >= 0; i–)
    for(int j = i; j  <= last; j++)
    因为这事想了半天,有点焦躁。

不用二维数组的算法(直接按中心对称的定义来,而不是每个pair算一遍,可以省一维空间):

  1. 以每个元素以及间隔为中心(共2 * n – 1个),以相同的速度往两侧扩张,直到两边儿的值不相等了,说明palindrome在此打破,于是输出。这样每次输出的肯定是以当前这个点为中心最长的palindrome)
  2. 这样过一遍数组,最长的就出来了。
public String longestPalindrome(String s) {
    String res = "";
    for (int i = 0; i < s.length(); i++) {
        String odd = expand(s, i, i);
        String even = expand(s, i, i + 1);
        String longer = odd.length() > even.length() ? odd : even;
        res = longer.length() > res.length() ? longer : res;
    }
    return res;
}
private String expand(String s, int i, int j) {
    while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
       i--;
       j++;
    }
    return s.substring(i + 1, j);
}

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

这个算法挺难想到的。让O(n)说明不能sort,那只能用container。但是用container怎么traverse连续的呢?又不能sort key。所以,我们人眼是怎么做这个题的呢,上来是100,我们其实就想找99和101,有的话就接着那个方向,直到找不着了为止。这样算过的数就从set里删掉,既然是别人的连续,那也是自己的连续,所以不用看了。这样过一遍就能把所有连续数列找全,没有元素重复处理的。

public int longestConsecutive(int[] num) {
    int res = 0;
    Set<Integer> set = new HashSet<Integer>();
    for (int i : num)
        set.add(i);
    for (int i = 0; i < num.length && !set.isEmpty(); i++) {
        int len = 0;
        //go up
        for (int j = num[i] - 1; set.contains(j); j--) {
            set.remove(j);
            len++;
        }
        //go down
        for (int j = num[i]; set.contains(j); j++) {
            set.remove(j);
            len++;
        }
        res = Math.max(res, len);
    }
    return res;
}
Tags:

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

The largest rectangle is shown in the shaded area, which has area = 10 unit.

这个也是看别人答案做出来的。感觉很像那道二维直方图盛水的题,可惜那道是短板固定在两边,这题则是短板可以随意在中间任何一个位置。总之,这种题一定能找到一个状态,就是在i之前,i怎么变都是越来越大(小),但是在i处就不一定了,所以对i才进行运算,之前的就不用算了反正都是比算i的小(大)。

算法:

穷举两个边界,但不是完全穷举,这里可以省掉一部分右右边界。对于i,如果A[i] <= A[i + 1],即当前递增,则无论对于哪个左边界,选i+1作为右边界都比选i作为右边界合适(宽本来就多一个,然后高还更高,所以肯定选右边的面积大)。所以就算i+1作为右边界的所有左边界配对就行,没必要算i作为右边界的了。所以递推下去,只有A[i] > A[i + 1]的时候,在递减,说明拿A[i]和拿A[i + 1]不一定谁大了,这时候算一下A[i]作为右边界的左边界所有可能,就是一个local peak。这题还有一个O(n)的解法,但是暂时懒的看了。下次再看。

public int largestRectangleArea(int[] height) {
    int res = 0;
    for (int i = height.length - 1; i >= 0; i--) {
        if (i == height.length - 1 || height[i] > height[i + 1]) { //A[i] is a local peak
            int min = Integer.MAX_VALUE;
            for (int j = i; j >= 0; j--) {
                min = Math.min(min, height[j]);
                res = Math.max(res, (i - j + 1) * min);
            }
        }
    }
    return res;
}

这个是要算到达最后一步最少要跳几步。不用非的跳到last index上,越过去也算。

还是,用dp的话有重复,不需要算d[i]跳到每一步用的最小步数,只要算最后一步能不能跳到,最少用几步就行了。这题其实自己还不够理解,下次还得重做。

算法:在每一步i,都已知到当前这一点用的最小step数k(但是不是存在数组里的,而是local variable)。在每层小循环里,都是“走一步能到的这些点中”, 最远能到哪一点,记载为p,那么到p就能最少用k + 1步就能到了。如果p出了数组边界,直接返回k + 1,就做出来了。要是没出界,则小循环之后,继续下一层循环,但是下一层循环和当前循环不重合,因为下一层说明多走了一步,则k++, 那循环的起始部分就是上一个小循环的尾。(每层小循环的意义是:用k步能走到的这些点,都试探一下,看看从他们身上走一步最远能走到哪)。

public int jump(int[] A) {
 if (A.length == 0 || A.length == 1)
   return 0;
 int examingStart = 0;
 int currSectionCanReach = 0;
 int steps = 0;
 while (examingStart <= currSectionCanReach) {
   int examingEnd = currSectionCanReach;
   int nextSectionCanReach = examingStart;
   for (int i = examingStart; i <= examingEnd; i++) {
     nextSectionCanReach = Math.max(nextSectionCanReach, i + A[i]);
     if (nextSectionCanReach >= A.length - 1)
       return steps + 1;
   }
   examingStart = examingEnd + 1;
   currSectionCanReach = nextSectionCanReach;
   steps++;
 }
 return -1;
}

这个好做,是build up的一维DP。还是,把状态d一定义,关系就自然出来了。d[i]表示能不能跳到i处,要是能跳,则再把从i处能跳到的别的地方全set up了。这样过一遍数组,就出来了。

public boolean canJump(int[] A) {
 boolean d[] = new boolean[A.length];
 d[0] = true;
 for (int i = 0; i < A.length; i++) {
   if (d[i] == true) {
     for (int j = 1; j <= A[i]; j++) {
       if (i + j < A.length)
         d[i + j] = true;
     }
   }
 }
 return d[A.length - 1];
}

然后大的就挂了有木有!!卧槽dp原来也不一定是最优解啊!!你的dp是个两层循环,有很多重复(比如到j,从i已经跳到它过了,但是i+1也能跳到它,所以重设了一遍true)。没必要把每个i能不能走到都算出来,只要算出最后一跳能不能>=n就行了。

算法:keep一个var,表示目前看来最远能走到哪。比如现在在i上,已知之前算的从0~i-1上最远就能跳到m处,那么如果i <= m,就说明i这个位置是能跳到的。为什么这就说明了呢?因为你的m是在0~i-1中的某个index上算出的,比如i0,那么说明从i0一直到m全能跳到(一步一步跳),而i还>i0,所以i到m之间当然也能一步一步跳到了。

public boolean canJump(int[] A) {
  int farIndex = 0;
  for (int i = 0; i < A.length; i++) {
    if (i <= farIndex){
      farIndex = Math.max(farIndex, A[i] + i);
    }
  }
  return farIndex >= A.length - 1;
}
Tags: ,

卧槽这道还真自己做出来了。感觉是二维DP,尽管没让求什么最大最小个数之类的,只是boolean,但是递推关系还是有的。其实只要把状态敢于定义出来(旁边标注物理意义),状态转移方程就能写出来了。然后就是处理最开始。思路:

  1. 一开始是这么定义的:d[i][j]: whether s1[0, i]的substring和s2[0, j]的substring能够cross match s3[0, i + j + 1]这个substring。比如ab和c,即(0, 1), (0, 0),能否合起来是acb(0, 2)。要点是s3的index要+1,这个一开始就想错了直接i + j的。
  2. d[i][j] = s3[i + j + 1] == s2[j] && d[i][j – 1] == true (用第s2的j个来填s3的i + j + 1个能成功), OR s3[i + j + 1] == s1[i] && d[i – 1][j] == true (用第s1的i个来填s3的i + j + 1个能成功)
  3. d[i][j]物理意义一定要搞清楚,因为d[0][0]说明拿了两个元素,那么它对应的是长度为2的s3,这index干脆就乱套了。这个时候,换一种方式定义:d[0][0]表示从s1中拿0个,s2中拿0个,然后组成s3(长度为0 + 0)能不能成。这个肯定是true。d[1][0]就能表达只从s1中拿一个,s2中不拿,然后组成长度为1的s3这种make sense的意义了。这样i, j就要一直到=size这么大。所以之前的i + j + 1改成i + j – 1,才能真的往string里charAt。
public boolean isInterleave(String s1, String s2, String s3) {
  if (s3.length() != s1.length() + s2.length())
    return false;
  boolean[][] d = new boolean[s1.length() + 1][s2.length() + 1];
  d[0][0] = true;
  for (int i = 0; i <= s1.length(); i++) {
    for (int j = 0; j <= s2.length(); j++) {
     if (j > 0 && s3.charAt(i + j - 1) == s2.charAt(j - 1) && d[i][j - 1])
       d[i][j] = true;
     else if (i > 0 && s3.charAt(i + j - 1) == s1.charAt(i - 1) && d[i - 1][j])
       d[i][j] = true; 
    }
  }
  return d[s1.length()][s2.length()];
}
Tags: ,

可算能自己做出来一道。。这个一开始被pre order给distract了,然后实在没招了就使出惯用的bottom up,然后一下就想出来了。不过最后逻辑分情况讨论有点复杂,忍了。

private Data getData(TreeNode root) {
  if (root == null)
    return null;
  Data left = getData(root.left);
  Data right = getData(root.right);
  root.left = null;
  if (left != null) {
    root.right = left.start;
    if (right != null) {
      left.end.right = right.start;
      return new Data(root, right.end);
    } else {
      return new Data(root, left.end);
    }
  } else {
    if (right != null) {
      root.right = right.start;
      return new Data(root, right.end);
    } else
      return new Data(root, root);
    }
  }
}

————————————十月份重做——————————-
按pre order,keep一个prev reference就行了。还是那个顺次的pattern。注意这是in place的改动右孩子,同时左孩子清空,所以要把他们先暂存下来。

	public void flatten(TreeNode root) {
		flatten(root, new TreeNode[1]);
	}
	private void flatten(TreeNode root, TreeNode[] prev) {
		if (root == null)
			return;
		if (prev[0] == null) {
			prev[0] = root;
		} else {
			prev[0].right = root;
			prev[0] = root;
		}
		TreeNode leftBeforeModification = root.left;
		TreeNode rightBeforeModification = root.right;
		root.left = null;
		flatten(leftBeforeModification, prev);
		flatten(rightBeforeModification, prev);
	}
Tags: ,

给一个无序int array,有正有负,找第一个missing正整数。比如[3,4,-1,1] return 2.要求O(n)时间,O(1)空间。

思路:

  • 要求这么高,还不让用空间换时间,说明不是dp,所以基本只让过一两遍数组,一边过一遍直接in place的改动数组(不让生成新数组啊)
  • 既然是大部分不missing,所以可以用index来直接和元素产生关系。
  • 试图让A[i]这个值x的index是x – 1,即每个index身上的元素都是index本身+1。所以{1 2 3}就是理想中的新数组,{1 5 3}就说明缺2。

算法:

  1. 如果A[i]不在自己该在的地方,就想办法把他换到该在的地方。如果A[i]是<=0或者A[i]比数组长度还大,说明没有它该在的地方,那就skip他,弄下一个(不用担心当前位置被它占了,如果后面有想在这个位置的正整数,它会被换走的)
  2. A[i]和A[A[i] – 1]换,然后继续回到i,接着换,直到第一种情况满足。但是如果这俩数一样,换也没用,就别原地打转了。
  3. 最后过一遍shift过的array,第一个不在原位的就是。
public int firstMissingPositive(int[] A) {
  for (int i = 0; i < A.length; i++) {
    //if it's not in its supposed to be place (the index of value x should be x - 1)
    if (i != A[i] - 1){
      if (A[i] <= 0 || A[i] > A.length || A[i] == A[A[i] - 1])
        continue;
      else {
        //put A[i] to A[A[i] - 1]
        swap(A, i, A[i] - 1);
        i--; //回到刚才这位接着做
      }
    }
  }
  for (int i = 0; i < A.length; i++) {
    if (i != A[i] - 1)
      return i + 1;
  }
  return A.length + 1;
}

看了http://www.cnblogs.com/AnnieKim/archive/2013/04/21/3034631.html的答案才会的。

Tags:

A message containing letters from A-Z is being encoded to numbers using the A=1, B=2.. Z=26 mapping, 问给一个全是digit的string,有几种decode成字母组合的方式。比如Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).

这个题一开始是正常做的,然后就挂掉大的test case了。其实一维数组,问“几种ways”,肯定是要dp的。这题类似于小孩上楼梯,求d[i]有两种情况:

  1. 当前这个single digit arr[i]是在1~9之内,说明能组成一个字母,则当前的组合方式(d[i – 1], arr[i])成功,则d[i] = d[i – 1]。不成功,则以i结尾的string没有组合方式,d[i] = 0
  2. 当前这个arr[i]是能组成一个10~26的两位数的第二个digit。这样的话,当前的组合(d[i -2], “arr[i – 1]arr[i]”)成功,则d[i] += d[i – 2]。不成功也不归零,因为可能之前single digit的方式成了,所以不动这个d[i]就行了。
public int numDecodings(String s) {
 if (s.length() == 0)
  return 0;
 int[] d = new int[s.length()];
  if (s.charAt(0) - '0' != 0)
   d[0] = 1;
 for (int i = 1; i < s.length(); i++) {
  if (s.charAt(i) - '0' != 0)
   d[i] = d[i - 1];
  else
   d[i] = 0;
  if (canFormTwo(s, i - 1, i + 1))
   d[i] += i > 1 ? d[i - 2] : 1;
 }
 return d[s.length() - 1];
}
boolean canFormTwo(String s, int i, int j) {
 if (j > s.length())
  return false;
 Integer firstTwo = Integer.valueOf(s.substring(i, j));
  return firstTwo > 9 && firstTwo <= 26;
 }

好像其实不用这么大的空间,应该两个变量就够了,一个prev, 一个prevprev,但是没想好,而且懒得想了。以后再说。

Tags:

将一个字符串变成另外一个字符串所用的最少操作数,每次只能增加、删除或者替换一个字符。如lamb -> slave

这是经典二维DP题。希望做完这个能掌握二维dp要领,因为实在是不够直观。

思路

  1. 状态:d[i][j]表示将A的0~i这个substring转化成b的0~j这个substring要的最少次数。
  2. 所以在二维递归的时候,我们一直有三个已经算好的值:(假设 i = m, j = v,即想算lam->slav)
    • d[i-1][j-1]: A, B不算当前char,把A转成B最少要几步 (la -> sla)
    • d[i-1][j]: A不算当前char,转成B最少要几步 (la -> slav)
    • d[i][j – 1]: A转成B比当前char少一个,最少要几步 (lam -> sla)
  3. 这样的话,怎么由介三个推d[i][j]呢?
    •  (la -> sla)已经算好了,所以看m和v是不是同一个字符,是的话,直接d[i][j]就=d[i-1][j-1];不是的话,相当于替换第m成v,lam就能变成slav了,即步数+1。
    • (la -> slav)已经算好,所以现在处理lam怎么办呢?删一个m成la就行了,因为已经知道怎么把la变成slav了。即步数+1。
    • (lam -> sla)已经算好,所以后面加一个v就行了,lamv就能变成slav了,即步数+1。
  4. 二维dp一定要先写出来已经有什么了,并且有的这三个东西的物理意义。这种求最小的,肯定是dp的,就别花时间自我怀疑了。
int minDistance(String word1, String word2) {
 int[][] d = new int[word1.length()][word2.length()];
 boolean hadSameInA = false;
 boolean hadSameInB = false;
 if (word1.charAt(0) == word2.charAt(0)) {
 d[0][0] = 0;
 hadSameInA = true;
 hadSameInB = true;
 } else {
 d[0][0] = 1;
 }
//do the same for string B. 但是别人做的都是直接assign i,不考虑有相同的情况。那是怎么回事?

 for (int i = 1; i < word1.length(); i++) {
  for (int j = 1; j < word2.length(); j++) {
  if (word1.charAt(i) == word2.charAt(j))
   d[i][j] = d[i - 1][j - 1];
  else {
   d[i][j] = Math.min(d[i - 1][j - 1] + 1, Math.min(d[i][j - 1] + 1, d[i - 1][j] + 1));
 }
return d[word1.length - 1][word2.length - 1];