Lexi's Leetcode solutions

Posts Tagged ‘array

这个题要用反证法来理解。算法:

  1. 从i开始,j是当前station的指针,sum += gas[j] – cost[j] (从j站加了油,再算上从i开始走到j剩的油,走到j+1站还能剩下多少油)
  2. 如果sum < 0,说明从i开始是不行的。那能不能从i..j中间的某个位置开始呢?假设能从k (i <=k<=j)走,那么i..j < 0,若k..j >=0,说明i..k – 1更是<0,那从k处就早该断开了,根本轮不到j。
  3. 所以一旦sum<0,i就赋成j + 1,sum归零。
  4. 最后total表示能不能走一圈。
  5. 这个题算法简单,写起来真是够呛。对数组一快一慢双指针的理解还是不行。注意千万不能出现while (i < 0) { i++, A[i]}这种先++然后取值的情况,必须越界。
public int canCompleteCircuit(int[] gas, int[] cost) {
    int i = 0, j = 0;
    int sum = 0;
    int total = 0;
    while (j < gas.length) {
        int diff = gas[j] - cost[j];
        if (sum + diff < 0) {
            i = j + 1;
            sum = 0;
        } else {
            sum += diff;
        }
        j++;
        total += diff;
    }
    return total >= 0 ? i : -1;
}

 

Advertisements

几道典型two pointers一快一慢题。记住j要匀速的走,i指向最终想要的数组的最后一个元素(或者倒数第二个,或者第一个可以被change的。。因题而异)。做的挺焦躁的,记下来对比练习:

1. Remove Elements:一个数组,删掉所有出现的x,返回新长度。

  • 两个pointer,一个正常走,一个指向真正去掉x的数组的长度。
  • public int removeElement(int[] A, int elem) {
        int i = 0;
        for (int j = 0; j < A.length; j++) {
            if (A[j] != elem) { //should be used 
                A[i] = A[j];
                i++;
            }
        }
        return i;
    }

2. 一个排好序的数组,删掉所有重复,让新数组每个数字只出现一次,返回新数组长度。不许用额外空间。

  • i指向最后一个保证valid的元素
  • j指向当前exam的元素,所以从1开始;当A[j] == A[i]的时候,说明A[j]是个invalid的,所以接着走
  • 可以看出来j肯定走的比i快,所以不用担心i的出界问题
  • public int removeDuplicates(int[] A) {
        if (A.length == 0)
            return 0;
        int i = 0; //right most valid element
        for (int j = 1; j < A.length; j++) {
            if (A[j] != A[i]) { //when not equals to right most valid, means this is also valid, extend the valid array
                i++;
                A[i] = A[j];
            }
        }
        return i + 1; 
    }

3.每个数字最多出现两次,比如1, 1, 1, 2, 2, 3 => 1, 1, 2, 2, 3

  • 同样,是当前exam的元素和valid array的最后一个(其实这里是倒数第二个)元素比较,而不是A[j]和A[j – 1]比较。后者就要混乱很多。
  • public int removeDuplicates(int[] A) {
        if (A.length < 2)
            return A.length;
        int i = 1;
        for (int j = 2; j < A.length; j++) {
            if (A[j] == A[i - 1]) {
                continue;
            } else {
                i++;
                A[i] = A[j];
            }
         }
         return i + 1;
    }

4. 单链表,删除所有出现过两次或以上的元素,比如1->1->2->2->2 => null

  • 同样,i指向最后一个valid元素,j是当前exam的
  • 但是当A[i] == A[j]的时候,i要缩短,所以要keep一个prev pointer
  • j一定要每次都往后走,保证匀速,千万别i动了j却没动。就挂在这了。
  • public ListNode deleteDuplicates(ListNode head) {
        if (head == null)
            return null;
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode prev = dummy, i = head, j = head.next;
        while (j != null) {
            if (i.val == j.val) {
                while (j != null && i.val == j.val) {
                    i.next = j.next; //remove j
                    j = j.next;
                }
                prev.next = i.next; //remove i
                i = prev.next;
                j = i == null ? null : i.next;
            } else {
                j = j.next;
                i = i.next;
                prev = prev.next;
            }
        }
        return dummy.next;
    }

