動態規劃初級

GZ2580發表於2020-11-21

第一週刷題總結
#leetcode
#動態規劃

本週所做的動態規劃型別題目大多是在一維陣列、二維矩陣中的簡單動態規劃。
*一般解決動態規劃問題,分為四個步驟,分別是
①問題拆解,找到問題之間的具體聯絡
②狀態定義,例如,dp[i]表示當前第i步的最優解
③遞推方程推導,找出狀態方程,dp[i]與前面最優解之間的關係,如dp[i]=dp[i-1]+dp[i-2]
④實現,返回結果需要的解

一、70.爬樓梯
假設你正在爬樓梯。需要 n 階你才能到達樓頂。
每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

示例 :
輸入: 3
輸出: 3
解釋: 有三種方法可以爬到樓頂。
1 階 + 1 階 + 1 階
1 階 + 2 階
2 階 + 1 階

int climbStairs(int n){   
    if(n<=2)
        return n;
    int dp[n+1],i;
    dp[0] = 0;dp[1] = 1;dp[2] = 2;
    for(i=3;i<=n;i++){
        dp[i] = dp[i-1] + dp[i-2];
    }
    return dp[n];
}

二、338.位元位計數
給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍中的每個數字 i ,計算其二進位制數中的 1 的數目並將它們作為陣列返回。
示例:
輸入: 5
輸出: [0,1,1,2,1,2]

/*
首先找到二進位制進位之間的規律
0    1    2     3     4      5       6     7      8      9     10     11     12
0    1    10    11    100    101     110   111    1000   1001  1010   1011   1100
0    1    1     2     1      2       2     3      1      2     2      3      2
*/
int* countBits(int num, int* returnSize){
    if(num==0)
        *returnSize =1;
    else
        *returnSize = num+1;
    int *res = malloc(sizeof(int)*(*returnSize));
    res[0] = 0;
    for(int i=1;i<=num;i++){    
        if(i%2==0){                 //i為偶數:相當於i/2整體向左移動一位
            res[i] = res[i/2];
        }else{
            res[i] = res[i-1] + 1;  //i為奇數:前一位i-1加上1
        }
    }
    return res;
}

三、62.不同路徑
在一個m*n的網格中,每次只能向下或向右移動一格,從左上角到右下角一共有多少不同的路徑
示例 :
輸入: m = 3, n = 2
輸出: 3

int uniquePaths(int m, int n){
    if(m<=0 || n<=0)
        return 0;
    int dp[m][n],i,j;  
    for(i=0;i<m;i++){  //首先初始化第一行、第一列為1,這些格子只能由上一個格子通過一種走法(向下或向右)到達
        dp[i][0]=1;
    }
    for(j=0;j<n;j++){
        dp[0][j]=1;
    }

    for(i=1;i<m;i++){
        for(j=1;j<n;j++){
            dp[i][j] = dp[i-1][j] + dp[i][j-1]; //狀態轉移方程,dp[i][j]表示到達(i,j)格子的所有不同路徑數
        }
    }
    return dp[m-1][n-1]; //返回到達右下角格子的不同路徑數
}

四、最小路徑和
給定一個包含非負整數的 m x n 網格 grid ,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。
示例 :
輸入:grid = [
[1,3,1],
[1,5,1],
[4,2,1]
]
輸出:7
解釋:因為路徑 1→3→1→1→1 的總和最小。

#define min(x,y)  ( x<y?x:y )
int minPathSum(int** grid, int gridSize, int* gridColSize){
    if(gridSize<=0 || gridColSize<=0){
        return 0;
    }
    int dp[gridSize][*gridColSize];
    int i,j;
    dp[0][0] = grid[0][0];
    for(i=1;i<gridSize;i++){
        dp[i][0] = dp[i-1][0] + grid[i][0];
    }
    for(j=1;j<*gridColSize;j++){
        dp[0][j] = dp[0][j-1] + grid[0][j];
    }

    for(i=1;i<gridSize;i++){
        for(j=1;j<*gridColSize;j++){
            dp[i][j] = min(dp[i-1][j] , dp[i][j-1]) + grid[i][j];
        }
    }
    return dp[gridSize-1][*gridColSize-1];  
}

