leetcode動態規劃已刷題彙總
1025.除數博弈
兩個思路:
- 常規dp
class Solution {
public:
bool divisorGame(int N) {
bool dp[1000+1];
dp[1]=false;
dp[2]=true;
if(N <3){
return dp[N];
}
else{
for(int i=3;i<=N;i++){
int cnt=0;
for(int j=1;j<=1 ;j++){
if(dp[i-j]==false){//愛麗絲先取出j,搜尋以前的情況爲i-j時愛麗絲的輸贏,這就是接下來的鮑勃的輸贏。(只要有一個能讓鮑勃輸,就有贏的可能,因爲愛麗絲狀態很好,有方法就一定會讓自己贏)
cnt++;
break;
}
}
if(cnt!=0){
dp[i]=true;
}else{
dp[i]=false;
}
}
return dp[N];
}
}
};
- 博弈問題 -> 數學問題(判斷奇偶就夠了)
class Solution {
public:
bool divisorGame(int N) {
return N%2 == 0;
};
303.區域和檢索-數組不可變
- 求得前綴和數組 sum[ n ],每次求區間[ i, j ],返回 sum[ j ] - sum[ i - 1];
121.買賣股票的最佳時期
53.最大子序和
題目要求至少選一個
- 原來這也叫動態規劃(因爲真正的暴力解法沒有備忘錄?
int maxSubArray(int* nums, int numsSize){
int max_sum = 0;
int sum = 0;
int num_max = nums[0];
for(int i = 0; i < numsSize; i++){
sum = sum + nums[i];
if(sum < 0)
sum = 0;
if(sum > max_sum)
max_sum = sum;
if(nums[i]>num_max)
num_max = nums[i];
}
if(max_sum == 0)//表明沒有正的序列和,也就意味着都是負數,由於必須要選一個,那麼就輸出最大的負數
max_sum = num_max;
return max_sum;
}
化簡:
int maxSubArray(int* nums, int numsSize){
int max_sum = nums[0];
int sum = 0;
for(int i = 0; i < numsSize; i++){
if(sum > 0){//sum當作一個數,sum > 0,保留這個數
sum = sum + nums[i];
}else{//sum < 0,沒有增益作用,捨棄這個數
sum = nums[i];
}
//sum = max(nums[i], sum + nums[i])
//(sum > 0)相當於((sum + num[i]) > num[i]) 和上面的if判斷一樣
//以每個位置爲終點的最大子數列 都是基於其前一位置的最大子數列計算得出,
//這樣看比較dp
if(sum > max_sum){//不斷更新max_sum
max_sum = sum;
}
}
return max_sum;
}
- 分治算法,其實就是它的最大子序和要麼在左半邊,要麼在右半邊,要麼是穿過中間,對於左右邊的序列,情況也是一樣,因此可以用遞歸處理。中間部分的則可以直接計算出來,時間複雜度應該是 O(nlogn)。代碼如下:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n = len(nums)
#遞歸終止條件
if n == 1:
return nums[0]
else:
#遞歸計算左半邊最大子序和
max_left = self.maxSubArray(nums[0:len(nums) // 2])
#遞歸計算右半邊最大子序和
max_right = self.maxSubArray(nums[len(nums) // 2:len(nums)])
#計算中間的最大子序和,從右到左計算左邊的最大子序和,從左到右計算右邊的最大子序和,再相加
max_l = nums[len(nums) // 2 - 1]
tmp = 0
for i in range(len(nums) // 2 - 1, -1, -1):
tmp += nums[i]
max_l = max(tmp, max_l)
max_r = nums[len(nums) // 2]
tmp = 0
for i in range(len(nums) // 2, len(nums)):
tmp += nums[i]
max_r = max(tmp, max_r)
#返回三個中的最大值
return max(max_right,max_left,max_l+max_r)
看不懂。。
//分治法,時間複雜度o(n),優點是可以並行運算。
class Solution {
public int maxSubArray(int[] nums) {
return mergeCount(nums,0,nums.length)[2];
}
/**
* @return 片段處理後的數組,依次爲:左通最大值,右通最大值,局部最大值,總和
* */
public int[] mergeCount(int[] nums,int fromIndex,int toIndex){
int[] result=new int[4];
if(toIndex-fromIndex!=1){
int midIndex=(toIndex+fromIndex)>>>1;
int[] resL=mergeCount(nums,fromIndex,midIndex);
int[] resR=mergeCount(nums,midIndex,toIndex);
result[0]=Math.max(resL[0],resL[3]+resR[0]);
result[1]=Math.max(resR[1],resL[1]+resR[3]);
result[2]=Math.max(Math.max(resL[2],resR[2]),resL[1]+resR[0]);
result[3]=resL[3]+resR[3];
return result;
}
Arrays.fill(result,nums[fromIndex]);
return result;
}
}
392.判斷子序列
給定字符串 s 和 t ,判斷 s 是否爲 t 的子序列。
假設 是 的子序列():
若 != ,當且僅當 是 的子序列;
若 == ,當且僅當 是 的子序列;
- 按順序遍歷,相同字符就 cnt++ ,最後判斷 cnt == s.size().
class Solution {
public:
bool isSubsequence(string s, string t) {
int j = 0 ;
bool judge;
for(int i = 0; i < t.size(); i++)
if(t[i] == s[j])
j++;
if(j == s.size())
judge = true;
else
judge = false;
return judge;
}
};
- 用 dp 反而慢一點:
class Solution {
public boolean isSubsequence(String s, String t) {
int sLen = s.length(), tLen = t.length();
if (sLen > tLen) return false;
if (sLen == 0) return true;
boolean[][] dp = new boolean[sLen + 1][tLen + 1];
//初始化
for (int j = 0; j < tLen; j++) {
dp[0][j] = true;
}
//dp
for (int i = 1; i <= sLen; i++) {
for (int j = 1; j <= tLen; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = dp[i][j - 1];
}
}
}
return dp[sLen][tLen];
}
}
- 後續挑戰 :
如果有大量輸入的 S,稱作S1, S2, … , Sk 其中 k >= 10億,你需要依次檢查它們是否爲 T 的子序列。在這種情況下,你會怎樣改變代碼?
Answer: 用二維數組list[26][]記下A~Z出現的所有位置,每次查詢,就依次查詢(注意次序大小關係)
bool isSubsequence(string s, string t) {
vector<vector<int>>dp(26);
int tag=-1;
for(int i=0;i<t.size();i++)
dp[t[i]-'a'].push_back(i);
for(int i=0;i<s.size();i++){
int now=s[i]-'a';
int left=0,right=dp[now].size()-1;
while(left<right){
int mid=(left+right)/2;
if(dp[now][mid]>tag)
right=mid;
else
left=mid+1;
}
if(right<left || dp[now][left]<tag)return false;
tag=dp[now][left];
}
return true;
}
70.爬樓梯
- dp[ i ] = dp[ i - 1 ] + dp[ i - 2 ]
class Solution {
public:
int climbStairs(int n) {
int ans = 0;
int dp[1000001];
dp[1] = 1;
dp[2] = 2;
if(n < 3){
ans = dp[n];
}else{
for(int i = 3;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
ans = dp[n];
}
return dp[n];
}
};
優化,本質爲斐波那契數列,空間複雜度爲 O(1):
class Solution {
public:
int climbStairs(int n) {
int third = 0;
int dp[1000001];
int first = 1;
int second = 2;
if(n = 1){
ans = first;
}else if(n == 2){
ans = second;
}else{
for(int i = 3;i<=n;i++){
third = first + second;
first = second;
second = third;
}
ans = third;
}
return ans;
}
};
746.使用最小花費爬樓梯
- dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int dp[1001];
dp[0] = 0;
dp[1] = 0;
for(int i = 2; i <= cost.size(); i++){
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
}
return dp[cost.size()];
}
};
198.打家劫舍
- dp[i] = max( dp[i-1] , dp[i-2] + nums[i] )
dp[i] = max(不偷(也就無所謂昨天有沒有偷,無腦繼承),偷(繼承前天的收益)) - 另外,注意一下特殊情況的特判,否則會有溢出的內存錯誤
class Solution {
public:
int rob(vector<int>& nums) {
int dp[100001];
if(nums.size() == 0){
return 0;
}else if(nums.size() == 1){
return nums[0];
}else{
dp[0] = nums[0];
dp[1] = max(dp[0] , nums[1]);
for(int i = 2;i < (int)nums.size(); i++){
dp[i] = max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[(int)nums.size()-1];
}
}
};
338.比特位計數
- 大概。。找規律。。
class Solution {
public:
vector<int> countBits(int num) {
vector<int> dp(num + 1);
dp[0] = 0;
int e = 1;
for(int i = 1; i <= num; i++){
if(e*2 == i){
e = e*2;
}
dp[i] = 1 + dp[i - e];
}
return dp;
}
};
//dp[num] = 1 + dp[num - 2^exp];
- 奇數:二進制表示中,奇數一定比前面那個偶數多一個 1,因爲多的就是最低位的 1。
-
舉例: 0 = 0 1 = 1 2 = 10 3 = 11
- 偶數:二進制表示中,偶數中 1 的個數一定和除以 2 之後的那個數一樣多。因爲最低位是 0,除以 2 就是右移一位,也就是把那個 0 抹掉而已,所以 1 的個數是不變的。
-
舉例: 2 = 10 4 = 100 8 = 1000 3 = 11 6 = 110 12 = 1100
- 另外,0 的 1 個數爲 0,於是就可以根據奇偶性開始遍歷計算了。
vector<int> countBits(int num) {
vector<int> result(num+1);
result[0] = 0;
for(int i = 1; i <= num; i++){
if(i % 2 == 1){
result[i] = result[i-1] + 1;
}
else{
result[i] = result[i/2];
}
}
return result;
}
877.石子游戲
-
方法一:動態規劃
-
思路:
讓我們改變遊戲規則,使得每當李得分時,都會從亞歷克斯的分數中扣除。
令 dp(i, j) 爲亞歷克斯可以獲得的最大分數,其中剩下的堆中的石子數是 piles[i], piles[i+1], …, piles[j]。
這在比分遊戲中很自然:我們想知道遊戲中每個位置的值。 -
我們可以根據 dp(i + 1,j) 和 dp(i,j-1) 來制定 dp(i,j) 的遞歸,我們可以使用動態編程以不重複這個遞歸中的工作。該方法可以輸出正確的答案,因爲狀態形成一個DAG(有向無環圖)。
-
算法:
當剩下的堆的石子數是 piles[i], piles[i+1], …, piles[j] 時,輪到的玩家最多有 2 種行爲。
可以通過比較 j-i和 N modulo 2 來找出輪到的人。 -
如果玩家是亞歷克斯,那麼她將取走 piles[i] 或 piles[j] 顆石子,增加她的分數。 之後,總分爲 piles[i] + dp(i+1, j) 或 piles[j] + dp(i, j-1);我們想要其中的最大可能得分。
-
如果玩家是李,那麼他將取走 piles[i] 或 piles[j] 顆石子,減少亞歷克斯這一數量的分數。 之後,總分爲 -piles[i] + dp(i+1, j) 或 -piles[j] + dp(i, j-1);我們想要其中的最小可能得分。
class Solution {
public:
bool stoneGame(vector<int>& piles) {
int N = piles.size();
// dp[i+1][j+1] = the value of the game [piles[i], ..., piles[j]]
int dp[N+2][N+2];
memset(dp, 0, sizeof(dp));
for (int size = 1; size <= N; ++size)
for (int i = 0, j = size - 1; j < N; ++i, ++j) {
int parity = (j + i + N) % 2; // j - i - N; but +x = -x (mod 2)
if (parity == 1)
dp[i+1][j+1] = max(piles[i] + dp[i+2][j+1], piles[j] + dp[i+1][j]);
else
dp[i+1][j+1] = min(-piles[i] + dp[i+2][j+1], -piles[j] + dp[i+1][j]);
}
return dp[1][N] > 0;
}
};
-
複雜度分析
時間複雜度:,其中 是石子堆的數目。
空間複雜度:,該空間用以存儲每個子游戲的中間結果。 -
方法二:數學
思路和算法 顯然,亞歷克斯總是贏得 2 堆時的遊戲。 通過一些努力,我們可以獲知她總是贏得 4 堆時的遊戲。 如果亞歷克斯最初獲得第一堆,她總是可以拿第三堆。 如果她最初取到第四堆, 她總是可以取第二堆。第一 + 第三,第二 + 第四 中的至少一組是更大的, 所以她總能獲勝。 我們可以將這個想法擴展到 N 堆的情況下。設第一、第三、第五、第七樁是白色的, 第二、第四、第六、第八樁是黑色的。 亞歷克斯總是可以拿到所有白色樁或所有黑色樁, 其中一種顏色具有的石頭數量必定大於另一種顏色的。 因此,亞歷克斯總能贏得比賽。
class Solution {
public:
bool stoneGame(vector<int>& piles) {
return true;
}
};
- 複雜度分析
時間和空間複雜度:。
拓展後的石子游戲
leetcode-解決博弈問題的動態規劃通用思路 sdl wsl
-
石子總數爲n(爲任意正整數),分爲m堆(任意正整數)。
- -初始化vector二維數組 //初始化行數爲maxm,pair<int,int>型的二維數組 vector<vector<pair<int,int>>> dp(maxm); for(int i=0;i<m;i++){ dp[i].resize(m);//對每一行修改列值 }
#include <cstdio>
#include <vector>
#define maxm 1001
using namespace std;
int n,m;
vector<vector<pair<int,int>>> dp(maxm);//初始化行數爲maxm,pair<int,int>型的二維數組
vector<int> list(maxm);
int main(){
while (~scanf("%d %d",&n,&m)){
for(int i=0;i<m;i++){
dp[i].resize(m);//對每一行修改列值
}
for(int i=0;i<m;i++){
scanf("%d",&list[i]);
}
for(int i=0;i<m;i++){
dp[i][i].first=list[i];
dp[i][i].second = 0;
for(int j=i+1;j<m;j++){
dp[i][j].first = 0;
dp[i][j].second = 0;
}
}
//觀察dp方程,需要i+1和j-1,故是斜着遍歷數組的
for(int l=1;l<m;l++){
for(int i=0;i<m;i++){
int j=i+l;
if(j>=m){
break;
}
int left = list[i]+dp[i+1][j].second;//先選左邊,下一輪變成後手
int right = list[j]+dp[i][j-1].second;
if(left > right){
dp[i][j].first = left;
dp[i][j].second = dp[i+1][j].first;//下一輪的先手的最優解,兩人都是精明的
}else{
dp[i][j].first = right;
dp[i][j].second = dp[i][j-1].first;
}
}
}
int ans = dp[0][m-1].first-dp[0][m-1].second;
if(ans>0){
printf("FirstOneWins\n");
}else if(ans<0){
printf("SecondOneWins\n");
}else{
printf("NoOneWins\n");
}
return 0;
}
96.不同的二叉搜索樹
-
複習賽做過
思路一:卡特蘭數,然後遞歸
class Solution {
public:
int numTrees(int n) {
int list[40]={0,};
list[0] = 1;
list[1] = 1;
list[2] = 2;
list[3] = 5;
list[4] = 14;
list[5] = 42;
int i,j,k;
for(i = 6; i <= n;i++){
for(j = 0, k = i - 1; j <= i - 1 && k >= 0; j++, k--){
list[i] += list[j] * list[k];
}
}
return list[n];
}
};
class Solution {
public int numTrees(int n) {
// Note: we should use long here instead of int, otherwise overflow
long C = 1;
for (int i = 0; i < n; ++i) {
C = C * 2 * (2 * i + 1) / (i + 2);
}
return (int) C;
}
}
- code2 用遞歸式:
-
令h(1)=1,h(0)=1,catalan數(卡特蘭數)滿足遞歸式: h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (其中n>=2)
-
另類遞歸式: h(n)=((4*n-2)/(n+1))*h(n-1);
-
該遞推關係的解爲: h(n)=C(2n,n)/(n+1) (n=1,2,3,...)
64.最小路徑和
- 常規dp
-
dp[i][j] = min(dp[ i - 1 ][ j ],dp[ i ][ j - 1 ]) + grid[i][j] 下移得來 , 從右移得來 ,前面的均爲最優解
- 二維,要注意有邊界限制
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int dp[1001][1001];
int m = grid.size();
int n = grid[0].size();
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0 && j == 0){
dp[i][j] = grid[i][j];
}
if(i != 0 && j == 0){
dp[i][j] = dp[i-1][j] + grid[i][j];
}
if(i == 0 && j != 0){
dp[i][j] = dp[i][j-1] + grid[i][j];
}
if(i != 0 && j != 0){
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
}
}
}
return dp[m-1][n-1];
}
};
//可化簡,在原數組上修改值(每處的最優解)
120.三角形最小路徑和
-
dp[i][j] = max(dp[i-1][j],dp[i-1][j-1]) 注意(兩側)邊界問題
- 可以在原數組上修改
int the_min(int a,int b){
return a<b?a:b;
}
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int n = triangle.size();
int dp[1001][1001];
int min = INT_MAX;
for(int i = 0; i < n; i++){
for(int j = 0; j <= i; j++){
if(j == 0 && i == 0){
triangle[i][j] = 0 + triangle[0][0];
}
else if(j == 0 && i != 0){
triangle[i][j] = triangle[i-1][j] + triangle[i][j];
}
else if(i == j){
triangle[i][j] = triangle[i-1][j-1] + triangle[i][j];
}
else if(i != 0 && j != 0 && i != j){
triangle[i][j] = the_min(triangle[i-1][j],triangle[i-1][j-1]) + triangle[i][j];
}
if(i == n-1){
if(triangle[i][j] < min){
min = triangle[i][j];
}
}
}
}
return min;
}
};
95.不同的二叉搜索樹Ⅱ
- 遞歸
class Solution {
public:
vector<TreeNode*> helper(int start,int end){
vector<TreeNode*> ret;
if(start > end)
ret.push_back(nullptr);
for(int i=start;i<=end;i++){
vector<TreeNode*> left = helper(start,i-1);
vector<TreeNode*> right = helper(i+1,end);
for(auto l : left){
for(auto r : right){
TreeNode* root = new TreeNode(i);
root -> left = l;
root -> right = r;
ret.push_back(root);
}
}
}
return ret;
}
vector<TreeNode*> generateTrees(int n) {
vector<TreeNode*> ret;
if(n == 0)
return ret;
ret = helper(1,n);
return ret;
}
};
712.兩個字符串的最小ASCII刪除和
題意是尋找一個共同子序列,將字符串s1和s2刪除爲該子序列時所刪除的ASCII綜合最小。
等價於求一個字符串s1和s2的ASCII碼總和最大的共同子序列。
因爲s1和s2的總和固定,當共同子序列的總和最大時,刪除成爲該子序列的代價必然最小。
注意幾個特判(i == 0,j == 0)
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
int dp[1001][1001];
int sum1 = 0, sum2 = 0;
for(int i = 0; i < s1.size(); i++){
sum1 += s1[i];
for(int j = 0; j < s2.size(); j++){
if(i == 0 ) sum2 += s2[j];
if(i == 0 || j == 0){
if(s1[i] == s2[j]){
dp[i][j] = 0 + s1[i];
}
else{//注意這裏的幾個特判,不能盲目的等於0
if( i == 0 && j == 0){
dp[i][j] = 0;
}
else if(i == 0){
dp[i][j] = dp[i][j - 1];
}
else if(j == 0){
dp[i][j] = dp[i - 1][j];
}
}
}else{
if(s1[i] == s2[j]){
dp[i][j] = dp[i - 1][j - 1] + s1[i];
}else{
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
}
return sum1 + sum2 - 2 * dp[s1.size() - 1][s2.size() - 1];
}
};
- 這樣好像避免了特判,也更快
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
//if (s1.empty() || s2.empty()); return 0;
int len1 = s1.size();
int len2 = s2.size();
int sum1 = 0;
int sum2 = 0;
int i, j;
vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));
cout << s1[0] << endl;
for (i = 0;i < len1; i++) {
sum1 += s1[i];
cout << sum1 << endl;
}
cout << sum1 << endl;
for (j = 0;j < len2; j++) {
sum2 += s2[j];
}
for (i = 0;i < len1; i++) {
for (j = 0; j < len2; j++) {
if(s1[i] == s2[j]) {
dp[i + 1][j + 1] = dp[i][j] + s1[i];
} else {
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);
}
}
}
return sum1 + sum2 - 2 * dp[i][j];
}
};
647.迴文子串
執行代碼和提交代碼結果不同 wsl 跳過吧
哦。。沒初始化dp
class Solution {
public:
int countSubstrings(string s) {
int len = s.size();
bool dp[1001][1001];
int cnt = 0;
for(int i=0;i<len-1;i++){
dp[i][i] = true;
cnt++;
if(s[i]==s[i+1]){
dp[i][i+1] = true;
cnt++;
}
}
dp[len-1][len-1] = true;
cnt++;
for(int k = 3 ; k <= len ;k++){
for(int i = 0 ;i< len - k + 1;i++){
if(dp[i+1][i+k-2]&&s[i]==s[i+k-1]){
dp[i][i+k-1] = true;
cnt++;
}
}
}
return cnt;
}
};
714.買賣股票的最佳時機含手續費
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
int dp_i_0 = 0;
int dp_i_1 = INT_MIN;
for(int i =0 ;i <n;i++){
int temp = dp_i_0;
dp_i_0 = max(dp_i_0,dp_i_1+prices[i]);
dp_i_1 = max(dp_i_1,temp-prices[i]-fee);
}
return dp_i_0;
}
};
931.下降路徑最小和
-
常規dp
dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]); 若在最左側,來自2,3 若在最右側,來自1,2
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& A) {
int n = A.size();
int min_num=INT_MAX;
for(int i = 0;i<n;i++){
for(int j= 0;j<n;j++){
if(i==0){
A[i][j] = A[i][j];
}else if(j==0){
A[i][j] =min(A[i-1][j],A[i-1][j+1])+A[i][j];
}else if(j==n-1){
A[i][j] =min(A[i-1][j-1],A[i-1][j])+A[i][j];
}else{
A[i][j] =min(min(A[i-1][j-1],A[i-1][j]),A[i-1][j+1])+A[i][j];
}
if(i==n-1){
if(A[i][j]<min_num){
min_num = A[i][j];
}
}
}
}
return min_num;
}
};
413.等差數列劃分
- 我是垃圾,是等差數列又不是迴文,爲什麼要兩邊延申。。腦子瓦特了吧
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n = A.size();
int cnt =0;
if(n==0){
cnt = 0;
}else{
bool dp[8001][8001]={false,};
for(int i=0;i<n-1;i++){
dp[i][i] = true;
dp[i][i+1] =true;
}
dp[n-1][n-1]=true;
for(int i = 3; i <=n;i++){
for(int j = 0;j<n-i+1;j++){
if(dp[j+1][j+i-2] == true && A[j]-A[j+1]==A[j+1]-A[j+2]&&A[j+i-1]-A[j+i-2]==A[j+i-2]-A[j+i-3]){
dp[j][j+i-1] =true;
cnt++;
}
}
}
}
return cnt;
}
};
//子數組至少三個數
-
我們可以觀察到區間 (0,i)(0,i) 中等差數列的個數只和這個區間中的元素有關。因此,這個問題可以用動態規劃來解決。
-
首先創建一個大小爲 nn 的一維數組 dpdp。dp[i]dp[i] 用來存儲在區間 (k,i) , 而不在區間 (k,j) 中等差數列的個數,其中 j<i,否則有重複,如123在123中,也在1234中。
-
與遞歸方法中後向推導不同,我們前向推導 dpdp 中的值。其餘的思路跟上一個方法幾乎一樣。對於第 ii 個元素,判斷這個元素跟前一個元素的差值是否和等差數列中的差值相等。如果相等,那麼新區間中等差數列的個數即爲 1+dp[i-1]1+dp[i−1]。sumsum 同時也要加上這個值來更新全局的等差數列總數。
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n = A.size();
int dp[10001] = {0,};
int sum = 0;
for(int i=2;i<n;i++){
if(A[i]-A[i-1]==A[i-1]-A[i-2]){
dp[i] = dp[i-1]+1;
sum+=dp[i];
}
}
return sum;
}
};
//化簡,只記錄前一個量
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& A) {
int n = A.size();
int dp_i = 0;
int sum = 0;
for(int i=2;i<n;i++){
if(A[i]-A[i-1]==A[i-1]-A[i-2]){
dp_i = dp_i+1;
sum+=dp_i;
}else{
dp_i = 0;
}
}
return sum;
}
};
62.不同路徑
- 常規dp
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[101][101]={{0,0},};
for(int i;i<m;i++){
for(int j = 0;j <n;j++){
if(i == 0&&j==0){
dp[i][j] =1;
}else if(i==0){
dp[i][j] = dp[i][j-1];
}else if(j==0){
dp[i][j] =dp[i-1][j];
}else{
dp[i][j] = dp[i][j-1]+dp[i-1][j];
}
}
}
return dp[m-1][n-1];
}
};
- 一維做法,小學奧數?
class Solution {
public int uniquePaths(int m, int n) {
int[] memo = new int[n];
Arrays.fill(memo, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
memo[j] += memo[j - 1];
}
}
return memo[n - 1];
}
}
1143.最長公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int dp[1001][1001];
memset(dp, 0, sizeof(dp));
int m = text1.size();
int n = text2.size();
for(int i = 1;i<=m;i++){
for(int j=1;j<=n;j++){
if(text1[i-1]==text2[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[m][n];
}
};