这题完全没印象,后来发现是之前做用了string.indexOf这个函数cheat成功了。这也是two pointers一快一慢的练习题,挺难做的,需要重做。

  • i:当前unique substring的头
  • j:当前unique substring的尾
  • 注意最后可能忘记算最后一段的长度了(因为dup那个条件已经不存在)
public int lengthOfLongestSubstring(String s) {
    int i = 0, len = 0, n = s.length();
    char A[] = s.toCharArray();
    Set<Character> set = new HashSet<Character>();
    for (int j = 0; j < n; j++) {
        if (set.contains(A[j])) { //found dup
            len = Math.max(len, j - i);
            while (i < n && A[i] != A[j]) {
                set.remove(A[i]);
                i++;
            }
            i++;
        } else {
            set.add(A[j]);
        }
    }
    return Math.max(len, n - i);
}

括号题也不是只用stack才能做,这题是算长度,所以stack那种一match就俩一起pop了的方式就不适合了。求极值,一维dp最好使。

  • d[i]: 以i开头的最长valid parentheses有多长。
  • d[i] =
    • if  (str[i] == ‘)’),以右括号开头必定invalid,d[i] = 0
    • if (str[i] == ‘(‘),以左括号开头
      1. 我们想看对应的有木有右括号。因为d[i + 1]代表的sequence肯定是左括号开头右括号结尾,所以我们想catch((…))这种情况。j = i + 1 + d[i + 1],正好就是str[i]后面越过完整sequence的下一个,若是右括号,d[i] = 2 + d[i + 1]
      2. 除此之外,还有包起来后因为把后面的右括号用了而导致跟再往后也连起来了的情况,如((..))()()()。所以d[i]还要再加上j后面那一段的d[j + 1]

这个定义和最长公共字串那题的定义类似,都是“以某个固定位置开始/结束”。看两头的方式又像palindrome。从后往前的一维dp也不常见。挺好玩的,一下复习好多东西。

public int longestValidParentheses(String s) {
    if (s.length() == 0)
        return 0;
    int maxLen = 0;
    int[] d = new int[s.length()];
    // d[i] means substring starts with i has max valid lenth of d[i]
    d[s.length() - 1] = 0;
    for (int i = s.length() - 2; i >= 0; i--) {
        if (s.charAt(i) == ')')
            d[i] = 0;
        else {
            int j = (i + 1) + d[i + 1];
            if (j < s.length() && s.charAt(j) == ')') {
                d[i] = d[i + 1] + 2; //(()())的外包情况
                if (j + 1 < s.length())
                    d[i] += d[j + 1];//()()的后面还有的情况
            }
        }
        maxLen = Math.max(maxLen, d[i]);
    }
    return maxLen;
}

—————————-用stack的做法———————————–

  1. stack里面装的一直是“还没配好对的那些可怜的括号的index”
  2. 是'(‘的时候push
  3. 是’)’的时候,说明可能配对了;看stack top是不是左括号,不是的话,push当前右括号
  4. 是的话,pop那个配对的左括号,然后update res:i和top的(最后一个配不成对的)index相减,就是i属于的这一段的当前最长。如果一pop就整个栈空了,说明前面全配好对了,那res就是最大=i+1
public int longestValidParentheses(String s) {
    int res = 0;
    Stack<Integer> stack = new Stack<Integer>();
    char[] arr = s.toCharArray();
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] == ')' && !stack.isEmpty() && arr[stack.peek()] == '(') {
            stack.pop();
            if (stack.isEmpty())
                res = i + 1;
            else
                res = Math.max(res, i - stack.peek());
        } else {
            stack.push(i);
        }
    }
    return res;
}

这个题是非常常见的面试题,自己试着implement了两种方法,一个O(logn),一个O(n),但是写起来都不是很简洁。

O(n)的方法:binary search by start,找到一个要insert的位置(start1<start<=start2),取那个大的start2(因为它肯定会被start给replace掉,start1则不一定)。插入这个新interval,然后从头到尾检查overlap(两个参数无论谁前谁后都能用这个helper查出来),有就merge,不断吞噬overlap的。代码写起来有点长,不过思路非常清晰,不用考虑任何边界条件(什么a.start <= b.end…那些都烦死人了)。interview的时候可以写这种,因为binary search那段可能都不让你写。

还有一个trick是用iterator可以直接修改正在iterate的list,不会报modification exception,第一次用,很方便。

