2015年7月26日星期日

Remove Duplicates from Sorted List II leetcode

Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list.
For example,
Given 1->2->3->3->4->4->5, return 1->2->5.
Given 1->1->1->2->3, return 2->3.
因为表头可能被删除, 创建一个dummy node, 令dummynode.next = head, return dummy.next
时间O(n) 空间O(1)
 
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = head;
        ListNode pre = dummy;
        while (cur != null && cur.next != null) {
            if (cur.val == cur.next.val) {
                int val = cur.val;
                while (cur != null && cur.val == val) {
                    cur = cur.next;
                }
                pre.next = cur;
            } else {
                pre = pre.next;
                cur = cur.next;
            }
        }
        return dummy.next;
    }
}
public class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null){
            return head;
        }
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        head = dummy;
        while (head.next != null && head.next.next != null){
            if (head.next.val == head.next.next.val){
                int val = head.next.val;
                while (head.next != null && head.next.val == val){
                    head.next = head.next.next;
                }
            } else {//注意一定要在else语句里head往下传递 否则会溢出
                head = head.next;
            }
        }
        return dummy.next;
    }
}

2015年7月25日星期六

链表总结

链表的基本形式是:1 -> 2 -> 3 -> null,反转需要变为 3 -> 2 -> 1 -> null。

  • 访问某个节点 curt.next 时,要检验 curt 是否为 null。 
  • 要把反转后的最后一个节点(即反转前的第一个节点)指向 null。

public ListNode reverse(ListNode head) {
    ListNode prev = null;
    while (head != null) {
        ListNode next = head.next;
        head.next = prev;
        prev = head;
        head = next;
    }
    return prev;
}




1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null变为 1 -> 5 -> 4 -> 3 -> 2 -> 6 -> null
翻转m--n, 不仅要把m-n转好, preMnode. next 要指向n, m.next 要指向postNnode

public class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if (m > n || head == null){
            return head;
        }
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        head = dummy;
        for (int i = 1; i < m; i++){
            head = head.next;
        }
        ListNode preM = head;
        ListNode mNode = head.next;
        ListNode nNode = mNode;
        ListNode postN = mNode.next;
        for (int i = m; i < n; i++){
            ListNode tem = postN.next;
            postN.next = nNode;
            nNode = postN;
            postN = tem;
        }
        preM.next = nNode;
        mNode.next = postN;
        return dummy.next;
    }
}

删除链表中的某个节点 

删除链表中的某个节点一定需要知道这个点的前继节点,所以需要一直有指针指向前继节点。
然后只需要把 prev -> next = prev -> next -> next 即可。但是由于链表表头可能在这个过程中产生变化,导致我们需要一些特别的技巧去处理这种情况。就是下面提到的 Dummy Node。

找中点

        ListNode fast = head;
        ListNode slow = head;
        while (fast!= null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }


链表指针的鲁棒性

综合上面讨论的两种基本操作,链表操作时的鲁棒性问题主要包含两个情况:
  • 当访问链表中某个节点 curt.next 时,一定要先判断 curt 是否为 null。
  • 全部操作结束后,判断是否有环;若有环,则置其中一端为 null。

Dummy Node


Dummy node 是一个虚拟节点,也可以认为是标杆节点。Dummy node 就是在链表表头 head 前加一个节点指向 head,即 dummy -> head。Dummy node 的使用多针对单链表没有前向指针的问题,保证链表的 head 不会在删除操作中丢失。除此之外,还有一种用法比较少见,就是使用 dummy node 来进行head的删除操作,比如 Remove Duplicates From Sorted List II,一般的方法current = current.next 是无法删除 head 元素的,所以这个时候如果有一个dummy node在head的前面。
所以,当链表的 head 有可能变化(被修改或者被删除)时,使用 dummy node 可以很好的简化代码,最终返回 dummy.next 即新的链表。

快慢指针

快慢指针也是一个可以用于很多问题的技巧。所谓快慢指针中的快慢指的是指针向前移动的步长,每次移动的步长较大即为快,步长较小即为慢,常用的快慢指针一般是在单链表中让快指针每次向前移动2,慢指针则每次向前移动1。快慢两个指针都从链表头开始遍历,于是快指针到达链表末尾的时候慢指针刚好到达中间位置,于是可以得到中间元素的值。快慢指针在链表相关问题中主要有两个应用:
  • 快速找出未知长度单链表的中间节点 设置两个指针 *fast*slow 都指向单链表的头节点,其中*fast的移动速度是*slow的2倍,当*fast指向末尾节点的时候,slow正好就在中间了。
  • 判断单链表是否有环 利用快慢指针的原理,同样设置两个指针 *fast*slow 都指向单链表的头节点,其中 *fast的移动速度是*slow的2倍。如果 *fast = NULL,说明该单链表 以 NULL结尾,不是循环链表;如果 *fast = *slow,则快指针追上慢指针,说明该链表是循环链表。


