f(n) = f(n-2) + f(n-1)
上面是一道编程题的原型,菲波拉契数列;往往,我们需要求解函数f(n)的结果。
一般有以下几种解法:
* 递归算法分;即通过递归调用进行计算,但是这种方法计算了过多的重复值,因而效率低下
* 记忆搜索算法:算法思路还是采用递归思想。不同的是,为了解决重复计算的问题,引入了一个记忆数组array[n],用来记录之前已经计算的结果,防止重复计算。
* 动态规划算法:以上的思想其实就是动态规划算法,即当前结果依赖之前的结果。我们这里指的动态规划是指优化后的规划算法。通过分析之前的重复计算原因,我们发现,罪魁祸首在于递归算法。递归的分录调用导致了各干各事,没有共享数据。因而,我们倒换个思路,如果知道最初的起点,直接从起点出发,向后遍历,这样的复杂的其实就是元素的个数。
如上:如果一次求出f(1)f(2)f(3)…,相当于一个1-n的遍历,复杂度为O(n)
常见的动态规划题:
- 一维动态规划:上梯子(一次1、2步)-菲波拉契数列
- 二维动态规划:二维图,最短路径,路径个数,成本等
动态规划的几个典型题:
- 一维问题:上台阶、费布拉奇数列等函数-最大递增子序列长度(dp[i]=max{dp[j]+1(0<=j<i,arr[j]<arr[i])})
- 二维问题:
- 求左上到左下的最短路径,考虑成本、障碍我等等
- 比较字符串是否相似度(可以替换,删除)
- 字符串相似度可以进一步应用在序列比较,基因序列,语音序列等
参考:直通BAT算法精讲
牛客网-leetcode编程-动态规划17道
package com.code;
import java.util.*;
public class Solution {
public static void main(String[] args) {
//int[][] arr = {
// {2},
// {3, 4},
// {6, 5, 7},
// {4, 1, 8, 3}
//};
int[][] arr = {
{1},
{2, 3}
};
ArrayList<ArrayList<Integer>> triangle = new ArrayList<>();
for (int i = 0; i < arr.length; i++) {
ArrayList<Integer> columnList = new ArrayList<Integer>();
for (int j = 0; j < arr[i].length; j++) {
columnList.add(j, arr[i][j]);
}
triangle.add(i, columnList);
}
int[] num = {1, 9, 8, 3, -1, 0};
int[][] grid = {{1, 2}, {3, 4}};
int[][] grid2 = {{0,0,0},{0,1,0}, {0,0,0}};
System.out.println(new Solution().uniquePaths(2,2));
}
/**场景1:求最短路径问题,成本问题
* unique-paths-二维矩阵求所有的道路nums[i][j]=nums[i-1][j]+nums[i][j-1];
* @param m
* @param n
* @return
*/
public int uniquePaths(int m, int n) {
if(m==0||n==0){
return 0;
}
int[][] nums = new int[m][n];
nums[0][0]=1;
for(int i=1;i<m;i++){
nums[i][0]=nums[i-1][0];
}
for(int i=1;i<n;i++){
nums[0][i]=nums[0][i-1];
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
nums[i][j]=nums[i-1][j]+nums[i][j-1];
}
}
return nums[m-1][n-1];
}
/**
* unique-paths-ii-思路同上:但是加了障碍物判断
* @param obstacleGrid
* @return
*/
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
if (obstacleGrid == null) {
return 0;
}
obstacleGrid[0][0] = obstacleGrid[0][0] == 0 ? 1 : 0;
for (int i = 1; i < obstacleGrid.length; i++) {
if (obstacleGrid[i - 1][0] > 0 && obstacleGrid[i][0] == 0) {
obstacleGrid[i][0] = 1;
} else {
obstacleGrid[i][0] = 0;
}
}
for (int j = 1; j < obstacleGrid[0].length; j++) {
if (obstacleGrid[0][j - 1] > 0 && obstacleGrid[0][j] == 0) {
obstacleGrid[0][j] = 1;
} else {
obstacleGrid[0][j] = 0;
}
}
for (int i = 1; i < obstacleGrid.length; i++) {
for (int j = 1; j < obstacleGrid[0].length; j++) {
if (obstacleGrid[i][j] == 0) {
if (obstacleGrid[i][j - 1] > 0) {
obstacleGrid[i][j] += obstacleGrid[i][j - 1];
}
if (obstacleGrid[i - 1][j] > 0) {
obstacleGrid[i][j] += obstacleGrid[i - 1][j];
}
} else {
obstacleGrid[i][j]=0;
}
}
}
return obstacleGrid[obstacleGrid.length - 1][obstacleGrid[0].length - 1];
}
/**
* minimum-path-sum-二维矩阵求所有的道路
* @param grid
* @return
*/
public int minPathSum(int[][] grid) {
if (grid == null) {
return 0;
}
for (int i = 1; i < grid.length; i++) {
grid[i][0] += grid[i - 1][0];
}
for (int j = 1; j < grid[0].length; j++) {
grid[0][j] += grid[0][j - 1];
}
for (int i = 1; i < grid.length; i++) {
for (int j = 1; j < grid[0].length; j++) {
grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]);
}
}
return grid[grid.length - 1][grid[0].length - 1];
}
/**场景2:求序列选择的所有情况
* climbing-stairs-一维数组-爬梯子
* 也可以当做排列组合来做
* @param n
* @return
*/
public int climbStairs(int n) {
int[] nums = new int[n + 2];
nums[0] = 0;
nums[1] = 1;
int i = 2;
while (i <= n + 1) {
nums[i] = nums[i - 2] + nums[i - 1];
i++;
}
return nums[n + 1];
}
/** 场景:3:序列相似度问题
* edit-distance-二维矩阵-比较字符串的距离-一般用于基因序列比较
* 这里的意思是可以替换,删除,字符
* 在基因序列对比中,会加入比较权重,删除、替换、插入代价有所不同,比较序列相似度。也可以比较字符串相似度。
* 各种序列相似度比较问题
* @param word1
* @param word2
* @return
*/
public int minDistance(String word1, String word2) {
if (word1 == null && word2 == null) {
return 0;
}
if (word1 == null) {
return word2.length();
}
if (word2 == null) {
return word1.length();
}
int[][] nums = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0; i <= word1.length(); i++) {
nums[i][0] = i;
}
for (int i = 0; i <= word2.length(); i++) {
nums[0][i] = i;
}
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
nums[i][j] = nums[i - 1][j - 1];
} else {
nums[i][j] = Math.min(Math.min(nums[i][j - 1], nums[i - 1][j]), nums[i - 1][j - 1]) + 1;
}
}
}
return nums[word1.length()][word2.length()];
}
/**
* subsets-求字符串的所有子串,深度递归+字符串复制
* @param S
* @return
*/
public ArrayList<ArrayList<Integer>> subsets(int[] S) {
ArrayList<ArrayList<Integer>> rst = new ArrayList<>();
if (S == null) {
return rst;
}
if (S.length == 0) {
rst.add(new ArrayList<Integer>());
return rst;
}
Arrays.sort(S);
ArrayList<Integer> one = new ArrayList<>();
find(S, 0, one, rst);
// 加入比较主要是为了输出顺序符合要求
rst.sort(new Comparator<ArrayList<Integer>>() {
@Override
public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
if (o1.size() > o2.size()) {
return 1;
} else if (o1.size() < o2.size()) {
return -1;
} else {
for (int i = 0; i < o1.size(); i++) {
if (o1.get(i) > o2.get(i)) {
return 1;
} else if (o1.get(i) < o2.get(i)) {
return -1;
} else {
continue;
}
}
}
return 0;
}
});
return rst;
}
/**
* scramble-string-树结构是否对称旋转-这里用了记忆搜索算法,并不是动态规划优化解
* 同样的题由子树的查找,对称旋转
* @param s1
* @param s2
* @return
*/
public boolean isScramble(String s1, String s2) {
if (s1 == null || s2 == null || s1.length() != s2.length()) {
return false;
}
if (s1.equals(s2)) {
return true;
}
//
int[] letters = new int[26];
for (int i = 0; i < s1.length(); i++) {
letters[s1.charAt(i) - 'a']++;
letters[s2.charAt(i) - 'a']--;
}
for (int i = 0; i < letters.length; i++) {
if (letters[i] != 0) {
return false;
}
}
// 前面这些都是为了降低复杂度,可以不用
for (int i = 1; i < s1.length(); i++) {
if (isScramble(s1.substring(0, i), s2.substring(0, i))
&& isScramble(s1.substring(i), s2.substring(i))) {
return true;
}
if (isScramble(s1.substring(0, i), s2.substring(s2.length() - i))
&& isScramble(s1.substring(i), s2.substring(0, s2.length() - 1))) {
return true;
}
}
return false;
}
/**
* gray-code-所有的排列
* @param n
* @return
*/
public ArrayList<Integer> grayCode(int n) {
ArrayList<Integer> list = new ArrayList<>();
int start = 0;
if (n == 0) {
list.add(0);
return list;
}
addList(list, start, 0, n, true);
addList(list, start, 1, n, true);
return list;
}
private void addList(ArrayList<Integer> list, int start, int i, int n, boolean flag) {
int newStart = (start << 1) + i;
if (n == 1) {
list.add(newStart);
return;
}
// 为了符合题目输出顺序
if ((i == 0 && flag) || (i == 1 && !flag)) {
addList(list, newStart, 0, n - 1, true);
addList(list, newStart, 1, n - 1, true);
} else {
addList(list, newStart, 1, n - 1, false);
addList(list, newStart, 0, n - 1, false);
}
}
/**场景4:加密解密,将数字解密为字符,根据制定规则
* decode-ways-将数字编码为a-z的字符表示,数字可能是一位或者两位
* 一维计数
* @param s
* @return
*/
public int numDecodings(String s) {
if (s == null) {
return 0;
}
if (s.length() == 0) {
return 0;
}
int len = s.length();
int[] nums = new int[len + 1];
nums[0] = 0;
for (int i = 1; i <= len; i++) {
if (i == 1) {
if (s.charAt(i - 1) == '0') {
return 0;
} else {
nums[i] = 1;
}
continue;
}
char ch1 = s.charAt(i - 2);
char ch2 = s.charAt(i - 1);
if ((ch1 == '0' && ch2 == '0') || (ch2 == '0' && ch1 > '2')) {
return 0;
}
if (inArr(ch1, ch2)) {
nums[i] = nums[i - 2] == 0 ? 1 : nums[i - 2];
}
if (ch2 == '0') {
continue;
}
nums[i] += nums[i - 1];
}
return nums[len];
}
private boolean inArr(char ch1, char ch2) {
if (ch1 > '0' && ch1 < '2') {
if (ch2 >= '0' && ch2 <= '9') {
return true;
} else {
return false;
}
} else if (ch1 == '2') {
if (ch2 >= '0' && ch2 <= '6') {
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* subsets-ii-同上i:
* @param num
* @return
*/
public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
ArrayList<ArrayList<Integer>> rst = new ArrayList<>();
if (num == null) {
return rst;
}
if (num.length == 0) {
rst.add(new ArrayList<Integer>());
return rst;
}
Arrays.sort(num);
ArrayList<Integer> one = new ArrayList<>();
find(num, 0, one, rst);
return rst;
}
private void find(int[] num, int index, ArrayList<Integer> one, ArrayList<ArrayList<Integer>> rst) {
rst.add(new ArrayList<Integer>(one));
for (int i = index; i < num.length; i++) {
if (i != index && num[i] == num[i - 1]) {
continue;
}
one.add(num[i]);
find(num, i + 1, one, rst);
one.remove(one.size() - 1);
}
}
/**
* interleaving-string-将两个字符串组合成对应的字符串
* 原理相当于路径排列,只是这里直接进行判断;数组内部使用boolean型
* 同样在基因问题中,求解基因序列变异的可能性
* @param s1
* @param s2
* @param s3
* @return
*/
public boolean isInterleave(String s1, String s2, String s3) {
if (s1.length() + s2.length() != s3.length()) {
return false;
}
boolean[][] array = new boolean[s1.length() + 1][s2.length() + 1];
array[0][0] = true;
for (int i = 1; i <= s1.length(); i++) {
if (array[i - 1][0] && s1.charAt(i-1) == s3.charAt(i-1)) {
array[i][0] = true;
}
}
for (int i = 1; i <= s2.length(); i++) {
if (array[0][i - 1] && s2.charAt(i-1) == s3.charAt(i-1)) {
array[0][i] = true;
}
}
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
if ((array[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1)
|| (array[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)))) {
array[i][j] = true;
}
}
}
return array[s1.length()][s2.length()];
}
/**
* distinct-subsequences-判断是否是一个字符串的子串
* 判断基因是否由某个基因变异而来
* @param S
* @param T
* @return
*/
public int numDistinct(String S, String T) {
if(S==null || T==null){
return 0;
}
int[][] arr = new int[S.length()+1][T.length()+1];
for(int i=0;i<S.length();i++){
arr[i][0]=1;
}
for(int i=1;i<=S.length();i++){
for(int j=1;j<=T.length();j++){
arr[i][j]=arr[i-1][j];
if(S.charAt(i-1)==T.charAt(j-1)){
arr[i][j]+=arr[i-1][j-1];
}
}
}
return arr[S.length()][T.length()];
}
public ArrayList<ArrayList<Integer>> generate(int numRows) {
ArrayList<ArrayList<Integer>> rs = new ArrayList<>();
for (int i = 0; i < numRows; i++) {
rs.add(getRow(i));
}
return rs;
}
public ArrayList<Integer> getRow(int rowIndex) {
ArrayList<Integer> list = new ArrayList<>();
int[] arr = new int[rowIndex + 1];
arr[0] = 1;
for (int i = 1; i <= rowIndex; i++) {
for (int j = i; j >= 1; j--) {
arr[j] += arr[j - 1];
}
}
for (int num : arr) {
list.add(num);
}
//int k=rowIndex;
//for(int i=0;i<=rowIndex;i++){
// list.add(find(k,i));
//}
return list;
}
private int find(int k, int i) {
if (i == 0 || i == k) {
return 1;
}
return find(k - 1, i - 1) + find(k - 1, i);
}
/**
* triangle-金字塔最小-递归算法,也可从下到上动态规划
* @param triangle
* @return
*/
public int minimumTotal(ArrayList<ArrayList<Integer>> triangle) {
int n = 0;
int left = find(triangle, n, 0);
return left;
}
private int find(ArrayList<ArrayList<Integer>> triangle, int row, int column) {
if (row<triangle.size()) {
if(row==triangle.size()-1){
return triangle.get(row).get(column);
}
int left = find(triangle, row + 1, column + 0);
int right = find(triangle, row + 1, column + 1);
int min = left < right ? left : right;
return triangle.get(row).get(column) + min;
} else {
return 0;
}
}
/**
* palindrome-partitioning-ii-白字符串切割成回文的字符串-再看
* @param s
* @return
*/
public int minCut(String s) {
if(s==null || s.length()==0) {
return 0;
}
int len = s.length();
int[] min = new int[len+1];
boolean[][] matrix = new boolean[len][len];
for(int i=0;i<len;i++) {
min[i]=len-i;
}
//dp过程
for (int i=len-1; i>=0; --i){
for (int j=i; j<len; ++j){
if ((s.charAt(i) == s.charAt(j) && (j-i<2))
|| (s.charAt(i) == s.charAt(j) && matrix[i+1][j-1]))
{
matrix[i][j] = true;
min[i] = getMinValue(min[i], min[j+1]+1);
}
}
}
return min[0]-1;
}
public int getMinValue(int a, int b){
return a > b ? b : a;
}
/**
* candy-分糖果
* 个子高的分的糖果比旁边的高,每个人至少一个糖
* @param ratings
* @return
*/
public int candy(int[] ratings) {
int len = ratings.length;
int[] array = new int[len];
// 1、初始化一个糖果
for (int i = 0; i < len; i++) {
array[i]=1;
}
// 2、从左到右,保证一个方向上数大的,糖果多
for (int i = 0; i < len-1; i++) {
if(ratings[i]<ratings[i+1]){
array[i+1] = array[i]+1;
}
}
// 3、从右到左,保证一个方向上数大的,糖果多
for (int i = len-1; i >0; i--) {
if(ratings[i]<ratings[i-1] && array[i]>=array[i-1]){
array[i-1] = array[i]+1;
}
}
int sum =0 ;
for (int i = 0; i < len; i++) {
sum+=array[i];
}
return sum;
}
/**
* word-break-字符串是否可以由字典中的字符串组合而成
* @param s
* @param dict
* @return
*/
public boolean wordBreak2(String s, Set<String> dict) {
if (s == null || s == "" || dict == null) {
return false;
}
int len = s.length();
// 用于记录i之前的字符串是否可分(不包括i)
boolean[] array = new boolean[len+1];
// 初始化第一个点
array[0] = true;
for (int i = 1; i <= len; i++) {
for (int j = 0; j < i; j++) {
if (array[j] && dict.contains(s.substring(j, i))) {
array[i] = true;
//如果i存在,直接跳出
break;
}
}
}
return array[len];
}
/**
* word-break-ii-字符串可以由字典中的字符串组合,求出所有的组合形式
* @param s
* @param dict
* @param map
* @return
*/
public ArrayList<String> wordBreak(String s, Set<String> dict) {
if (s == null || s == "" || dict == null) {
return null;
}
// 保存已存在子字符串组成,防止重复操作
Map<String, ArrayList<String>> map = new HashMap<>();
// 深度优先搜索
return dfs(s, dict, map);
}
private ArrayList<String> dfs(String s, Set<String> dict, Map<String, ArrayList<String>> map) {
// 判断map中是否存在当前字符串列表
if (map.containsKey(s)) {
return map.get(s);
}
int len = s.length();
ArrayList<String> list = new ArrayList<>();
if (len == 0) {
// len变为1
list.add("");
} else {
for (int i = len-1; i >=0; i--) {
String subStr = s.substring(i);
// 字典中不存在,返回
if (!dict.contains(subStr)) {
continue;
}
// 字典中存在
// 递归划分右边的子串
List<String> rightList = dfs(s.substring(0,i), dict, map);
// 如果右部不能组成,则跳过(注意:当时"",size==1)
if (rightList.size() == 0) {
continue;
}
// 合并(注释地方需要更具体以,从后向前输出)
for (String str : rightList) {
StringBuffer stringBuffer = new StringBuffer();
stringBuffer=new StringBuffer(subStr).append(stringBuffer);
// stringBuffer.append(subStr);
if (i != 0 && i != len) {
// 如果左右有空值,则不需要空格
stringBuffer=new StringBuffer(" ").append(stringBuffer);
// stringBuffer.append(" ");
}
stringBuffer=new StringBuffer(str).append(stringBuffer);
// stringBuffer.append(str);
list.add(stringBuffer.toString());
}
}
}
map.put(s,list);
return list;
}
}