public ArrayList<Interval> insert(ArrayList<Interval> intervals, Interval newInterval) {
    int insertPosition = insertByStart(intervals, newInterval.start, 0, intervals.size() - 1);
    intervals.add(insertPosition, newInterval);
    Iterator<Interval> it = intervals.iterator();
    Interval prev = it.next();
    while (it.hasNext()) {
        Interval curr = it.next();
        if (overlap(prev, curr)) {
            prev.start = Math.min(prev.start, curr.start);
            prev.end = Math.max(prev.end, curr.end);
            it.remove();
        } else {
            prev = curr;
        }
    }
    return intervals;
}
private int insertByStart(ArrayList<Interval> intervals, int x, int p, int q) {
    if (p > q)
        return p;
    int mid = (p + q) / 2;
    if (intervals.get(mid).start < x) {
        return insertByStart(intervals, x, mid + 1, q);
    } else {
        return insertByStart(intervals, x, p, mid - 1);
    }
}
private boolean overlap(Interval a, Interval b) {
    return within(a.start, b) || within(a.end, b) || within(b.start, a) || within(b.end, a);
}
private boolean within(int v, Interval b) {
    return v >= b.start && v <= b.end;
}

另一种方法是binary search,找出要start属于的位置,end属于的位置。但是很不好写,需要判断那些this.start >=that.end之类的,就更长了。回头看看能不能写简洁点。

Tags: , ,

和3sum一样,多加一维就多n的复杂度,这个是O(n^3),没看到别人有什么好的做法。注意防止duplicate的地方都标红了,算是比较简洁的two pointers防止dup的方式,以后可以都这么些。

    public ArrayList<ArrayList> fourSum(int[] num, int target) {
    	ArrayList<ArrayList> result = new ArrayList<ArrayList>();
    	Arrays.sort(num);
    	for (int i = 0; i < num.length; i++) {     		
                if (i > 0 && num[i] == num[i - 1])
    			continue;
    		for (int j = i + 1; j < num.length; j++) {     			
                        if (j > i + 1 && num[j] == num[j - 1] ) //一开始写的j > 0,就把1111 x=4的组合给漏掉了
    				continue;
    			int p = j + 1, q = num.length - 1;
    			while (p < q) {
    				int sum = num[i] + num[j] + num[p] + num[q];
    				if (sum == target) {
    					ArrayList quad = new ArrayList();
    					quad.add(num[i]);
    					quad.add(num[j]);
    					quad.add(num[p]);
    					quad.add(num[q]);
					result.add(quad);
					do {p++;} while (p < q && num[p] == num[p - 1]);
					do {q--;} while (p < q && num[q] == num[q + 1]);
    				} else if (sum < target) {
    					p++;
    				} else {
    					q--;
    				}
    			}
    		}
    	}
    	return result;
    }

本来以为是弱智题,但是要求不能duplicate之后,发现代码还真不好写了。难怪通过率只有16%这么低。

  • 如何处理“和上一个一样就不要做了”这种duplicate情况?(形成自己的代码习惯)
    • if (i > 0 或类似condition && num[i] == num[i – 1]) continue;
    • 这样可以保证永远笼罩在while (some condition)的保证之下,用continue就不会出现越界等情况。
    • 如果是递归,外层没有while,则用while (boundary &&  num[i] == num[i – 1[) {i++;}
public ArrayList<ArrayList> threeSum(int[] num) {
    ArrayList<ArrayList> result = new ArrayList<ArrayList>();
    Arrays.sort(num);
    for (int i = 0; i < num.length; i++) { 			
        if (i > 0 && num[i] == num[i - 1])
            continue;
	int x = 0 - num[i];
	int p = i + 1, q = num.length - 1;
	while (p < q) {
            if (p > i + 1 && num[p] == num[p - 1]) {
                p++;
                continue;
            }
            if (q < num.length - 1 && num[q] == num[q + 1]) {
                q--;
                continue;
            }
	    if (num[p] + num[q] == x) {
                ArrayList triple = new ArrayList();
		triple.add(num[i]);
		triple.add(num[p]);
		triple.add(num[q]);
		result.add(triple);
	    } else if (num[p] + num[q] < x) {
		p++;
	    } else {
	        q--;
	    }
	}
    }
    return result;
}