一、動態規劃
動態規劃?是一種用于解決優化問題的算法設計技術,尤其適用于具有重疊子問題和最優子結構性質的問題。它通過將復雜問題分解為更簡單的子問題,并保存這些子問題的解以避免重復計算,從而提高效率。
動態規劃的核心思想
-
最優子結構(Optimal Substructure):
- 一個問題的最優解可以通過其子問題的最優解來構造。
- 比如在最短路徑問題中,從起點到終點的最短路徑可以由起點到某個中間點的最短路徑和該中間點到終點的最短路徑組合而成。
-
重疊子問題(Overlapping Subproblems):
- 在遞歸求解過程中,相同的子問題會被多次求解。
- 動態規劃通過存儲子問題的解來避免重復計算,通常使用數組或哈希表等數據結構來存儲這些解。
1.算法原理
1)狀態表示
dp表(一維數組? 填滿該表其中某一個值就是結果 數組中某個數據值的意義就是狀態表示)
通過題目要求 經驗? 分析問題發現重復子問題 來創建dp表
2)狀態轉移方程
dp[i]等于什么?
3)初始化
根據狀態轉移方程填表,保證填表的時候不越界
4)填表順序
為了填寫當前狀態的時候,所需要的狀態已經計算過了
5)返回值
題目要求+狀態表示
2.例題
泰波那契序列?Tn?定義如下:?
T0?= 0, T1?= 1, T2?= 1, 且在 n >= 0?的條件下 Tn+3?= Tn?+ Tn+1?+ Tn+2
給你整數?n
,請返回第 n 個泰波那契數?Tn?的值。
示例 1:輸入:n = 4 輸出:4? ??解釋: T_3 = 0 + 1 + 1 = 2 T_4 = 1 + 1 + 2 = 4
狀態表示:dp[i]第i個泰波那契數的值
狀態轉移方程:dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
初始化:dp[0]=0;dp[1]=1;dp[2]=1
填表順序:從左向右
返回值:dp[n]
JAVA代碼
class Solution {public int tribonacci(int n) {
if(n==0) return 0;
if(n==1||n==2) return 1;
int[] dp=new int[n+1];
dp[0]=0;dp[1]=1;dp[2]=1;
for(int i=3;i<=n;i++){dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
}
return dp[n];}
}
3.空間優化
求解某個狀態時僅需要其的前幾個狀態
一定要確定好賦序
int tribonacci(int n){
//空間優化 滾動數組
if(n==0) return 0;
if(n==1||n==2) return 1;
int a=0;int b=1,c=1;int d=0;
for(int i=3;i<=n;i++){d=a+b+c;a=b;b=c;c=d;}
return d;
}
?二、貪心算法
1.算法原理:
局部最優->全局最優
把解決問題的過程分為若干步,解決每一步的時候都選擇當前看起來最優的解法
希望得到全局最優解
但貪心算法并不總是保證全局最優解
2.例題
給定一個數組?prices
?,它的第?i
?個元素?prices[i]
?表示一支給定股票第?i
?天的價格。
你只能選擇?某一天?買入這只股票,并選擇在?未來的某一個不同的日子?賣出該股票。設計一個算法來計算你所能獲取的最大利潤。
示例 1:
[7,1,5,3,6,4] 輸出:5 解釋:在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。注意利潤不能是 7-1 = 6, 因為賣出價格需要大于買入價格;同時,你不能在買入前賣出股票。
示例 2:
輸入:prices = [7,6,4,3,1] 輸出:0 解釋:在這種情況下, 沒有交易完成, 所以最大利潤為 0。
在數組中挑出兩個數,使其差值最大
1)暴力解法(兩層for循環) 超出時間限制時間復雜度O(n^2)
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;class Solution {public int maxProfit(int[] prices) {int ret = 0;if (prices == null || prices.length < 2) {return 0;}for (int i = 0; i < prices.length - 1; i++) {for (int j = i+1; j < prices.length; j++) {ret = Math.max(ret, prices[j] - prices[i]);}}return ret;}public static void main(String[] args) {Scanner sc=new Scanner(System.in);Solution solution=new Solution();List<Integer> pricesList = new ArrayList<>();while (sc.hasNextInt()) {pricesList.add(sc.nextInt());}int[] prices = new int[pricesList.size()];for (int i = 0; i < pricesList.size(); i++) {prices[i] = pricesList.get(i);}int ret=solution.maxProfit(prices);System.out.println(ret);sc.close();}
}//在輸入結束后輸入一個非數字字符(如字母 'q'),這樣 hasNextInt() 會返回 false,從而結束循環,或 Ctrl + Z(Windows)來發送 EOF 信號。
2)貪心
分為兩段,prevmin表示前一段中最小值(買入),prices[i]賣出去的價錢
class Solution {public int maxProfit(int[] prices) {int ret=0;for(int i=0,prevmin=Integer.MAX_VALUE;i<prices.length;i++){ret=Math.max(ret,prices[i]-prevmin);prevmin=Math.min(prevmin,prices[i]);}return ret;}
}
三、 (雙指針算法)
給定一個數組?nums
,編寫一個函數將所有?0
?移動到數組的末尾,同時保持非零元素的相對順序。
請注意?,必須在不復制數組的情況下原地對數組進行操作。
示例 1:
輸入: nums =[0,1,0,3,12]
輸出:[1,3,12,0,0]
數組劃分
利用數組下標充當指針?
兩個指針的作用:
cur:從左往右掃描,遍歷數組
dest:已處理的區間內,非0元素的最后一個位置
三個區間:
[0,dest]? ?[dest+1,cur-1]? [cur,n-1]
非0? ? ? ? ?0元素? ? ? ? ? ? ? ? ?待處理的區間
解法:
初始dest=-1 cur=0
遇到0元素,cur++;
遇到非0元素 swap(dest+1,cur)交換位置 dest++ cur++
class Solution {public void moveZeroes(int[] nums) {
int dest=-1;
int cur=0;
int k=0;
int n=nums.length;
while(cur<n){
if(nums[cur]==0)
cur++;
else
{dest++;
k=nums[dest];
nums[dest]=nums[cur];
nums[cur]=k;
cur++;
}}}
}
?
給你一個整數數組?prices
?,其中?prices[i]
?表示某支股票第?i
?天的價格。
在每一天,你可以決定是否購買和/或出售股票。你在任何時候?最多?只能持有?一股?股票。你也可以先購買,然后在?同一天?出售。
返回?你能獲得的?最大?利潤?。
輸入:prices = [7,1,5,3,6,4] 輸出:7 解釋:在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能獲得利潤 = 5 - 1 = 4。 隨后,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能獲得利潤 = 6 - 3 = 3。 最大總利潤為 4 + 3 = 7 。
利用數組下標充當指針(i,j)
class Solution {public int maxProfit(int[] prices) {//雙指針算法int ret=0;int i=0,j=0;for(i=0;i<prices.length;i++){j=i;while(j+1<prices.length && prices[j+1]-prices[j]>0){j++;}ret+=prices[j]-prices[i];i=j;}return ret;}
}
四、遞歸
1.什么是遞歸
函數自己調用自己(主問題->相同的子問題)
出口(一個問題不能在分割了)? ? ? ? ? ?
1.算法思想
函數頭 函數體 遞歸出口
把遞歸的函數當成一個黑盒,不在意遞歸的細節
深度優先遍歷(后序遍歷)
2.例題
計算布爾二叉樹的值
給你一棵?完整二叉樹?的根,這棵樹有以下特征:
- 葉子節點?要么值為?
0
?要么值為?1
?,其中?0
?表示?False
?,1
?表示?True
?。 - 非葉子節點?要么值為?
2
?要么值為?3
?,其中?2
?表示邏輯或?OR
?,3
?表示邏輯與?AND
?。
計算?一個節點的值方式如下:
- 如果節點是個葉子節點,那么節點的?值?為它本身,即?
True
?或者?False
?。 - 否則,計算?兩個孩子的節點值,然后將該節點的運算符對兩個孩子值進行?運算?。
返回根節點?root
?的布爾運算值。
完整二叉樹?是每個節點有?0
?個或者?2
?個孩子的二叉樹。
葉子節點?是沒有孩子的節點。
class Solution {public boolean evaluateTree(TreeNode root) {if(root.left==null) return root.val==0 ? false: true;boolean left=evaluateTree(root.left);boolean right=evaluateTree(root.right);return root.val==2 ?left | right : left & right;}
}