Remove Duplicates from Sorted List

Remove Duplicates from Sorted ListII



Reorder List

Merge k Sorted Lists

Remove Nth Node From End of List

List Cycle

Linked List Cycle II


Reverse Nodes in k-Group

Rotate List

Insertion Sort List

2015年7月23日星期四

binary search 总结

二分法比较简单, 时间复杂度为O(log n)
mid = right + (left - right) / 2 防止left right 都大时候溢出
两种二分法
1. start <= end 每次start = mid + 1 或者 end = mid - 1
2. start + 1 < end 每次 start = mid 或者 end = mid

正常情况下用1的方法, 但是如果mid+1 或者mid-1 可能会错过target的话(mid 为target) 例如Find Minimum in Rotated Sorted Array 用方法2

Search for a Range

Search Insert Position

Sqrt(x)

Search in Rotated Sorted Array

前边的题只需要mid 跟target比较 而这两道题则还需要跟左右边界比较 所以要注意跟边界相等的情况下不仅会出现 > < 还有>= <=


Find Minimum in Rotated Sorted Array
与之前不同的是如果这道题每次 Amid < A[right] -->mid - 1 = right 的话那么 可能会出现如果此时mid是最小值 但是右边界确实最大值 为了防止这种情况 每次left right 都取 mid 而不是mid +-1 但是这么取得话就不能用left <= right 了 否则会无限循环 所以这里用left + 1 < right
Find Minimum in Rotated Sorted Array II

Search a 2D Matrix

2015年7月20日星期一

Unique Paths II leetcode

Follow up for "Unique Paths":
Now consider if some obstacles are added to the grids. How many unique paths would there be?
An obstacle and empty space is marked as 1 and 0 respectively in the grid.
For example,
There is one obstacle in the middle of a 3x3 grid as illustrated below.
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]
The total number of unique paths is 2.
在第i个位置上设置一个障碍物后,说明位置i到最后一个格子这些路都没法走 为0
所以说明,在初始条件时,如果一旦遇到障碍物,障碍物后面所有格子的走法都是0
再看求解过程,当然按照上一题的分析dp[i][j] = dp[i-1][j] + dp[i][j-1] 的递推式依然成立.碰到了障碍物,那么这时的到这里的走法应该设为0,因为机器人只能向下走或者向右走,所以到这个点就无法通过。
时间O(m*n) 空间O(m*n) 
public class Solution {
public class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        if (obstacleGrid == null || obstacleGrid.length == 0 || obstacleGrid[0].length == 0) {
            return 0;
        }
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int [][] sum = new int [m][n];
        for (int i = 0; i < m; i++) {
            if (obstacleGrid[i][0] != 1) {
                sum[i][0] = 1;
            } else {
                break;//后面所有的都无法到达所以break
            }
        }
        for (int j = 0; j < n; j++) {
            if (obstacleGrid[0][j] != 1) {
                sum[0][j] = 1;
            } else {
                break;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (obstacleGrid[i][j] != 1) {
                    sum[i][j] = sum[i-1][j] + sum[i][j-1];
                } else {
                    sum[i][j] = 0;
                }
            }
        }
        return sum[m-1][n-1];
        
    }
}

2015年7月16日星期四

动态规划总结

When to use DP?


  • Input cannot sort
  • Find minimum/maximum result
  • Check the feasibility 找可行性
  • Count all possible solutions 列出所有解


(1) 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2) 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势

4 Types of DP

  • 1. Matrix DP (10%)
  • 2. Sequence (40%)
  • 3. Two Sequences DP (40%)*
  • 4. Backpack (10%)

通用解法:

1.  状 态 State

2. 方程 Function
状态之间的联系,怎么通过小的状态,来算大的状态

3. 初始化 Intialization
最极限的小状态是什么, 起点

4. 答案 Answer
最大的那个状态是什么,终点

Matrix DP


  • state: f[x][y] 表示我从起点走到 坐 标x,y……
  • function: 研究走到xy 这个点之前的一步是从哪里走的
  • intialize: 起点
  • answer: 终点

Sequence Dp

  • state: f[i]表示“ 前i”个位置/数字/字母,(以第i个为)...
  • function: f[i] = f[j] … j 是i之前的一个位置
  • intialize: f[0]..
  • answer: f[n-1]..


Two Sequences Dp


  • state: f[i][j]代表了第一个sequence的前i个数字/字符 配上第二个sequence的前j个...
  • function: f[i][j] = 研究第i个和第j个的匹配关系
  • intialize: f[i][0] 和 f[0][i](二维数组都要初始化第0行和第0列)
  • answer: f[s1.length()][s2.length()]

1. sequences
Climbing Stairs

Decode Ways

Unique Binary Search Trees

