LeetCode--【问题记录】
30 Substring with Concatenation of All Words
package com037.SudokuSolver; public class Solution2 { public static void main(String[] args) { char[][] board = { {'5','3','.','.','7','.','.','.','.'}, {'6','.','.','1','9','5','.','.','.'}, {'.','9','8','.','.','.','.','6','.'}, {'8','.','.','.','6','.','.','.','3'}, {'4','.','.','8','.','3','.','.','1'}, {'7','.','.','.','2','.','.','.','6'}, {'.','6','.','.','.','.','2','8','.'}, {'.','.','.','4','1','9','.','.','5'}, {'.','.','.','.','8','.','.','7','9'} }; solveSudoku(board); for(int i = 0 ; i < 9 ; i++){ for(int j = 0 ; j< 9 ; j++){ System.out.print(board[i][j]+" "); if(j%3==2) System.out.print("| "); } if(i%3==2) System.out.print("\n-----------------------"); System.out.println(); } } public static void solveSudoku(char[][] board) { if(board == null || board.length == 0) return; solve(board); } public static boolean solve(char[][] board){ for(int i = 0; i < 9; i++){ for(int j = 0; j < 9; j++){ if(board[i][j] == '.'){ for(char c = '1'; c <= '9'; c++){//trial. Try 1 through 9 for each cell if(isValid(board, i, j, c)){ board[i][j] = c; //Put c for this cell if(solve(board)) return true; //If it's the solution return true else board[i][j] = '.'; //Otherwise go back } } return false; } } } return true; } public static boolean isValid(char[][] board, int i, int j, char c){ //Check colum for(int row = 0; row < 9; row++) if(board[row][j] == c) return false; //Check row for(int col = 0; col < 9; col++) if(board[i][col] == c) return false; //Check 3 x 3 block for(int row = (i / 3) * 3; row < (i / 3) * 3 + 3; row++) for(int col = (j / 3) * 3; col < (j / 3) * 3 + 3; col++) if(board[row][col] == c) return false; return true; } }
48 Rotate Image
https://leetcode.com/problems/rotate-image/
62 Unique Paths (求矩阵从左上到右下有多少种走法)
63 Unique Paths II (在62基础上加上了限制:矩阵中可能存在障碍)
64 Minimum Path Sum (求矩阵从左上到右下最短的走法)
62. 方法:另开一个二维数组,每一格存放到当前点的走法。每一个点只能从自己上面的或者左面的那个点走过来,所以就是到达上侧点的走法数+到达左侧点的走法数。
public static int uniquePaths(int m, int n) { if(m==0||n==0) return 0; if(m==1||n==1) return 1; int[][] solu = new int[m][n]; for(int i = 0 ; i < m ; i++){ //第0列置为1 solu[i][0] = 1; } for(int j = 0 ; j < n ; j++){ //第0行置为1 solu[0][j] = 1; } for(int i = 1 ; i < m ; i++){ for(int j = 1 ; j < n ; j++) solu[i][j] = solu[i][j-1]+solu[i-1][j]; } return solu[m-1][n-1]; }
63. 方法:类似62 只是二维数组中,需要先判断该点是不是障碍点。如果是,则置为0
public int uniquePathsWithObstacles(int[][] obstacleGrid) { int m = obstacleGrid.length; //行数量 if(m==0) return 0 ; int n = obstacleGrid[0].length; //列数量 if(n==0) return 0; int[][] solu = new int[m][n]; for(int i = 0 ; i < m ; i++){ //第0列置为1 if(obstacleGrid[i][0]==1) //如果存在障碍,那么障碍以及障碍后全为0 break; else solu[i][0] = 1; } for(int j = 0 ; j < n ; j++){ //第0行置为1 if(obstacleGrid[0][j]==1) break; solu[0][j] = 1; } for(int i = 1 ; i < m ; i++){ for(int j = 1 ; j < n ; j++) if(obstacleGrid[i][j]==1) solu[i][j]=0; else solu[i][j] = solu[i][j-1]+solu[i-1][j]; } return solu[m-1][n-1]; }
64. 方法:62的拓展 每格存放的时当前点最短的走法。
public int minPathSum(int[][] grid) { int row = grid.length ; if(row == 0) return 0; int col = grid[0].length; int[][] sum = new int[row][col]; //新建数组存放到当前点的最小和 sum[0][0] = grid[0][0]; for(int i = 1 ; i < row ; i++){ //为第0列赋值 sum[i][0] = sum[i-1][0]+grid[i][0]; } for(int j = 1 ; j < col ; j++){ sum[0][j] = sum[0][j-1]+grid[0][j]; } for(int i = 1 ; i < row ; i++){ for(int j = 1 ; j < col ; j++){ sum[i][j] = Math.min(sum[i][j-1], sum[i-1][j])+grid[i][j]; } } return sum[row-1][col-1]; }
69 sqrt(x)
求平方根有三种方法:二分搜索,牛顿迭代,约翰·卡马克 雷神之锤III中的方法
http://blog.csdn.net/wangxiaojun911/article/details/18203333
73 Set Matrix Zeroes
题目大意:给定一个m*n的矩阵,若a[i][j]==0,将它所在的行列均设置为0。(要求只使用矩阵本身的空间)
方法:首先想到的是新建另一个数组,然后遍历原始数组给新数组赋值。但是题目限制了空间因此不行。
使用第一行和第一列来记录。 若a[i][j]==0,就将a[i][0]和a[0][j]设置为0。 之后只需令行开头为0的行、列开头为0的列全置为0即可。
注意:第1行和第1列要单独处理是否应该置为0。
代码:
public static void setZeroes(int[][] matrix) { int row = matrix.length; //指示行数目 int col = matrix[0].length; //指示列数目 boolean flagcol = false; //查看第一列是否有为0的元素 for(int i = 0 ; i < row ; i++){ if(matrix[i][0]==0) flagcol=true; } boolean flagrow = false; //查看第一行是否有为0的元素 for(int i = 0 ; i < col ; i++){ if(matrix[0][i]==0) flagrow=true; } for(int i = 1 ; i< row ; i++){ for(int j = 1 ; j < col ; j++){ if(matrix[i][j]==0){ matrix[0][j] = 0; matrix[i][0] = 0; } } } for(int i = 1 ; i < row ; i++){ if(matrix[i][0]==0){ for(int j = 1 ; j < col ; j++){ matrix[i][j] = 0; } } } for(int j = 1 ; j < col ; j++){ if(matrix[0][j]==0){ for(int i = 1 ; i < row ; i++){ matrix[i][j]=0; } } } if(flagcol == true){ //第一列有为0的元素,将第一列置为0 for(int i = 0 ; i < row ;i++){ matrix[i][0]=0; } } if(flagrow == true){//第一行有为0的元素,将第一行置为0 for(int j = 0 ; j < col ; j++){ matrix[0][j]=0; } } }
77 Combinations Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
方法:类似编程之美3.2 电话号码对应英语单词。
//从1到n中选出k个数加入list public static List<List<Integer>> combine(int n, int k) { List<List<Integer>> solu = new ArrayList<List<Integer>>(); if(k==0) return solu; int[] tag = new int[k]; //每一位数字设定一个tag int[] start = new int[k]; //每位数字的最小值 int[] end = new int[k]; //每位数字的最大值(假设我们选择的组合结果每一位都按从小到大排列) for(int i = 0 ; i < k ; i++){ start[i] = i+1; end[i] = n-k+1 +i; tag[i] = i+1; System.out.println(i+" "+start[i]+" "+end[i]); } while(tag[0]<=end[0]){ List<Integer> list = new ArrayList<Integer>(); for(int i = 0 ; i < k ; i++){ list.add(tag[i]); } solu.add(list); System.out.println(list); tag[k-1]++; int i ; for(i = k-1 ; i >0 ; i--){ if(tag[i]>end[i]){ //若当前数字已经到头了,令当前从头开始,前一个数字加1 tag[i-1]++; } else break; } //在上面的for循环中只是得到了最前面变化的那个元素,后面的在更新过程中都被更改了 //因此下面根据最前面变化的元素来计算后面的元素。由所有元素递增的原则,只需令后面的比前面的大1即可 for(int j = i ; j <k-1 ; j++){ tag[j+1] = tag[j]+1; } } return solu; }
注意:此类问题还可以用深度优先搜索,如下一题:
216 Combination Sum III
给定一个target和k,求出k个数字,使得这k个数字的和为target。
方法1:类似于77,只是每次得到一个序列后需要先看他们的和是不是target,如果是才加入到结果集中
public static List<List<Integer>> combinationSum3(int k, int n) { int maxn = n; for(int i =1 ; i < k ; i++){ maxn = n-i; } if(maxn > 9) maxn = 9; List<List<Integer>> solu = new ArrayList<List<Integer>>(); if(k==0) return solu; int[] tag = new int[k]; //每一位数字设定一个tag int[] start = new int[k]; //每位数字的最小值 int[] end = new int[k]; //每位数字的最大值(假设我们选择的组合结果每一位都按从小到大排列) for(int i = 0 ; i < k ; i++){ start[i] = i+1; end[i] = maxn-k+1 +i; tag[i] = i+1; System.out.println("start "+start[i]+" end "+end[i]); } int sum ; while(tag[0]<=end[0]){ sum = 0; List<Integer> list = new ArrayList<Integer>(); for(int i = 0 ; i < k ; i++){ sum = sum+tag[i]; list.add(tag[i]); } if(sum == n){ solu.add(list); System.out.println(list); } tag[k-1]++; int i ; for(i = k-1 ; i >0 ; i--){ if(tag[i]>end[i]){ //若当前数字已经到头了,令当前从头开始,前一个数字加1 tag[i-1]++; } else break; } //在上面的for循环中只是得到了最前面变化的那个元素,后面的在更新过程中都被更改了 //因此下面根据最前面变化的元素来计算后面的元素。由所有元素递增的原则,只需令后面的比前面的大1即可 for(int j = i ; j <k-1 ; j++){ tag[j+1] = tag[j]+1; } } return solu; }
方法2:深度优先遍历,题目中限定了{1,2,3,4,5,6,7,8,9} 9个数字。首先将1加入到本次结果中,然后在对(target-1)进行搜索。当本次结果集的长度大于等于k且和不等于target,说明这个结果不满足条件,这次结果不保存,进行下一次搜索。
public static List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> res = new ArrayList<>(); int[] can = {1,2,3,4,5,6,7,8,9}; Arrays.sort(can); helper(can, 0, n, new ArrayList<Integer>(), k ,res); return res; } //使用深度优先搜索 private static void helper(int[] can, int start, int target, List<Integer> each, int len , List<List<Integer>> res ) { if (each.size() >= len) {return;} for (int i = start; i < can.length; i++) { List<Integer> temp = new ArrayList<>(each); if (can[i] == target) { if (each.size() == len - 1) { temp.add(can[i]); res.add(temp); } break; } else if (can[i] < target) { temp.add(can[i]); helper(can, i+1, target-can[i], new ArrayList<>(temp),len , res); } else {break;} } return; }
注意:上面的方法二还在 78 39 40题中用到了
87 Scramble String http://www.cnblogs.com/easonliu/p/3696135.html
public static boolean isScramble(String s1, String s2) { if (s1.length() != s2.length()) return false; // 如果两个字符串长度不同,则一定不可能 int len = s1.length(); if (len == 1) return s1.equals(s2); char[] str1 = s1.toCharArray(); char[] str2 = s2.toCharArray(); Arrays.sort(str1); Arrays.sort(str2); // 判断两个字符串排序后是否相等。如果不相等,则一定不可能 for (int i = 0; i < len; i++) { if (str1[i] != str2[i]) return false; } String s11, s12, s21, s22; boolean res = false; for (int i = 1; i < len ; ++i) { s11 = s1.substring(0, i); s12 = s1.substring(i, len); s21 = s2.substring(0, i); s22 = s2.substring(i, len); System.out.println(s11 + " " + s12); System.out.println(s21 + " " + s22); res = isScramble(s11, s21) && isScramble(s12, s22); if (res == false) { s21 = s2.substring(0, len - i); s22 = s2.substring(len - i, len); System.out.println("呵呵 "+s21 + " " + s22); res = isScramble(s11, s22) && isScramble(s12, s21); } if(res == true ) return true; } return res; }
88 Merge Sorted Array(将两个排好序的数组合并,后面的合并到前面的数组中,前面数组的空间足够大)
89 Gray Code 求指定二进制位数格雷码(二进制怎么加密为格雷码)
/** * * Gray Code: 000, 001, 011, 010, 110, 111, 101, 100, 最右边值为 “1” 的bit在最左边了,结束。 * Binary : 000, 001, 010, 011, 100, 101, 110, 111 * 再者就是Binary Code 转换为Gray Code了。 * 如: * Binary Code :1011 要转换成Gray Code * 1011 = 1(照写第一位), 1(第一位与第二位异或 1^0 = 1), 1(第二位异或第三位, 0^1=1), 0 (1^1 =0) = 1110 * 其实就等于 (1011 >> 1) ^ 1011 = 1110 * */ public List<Integer> grayCode(int n) { int num = 1<<n; List<Integer> solu = new ArrayList<Integer>(); for(int i = 0 ; i < num ; i++){ int temp = (i>>1)^i; solu.add(temp); } return solu; }
92 Reverse Linked List II (逆置链表中从m到n的元素)
在处理链表相关问题的时候,如果头结点不好处理,可以新建一个节点,然后将链表连接在该节点之后,这样就可以将原来的头节点作为一般节点处理了。
public static ListNode reverseBetween(ListNode head, int m, int n) { if(m==n||head==null) return head; ListNode h = new ListNode(0); h.next = head; //强行加一个头节点 ListNode pre = h; // 当前要头插的节点的前一个节点 ListNode q = null; while(m>1){ //找到要逆置的部分的前一个节点,作为头插的头部 pre = pre.next; m--; n--; } ListNode p = pre.next; //pre下一个节点 ListNode end = p; while(n>0){ q = p.next; p.next = pre.next; pre.next = p ; p = q; n--; } end.next = q; //连接中间和后面的部分 return h.next; }
96 Unique Binary Search Trees(求二叉排序树的个数)
方法:由n个元素可以组成的二叉排序树的个数实际上就是n为参数的卡特兰数:h(n)=C(2n,n)/(n+1)
另一种方法:递推
递推式:
s[0]=1;
s[1]=1;
...
s[i]=sum(s[j]*s[i-j-1]) 0<=j<i;
public static int numTrees(int n) { if(n==0||n==1||n==2) return n; if(n==3) return 5; int[] a = new int[n+1]; a[0] = 1 ; a[1] = 1 ; a[2] = 2 ; a[3] = 5 ; for(int i = 4 ; i <= n ; i++){ for(int j = 0 ; j < i ; j++){ a[i] += a[j] * a[i-j-1]; } } return a[n]; }
95 Unique Binary Search Trees II 将所有不同的二叉搜索树存到list中
public List<TreeNode> generateTrees(int n) { return generateTrees(1,n); } public List<TreeNode> generateTrees(int start,int end){ List<TreeNode> trees = new ArrayList<TreeNode>(); if(start>end){ trees.add(null); return trees;} for(int rootValue=start;rootValue<=end;rootValue++){ List<TreeNode> leftSubTrees=generateTrees(start,rootValue-1); //左子树 List<TreeNode> rightSubTrees=generateTrees(rootValue+1,end); //右子树 for(TreeNode leftSubTree:leftSubTrees){ for(TreeNode rightSubTree:rightSubTrees){ //对于每一个左子树和右子树,连接到根上 TreeNode root=new TreeNode(rootValue); root.left=leftSubTree; root.right=rightSubTree; trees.add(root); //添加到集合中 } } } return trees; }
108 Convert Sorted Array to Binary Search Tree (用排好序的数组构建二叉搜索树)
public static TreeNode sortedArrayToBST(int[] nums) { int end = nums.length-1; if(end==-1) return null; return buildTree(nums,0,end); } public static TreeNode buildTree(int[] nums, int start , int end){ if(start<=end){ int mid = (start+end)/2; TreeNode t = new TreeNode(nums[mid]); t.left = buildTree(nums,start,mid-1); t.right = buildTree(nums,mid+1,end); return t; } else return null; }
109 Convert Sorted List to Binary Search Tree (用排好序的链表构建二叉搜索树)
法1 转换成哈希表,然后同108;
public TreeNode sortedListToBST(ListNode head) { HashMap<Integer,TreeNode> map = new HashMap<Integer,TreeNode>(); ListNode p ; int i = 0; for(p=head ; p!=null ; p = p.next){ map.put(i, new TreeNode(p.val)); i++; } return buildTree(map,0,i-1); } public static TreeNode buildTree(HashMap<Integer,TreeNode> map, int start , int end){ if(start<=end){ int mid = (start+end)/2; TreeNode t = map.get(mid); t.left = buildTree(map,start,mid-1); t.right = buildTree(map,mid+1,end); return t; } else return null; }
法2 递归处理
public TreeNode sortedListToBST(ListNode head) { if(head==null) return null; ListNode fast = head ; ListNode slow = head ; ListNode temp = null; while(fast.next!=null && fast.next.next!=null){ fast = fast.next.next; temp = slow; slow = slow.next; }//遍历完后slow就是第二部分的头部 if(temp!=null) //将链表切割成两部分 temp.next = null; else head = null; TreeNode root = new TreeNode(slow.val); root.left = sortedListToBST(head); root.right = sortedListToBST(slow.next); return root; }
114 Flatten Binary Tree to Linked List 将二叉树展开成链表
方法1:先根遍历二叉树,并将遍历结果存放在List中,然后依次处理list的每一个元素
public static void flatten(TreeNode root) { if(root==null) return; List<TreeNode> list = new ArrayList<TreeNode>(); dfs(root,list); int len = list.size(); for(int i = 0 ; i < len-1 ; i++){ list.get(i).left = null; list.get(i).right = list.get(i+1); } root = list.get(0); } public static void dfs(TreeNode root,List<TreeNode> list){ if(root!=null){ //System.out.println(root.val); list.add(root); dfs(root.left,list); dfs(root.right,list); } }
方法2:递归,根节点的左右子树展开后,将左子树连在根的右侧,右子树连接到原先左子树的最后。
public static void flatten(TreeNode root) { if(root==null) return; flatten(root.left); //展开左子树 flatten(root.right); //展开右子树 //左右子树均展开后,将左子树连在右侧,将右子树连在左子树最后一个节点之后 TreeNode temp = root.right; root.right = root.left; root.left = null; while(root.right!=null){ root = root.right; } //将原先已经展开的右子树连接到后面 root.right = temp; }
116 Populating Next Right Pointers in Each Node
117 Populating Next Right Pointers in Each Node II
题目要求只能使用常量空间,因此不能使用队列,但是思路还是一样的。
116:
public static void connect(TreeLinkNode root) { if(root==null) return; TreeLinkNode temp = root; TreeLinkNode curr ; while(temp.left!=null){ curr = temp; while(curr!=null){ curr.left.next = curr.right; if(curr.next!=null){ curr.right.next = curr.next.left; } curr = curr.next; } temp = temp.left; } }
117:
public static void connect(TreeLinkNode root) { if (root == null) return; // link root's child nodes link(root); //跟节点的左右子节点的next //寻找当前层所有节点的左右子节点的next,并连接 TreeLinkNode curr = root.next; while (curr != null) { link(curr); curr = curr.next; } //对当前节点的左右节点递归进行处理 connect(root.left); connect(root.right); } // helper function // link root node's left and right nodes public static void link(TreeLinkNode root) { if (root == null) return; if (root.left != null) { //若跟节点的左节点不空,连接左节点及其下一个节点 root.left.next = ( root.right != null ? root.right : getNext(root) ); } if (root.right != null) {//若跟节点的右节点不空,连接右节点及其下一个节点 root.right.next = getNext(root); } } // get the left most node at the next level // 寻找相邻节点 public static TreeLinkNode getNext(TreeLinkNode node) { TreeLinkNode next = node.next; while (next != null) { if (next.left != null) return next.left; if (next.right != null) return next.right; next = next.next; } return null; }
123 Best Time to Buy and Sell Stock III (有两次买入和卖出股票的机会,第二次买入时间必须大于等于第一次卖出时间)
动态规划,两个数组分别保存 0到i 的最大收益和 i到n-1 的最大收益
public class Solution { public int maxProfit(int[] prices) { if(prices.length == 0) return 0; int ans = 0; int n = prices.length; //正向遍历,opt[i]表示 prices[0...i]内做一次交易的最大收益. int opt[] = new int[n]; opt[0] = 0; int low = prices[0]; int curAns = 0; for(int i = 1; i<n; i++){ if(prices[i] < low) low = prices[i]; else if(curAns < prices[i] - low) curAns = prices[i] - low; opt[i] = curAns; } //逆向遍历, opt[i]表示 prices[i...n-1]内做一次交易的最大收益. int optReverse[] = new int[n]; optReverse[n - 1] = 0; curAns = 0; int high = prices[n - 1]; for(int i=n-2; i>=0; i--){ if(prices[i] > high) high = prices[i]; else if(curAns < high - prices[i]) curAns = high - prices[i]; optReverse[i] = curAns; } //再进行划分,分别计算两个部分 for(int i=0; i<n; i++){ int tmp = opt[i] + optReverse[i]; if(ans < tmp) ans = tmp; } return ans; } }
188 BestTimetoBuyandSellStockIV (规定最多进行K次交易)
动态规划,二维数组t[k+1][len], 其中t[i][j]表示最多进行i次交易,到第j天的时候的最大收益
public int maxProfit(int k, int[] prices) { int len = prices.length; if (k >= len-1) return quickSolve(prices); //最大交易次数足够大,那么只要有 int[][] t = new int[k + 1][len]; for (int i = 1; i <= k; i++) { int tmpMax = -prices[0]; for (int j = 1; j < len; j++) { t[i][j] = Math.max(t[i][j - 1], prices[j] + tmpMax); tmpMax = Math.max(tmpMax, t[i - 1][j - 1] - prices[j]); } } return t[k][len - 1]; } //处理特殊情况,最大交易次数足够大,那么只要有收益就可以进行交易 private int quickSolve(int[] prices) { int len = prices.length, profit = 0; for (int i = 1; i < len; i++) //只要前后差值为正,就有收益 if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1]; return profit; }
128 Longest Consecutive Sequence(最长连续序列)
给定一个数组,求其最长连续序列长度 如 [100, 4, 200, 1, 3, 2], 最长连续序列为[1,2,3,4] 长度为4.
题目要求时间复杂度O(n),因此想到使用HashMap,将所有元素添加进去,然后以每一个没有被查找过的元素为中心,向两边进行查找,每次查找的key在上次的基础上加1。
public static int longestConsecutive(int[] nums) { HashMap<Integer,Integer> map = new HashMap<Integer,Integer>(); int len = nums.length; for(int i = 0 ; i < len ; i++){ map.put(nums[i], 1); } int j = 0; int templen = 0; int maxlen = 0; for(int i = 0 ; i < len ; i++){ if(map.get(nums[i])!=0){ //获取中心数字 j = 1; templen = 1 ; while(map.get(nums[i]+j)!=null){ map.put(nums[i]+j, 0); templen++; j++; } j = 1; while(map.get(nums[i]-j)!=null){ map.put(nums[i]-j, 0); templen++ ; j++; } if(maxlen<templen) maxlen = templen; System.out.println("当前中心"+nums[i]+"当前长度"+templen+"最大长度"+maxlen); } } return maxlen; }
133 Clone Graph (克隆一个图)
方法:由于题目中给的图label是唯一的,因此可以利用hashmap,key为label,value为图节点,用这样的hashmap确认图是否唯一。用深度优先搜索遍历并复制每一个节点
//深度优先搜索 private HashMap<Integer, UndirectedGraphNode> map = new HashMap<>(); public UndirectedGraphNode cloneGraph(UndirectedGraphNode node) { return helper(node); } private UndirectedGraphNode helper(UndirectedGraphNode node) { if (node == null) return null; if (map.containsKey(node.label)) { return map.get(node.label); } UndirectedGraphNode clone = new UndirectedGraphNode(node.label); map.put(clone.label, clone); for (UndirectedGraphNode neighbor : node.neighbors) { clone.neighbors.add(helper(neighbor)); } return clone; }
138 Copy List with Random Pointer
复制一个带有随机指针的链表。即除了next指针,还有一个random指针,这个random指针指向链表中随机的节点
//数据结构 class RandomListNode { int label; RandomListNode next, random; RandomListNode(int x) { this.label = x; } }; //复制带有随机指针的链表 public static RandomListNode copyRandomList(RandomListNode head) { if(head==null) return null; RandomListNode solu = new RandomListNode(0); HashMap<RandomListNode,Integer> IndexTable = new HashMap<RandomListNode,Integer>(); HashMap<Integer,RandomListNode> soluTable = new HashMap<Integer,RandomListNode>(); RandomListNode p = head; int i = 0 ; while(p!=null){ IndexTable.put(p,i); i++; p = p.next; } p = head ; i = 0 ; RandomListNode q = solu; //q为结果表的头节点 while(p!=null){ if(soluTable.get(i)==null){ RandomListNode temp = new RandomListNode(p.label); soluTable.put(i, temp); q.next = temp; } else{ q.next = soluTable.get(i); } q = q.next; if(p.random!=null){ //存在后继节点 Integer ran_index = IndexTable.get(p.random); if(!soluTable.containsKey(ran_index)){ RandomListNode temp_ran = new RandomListNode(p.random.label); soluTable.put(ran_index, temp_ran); q.random = temp_ran; } else q.random = soluTable.get(ran_index); } i++; p = p.next; } return solu.next; }
142 LinkedListCycleII (找出循环链表的头部)
http://www.cnblogs.com/gnivor/articles/4662591.html
155 Min Stack (最小栈问题)
解法1:http://www.cnblogs.com/gnivor/articles/4605764.html (空间复杂度O(n))
解法2:http://m.blog.csdn.net/blog/yangcs2009/38414229 (空间复杂度O(1))
下面说一种具有常数空间复杂度的方法: 在这个方法里,只需要额外开一个用于存放当前最小值的变量min即可.因此下面提到的push和pop操作都是对于题目中要求的栈来操作的,当然,这也是这个算法里唯一的栈. 设push的参数为v_push,pop的返回值为v_pop. 先说下整体思路:因为栈中所有元素的值都不会小于当其为栈顶元素时min函数的值,所以在栈中其实只需要保存某元素比相应最小值大出来的值就可以了.而对于最小值更新的位置,栈元素肯定为0,因此可以利用这个位置来保存更多的信息,在这里是更新后前两个最小值的差值,而这个值肯定是非正的. 根据上面的思路,push函数按照如下策略进行: 首先push (v_push-min),如果v_push < min,更新min为v_push. 相应的,pop函数按照如下策略进行(称栈顶元素为top): 如果top >= 0, v_pop = min+top, 如果top < 0, v_pop = min,然后更新min为min-top. 显然,对于min函数来说,只需要返回min空间的内容即可. 与张霄学长交流后,学长也讲了一个类似的方法: push时候 如果 v_push >= min, v_push 直接入栈, 如果 v_push < min, 那么入栈的是 2 * v_push - min, 然后 min = v_push. 出栈时, 如果栈顶的top >= min 直接出,如果 top < min 则出现异常,将min作为pop的返回值,另外需要还原前一个最小值,方法是 min = 2 * min - top 其实这两种方法在思路上是完全一样的,只是学长提供的方法里,栈中所有元素都比我的方法里大v_push.不过,这种变化直接带来的好处是,在不更新最小值的情况下,压栈值和出栈值都不需要额外的计算,在高级语言层面上,一次加减法运算比单纯的赋值至少多了一次访存操作和一次alu的运算,这样估计来我的方法耗时会是学长方法的2倍左右,虽然时间复杂度都是一样的.
代码(没通过)
class MinStack { Stack<Integer> stack = new Stack<Integer>(); int min; public void push(int x) { if (stack.empty()) { stack.add(x); min = x; } else { if (x < min) { stack.add(x - min); min = x; } else { stack.add(x); } } } public void pop() { if (stack.peek() < min) { min = min - stack.peek(); } stack.pop(); } public int top() { int temp; if (stack.peek() < min) { temp = min; } else temp = stack.peek(); return temp; } public int getMin() { return min; } }
200 Number of Islands(一个由0,1组成的二维矩阵,1代表陆地,求整个矩阵中有的岛的个数)
解法:DFS、BFS。只要遍历一遍,碰到一个1,就把它周围所有相连的1都标记为非1,这样整个遍历过程中碰到的1的个数就是所求解。
public class Solution { //DFS、BFS。只要遍历一遍,碰到一个1,就把它周围所有相连的1都标记为非1, //这样整个遍历过程中碰到的1的个数就是所求解。 private int m, n; public int numIslands(char[][] grid) { m = grid.length; if(m<=0) return 0; n = grid[0].length; if(n<=0) return 0; int solu = 0; for(int i = 0 ; i < m ; i++){ for(int j = 0 ; j < n ; j++){ if(grid[i][j]=='1'){ solu++; dfs(grid,i,j); } } } return solu; } public void dfs(char[][] grid, int i , int j ){ if(i<0||i>=m||j<0||j>=n) return; if(grid[i][j]=='1'){ grid[i][j]='2'; dfs(grid,i-1,j); dfs(grid,i+1,j); dfs(grid,i,j-1); dfs(grid,i,j+1); } } }
209 Minimum Size Subarray Sum
220 Contains Duplicate III (给一数组,寻找是否存这样的i和j,使得nums[i],nums[j]最大差了t,并且i和j最大差了k)
解法:利用二叉搜索树(java可使用TreeSet,其中set.subSet(a,b)方法用于返回a和b之间的集合)
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) { int len = nums.length; if(k<1 || t<0 || nums==null || len<2) return false; SortedSet<Long> set = new TreeSet<Long>(); for(int i = 0 ; i < len ; i++){ if(!set.subSet((long)nums[i]-t,(long)nums[i]+t+1).isEmpty()) return true; if(i>=k){ set.remove((long)nums[i-k]); } set.add((long)nums[i]); } return false; }
235 Lowest Common Ancestor of a Binary Search Tree(二叉搜索树的性质)
对于二叉搜索树的每一个节点,左侧节点的值比根节点的值小,右侧节点的值比根节点的值大。
因此,如果两个节点一个比根节点值大,一个比根节点值小,那么这两个节点必定在根节点的两侧。
http://blog.csdn.net/xudli/article/details/46838747
238 Product of Array Except Self
新建两个数组,搜索两遍。第一遍从前到后,第i个元素存放前i-1个数字的乘积。 第二遍从后向前,倒数第i个元素存放后i-1个数字的乘积
这样结果数组中每个元素就是两个数组对应数字相乘。
public static int[] productExceptSelf(int[] nums) { int len = nums.length; int[] arr1 = new int[len]; int[] arr2 = new int[len]; arr1[0] = 1; arr2[len-1] = 1; for(int i = 1 ;i < len ; i++){ arr1[i]=arr1[i-1] * nums[i-1]; //第i个元素代表前i-1个数字的乘积 arr2[len-1-i]=arr2[len-i] * nums[len-i]; } int[] solu = new int[len]; for(int i = 0 ; i < len ; i++){ solu[i]= arr1[i]*arr2[i]; } return solu; }
239 Sliding Window Maximum
问题:给定一数组nums[],滑动窗口大小为k,寻找滑动窗口中的最大值
解法:利用双端队列,双端队列中存放的nums的下标,下标对应的数字始终是递减的。
http://m.blog.csdn.net/blog/DERRANTCM/46872411
http://blog.csdn.net/suwei19870312/article/details/9296423
public class Solution { public int[] maxSlidingWindow(int[] nums, int k) { ArrayList<Integer> solu = new ArrayList<Integer>(); if(nums==null || nums.length==0 || k<1) return new int[0]; //注意 这个双端队列存放的是下标 Deque<Integer> queue = new LinkedList<Integer>(); //窗口还没有填满,找最大值索引 for(int i = 0 ; i < k && i < nums.length ; i++){ while(!queue.isEmpty() && nums[i]>nums[queue.getLast()]){ queue.removeLast(); } //添加索引 queue.addLast(i); } //窗口已经填满 for(int i = k ; i < nums.length ; i++){ //保存第一个窗口的最大值 solu.add(nums[queue.getFirst()]); while(!queue.isEmpty()&&nums[i]>nums[queue.getLast()]){ queue.removeLast(); } //数量超过窗口限制,删除最开始的 if(!queue.isEmpty() && queue.getFirst()<=( i-k)) queue.pollFirst(); //新的数加入窗口,这时候加入能保证窗口内的下标对应数字是递减的 queue.add(i); } solu.add(nums[queue.getFirst()]); int[] solu_arr = new int[solu.size()]; for(int i = 0 ; i< solu.size() ; i++){ solu_arr[i] = solu.get(i); } return solu_arr; } }
241 Different Ways to Add Parentheses
给出一串数字和运算符号组成的式子,给出所有组合的结果。例:
Input: "2*3-4*5" (2*(3-(4*5))) = -34 ((2*3)-(4*5)) = -14 ((2*(3-4))*5) = -10 (2*((3-4)*5)) = -10 (((2*3)-4)*5) = 10
Output: [-34, -14, -10, -10, 10]
要注意的问题:
1. 数字的长度,因为式子是用字符串存储的,如何区分一个数字
2. 计算顺序, (2*3)-(4*5) 先算2*3 再算 4*5然后相减,和先算 4*5再算2*3 然后相减是一样的。
方法:递归。 在找到一个运算符后,先计算左侧所有可能的结果,后计算右侧所有可能的结果,然后双层循环将左右两侧所有可能结果两两组合。
public static List<Integer> diffWaysToCompute(String input) { List<Integer> ret = new LinkedList<Integer>(); for (int i=0; i<input.length(); i++) { if (input.charAt(i) == '-' || input.charAt(i) == '*' || input.charAt(i) == '+' ) { String part1 = input.substring(0, i); String part2 = input.substring(i+1); List<Integer> part1Ret = diffWaysToCompute(part1); List<Integer> part2Ret = diffWaysToCompute(part2); for (Integer p1 : part1Ret) { for (Integer p2 : part2Ret) { int c = 0; switch (input.charAt(i)) { case '+': c = p1+p2; break; case '-': c = p1-p2; break; case '*': c = p1*p2; break; } ret.add(c); } } } } if (ret.size() == 0) { ret.add(Integer.valueOf(input)); } return ret; }
300.Longest Increasing Subsequence(求最长递增子序列的长度)
例如: [10, 9, 2, 5, 3, 7, 101, 18] 的最长递增子序列为2 3 7 18(不唯一),长度为4
方法1:动态规划。从后向前求以当前元素为开头的最长递增子序列长度,时间复杂度O(n^2)
//时间复杂度O(n^2) public static int lengthOfLIS(int[] nums) { int len = nums.length; if(len==0) return 0; int[] sarr = new int[len]; sarr[len-1] = 1; for(int i = len-2 ; i>=0 ; i--){ sarr[i] = 1; for(int j = i+1 ; j < len ; j++){ if(nums[i]<nums[j]){ sarr[i] = Math.max(sarr[i], sarr[j]+1); } } } int solu = 0; for(int i = 0; i < len ; i++){ if(sarr[i]>solu) solu = sarr[i]; } return solu; }
方法2:动态规划。dp[i]表示长度为i+1的递增子数组的最后一个元素的最小值,时间复杂度O(nlog(n)) 解析:LINK
//时间复杂度O(nlog(n)) //dp[i]表示长度为i+1的递增子数组的最后一个元素的最小值 public static int lengthOfLIS2(int[] nums) { ArrayList<Integer> dp = new ArrayList<>(nums.length); //注:dp内的元素是有序的 for (int num : nums) { //如果当前元素大于dp内最后一个元素,则直接添加到后面 if (dp.size() == 0 || dp.get(dp.size() - 1) < num){ dp.add(num); } else { //确定元素要覆盖的位置 int i = Collections.binarySearch(dp, num); if(i<0){ //i<0 说明dp内没有这个元素 i = -(i+1); //-(i+1)为插入位置 dp.set(i, num); //后面的值比前面小,应该替换,将值覆盖 } } System.out.println(dp); } return dp.size(); //注意!!dp内保存的不是最终的递增数组 }
315.Count of Smaller Numbers After Self
给出 nums = [5, 2, 6, 1],求每个元素右侧有几个数比该元素小。本例结果返回[2, 1, 1, 0].
方法1:首先,nums排序,保存为tmp。然后给出每个元素从小到大排序的下标,保存为数组tmp。tmp[0]就是nums[0]在nums数组排序之后所在的位置,即nums[0]=2。然后从右边开始计算每一位的结果。详细见注释
public List<Integer> countSmaller(int[] nums) { int len = nums.length; int[] tmp = nums.clone(); int[] i_nums = new int[len]; //保存nums每一个元素在排序后数组中的下标 Arrays.sort(tmp); for (int i = 0; i < len; i++) { i_nums[i] = Arrays.binarySearch(tmp, nums[i]); } int[] bit = new int[len]; //保存数字的结果 Integer[] ans = new Integer[len]; //保存最终结果 for (int i = len - 1; i >= 0; i--) { ans[i] = bit[i_nums[i]]; //从最右边开始计算结果 for(int j = i_nums[i]+1 ; j < bit.length ; j++){ bit[j] ++ ; //j从nums[i]在排序后下下标位置后一个开始,bit[j]++, //因为排序后下标在右侧的数字一定比在左侧的数字大。 //注意:该位置右边的都要增加1(已经计算完了的再增加没有影响,因为结果已经保存了) } } return Arrays.asList(ans); }
方法2:更新bit数组和求和进行改进,使用树状数组,其他原理与上面一致
private void add(int[] bit, int i, int val) { for (; i < bit.length; i += i & -i) bit[i] += val; } private int query(int[] bit, int i) { int ans = 0; for (; i > 0; i -= i & -i) ans += bit[i]; return ans; } public List<Integer> countSmaller(int[] nums) { int[] tmp = nums.clone(); Arrays.sort(tmp); for (int i = 0; i < nums.length; i++) nums[i] = Arrays.binarySearch(tmp, nums[i]) + 1; int[] bit = new int[nums.length + 1]; Integer[] ans = new Integer[nums.length]; for (int i = nums.length - 1; i >= 0; i--) { ans[i] = query(bit, nums[i] - 1); add(bit, nums[i], 1); } return Arrays.asList(ans); }
316. Remove Duplicate Letters
移除一个字符串的所有重复字符,只保留一个,且剩余字符的相对顺序不变,并且剩余字符的字典序为最小。
方法:贪心法
package com316.RemoveDuplicateLetters; //贪心法 public class Solution { public static void main(String args[]){ Solution s = new Solution(); System.out.println(s.removeDuplicateLetters("cbacdcbc")); } public String removeDuplicateLetters(String s) { int[] cnt = new int[26]; int pos = 0; // 最小字符的位置 //计算每一个数字的数量 for (int i = 0; i < s.length(); i++) cnt[s.charAt(i) - 'a']++; //从前向后遍历字符串的每一个字符,每遍历一个字符,就将对应的数量减1, //如果当前遍历的字符小于最小的字符,那么更新最小字符为当前的字符,保存这个字符的位置。 //直到某个字符的数量为0,停止操作,之前保存的那个最小字符就是头部, //然后删除最小字符位置后面的所有与最小字符相同的字符,(相同字符只能出现一次) //最后对后面的字符递归进行上面的操作 for (int i = 0; i < s.length(); i++) { char ch = s.charAt(i); if (ch < s.charAt(pos)) pos = i; if (--cnt[ch - 'a'] == 0) break; } if(s.length() == 0) return ""; else return s.charAt(pos) + removeDuplicateLetters( s.substring(pos + 1).replaceAll("" + s.charAt(pos), "")); } } /*Given the string s, the greedy choice (i.e., the leftmost letter in the answer) is the smallest s[i], s.t. the suffix s[i .. ] contains all the unique letters. (Note that, when there are more than one smallest s[i]'s, we choose the leftmost one. Why? Simply consider the example: "abcacb".) * After determining the greedy choice s[i], we get a new string s' from s by removing all letters to the left of s[i], removing all s[i]'s from s. We then recursively solve the problem w.r.t. s'. The runtime is O(26 * n) = O(n). * */
322. Coin Change
给一组硬币coins={1,2,5},和一个总面值amount=11,求将硬币凑成总面值所需要硬币的最小数量(每一种硬币不限数量),本例为1,5,5,总共3个
方法:动态规划,分别求从面值1开始到面值amount所需要的最小硬币数量(不能组成的就为Integer.MAX_VALUE)
//采用动态规划,分别求出从1开始到amount的和所使用的coins的数量 public int coinChange(int[] coins, int amount) { int[] dp = new int[amount+1]; for(int i = 1 ; i <= amount ; i++){ dp[i] = Integer.MAX_VALUE; } for(int i = 1 ; i <= amount ; i++){ for(int j = 0 ; j < coins.length ; j++){ if(coins[j] <= i){ int temp = dp[i-coins[j]]; if(temp!= Integer.MAX_VALUE && temp < dp[i]){ dp[i] = temp +1; } } } } if(dp[amount] == Integer.MAX_VALUE) return -1; return dp[amount]; }

浙公网安备 33010602011771号