五、55 .跳躍遊戲
給定一個非負整數陣列,你最初位於陣列的第一個位置。陣列中的每個元素代表你在該位置可以跳躍的最大長度。判斷你是否能夠到達最後一個位置。
示例 1:
輸入: [2,3,1,1,4]
輸出: true
解釋: 可以先跳 1 步,從位置 0 到達 位置 1, 然後再從位置 1 跳 3 步到達最後一個位置。

示例 2:
輸入: [3,2,1,0,4]
輸出: false
解釋: 無論怎樣,總會到達索引為 3 的位置。但該位置的最大跳躍長度是 0 , 所以永遠不可能到達最後一個位置。

#define max(x,y)  ( x>y?x:y )
bool canJump(int* nums, int numsSize) {
	int k = 0; //k表示此時可以到達的最大索引位置
	for (int i = 0; i < numsSize; i++){
		if (i > k) return false;
		k = max(k, i + nums[i]); //如果此時索引位置+可跳躍步數大於k,更新k
	}
	return true;
}

六、198.打家劫舍
一個小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響偷竊的唯一制約因素就是如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。給定一個代表每個房屋存放金額的非負整數陣列,計算不觸動警報裝置的情況下 ,一夜之內能夠偷竊到的最高金額。
示例 :
輸入:[2,7,9,3,1]
輸出:12
解釋:偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接著偷竊 5 號房屋 (金額 = 1)。
偷竊到的最高金額 = 2 + 9 + 1 = 12 。

#define max(x,y)  ( x>y?x:y )
int rob(int* nums, int numsSize){
    if(numsSize==0)
        return 0;
    if(numsSize==1)
        return nums[0];
    int i,dp[numsSize+1];
    dp[0] = nums[0];
    dp[1] = nums[0]>nums[1] ? nums[0] : nums[1]; 
    for(i=2;i<numsSize;i++){
        dp[i] = max(dp[i-1],dp[i-2]+nums[i]);//相當於在dp[i-2]、dp[i-1]、dp[i-2]+nums[i]中選擇一個最大的作為dp[i]  
    }
    return dp[numsSize-1];
}

七、650.只有兩個鍵的鍵盤
開始在一個記事本上只有一個字元 ‘A’。每次可以對這個記事本進行兩種操作:
Copy All (複製全部) ,Paste (貼上)
給定一個數字 n 。輸出能夠列印出 n 個 ‘A’ 的最少操作次數。
示例 :
輸入: 3
輸出: 3
解釋:
最初, 我們只有一個字元 ‘A’。
第 1 步, 我們使用 Copy All 操作。
第 2 步, 我們使用 Paste 操作來獲得 ‘AA’。
第 3 步, 我們使用 Paste 操作來獲得 ‘AAA’。

/*
首先找出一定的規律:(要獲得i個A,可以由i的最大因子的最小運算元+i除以其最大因子的倍數)
1  2  3  4  5  6  7  8  9  10  ... 16 ... 32
0  2  3  4  5  5  7  6  6  7       8      10
*/
int minSteps(int n){
    if(n==1)
        return 0;
    int dp[n+1],num;
    dp[0]=-1;
    dp[1]=0;
    for(int i=2;i<=n;i++){
        num = maxdiv(i);
        dp[i] = dp[num] + i/num;
    }
    return dp[n];
}

int maxdiv(int x){//此函式用於獲取一個數的最大因子,例如24->12,39->13
    int i;
    for(i=x/2;i>0;i--){
        if(x%i==0){
            break;
        }
    }
    return i;
}

八、120.三角形最小路徑和
給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。相鄰的結點 在這裡指的是下標 與 上一層結點下標 相同或者等於 上一層結點下標 + 1 的兩個結點。
例如,給定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。