Maximum Subarray


Word Break

Palindrome Partitioning II



2. Matrix
Triangle

Unique Paths I

Unique Paths II

Minimum Path Sum

3. two sequences

Edit Distance

Distinct Subsequences

Interleaving String

Scramble String(3 sequences)

2015年7月15日星期三

N-Queens II leetcode

Follow up for N-Queens problem.
Now, instead outputting board configurations, return the total number of distinct solutions.
N-Queens I 完全相同, 只是返回的是个数
ublic class Solution {
    public int totalNQueens(int n) {
        int[] res = new int[1];
        if (n <= 0) {
            return res[0];
        }
        int[] column = new int[n];
        helper(res, column, n, 0);
        return res[0];
    }
    public void helper(int[] res, int[] column, int n, int pos) {
        if (pos == n) {
            res[0]++;
        } else {
            for (int i = 0; i < n; i++) {
                column[pos] = i;
                if (isValid(column, pos)) {
                    helper(res,column, n, pos + 1);
                }
            }
        }
    }
    public boolean isValid(int[] column, int pos) {
        for (int i = 0; i < pos; i++) {
            if (column[i] == column[pos] || Math.abs(column[i] - column[pos]) == pos - i) {
                return false;
            }
        }
        return true;
    } 
}   


2015年7月13日星期一

backtracking类型题目总结


subsets 这道题属于backtracking 类的模板


subsets II 与i不同的是有重复的数字, 例如[1,2(1)] [1,2(2)]情况相同, 递归过程中每次新选取的可以是和递归数组相同的,(比如nums是[1,2,2,2] 当前递归栈是[1,2(1)] 新加入的[1,2(1), 2(2)]是合法的) 但是后面的元素就不能相同了([1, 2(1), 2(3)]就不合法 因为重复了)。
if ( i != pos && num[i] == num[i - 1]) {
                continue;
            }    
用这个方法防止重复。


permutations
与subset不同的是递归传入参数每次从0开始, 为了避免重复 需要构建一个boolean[] 以记录哪个点访问过


permutations II与I 不同的是出现了重复的问题, 所以如果当前元素与之前元素相同 而之前的元素没有访问过 说明相似的情况已经出现过 要避免再出现 所以用
if(i > 0 && num[i] == num[i-1] && visit[i-1] == false ){
                continue;
            }





Combinations
combination sum 递归条件是 target = 0 加入结果 〈0就return
Combination Sum II


Generate Parentheses  返回条件是left 和right都为0
递归条件有两个: 1. left › 0时候可以选择增加left 2. left ‹ right 时 可以增加right

Letter Combinations of a Phone Number

Restore IP Addresses

Palindrome Partitioning


N-Queens I
N-Queens II

Word Search

Word Break II 

Path Sum II
先把给定数组排序 Arrays.sort()

1. 递归返回条件 
在permutation(排列)和combination时候 要当数组里面元素和所给元素数相等时候返回,
combination sum 时候则需要当target=0时候返回。

  2. 递归的时候传入什么样的参数 (0, pos or pos + 1)

2.1(对于在dfs内recursive的使用dfs时候 ) 如果每次是从i开始扫, 则得到的结果是不会重复的。(会出现[1,2]不会出现[2,1])

2.2 但是如果[1,2] 和[2,1]两个重复情况都要的话, 则要每次从0开始扫.并且要加一个boolean[] visit 来记录某些点是否被访问过


  3. 是否重复选取

如果已选取[1,2(1)] 那么[1,2(2)]就是重复选取 为了确保这种情况不发生,
只要确定当前所选取的值num[i]和num[i-1]不重复就可以, 如果重复跳过num[i].
对于每次从i扫的情况 :
if ( i != pos && num[i] == num[i - 1]) {
                continue;
            }    
对于每次从0开始扫的情况:
if (i > 0 && num[i] == num[i - 1] && !visit[i - 1]){
                continue;
                // important: if previous number have same value as (i)th
                // but have never been visited, then skip current number
            }

a. a4
a. 关于递归返回条件满足时, 往往要把结果存入arraylist, 这时arraylist.add(new(tem))。
当arraylist里存的是list这样可以引用的话要new 一下 存string int这样的就可以直接添加
b. 关于继续递归时候往往要把下层的结果存入中间变量 例如 tem.add() 然后递归helper(), 再tem.delete()
当中间变量是string 时候 再下层递归中可以直接把加入的东西带入string中 以免除这个步骤 例如Palindrome Partitioning中第二种解法

2015年7月12日星期日

二叉查找树 总结

Validate Binary Search Tree 利用中序遍历, 比较之前遍历的是否比当前点小, 如果小就返回true 否则false

Recover Binary Search Tree 同样利用中序遍历, 比较之前的点和当前的点, 把逆序的node存储 最后对换

2015年7月7日星期二

