1、跳台阶/楼梯
public class Solution { public int jumpFloor(int target) { int p = 1, q = 1; //和自下而上的方法使用相同的循环结构 for(int i = 2; i <= target; i++) { int temp = p; p = p + q; q = temp; } return p; } } |
2、最长公共子串
import java.util.*; public class Solution { /** * longest common substring * @param str1 string字符串 the string * @param str2 string字符串 the string * @return string字符串 */ public String LCS (String str1, String str2) { int len1 = str1.length(), len2 = str2.length(); int[][] dp = new int[len1 + 1][len2 + 1]; int max = 0; int index = 0; for(int i = 0; i < len1; i++) { for(int j = 0; j < len2; j++) { if(str1.charAt(i) == str2.charAt(j)) { dp[i + 1][j + 1] = dp[i][j] + 1; } if(dp[i + 1][j + 1] > max) { max = dp[i + 1][j + 1]; index = i; } } } return str1.substring(index - max + 1, index + 1); } } |
3、最小代价爬楼梯
思路:每个台阶对应一个cost体力值,目标是花费最低,dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]
class Solution { public int minCostClimbingStairs(int[] cost) { if (cost == null || cost.length == 0) { return 0; } if (cost.length == 1) { return cost[0]; } int[] dp = new int[cost.length]; dp[0] = cost[0]; dp[1] = cost[1]; for (int i = 2; i < cost.length; i++) { dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; } //最后一步,如果是由倒数第二步爬,则最后一步的体力花费可以不用算 return Math.min(dp[cost.length - 1], dp[cost.length - 2]); } } |
4、网格不同路径
方法1:DFS回溯
class Solution { private: int dfs(int i, int j, int m, int n) { if (i > m || j > n) return 0; // 越界了 if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点 return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n); } public: int uniquePaths(int m, int n) { return dfs(1, 1, m, n); } }; |
方法2:动态规划
/** * 1. 确定dp数组下表含义 dp[i][j] 到每一个坐标可能的路径种类 * 2. 递推公式 dp[i][j] = dp[i-1][j] dp[i][j-1] * 3. 初始化 dp[i][0]=1 dp[0][i]=1 初始化横竖就可 * 4. 遍历顺序 一行一行遍历 * 5. 推导结果 。。。。。。。。 * * @param m * @param n * @return */ public static int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; //初始化 for (int i = 0; i < m; i++) { dp[i][0] = 1; } for (int i = 0; i < n; i++) { dp[0][i] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i-1][j]+dp[i][j-1]; } } return dp[m-1][n-1]; } |
5、有障碍物不同路径
class Solution { public int uniquePathsWithObstacles(int[][] obstacleGrid) { int n = obstacleGrid.length, m = obstacleGrid[0].length; int[][] dp = new int[n][m]; dp[0][0] = 1 - obstacleGrid[0][0]; for (int i = 1; i < m; i++) { if (obstacleGrid[0][i] == 0 && dp[0][i - 1] == 1) { dp[0][i] = 1; } } for (int i = 1; i < n; i++) { if (obstacleGrid[i][0] == 0 && dp[i - 1][0] == 1) { dp[i][0] = 1; } } for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { if (obstacleGrid[i][j] == 1) continue; dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } } return dp[n - 1][m - 1]; } } |
6、斐波那契数列
方法1:带备忘录的递归(自顶向下)
class Solution { //初始化备忘录为0 int[] mome; public int fib(int n) { mome = new int[n+1]; if(n < 1) return 0; return comput(mome,n); } public int comput(int []mome,int n){ if(n == 1 || n == 2) return 1; if(mome[n]!=0) return mome[n]; mome[n] = comput(mome, n-1) + comput(mome, n-2); return mome[n]; } } |
方法2:自底向上的dp数组
class Solution { public int fib(int n) { if(n < 1) return 0; if(n == 1 || n ==2) return 1; int[] arr = new int[n+1]; //Java默认将数组初始化为0 arr[1] = arr[2] = 1; for(int i = 3; i <= n; i++){ arr[i] =arr[i-1] +arr[i-2]; } return arr[n]; } } |
方法3:状态压缩
class Solution { public int fib(int n) { if(n < 1) return 0; if(n==1 || n==2) return 1; int prev=1,curr=1,sum; for(int i=3;i<=n;i++){ sum = prev + curr; prev = curr; curr = sum; } return curr; } } |
7、凑零钱
含义:使用不同面值零钱凑出指定数值
思路:初始条件、状态、选择、dp数组
class Solution { public int coinChange(int[] coins, int amount) { if(amount < 0) return -1; int[] dp = new int[amount+1]; Arrays.fill(dp,amount+1); dp[0] = 0; for(int i = 0; i < dp.length; i++){ for(int coin:coins){ if(i -coin < 0) continue; dp[i] = Math.min(dp[i],1+dp[i-coin]); } } return dp[amount] == amount + 1 ? - 1 : dp[amount]; } } |
8、≤数的二进制中1的个数
思路:n个数,返回长度为n的数组
package com.iflytek; import java.util.Arrays; public class DP_bin_1 { public static void main(String[] args) { int[] count = testDp(5); //Arrays.asList(count).forEach(System.out::println); --只适用于对象类型的数组 Arrays.stream(count).forEach(System.out::println); } public static int[] testIntegerBitCount(int n){ int[] ans = new int[n+1]; for (int i = 0; i <= n; i ++){ ans[i] = Integer.bitCount(i); } return ans; } public static int[] testDp(int n){ int[] dp = new int[n+1]; // java默认赋初值 for (int i = 0; i < n/2; i++) { dp[2*i] = dp[i]; if (2*i + 1 < n){ dp[2*i+1] = dp[i] + 1; } } return dp; } } |
9、接雨水
思路:左右最小-当前值并累加
方法1:双指针
import java.util.*; public class Solution { /** * max water * @param arr int整型一维数组 the array * @return long长整型 */ //双指针 public long maxWater (int[] arr) { long res = 0; int left = 0, right = arr.length - 1; int leftMax = 0, rightMax = 0; while(left < right) { leftMax = Integer.max(leftMax, arr[left]); rightMax = Integer.max(rightMax, arr[right]); if(leftMax < rightMax) { res += leftMax - arr[left]; left++; } else { res += rightMax - arr[right]; right--; } } return res; } } |
方法2:动态规划
import java.util.*; public class Solution { /** * max water * @param arr int整型一维数组 the array * @return long长整型 */ //双指针 public long maxWater (int[] arr) { long res = 0; int n = arr.length; int[] leftMax = new int[n]; leftMax[0] = arr[0]; for(int i = 1; i < n; i++) { leftMax[i] = Math.max(leftMax[i - 1], arr[i]); } int[] rightMax = new int[n]; rightMax[n - 1] = arr[n - 1]; for(int i = n - 2; i >= 0; i--) { rightMax[i] = Math.max(rightMax[ i + 1], arr[i]); } for(int i = 0; i < n; i++) { res += Math.min(leftMax[i], rightMax[i]) - arr[i]; } return res; } } |
10、最大子序和
含义:连续子数组的最大和,可以使用贪心/动态规划解决
// 动态规划 class Solution { public int maxSubArray(int[] nums) { int fi = 0, maxRes = nums[0]; //初始化 for(int x : nums) { fi = Math.max(x, fi + x); maxRes = Math.max(maxRes, fi); } return maxRes; } } //贪心 class Solution { public int maxSubArray(int[] nums) { int maxRes = nums[0]; int sum = 0; for(int x : nums) { if(sum > 0) { sum += x; } else { sum = x; // 等于x而不等于0 } maxRes = Math.max(maxRes, sum); } return maxRes; } } |
11、最长上升子序列长度
思路:最长递增子序列(longest increasing subsequence),简称LIS,贪心/动态规划
方法1:贪心+二分
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 给定数组的最长严格上升子序列的长度。 * @param arr int整型一维数组 给定的数组 * @return int整型 */ public int LIS (int[] arr) { int len = 1, n = arr.length; if(n == 0) { return 0; } int[] lisArr = new int[n + 1]; lisArr[len] = arr[0]; for(int i = 1; i < n; i++) { //len代表lisArr中的最后一个元素 if(lisArr[len] < arr[i]) { lisArr[++len] = arr[i]; } else { //二分,在lisArr数组中找比arr[i]小的,最靠后的位置pos //未找到,即lisArr数组中所有元素均大于arr[i] //需要更新lisArr数组的位置,为何不设为1呢? int l = 1,r = len, pos = 0; while(l <= r) { //int mid = l + (r - l) >> 1;是错误的,原因是优先级导致的 int mid = l + ((r - l) >> 1);//位运算是为了避免溢出 //int mid = (l + r) >> 1; if(lisArr[mid] < arr[i]) { //更新指针 l = mid + 1; pos = mid; } else { r = mid - 1; } } //循环结束,找到了pos lisArr[pos + 1] = arr[i]; //pos是最后一个位置,len不变 //pos是中间位置,len仍不变,元素继续往后填 //所以没有必要更新len位置 } } return len; } } |
方法2:动态规划
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * 给定数组的最长严格上升子序列的长度。 * @param arr int整型一维数组 给定的数组 * @return int整型 */ public int LIS (int[] arr) { //动态规划实现 //最长子序列应当使用max值记录 int n = arr.length; if(n == 0) { return 0; } int[] dp = new int[n]; Arrays.fill(dp, 1); int maxLen = Integer.MIN_VALUE; for(int i = 0; i < n; i++) { for(int j = 0; j < i; j++) { if(arr[j] < arr[i]) { dp[i] = Math.max(dp[i], dp[j] + 1); } maxLen = Math.max(maxLen, dp[i]); } } return maxLen; } } |
12、最长上升子序列(三)
思路:动态规划+二分,避免超时
import java.util.*; public class Solution { /** * retrun the longest increasing subsequence * @param arr int整型一维数组 the array * @return int整型一维数组 */ public int[] LIS (int[] arr) { int n = arr.length; int[] dp = new int[n]; int[] tail = new int[n + 1]; int end = 0; tail[0] = Integer.MIN_VALUE; //存储对应位置元素的值 for(int i = 0; i < n; i++) { int num = arr[i]; if(tail[end] < num) { end++; tail[end] = num; dp[i] = end; } else { int site = Arrays.binarySearch(tail, 1, end, arr[i]); if(site >= 0) { continue; } int ins = -(site + 1); tail[ins] = arr[i]; dp[i] = ins; } } int[] res = new int[end]; int len = end; for(int i = n - 1; i >= 0; i--) { if(dp[i] == len) { res[len - 1] = arr[i]; len--; } } return res; } } |
13、最长公共子序列的长度
思路:相等时为对角线+1,不相等时为左上最大值
class Solution { public int longestCommonSubsequence(String text1, String text2) { int len1= text1.length(), len2= text2.length(); int[][] dp = new int[len1 + 1][len2 + 1]; for(int i = 1; i <= len1; i++) { char c1 = text1.charAt(i - 1); for(int j = 1; j <= len2; j++) { char c2= text2.charAt(j - 1); if(c1 == c2) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i -1][j], dp[i][j -1]); } } } return dp[len1][len2]; } } |
14、最长公共子序列
思路:知道长度后,倒着寻找
import java.util.*; public class Solution { /** * longest common subsequence * @param s1 string字符串 the string * @param s2 string字符串 the string * @return string字符串 */ public String LCS (String s1, String s2) { int row = s1.length(), col = s2.length(); int[][] dp = new int[row + 1][col + 1]; for(int i = 1; i <= row; i++) { for(int j = 1; j <= col; j++) { if(s1.charAt(i - 1) == s2.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } if(dp[row][col] == 0) { return "-1"; } //数组大小为公共子串的长度 char[] lcs = new char[dp[row][col]]; int cur = lcs.length - 1; while(true) { //什么时候把元素添加到结果中,应该倒序添加 if(s1.charAt(row - 1) == s2.charAt(col - 1)) { lcs[cur--] = s1.charAt(row - 1); if(cur < 0) { return new String(lcs); } row--; col--; } else { if(dp[row][col - 1] > dp[row - 1][col]) { col--; } else { row--; } } } } } |
15、编辑距离
思路:类似求公共子序列
//动态规划 //dp[len1][len2] //初始化、值相等、不相等 class Solution { public int minDistance(String word1, String word2) { int len1 = word1.length(); int len2 = word2.length(); int[][] dp = new int[len1 + 1][len2 + 1]; for(int i = 0; i <= len1; i++) { //到len dp[i][0] = i; } for(int j = 0; j <= len2; j++) { //到len dp[0][j] = j; } for(int i = 1; i <= len1; i++) { for(int j = 1; j <= len2; j++) { //到len if(word1.charAt(i - 1) == word2.charAt(j - 1)) { //字符是-1 dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = 1 + Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1])); } } } return dp[len1][len2]; } } |