#define min(x,y)  ( x<y?x:y )
int minimumTotal(int** triangle, int triangleSize, int* triangleColSize){
    if(triangleSize==1)
        return triangle[0][0];
    int i,j; 
    int **dp=(int**)malloc(sizeof(int*)*triangleSize);  //定義dp陣列的行數
    for(i=0;i<triangleSize;i++){
        dp[i]=(int*)malloc(sizeof(int)*triangleColSize[i]);//定義dp陣列每一行的列數
    }
    dp[0][0]=triangle[0][0];
    dp[1][0]=dp[0][0]+triangle[1][0];
    dp[1][1]=dp[0][0]+triangle[1][1];
 
    for(i=2;i<triangleSize;i++){
        for(j=0;j<triangleColSize[i];j++){
            if(j==0){
                dp[i][j] = dp[i-1][j] + triangle[i][j]; //對於第一列的陣列
            }else if(j==i){
                dp[i][j] = dp[i-1][j-1] + triangle[i][j];  //對於斜邊上的陣列
            }else{
                dp[i][j] = min(dp[i-1][j-1],dp[i-1][j]) + triangle[i][j];
            }  
        }
    }

    i=i-1;
    int res=dp[i][0];
     for(j=1;j<triangleColSize[i];j++){  //遍歷最後一行的陣列,找出路徑和最小的值
        if(res>dp[i][j]) res=dp[i][j];
    }
    return res;
}

九、645.錯誤的集合
集合 S 包含從1到 n 的整數。不幸的是,因為資料錯誤,導致集合裡面某一個元素複製了成了集合裡面的另外一個元素的值,導致集合丟失了一個整數並且有一個元素重複。給定一個陣列 nums 代表了集合 S 發生錯誤後的結果。你的任務是首先尋找到重複出現的整數,再找到丟失的整數,將它們以陣列的形式返回。
示例 :
輸入: nums = [1,2,2,4]
輸出: [2,3]
*此題使用了雜湊表的思想,整數1~n為雜湊表中的鍵,其出現的次數為雜湊表中的值

int* findErrorNums(int* nums, int numsSize, int* returnSize){
    int *res = (int *)malloc(sizeof(int) * 2);
    int *hash  =(int *)malloc(sizeof(int )* (numsSize+1));
    memset(hash,0,sizeof(int )* (numsSize+1));
    int i;
    for(i=0;i<numsSize;i++){//進行一次遍歷,得到所有的鍵值對
        hash[nums[i]] ++;
    }
    for(i=1;i<numsSize+1;i++){
        if(hash[i]==0){//值為0的鍵表示丟失的整數
            res[1] = i;
        }
        if(hash[i] == 2){//值為2的鍵表示重複的鍵
            res[0]  = i;
        }
    }
    *returnSize = 2;
    return res;
}

十、41.缺失的第一個正數
給你一個未排序的整數陣列,預設沒有重複的數,請找出其中沒有出現的最小的正整數。
示例 1:
輸入: [3,4,-1,1]
輸出: 2

示例 2:
輸入: [7,8,9,11,12]
輸出: 1
*由題意可以知道一些隱含條件,只要產生缺失,最後的結果一定是在1~numSize之間,首先可以遍歷一遍陣列,將1到numSize之間的值,放在對應的陣列下標為nums[i]-1位置下

int firstMissingPositive(int* nums, int numsSize){
    int i,temp;
    for(i=0;i<numsSize;i++){
    //當num[i]在1~numSize之間並且沒有位於正確的位置,進行交換
       while ((nums[i] >= 1 && nums[i] <= numsSize) && (nums[i] != nums[nums[i]-1])){
            int tmp = nums[i];
            nums[i] = nums[tmp-1];
            nums[tmp-1] = tmp;
        }
    }
    for(i=0;i<numsSize;i++){//再次遍歷陣列,找到第一個鍵值不對應的值
        if(nums[i]!=(i+1)){
            return i+1;
            break;
        }
    }
    return numsSize+1;//如果都對應,表示沒有產生缺失,返回numsSize+1
}

