------------------------------------------------
本笔记主要记录在刷九章题目中的思路、问题,以及不能bug-free的原因。
Time Complexity in Coding Interview
• O(1) 极少
• O(logn) 几乎都是二分法
• O(√n) 几乎是分解质因数
• O(n) 高频
• O(nlogn) 一般都可能要排序
• O(n 2 ) 数组,枚举,动态规划
• O(n 3 ) 数组,枚举,动态规划
• O(2 n ) 与组合有关的搜索
• O(n!) 与排列有关的搜索
------------------排列组合问题---------------------
- 适用范围:几乎所有搜索问题,但具体根据题目有改动(比如什么时候去除重复的)
- 特别注意的点:什么时候输出、那些情况跳过。
-
解决重复例子的方法:sort一下再判断下上次取的位置和这次的位置的数一不一样。if(i != pos && nums[i] == nums[i - 1]) continue;
有一些题目,是要求所有方案,这种题目90%是搜索问题,而这90的搜索问题中90%是用递归,另外10%是用非递归写递归。而这些搜索问题一般都是排列组合。
1.---------Permutaions
题意:找一个数组里元素里的全排列,数组中没有重复元素
思路:递归,在一个集合里找数组成排列,那么就需要一个set来记录当前集合里有没有这个数
一刷:WA:原因:没有考虑使用set。
二刷:AC
public class Solution { /* * @param nums: A list of integers. * @return: A list of permutations. */ public List<List<Integer>> permute(int[] nums) { // write your code here List<List<Integer>> res = new ArrayList<>(); List<Integer> permutation = new ArrayList<>(); if (nums.length == 0 || nums == null) { return res; } Set<Integer> set = new HashSet<>(); Helper(nums, res, permutation, set); return res; } private void Helper(int[] nums, List<List<Integer>> res, List<Integer> permutation, Set<Integer> set) { if (permutation.size() == nums.length) { res.add(new ArrayList<Integer>(permutation)); return; } for (int i = 0; i < nums.length; i++) { if (set.contains(nums[i])) continue; set.add(nums[i]); permutation.add(nums[i]); Helper(nums, res, permutation, set); set.remove(nums[i]); permutation.remove(permutation.size() - 1); } } }
2.---------SubSets ||
题意:找一个数组的全部子集,这个数组终有重复元素,但是答案里不能有重复的子集。
思路:递归。每次选取几个数,所以需要记录选取数的位置。
一刷:AC
public class Solution { /* * @param nums: A set of numbers. * @return: A list of lists. All valid subsets. */ public List<List<Integer>> subsetsWithDup(int[] nums) { // write your code here List<List<Integer>> res = new ArrayList<>(); List<Integer> set = new ArrayList<>(); if (nums.length == 0 || nums == null) { return res; } Arrays.sort(nums); Helper(res, set, nums, 0); return res; } private void Helper(List<List<Integer>> res, List<Integer> set, int[] nums, int pos) { res.add(new ArrayList<Integer>(set)); for (int i = pos; i < nums.length; i++) { if (i != pos && nums[i] == nums[i - 1]) { continue; } set.add(nums[i]); Helper(res, set, nums, i + 1); set.remove(set.size() - 1); } } }
3.----------Combination Sum ||
题意:就是给一串数C, 以及一个target T,从这串数中找到能使其和为给定的T的组合。combinations sum to T。在Combination Sum I 中允许可以重复选取每个数,但是在Combination Sum II里每个数只能选一次。就是每个组合里每个数只能选一次。
思路:仍然用回溯,需要注意的是怎么排除重复的,就是在每次向下传递的时候应该设定为 i + 1 的位置。
一刷:WA : 原因:模版不熟练;而且在传递的时候将 i + 1 写成了 pos + 1;
二刷:超时:参考了九章的代码才发现中间还需要一个判断,if (target < candidates[i]) break,这样就后面就可以不用再加入比较了,相当于一个剪枝优化。
class Solution { public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> results = new ArrayList<>(); if (candidates.length == 0 || candidates == null) return results; List<Integer> combination = new ArrayList<Integer>(); Arrays.sort(candidates); Helper(candidates, results, combination, target, 0); return results; } private void Helper(int[] candidates, List<List<Integer>> results, List<Integer> combination, int target, int pos) { if (target == 0) { results.add(new ArrayList<Integer>(combination)); return; } for (int i = pos; i < candidates.length; i++) { if (i != pos && candidates[i] == candidates[i - 1]) continue; if (target < candidates[i]) break; combination.add(candidates[i]); Helper(candidates, results, combination, target - candidates[i], i + 1); combination.remove(combination.size() - 1); } } }
4.----------Palindrome Partitioning (这题就是组合排列问题的一个很好的变形)
题意:将字符串分割为都是回文串的子串。
一刷:WA:原因:如果子串不是回文串时,应该继续本次循环,而不是退出循环,回溯。(因为也许可以继续划分到后面然后这个子串是回文,但是当前的不是,所以应该结束本次循环,继续划分)。 if (!isPalindrome(sub)) continue; 并且当次划分的子串开始位置应该是上次划分的位置,结束位置就是i + 1,所以 sub = s.substring(startIndex, i + 1)
二刷:AC
public class Solution { /* * @param s: A string * @return: A list of lists of string */ public List<List<String>> partition(String s) { // write your code here List<List<String>> res = new ArrayList<>(); List<String> partition = new ArrayList<>(); if (s.length() == 0 || s == null) { return res; } Helper(s, res, partition, 0); return res; } private void Helper (String s, List<List<String>> res, List<String> partition, int startIndex) { if (startIndex == s.length()) { res.add(new ArrayList<String>(partition)); return; } for (int i = startIndex; i < s.length(); i++) { String sub = s.substring(startIndex ,i + 1); if (!isPalindrome(sub)) continue; partition.add(sub); Helper(s, res, partition, i + 1); partition.remove(partition.size() - 1); } } private boolean isPalindrome(String s) { if (s.length() == 0) return true; int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } }
5.-----------------Permutation Sequence
题意:给n个数的第k个排列组合值。
一刷:用排列组合模版做,递归回溯超时,需要改成非递归。(这就是递归问题改成非递归)
二刷:参考被人的做法AC。涉及到一些数学知识。具体思路可见http://bangbingsyb.blogspot.com/2014/11/leetcode-permutation-sequence.html
class Solution { public String getPermutation(int n, int k) { StringBuilder sb = new StringBuilder(); List<Integer> nums = new ArrayList<>(); for (int i = 1; i <= n; i++) nums.add(i); int factor = 1; for (int i = 1; i < n; i++) { factor *= i; } k = k - 1; for (int i = 0; i < n; i++) { int index = k / factor; k = k % factor; sb.append(nums.get(index).toString()); nums.remove(index); if (i < n - 1) factor = factor / (n - 1 - i); } return sb.toString(); } }
------------------二分查找-----------------
- 循环结束的条件:start + 1 < end : 当循环进行到数组里只有两个数时,即当start与end相邻时。里面就不用加1和减1
- 使用 mid = start + (end - start) / 2 而不是 mid = (start + end) / 2,因为后者可能会导致溢出(细节)
- 然后比较A[mid] == , <, > : 如果是要找last position, 当A[mid] == target时,执行start = mid,而不是直接返回;如果找first position ,则要执行end = mid,且后面判断的时候先判断A[start]
- 结束循环最后还需比较A[start]和A[end]哪个是target,如果需要的是找最后一个出现的target,则需要先判断A[end];如果需要找第一个出现的target,则首先需要判断A[start]是不是target
- 如果问题用O(n)可以解决,但是面试官不满意,则要找O(logn)的方法
- 没有重复元素时:可以用二分法 : 怎样划分区间 (二分法实质上是对区间的划分,每次去掉一部分空间)
- 有重复元素:不能用二分法,因为有重复元素时实现的复杂度最坏就是O(n),证明:黑盒测试=> 假设前n-1次取出来的都是相同的,则不能得到任何有用的信息,第n次取到了最小的,才能知道最小的元素的位置。所以存在重复元素时的时间复杂度最小都是o(n),即不存在o(logn)的解法。
二分的三个境界:
• 第一境界 二分法模板
时间复杂度小练习
递归与非递归的权衡
二分的三大痛点
通用的二分法模板
• 第二境界:二分位置 之 圈圈叉叉 Binary Search on Index - OOXX
找到满足某个条件的第一个位置或者最后一个位置
• 第三境界:二分位置 之 保留一半 Binary Search on Index - Half half
保留有解的一半,或者去掉无解的一半(二分的核心思想,保证了logn的复杂度)
6.-------------- Search in Sorted Array
一刷:AC
class Solution { public int search(int[] nums, int target) { if (nums == null || nums.length == 0) { return -1; } int start = 0, end = nums.length - 1; int mid; while (start + 1 > end) { mid = start + (end - start) / 2; if (nums[mid] == target) { return mid; } else if (nums[mid] > nums[start]) { if (target >= nums[start] && target <= nums[mid]) { end = mid; } else { start = mid; } } else { if (target >= nums[mid] && target <= nums[end]) { start = mid; } else { end = mid; } } } if (nums[start] == target) { return start; } else if (nums[end] == target) { return end; } return -1; } }
---------补一道easy题,但是超容易出错!--------
leetcode 633. Sum of Square Numbers
Given a non-negative integer c
, your task is to decide whether there're two integers a
and b
such that a2 + b2 = c.
坑:需要将类型都设置为long!!!
class Solution { // Binary Search public boolean judgeSquareSum(int c) { for (long a = 0; a * a <= c; a++) { if (find(0, c - a * a)) return true; } return false; } private boolean find(long start, long end) { long target = end; while (start + 1 < end) { long mid = start + (end - start) / 2; if (mid * mid == target) return true; else if (mid * mid < target) start = mid; else end = mid; } if (start * start == target || end * end == target) return true; else return false; } }
7.-------------- Russian Doll Envelopes (二分的应用)
题意:就是像套俄罗斯娃娃那样套信封。 要求宽度与长度都大于另一个信封的才能套进另一个信封。
一刷:WA 完全不知道怎么用二分来解决。
二刷:原来这个就是 Longest Increasing Subsequence的二维形式。本来最长递增子序列这题是dp的解法,但是还可以二分来解决!(LIS的follow 就是需要O(nlogn)的复杂度)
思路:LIS:先建立一个空的dp数组,然后开始遍历原数组,对于每一个遍历到的数字,我们用二分查找法在dp数组找第一个不小于它的数字,如果这个数字不存在,那么直接在dp数组后面加上遍历到的数字,如果存在,则将这个数字更新为当前遍历到的数字,最后返回dp数字的长度即可。
class Solution { //version1 : dp public int lengthOfLIS(int[] nums) { if (nums.length == 0 || nums == null) return 0; int[] dp = new int[nums.length]; int res = 0; for (int i = 0; i < nums.length; i++) dp[i] = 1; for (int i = 0; i < nums.length; i++) { for (int j = 0; j < i; j++) { if (nums[j] < nums[i]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } res = Math.max(dp[i], res); } return res; } //version2 : binary search public int lengthOfLIS(int[] nums) { if (nums.length == 0 || nums == null) return 0; int[] dp = new int[nums.length]; int len = 0; for (int i = 0; i < nums.length; i++) { int index = Arrays.binarySearch(dp, 0, len, nums[i]); if (index < 0) index = -index - 1; dp[index] = nums[i]; if (index == len) len++; } return len; } }
有了这个,原题 Russian Doll Envelopes 的思路就清楚了:对信封的一个维度进行排序,对另一个维度计算LIS即可。
class Solution { public int maxEnvelopes(int[][] envelopes) { if (envelopes.length == 0 || envelopes == null) return 0; Arrays.sort(envelopes, new Comparator<int []>() { public int compare(int[] arr1, int[] arr2) { if (arr1[0] == arr2[0]) return arr2[1] - arr1[1]; else return arr1[0] - arr2[0]; } }); int[] dp = new int[envelopes.length]; int len = 0; for (int[] envelope : envelopes) { int index = Arrays.binarySearch(dp, 0, len, envelope[1]); if (index < 0) index = -index - 1; dp[index] = envelope[1]; if (index == len) len++; } return len; } }
总结:这两题原本是dp的题目,但是二分在这里起到了优化的作用。
- 2 Queues
- 1 Queue and dummy node : => 在每一层结尾的位置加入yi
- 1 Queue => 就是需要再取一下size,即当前队列中存在的元素个数,即这一层的个数)
while (!stack.isEmpty()) { TreeNode node = stack.pop(); visit(node); if (node.right != null) { //先压入右节点,即会后访问右节点 stack.push(node.right); } if (node.left != null) { stack.push(node.left); } }
public class Solution { /* * @param root: A Tree * @return: Inorder in ArrayList which contains node values. */ public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); Stack<TreeNode> stack = new Stack<TreeNode>(); if (root == null) { return res; } TreeNode cur = root; while (cur != null || !stack.isEmpty()) { while (cur != null) { stack.push(cur); cur = cur.left; } cur = stack.pop(); res.add(cur.val); cur = cur.right; } return res; } }
public class Solution { /* * @param root: A Tree * @return: Postorder in ArrayList which contains node values. */ public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); Stack<TreeNode> stack = new Stack<>(); if (root == null) { return res; } TreeNode cur = root; TreeNode pre = null; stack.push(root); while (!stack.isEmpty()) { cur = stack.peek(); //返回栈顶对象而不移除它 //如果上一次遍历的节点是空,或它的左儿子是当前节点,或它的右儿子是当前节点,那么应该继续遍历当前节点的左、右儿子 if (pre == null || pre.left == cur || pre.right == cur) { if (cur.left != null) { stack.push(cur.left); }else if (cur.right != null) { stack.push(cur.right); } } else if (cur.left == pre) { //如果上一次遍历的节点是当前节点的左儿子,那么这次应该遍历当前节点的右儿子 if (cur.right != null) { stack.push(cur.right); } } else { //如果上一次遍历的节点是当前节点的右儿子,则现在应该遍历根节点 res.add(cur.val); stack.pop(); } pre = cur; } return res; } }
一刷: WA,知道应该用分治,但是想不出具体算法。
二刷:AC。使用一个resultType类,来维护一个单路径,和最大路径。
public class Solution { /* * @param root: The root of binary tree. * @return: An integer */ private class ResultType { private int SinglePath; //从任意点为root向下走到任意点的最大路径,这条路可以不包含任意点 private int MaxPath; //树中从任意点到任意点的最大路径,这条路至少包含一个节点 ResultType(int s, int m) { SinglePath = s; MaxPath = m; } } private ResultType Helper(TreeNode root) { //分治法 if (root == null) { return new ResultType(0, Integer.MIN_VALUE); } //分 ResultType left = Helper(root.left); ResultType right = Helper(root.right); //治 int SinglePath = Math.max(left.SinglePath, right.SinglePath) + root.val; SinglePath = Math.max(SinglePath, 0); //如果这条路径的值为负数,则不包括任何一个节点 int MaxPath = Math.max(left.MaxPath, right.MaxPath); MaxPath = Math.max(MaxPath, left.SinglePath + right.SinglePath + root.val); return new ResultType(SinglePath, MaxPath); } public int maxPathSum(TreeNode root) { ResultType result = Helper(root); return result.MaxPath; } }
总结:树的题目,一般的解法有遍历(DFS、BFS)、分治。
- 实现可以带哈希表的递归或者循环。面试一般提倡使用循环。
- 自底向上的实现
- 自顶向下的实现
2.如何想到用动归:(两个条件都需满足)
- Maximum/Minimum;Yes/No;Count(*)
- 给你的数据不能更改位置。
- Matrix DP (10%) :
- Sequence DP(40%)
- Two Sequences DP (40%)
- Backpack (10%)
10.----------------Triangle (记忆化搜索)
一刷:AC。注意这题的实现方式有三种:带hash的递归、自底向上的循环、自顶向下的循环。
public class Solution { /* * @param triangle: a list of lists of integers * @return: An integer, minimum path sum */ //method1: recursive search,总耗时: 2566 ms // private int[][] minSum; // public int minimumTotal(int[][] triangle) { // // write your code here // if (triangle == null || triangle.length == 0) // return -1; // if (triangle[0] == null || triangle.length == 0) // return -1; // int n = triangle.length; // minSum = new int[n][n]; // for (int i = 0; i < n; i++) { // for (int j = 0; j < n; j++) { // minSum[i][j] = Integer.MAX_VALUE; // } // } // return Helper(triangle, 0, 0); // } // private int Helper(int[][] triangle, int x, int y) { // if (x >= triangle.length) return 0; // if (minSum[x][y] != Integer.MAX_VALUE) // return minSum[x][y]; // int leftSum = Helper(triangle, x + 1, y); // int rightSum = Helper(triangle, x + 1, y + 1); // minSum[x][y] = Math.min(leftSum, rightSum) + triangle[x][y]; // return minSum[x][y]; // } // //method2: bottom-up,总耗时: 2451 ms // public int minimumTotal(int[][] triangle) { // if (triangle == null || triangle.length == 0) // return -1; // if (triangle[0] == null || triangle.length == 0) // return -1; // int n = triangle.length; // int[][] dp = new int[n][n]; // for (int i = 0; i < n; i++) { // dp[n - 1][i] = triangle[n - 1][i]; // } // for (int i = n - 2; i >= 0; i--) { // for (int j = 0 ; j <= i; j++) { // dp[i][j] = Math.min(dp[i + 1][j + 1], dp[i + 1][j]) + triangle[i][j]; // } // } // return dp[0][0]; // } //method3: top-down public int minimumTotal(int[][] triangle) { if (triangle == null || triangle.length == 0) { return -1; } if (triangle[0] == null || triangle[0].length == 0) { return -1; } // state: f[x][y] = minimum path value from 0,0 to x,y int n = triangle.length; int[][] f = new int[n][n]; // initialize f[0][0] = triangle[0][0]; for (int i = 1; i < n; i++) { f[i][0] = f[i - 1][0] + triangle[i][0]; f[i][i] = f[i - 1][i - 1] + triangle[i][i]; } // top down for (int i = 1; i < n; i++) { for (int j = 1; j < i; j++) { f[i][j] = Math.min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j]; } } // answer int best = f[n - 1][0]; for (int i = 1; i < n; i++) { best = Math.min(best, f[n - 1][i]); } return best; } }
class Solution { public: /* * @param obstacleGrid: A list of lists of integers * @return: An integer */ int uniquePathsWithObstacles(vector<vector<int>> &obstacleGrid) { int m = obstacleGrid.size(); int n = obstacleGrid[0].size(); if (m == 0 || n == 0) return 0; int dp[101][101]; dp[0][0] = 1; for (int i = 1 ; i < m; i++) { if (obstacleGrid[i][0] == 1) dp[i][0] = 0; else dp[i][0] = 1; } for (int j = 1; j < n; j++) { if (obstacleGrid[0][j] == 1) dp[0][j] = 0; else dp[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { if (obstacleGrid[i][j] == 1) dp[i][j] = 0; else dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } } };
12.--------------Jamp Game || 跳跃游戏 (Sequence DP)
题意:最初位置在数组的第一个位置,数组中每个位置的值表示在那个位置能够跳跃的最大长度,求跳到最后一个位置的最少跳跃次数。
一刷:AC
class Solution { public: /* * @param A: A list of integers * @return: An integer */ int jump(vector<int> &A) { int n = A.size(); if (n == 0) return 0; int dp[n]; dp[0] = 0; for (int i = 1; i < n; i++) dp[i] = INT_MAX; for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { if (A[j] >= (i - j)) { dp[i] = min(dp[i], dp[j] + 1); } } } return dp[n - 1]; } };
-
-
- 注意这题有个初始化的技巧:有时候动归要设置dp[n]的数组,有时候要设置成dp[n + 1],为什么呢?还有将每个初始化为dp[i]=i-1:因为这道题目里我们的状态定义是dp[i]表示“前i个”字符组成的子串满足条件所需要的最少切分次数。所以最长的应该是dp[len],则矩阵长度应为dp[n + 1],那么dp[0]应该为什么呢?其实这里我们都可以初始化为最大的。前1个字符最多需要分割0次,前2个字符最多需要分割1次,即dp[i]=i-1,则dp[0]=-1;
- 注意这题有个初始化的技巧:有时候动归要设置dp[n]的数组,有时候要设置成dp[n + 1],为什么呢?还有将每个初始化为dp[i]=i-1:因为这道题目里我们的状态定义是dp[i]表示“前i个”字符组成的子串满足条件所需要的最少切分次数。所以最长的应该是dp[len],则矩阵长度应为dp[n + 1],那么dp[0]应该为什么呢?其实这里我们都可以初始化为最大的。前1个字符最多需要分割0次,前2个字符最多需要分割1次,即dp[i]=i-1,则dp[0]=-1;
-
class Solution {
public:
/**
* @param s a string
* @return an integer
*/
int minCut(string s) {
int n = s.length();
if (n == 0) return 0;
int dp[n + 1]; //dp[i]表示第i个位置的串最少的分割次数
bool isPalin[n][n]; //将判断是否是回文串的操作存储下来,就可以将时间复杂度从O(n3)降到O(n2)
//isPalin初始化
for (int i = 0; i < n; i++) {
isPalin[i][i] = true;
if (i + 1 < n) {
isPalin[i][i + 1] = (s[i] == s[i + 1]);
}
}
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 2; j < n; j++) {
isPalin[i][j] = isPalin[i + 1][j - 1] && (s[i] == s[j]);
}
}
dp[0] = -1;
for (int i = 1; i < s.length() + 1; i++) {
dp[i] = i - 1;
}
for (int i = 1; i < s.length() + 1; i++) {
for (int j = 0; j < i; j++) { //假设在j这里切一刀
if (isPalin[j][i - 1]) {
dp[i] = min(dp[i], dp[j] + 1);
}
}
}
return dp[n];
}
};
class Solution { public: /* * @param s: A string * @param dict: A dictionary of words dict * @return: A boolean */ bool wordBreak(string &s, unordered_set<string> &dict) { bool canSeg[s.length() + 1]; int maxWordLen = 0; for (auto it = dict.begin(); it != dict.end(); it++) { if ((*it).length() > maxWordLen) maxWordLen = (*it).length(); } canSeg[0] = true; for (int i = 1; i < s.length() + 1; i++) { canSeg[i] = false; for (int j = 0; j < i; j++) { if ((i - j) > maxWordLen) continue; if (!canSeg[j]) continue; string sub = s.substr(j, i - j); if (dict.find(sub) != dict.end()) { canSeg[i] = true; break; } } } return canSeg[s.length()]; } };
=>Tips: 动态规划的注意点:初始化要想清楚是什么;还有循环的边界是什么?需要在具体题目里自己体会。
class Solution { public: /* * @param nums: An integer array * @return: The length of LIS (longest increasing subsequence) */
int longestIncreasingSubsequence(vector<int> &nums) { int n = nums.size(); if (n == 0) return 0; int dp[nums.size()]; for (int i = 0; i < n; i++) { dp[i] = 1; } for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1); } } int max = 0; for (int i = 0; i < n; i++) { if (dp[i] > max) max = dp[i]; } return max; } };
16.-------------最长回文子序列
一刷:35 / 83 test cases passed.l 思路错了,这个回文串应该考虑[i,j]区间的串,而不是for i for j的思路。应该设dp[i][j]表示[i,j]区间内的最长子串,考虑下一步的状态转移:i 向左,j向右。则可知应该分s[i]==s[j]来考虑,详见代码:
class Solution { public: int longestPalindromeSubseq(string s) { int n = s.size(); vector<vector<int>> dp(n, vector<int>(n)); for (int i = n - 1; i >= 0; --i) { dp[i][i] = 1; for (int j = i + 1; j < n; ++j) { if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); } } } return dp[0][n - 1]; } };
-------Two Sequence问题-------
state: f[i][j]代表了第一个sequence的前i个数字 /字符 配上第二个sequence的前j个...
function: f[i][j] = 研究第i个和第j个的匹配关系 intialize: f[i][0] 和 f[0][i]
answer: f[s1.length()][s2.length()]
class Solution { public: /* * @param A: A string * @param B: A string * @return: The length of longest common subsequence of A and B */ int longestCommonSubsequence(string &A, string &B) { int n = A.size(); int m = B.size(); if (n == 0 || m == 0) return 0; int dp[m + 1][n + 1]; for (int i = 0; i < n; i++) dp[i][0] = 0; for (int j = 0; j < m; j++) dp[0][j] = 0; for (int i = 1; i < n + 1; i++) { for (int j = 1; j < m + 1; j++) { //注意这里A[i-1]表示的是第i个 if (A[i - 1] == B[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } return dp[n][m]; } };
class Solution { public: /** * @param A, B: Two string. * @return: the length of the longest common substring. */ int longestCommonSubstring(string &A, string &B) { // write your code here int res=0; if(A.length()==0 || B.length()==0) { return res; } int len1=A.length(); int len2=B.length(); int dp[len1+1][len2+1]; for(int i=0;i<=len1;i++){ dp[i][0]=0; } for(int j=0;j<=len2;j++){ dp[0][j]=0; } for(int i=1;i<=len1;i++){ for(int j=1;j<=len2;j++){ if(A[i-1]==B[j-1]){ dp[i][j]=dp[i-1][j-1]+1; }else{ dp[i][j]=0; } res=max(res,dp[i][j]); } } return res; } };
18.---------最小编辑距离
state: f[i][j]a的前i个字符“配上”b的前j个字符 最少要用几次编辑使得他们相等 function: f[i][j] = MIN(f[i-1][j-1], f[i-1][j]+1, f[i][j-1]+1) // a[i] == b [j] = MIN(f[i-1][j] + 1, f[i][j-1] + 1, f[i-1][j-1] + 1) // a[i] != b[j] intialize: f[i][0] = i, f[0][j] = j |
answer: f[a.length()][b.length()] |
class Solution { public: int minDistance(string word1, string word2) { int len1 = word1.length(); int len2 = word2.length(); if (len1 == 0) return len2; if (len2 == 0) return len1; int dp[len1 + 1][len2 + 1]; //initialize for (int i = 0; i <= len1; i++) dp[i][0] = i; for (int j = 0; j <= len2; j++) dp[0][j] = j; // for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = dp[i - 1][j - 1] + 1; dp[i][j] = min(dp[i][j], min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)); } } return dp[len1][len2]; } };
模版1):
n个整数a[1..n],装m的背包
state: f[i][j] “前i”个数,取出一些能否组成和为j
function: f[i][j] = f[i-1][j - a[i]] or f[i-1][j]
intialize: f[X][0] = true; f[0][1..m] = false
answer: 能够使得f[n][X]最大的X(0<=X<=m)
2)
n个物品,背包为m,体积a[1..n],价值v[1..n]
state: f[i][j]表示前i个物品中,取出“若干”物品
后,体积“正好”为j的最大价值。
function: f[i][j] = max{f[i-1][j], f[i-1][j - a[i]] +
v[i]}
intialize: f[X][0] = 0, f[0][1..m] =-oo
answer: f[n][1..m]中最大值
19.------------Backpack I II
1) 其实这种二维的可以简写为一维的,后面熟练了可以写成一维的,开始时写二维的体会思想
lass Solution { public: /** * @param m: An integer m denotes the size of a backpack * @param A: Given n items with size A[i] * @return: The maximum size */ int backPack(int m, vector<int> A) { // write your code here int n = A.size(); bool dp[n + 1][m + 1]; for (int i = 0; i <= n; i++) dp[i][0] = true; for (int j = 1; j <= m; j++) dp[0][j] = false; dp[0][0] = true; for (int i = 1; i <= n; i++) { for (int j = 0; j <= m; j++) { if (j >= A[i - 1]) { dp[i][j] = dp[i - 1][j] || dp[i - 1][j - A[i - 1]]; } else { dp[i][j] = dp[i - 1][j]; } } } for (int i = m; i >= 0; i--) { if (dp[n][i]) return i; } } };
2)
int backPackII(int m, vector<int> &A, vector<int> &V) { // write your code here //dp[i][j]表示前i个物品,取出若干个能够成体积正好为j的最大价值 int dp[A.size() + 1][m + 1]; for (int i = 0; i < A.size() + 1; i++) dp[i][0] = 0; dp[0][0] = 0; for (int j = 1; j < m + 1; j++) dp[0][j] = 0; for (int i = 1; i < A.size() + 1; i++) { for (int j = 0; j < m + 1; j++) { if (j >= A[i - 1]) { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - A[i - 1]] + V[i - 1]); } else { dp[i][j] = dp[i - 1][j]; } } } int res = INT_MIN; for (int i = 1; i <= m; i++) { if (dp[A.size()][i] > res) res = dp[A.size()][i]; } return res; }