算法很差,最近準備刷算法題,先從動態規劃開始。
含義
動態規劃(Dynamic programming,簡稱 DP)是一種在數學、管理科學、計算機科學、經濟學和生物信息學中使用的,通過把原問題分解爲相對簡單的子問題的方式求解複雜問題的方法。
動態規劃常常適用於有重疊子問題和最優子結構性質的問題,動態規劃方法所耗時間往往遠少於樸素解法。
動態規劃背後的基本思想非常簡單。大致上,若要解一個給定問題,我們需要解其不同部分(即子問題),再根據子問題的解以得出原問題的解。動態規劃往往用於優化遞歸問題,例如斐波那契數列,如果運用遞歸的方式來求解會重複計算很多相同的子問題,利用動態規劃的思想可以減少計算量。
通常許多子問題非常相似,爲此動態規劃法試圖僅僅解決每個子問題一次,具有天然剪枝的功能,從而減少計算量:一旦某個給定子問題的解已經算出,則將其記憶化存儲,以便下次需要同一個子問題解之時直接查表。這種做法在重複子問題的數目關於輸入的規模呈指數增長時特別有用。
Easy難度
1 最大子序和
public int maxSubArray(int[] nums) {
int max=nums[0];
int temp=nums[0];
for(int i=1;i<nums.length;i++){
//當前和爲負數,越加越小,直接使用當前值
if(temp<0)
temp=nums[i];
else
temp+=nums[i];
max=Math.max(max,temp);
}
return max;
}
2 爬樓梯
public int climbStairs(int n) {
//經典題目了,爬到n的結果是n-1的結果加上n-2的結果
if(n==0||n==1||n==2)
return n;
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=1;
dp[2]=2;
for(int i=3;i<n+1;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
3 買賣股票的最佳時機
public int maxProfit(int[] prices) {
if(prices.length==0)
return 0;
int max=0;
int min=prices[0];
for(int i=1;i<prices.length;i++){
if(max<prices[i]-min)
max=prices[i]-min;
min=Math.min(min,prices[i]);
}
return max;
}
4 打家劫舍
public int rob(int[] nums) {
//dp[i]表示偷到第i家的最大金額,
//根據題意dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1])
if(nums.length==0)
return 0;
if(nums.length==1)
return nums[0];
int[] dp=new int[nums.length+1];
dp[0]=0;//沒有偷,金額0
dp[1]=nums[0];//偷到第一家,最高金額肯定就是第一家的金額
for(int i=2;i<nums.length+1;i++){
//由於dp中加了第0家的概念,因此對應nums[i-1]
dp[i]=Math.max(dp[i-2]+nums[i-1],dp[i-1]);
}
return dp[nums.length];
}
5 區域和檢索-數組不可變
class NumArray {
//sum[i+1]表示nums[0]到nums[i]的和
private int[] sum;
public NumArray(int[] nums) {
sum=new int[nums.length+1];
for(int i=0;i<nums.length;i++){
sum[i+1]=sum[i]+nums[i];
}
}
public int sumRange(int i, int j) {
//sumRange(i,j)=sumRange(0,j)-sumRange(0,i-1)
return sum[j+1]-sum[i];
}
}
6 判斷子序列
public boolean isSubsequence(String s, String t) {
int index=-1;
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
//每次判斷從當前索引的下一個開始,indexOf方法尋找從index+1開始第一個爲c的索引
index=t.indexOf(c,index+1);
if(index==-1)
return false;
}
return true;
}
7 使用最小花費爬樓梯
public int minCostClimbingStairs(int[] cost) {
//動態規劃
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[dp.length-1],dp[dp.length-2]);
}
8 除數博弈
public boolean divisorGame(int N) {
//N爲2時,贏,N爲3時,輸
//因此N爲4時,只要取1個,就可以贏(對方是3,輸)
//因此N爲偶數可以贏,爲奇數就輸了
return N%2==0;
}
9 三步問題
難度不大,主要主要要取模運算
public int waysToStep(int n) {
int num=1000000007;
//一樓一種:1,二樓兩種1 1,2
if(n==1||n==2)
return n;
//三樓4種 1 1 1 1,1 2,2 1,3
if(n==3)
return 4;
//dp[i]表示i+1樓的方法數
int[] dp=new int[n];
dp[0]=1;
dp[1]=2;
dp[2]=4;
for(int i=3;i<dp.length;i++){
dp[i]=((dp[i-1]%num+dp[i-2]%num)%num+dp[i-3]%num)%num;
}
return dp[n-1];
}
10 按摩師
跟打家劫舍是一道題
public int massage(int[] nums) {
if(nums.length==0)
return 0;
if(nums.length==1)
return nums[0];
int[] dp=new int[nums.length+1];
dp[0]=0;
dp[1]=nums[0];
for(int i=2;i<dp.length;i++){
dp[i]=Math.max(dp[i-2]+nums[i-1],dp[i-1]);
}
return dp[dp.length-1];
}
Medium難度
1 最長迴文子串
public String longestPalindrome(String s) {
int len=s.length();
if(len<2)//長度爲0或1,返回自己
return s;
int maxLen=1;
int start=0;
//dp[i][j]表示字符串s的i到j字串是否爲迴文串
boolean[][] dp=new boolean[len][len];
//初始化,單個字符串肯定是迴文串
for(int i=0;i<dp.length;i++)
dp[i][i]=true;
//由於是迴文串,只需要判斷一半就行
for(int j=1;j<s.length();j++){
for(int i=0;i<j;i++){
//首尾不相等,肯定不是迴文串,相等判斷才繼續判斷
if(s.charAt(i)==s.charAt(j)){
if(j-i<3)//表示字串長度爲0-2,首尾又相等,肯定是迴文串
dp[i][j]=true;
else
dp[i][j]=dp[i+1][j-1];//除去首尾繼續判斷
}
if(dp[i][j]){//保持狀態
len=j-i+1;
if(len>maxLen){
maxLen=len;
start=i;
}
}
}
}
//從start開始,長度爲maxLen的字串就是結果
return s.substring(start,start+maxLen);
}
2 不同路徑
public int uniquePaths(int m, int n) {
//n行m列
int[][] dp=new int[n][m];
//到第一行哪一列都m只有一種,就是向右走幾個的問題
for(int i=0;i<m;i++)
dp[0][i]=1;
//到第一列哪一行都只有一種,就是向下走幾個的問題
for(int i=0;i<n;i++)
dp[i][0]=1;
//到i行j列的路徑數=到i-1行j列(上邊格子)的數量+到i行j-1列(左邊格子)的數量
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
dp[i][j]=dp[i-1][j]+dp[i][j-1];
return dp[n-1][m-1];
}
3 不同路徑2
public int uniquePathsWithObstacles(int[][] dp) {
if(dp[0][0]==1)//第一個就是障礙物,不用計算了
return 0;
//行和列
int m=dp.length;
int n=dp[0].length;
//初始化第一行
int index=0;
//沒遇到障礙物就初始化爲0,障礙物及其之後都初始化0
while(index<n&&dp[0][index]!=1)
dp[0][index++]=1;
while(index<n){
dp[0][index++]=0;
}
//初始化第一列
index=1;
while(index<m&&dp[index][0]!=1)
dp[index++][0]=1;
while(index<m){
dp[index++][0]=0;
}
//動態規劃填表
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(dp[i][j]==1)
dp[i][j]=0;//當前位置是障礙物,到達路徑爲0
else
dp[i][j]=dp[i-1][j]+dp[i][j-1];
}
}
return dp[m-1][n-1];
}
4 最小路徑和
public int minPathSum(int[][] grid) {
//初始化第一行
for(int i=1;i<grid[0].length;i++){
grid[0][i]+=grid[0][i-1];
}
//初始化第一列
for(int i=1;i<grid.length;i++){
grid[i][0]+=grid[i-1][0];
}
//動態規劃
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];
}
5 解碼方法
當前位置如果不爲0,但不能和前一位數字組成1~26的數字,就等於前一位的解法數
例如1231的解法數=123的解法數
如果當前位置和前一位可以組成1~26的數字,就額外加上前兩位的解法
例如1211的解法數=121的解法數+12的解法數
public int numDecodings(String s) {
if(s.charAt(0)=='0')
return 0;
//dp[i]表示到i-1位置爲止的解密方法數
int[] dp=new int[s.length()+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<dp.length;i++){
if(s.charAt(i-1)!='0')
dp[i]=dp[i-1];
if(s.charAt(i-2)=='1'||(s.charAt(i-2)=='2'&&s.charAt(i-1)<='6'))
dp[i]+=dp[i-2];
}
return dp[dp.length-1];
}
6 不同的二叉搜索樹
設dp[N]爲1-N的不同二叉樹個數,而f(i)是以i爲節點的不同二叉樹個數,那麼dp[N]=f(1)+f(2)+…+f(n),f(i)=dp(i-1)*dp(N-i),因爲左邊有i-1個節點,右邊有N-i個節點。
根據此關係寫代碼:
public int numTrees(int n) {
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int N=2;N<=n;N++){
for(int i=1;i<N+1;i++){
dp[N]+=dp[i-1]*dp[N-i];
}
}
return dp[n];
}
7 不同的二叉搜索樹2
public List<TreeNode> generateTrees(int n) {
if(n==0)
return new ArrayList<TreeNode>();
return generateTrees(1,n);
}
private List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> list=new ArrayList<>();
if(start>end){
list.add(null);
return list;
}
for(int i=start;i<=end;i++){
//以i爲根節點,i左邊爲左子樹,i右邊爲右子樹
List<TreeNode> leftList=generateTrees(start,i-1);
List<TreeNode> rightList=generateTrees(i+1,end);
for(TreeNode lChild:leftList){
for(TreeNode rChild:rightList){
TreeNode node = new TreeNode(i);
node.left=lChild;
node.right=rChild;
list.add(node);
}
}
}
return list;
}
8 三角形最小路徑和
如果是邊界的兩個值,那麼最小值就是上面邊界的累加值加上自己,否則就是兩邊的較小值加上自己,最後把最後一行遍歷,尋找最小值即可。
public int minimumTotal(List<List<Integer>> triangle) {
int n=triangle.size();
int[][] dp=new int[n][n];
dp[0][0]=triangle.get(0).get(0);
for(int i=1;i<n;i++){
List<Integer> list = triangle.get(i);
for(int j=0;j<=i;j++){
if(j==0) {//左邊界
dp[i][j] = dp[i-1][j] + list.get(j);
}else if(j==i){//右邊界
dp[i][j] = dp[i-1][j-1] + list.get(j);
}else {
dp[i][j] = Math.min(dp[i-1][j-1],dp[i-1][j])+list.get(j);
}
}
}
int min=Integer.MAX_VALUE;
for(int j=0;j<n;j++){
if(dp[n-1][j]<min)
min=dp[n-1][j];
}
return min;
}
9 單詞拆分
public boolean wordBreak(String s, List<String> wordDict) {
//去重
HashSet<String> set = new HashSet<>(wordDict);
//dp[i]表示到i+1位置截至,是否可成功拆分
boolean[] dp=new boolean[s.length()+1];
dp[0]=true;
for(int i=1;i<dp.length;i++){
for(int j=0;j<i;j++){
if(dp[j]&&set.contains(s.substring(j,i))){
dp[i]=true;
break;
}
}
}
return dp[dp.length-1];
}
10 乘積最大子數組
public int maxProduct(int[] nums) {
int max=Integer.MIN_VALUE;
int tmpMax=1;
int tmpMin=1;
for(int i=0;i<nums.length;i++){
if(nums[i]<0){
int tmp=tmpMax;
tmpMax=tmpMin;
tmpMin=tmp;
}
tmpMax=Math.max(tmpMax*nums[i],nums[i]);
tmpMin=Math.min(tmpMin*nums[i],nums[i]);
max=Math.max(tmpMax,max);
}
return max;
}
11 打家劫舍2
相等於計算兩邊打家劫舍1,範圍是第一個到倒數第二個,或者第二個到最後一個
public int rob(int[] nums) {
if(nums.length==0)
return 0;
if(nums.length==1)
return nums[0];
int max1=robHelp(Arrays.copyOfRange(nums,0,nums.length-1));
int max2=robHelp(Arrays.copyOfRange(nums,1,nums.length));
return Math.max(max1,max2);
}
public int robHelp(int[] nums) {
int[] dp=new int[nums.length+1];
dp[0]=0;
dp[1]=nums[0];
for(int i=2;i<nums.length+1;i++){
dp[i]=Math.max(dp[i-2]+nums[i-1],dp[i-1]);
}
return dp[nums.length];
}
12 最大正方形
public int maximalSquare(char[][] matrix) {
int maxLen=0;
int row=matrix.length;
int col=row>0?matrix[0].length:0;
int[][] dp=new int[row+1][col+1];
for(int i=1;i<=row;i++){
for(int j=1;j<=col;j++){
if(matrix[i-1][j-1]=='1'){
dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i-1][j-1],dp[i][j-1]))+1;
maxLen=Math.max(maxLen,dp[i][j]);
}
}
}
return maxLen*maxLen;
}
13 醜數
public int nthUglyNumber(int n) {
int[] dp=new int[n];
dp[0]=1;
int index2=0;
int index3=0;
int index5=0;
for(int i=1;i<n;i++){
int min2=dp[index2]*2;
int min3=dp[index3]*3;
int min5=dp[index5]*5;
int min=Math.min(min2,Math.min(min3,min5));
dp[i]=min;
if(min==min2)
index2++;
if(min==min3)
index3++;
if(min==min5)
index5++;
}
return dp[n-1];
}
14 完全平方數
public int numSquares(int n) {
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<dp.length;i++){
dp[i]=i;
for(int j=1;i-j*j>=0;j++){
dp[i]=Math.min(dp[i],dp[i-j*j]+1);
}
}
return dp[n];
}
15 最長上升子序列
public int lengthOfLIS(int[] nums) {
if(nums.length==0)
return 0;
int[] dp=new int[nums.length];
Arrays.fill(dp,1);
//保持最大值
int res=1;
for(int i=1;i<dp.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(res,dp[i]);
}
return res;
}