十一、274.H指數
h 指數的定義:h 代表“高引用次數”(high citations),一名科研人員的 h 指數是指他(她)的 (N 篇論文中)總共有 h 篇論文分別被引用了至少 h 次。且其餘的 N - h 篇論文每篇被引用次數 不超過 h 次。
例如:某人的 h 指數是 20,這表示他已發表的論文中,每篇被引用了至少 20 次的論文總共有 20 篇。
示例:
輸入:citations = [3,0,6,1,5]
輸出:3
解釋:給定陣列表示研究者總共有 5 篇論文,每篇論文相應的被引用了 3, 0, 6, 1, 5 次。由於研究者有 3 篇論文每篇 至少 被引用了 3 次,其餘兩篇論文每篇被引用 不多於 3 次,所以她的 h 指數是 3。
*由題意可以得到一些隱含條件,最後的結果一定是在1~citationsSize之間

int hIndex(int* citations, int citationsSize){
    int i,*hash = (int*)calloc(citationsSize+1,sizeof(int));//cmolloc可以預設將陣列賦值0
    for(i=0;i<citationsSize;i++){
        if(citations[i]>=citationsSize){//當鍵大於等於citationsSize,均相當於citationsSize的值加一
            hash[citationsSize]++;
        }else if(citations[i]>0){ //當鍵在1~citationsSize之間,相應鍵的值加一
            hash[citations[i]]++;
        }
    }
    if(hash[citationsSize]==citationsSize){
        return citationsSize;
    }
    for(i=citationsSize-1;i>=0;i--){//對鍵從大到小遍歷,並且逐步疊加,找出第一個得到的引用次數大於等於鍵的,然後返回引用次數
        hash[i] += hash[i+1];
        if(hash[i]>=i){
            return i;
        }
    }
    return 0;//沒有找到則返回0
}

十二、697.陣列的度
給定一個非空且只包含非負數的整數陣列 nums, 陣列的度的定義是指陣列裡任一元素出現頻數的最大值。找到與 nums 擁有相同大小的度的最短連續子陣列,返回其長度。
示例 :
輸入: [1, 2, 2, 3, 1]
輸出: 2
解釋:
輸入陣列的度是2,因為元素1和2的出現頻數最大,均為2.
連續子陣列裡面擁有相同度的有如下所示:
[1, 2, 2, 3, 1], [1, 2, 2, 3], [2, 2, 3, 1], [1, 2, 2], [2, 2, 3], [2, 2]
最短連續子陣列[2, 2]的長度為2,所以返回2.

int findShortestSubArray(int* nums, int numsSize){
    int times[50000]={0},start[50000]={0},end[50000]={0}; //用times存放每個數的度
    int i,count = 0,min = 50000; //start表示某個數首次出現的位置,end表示其最後出現的位置
    for(i=0;i<numsSize;i++){
        times[nums[i]]++;
        if(times[nums[i]] > count)
            count = times[nums[i]];  //count存放最大的度
        if(times[nums[i]]==1){
            start[nums[i]] = i;
            end[nums[i]] = i;
        }else if(times[nums[i]]>1){
            end[nums[i]] = i;
        }
    }
    for(i=0;i<50000;i++){   //在所有度數最大的值中,找出跨度最小的值,並返回跨度+1
        if(times[i] == count){
            if(end[i]-start[i]<min){
                min = end[i]-start[i];
            }
        }
    }
    return min+1;
}

十三、300.最長上升子序列
給定一個無序的整數陣列,找到其中最長上升子序列的長度。
示例:
輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

int lengthOfLIS(int* nums, int numsSize){
    if(numsSize==0)
        return numsSize;
    int dp[numsSize],i,j,max=1;
    for(i=0;i<numsSize;i++)//將dp陣列全部初始化為1
        dp[i]=1;

    for(i=1;i<numsSize;i++){
        for(j=0;j<i;j++){
            if(nums[j]<nums[i] && dp[j]+1>dp[i]){
                dp[i] = dp[j] + 1;
                if(dp[i]>max){
                    max = dp[i];
                }
            }
        } 
    }
    return max;
}

相關文章