Binary Tree Postorder Traversal leetcode

Given a binary tree, return the postorder traversal of its nodes' values.
For example:
Given binary tree {1,#,2,3},
   1
    \
     2
    /
   3
return [3,2,1].
postorder: 左-右-中

public class Solution {
    public ArrayList<Integer> postorderTraversal(TreeNode root) {
        ArrayList<Integer> result = new ArrayList<Integer>();
        if (root == null){
            return result;
        }
        ArrayList<Integer> left = postorderTraversal(root.left);
        ArrayList<Integer> right = postorderTraversal(root.right);
        result.addAll(left);
        result.addAll(right);
        result.add(root.val);
        return result;
        
    }
}
public class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        helper(res, root);
        return res;
    }
    public void helper(List<Integer> res, TreeNode root) {
        if (root == null) {
            return;
        }
        helper(res, root.left);
        helper(res, root.right);
        res.add(root.val);
    }
}
public class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        TreeNode pre = null;
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (root != null || !stack.isEmpty()) {
            if (root != null) {
                stack.push(root);
                root = root.left;
            } else {
                TreeNode peak = stack.peek();
                if (peak.right != null && pre != peak.right) {///如果当前栈顶元素的右结点存在并且还没访问过(也就是右结点不等于上一个访问结点)就访问右结点 /
                    root = peak.right;
                } else {////如果栈顶元素右结点是空或者已经访问过,那么说明栈顶元素的左右子树都访问完毕 需要把栈顶元素加入结果并且回溯上一层
                    stack.pop();
                    res.add(peak.val);
                    pre = peak;
                }
                
            }
        }
        return res;
    }
}

tree 总结

1. tree的性质:


Maximum Depth of Binary Tree
Minimum Depth of Binary Tree
Balanced Binary Tree
Same Tree
Symmetric Tree


树的性质判断是树的数据结构比较基本的操作, 争取一遍bug free。
前3道考树的深度问题,可能会考使用非递归做法(用queue)
Maximum Depth of Binary Tree就是遇到空结点返回0, 递归返回左右子树里面最大的那个。
Minimum Depth of Binary Tree稍微复杂一些, 因为当左子树为空时候按照定义深度不为0 而应该是右边子树到底的深度。 所以要对左子树右子树是否为空做一个判断。
Balanced Binary Tree原理也是求深度, 但是他的返回类型是boolean 所以要建立一个helper函数如果不平衡就返回-1。
后两道考树的便利, 递归退出条件是这两道题最难的地方。
Same Tree就是对两棵树同时便利
Symmetric Tree与前一道题基本相同, 但是输入只有一个treenode 所以要建立一个helper函数 让node的左右子树作为两个node比较。

2. 树的遍历


Binary Tree Preorder Traversal 中左右
Binary Tree Inorder Traversal 左中右
Binary Tree Postorder Traversal 左右中




前三道题属于图的深度优先搜索问题
这三种遍历的递归方法 就是递归左右节点直到空为止。中在哪个位置就在哪个位置把root加入最后的结果。 例如:preoder的递归式就是先加入root然后递归左右子树。
对于迭代的解法就是用栈来实现, 前两题用一个栈来保存前驱的分支结点(相当于图的深度搜索的栈), 然后用一个结点来记录当前结点就可以了。 preorder要在root入栈时候就加入res, inorder则递归完左子树再加入res




Binary Tree Level Order Traversal

Binary Tree Level Order Traversal II

Binary Tree Zigzag Level Order Traversal



三道题属于树的层序遍历, 都是广度优先搜索
用queue来实现


3. 树的求和



Path Sum

Path Sum II

Binary Tree Maximum Path Sum


Sum Root to Leaf Numbers



树的题目基本都是用递归或者分治来解决,主要考虑两个问题:
1)如何把问题分治成子问题给左子树和右子树。
2)考虑结束条件是什么。
难点就是考虑递归结束的条件是什么。
path sum II 要保存所有节点 所有要递归的dfs遍历
b

4. 树的构造


Convert Sorted Array to Binary Search Tree leetcode


Convert Sorted List to Binary Search Tree

array本身就是有序的 只需要取中点作为根 然后递归的寻找左右子树
list无序, 如果每次都寻找中点, 复杂度很高。 所以用中序遍历的方法来构造树
Construct Binary Tree from Preorder and Inorder Traversal

这两道题完全相同 分别通过preorder 和 postorder确定root的值 然后在inorder里找到root的位置 然后递归查找。 比较巧妙的是用了hashmap来存贮inorder的root和index 这样可以在o(1)时间里找到root的位置。

Flatten Binary Tree to Linked List 
这道题可以用先序遍历来逐个点递归解 也可以用stack的非递归方法

5. 树的变换

Populating Next Right Pointers in Each Node
Populating Next Right Pointers in Each Node II