【leetcode算法面试】leetcode题目9-动态规划

动态规划基本思想

    态规划算法的核心就是记住已经解决过的子问题的解

 基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了信息

在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其它局部解。依次解决各子问题,最后一个子问题就是初始问题的解

    因为动态规划解决的问题多数有重叠子问题这个特点。为降低反复计算,将其不同阶段的不同状态保存在二维数组中

    与分治法最大的区别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

 

动态规划基本步骤

    求解的方式有两种

  • 自顶向下的备忘录法 
  • 自底向上

 

题号题目说明
5

Longest Palindromic Substring 最长回文子串

 
53Maximum Subarray 最大子数组分治法 / 动态规划
55Jump Game 跳跃游戏贪心法 / 动态规划
62Unique Paths 不同的路径 
63Unique Paths II 不同的路径之二 
64

Minimum Path Sum 最小路径和

 
70Climbing Stairs 爬梯子 
72Edit Distance 编辑距离 
97Interleaving String 交织相错的字符串 
123Best Time to Buy and Sell Stock III 
152Maximum Product Subarray 求最大子数组乘积 
   
   

        

5. [LeetCode] Longest Palindromic Substring 最长回文子串

      维护一个二维数组dp,其中dp[i][j]表示字符串区间[i, j]是否为回文串,当i = j时,只有一个字符,肯定是回文串,如果i = j + 1,说明是相邻字符,此时需要判断s[i]是否等于s[j],如果i和j不相邻,即i - j >= 2时,除了判断s[i]和s[j]相等之外,dp[j + 1][i - 1]若为真,就是回文串,通过以上分析,可以写出递推式如下:

dp[i, j] = 1                                               if i == j

           = s[i] == s[j]                                if j = i + 1

           = s[i] == s[j] && dp[i + 1][j - 1]    if j > i + 1      

func longestPalindrome(s string) string {
	var n = len(s)
	if n <= 1 {
		return s
	}

	var dp [][]int
	for i := 0; i < n; i++ {
		tmp := make([]int, n)
		dp = append(dp, tmp)
		dp[i][i] = 1
	}

	var left, right, len = 0, 0, 0

	for j := 0; j < n; j++ {
		for i := 0; i < j; i++ {
			if s[i] == s[j] && (j-i < 2 || dp[i+1][j-1] == 1) {
				dp[i][j] = 1
			}

			if dp[i][j] == 1 && len < j-i+1 {
				len = j - i + 1
				left = i
				right = j
			}
		}
	}
	return s[left : right+1]
}

 

53. [LeetCode] Maximum Subarray 最大子数组

Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.

Example:

Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.

Follow up:

If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.

   连续子数组最大和

      设 sum[i] 以第 i 个元素结尾且和最大的连续子数组。即 sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断 sum[i-1] + a[i] 是否大于 a[i] 来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,只需要保留上一次的即可。

func maxSubArray(nums []int) int {
	if len(nums) == 0 {
		return 0
	}
	dp := make([]int, len(nums))
	dp[0] = nums[0]
	max := nums[0]

	for i:=1; i<len(nums); i++ {
		if dp[i-1] > 0 {
			dp[i] = nums[i] + dp[i-1]
		} else {
			dp[i] = nums[i]
		}
		if max < dp[i] {
			max = dp[i]
		}
	}
	return max
}

 

55. [LeetCode] Jump Game 跳跃游戏

Given an array of non-negative integers, you are initially positioned at the first index of the array.

Each element in the array represents your maximum jump length at that position.

Determine if you are able to reach the last index.

Example 1:

Input: [2,3,1,1,4]
Output: true
Explanation: Jump 1 step from index 0 to 1, then 3 steps to the last index.

Example 2:

Input: [3,2,1,0,4]
Output: false
Explanation: You will always arrive at index 3 no matter what. Its maximum
             jump length is 0, which makes it impossible to reach the last index.

     dp[i]表示达到i位置时剩余的步数,dp[i] = max(dp[i - 1], nums[i - 1]) - 1

class Solution {
public:
    bool canJump(vector<int>& nums) {
        vector<int> dp(nums.size(), 0);
        for (int i = 1; i < nums.size(); ++i) {
            dp[i] = max(dp[i - 1], nums[i - 1]) - 1;
            if (dp[i] < 0) return false;
        }
        return true;
    }
};

     贪婪算法Greedy Algorithm,reach,表示最远能到达的位置,初始化为0。遍历数组中每一个数字,如果当前坐标大于reach或者reach已经抵达最后一个位置则跳出循环,否则就更新reach的值为其和i + nums[i]中的较大值

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size(), reach = 0;
        for (int i = 0; i < n; ++i) {
            if (i > reach || reach >= n - 1) break;
            reach = max(reach, i + nums[i]);
        }
        return reach >= n - 1;
    }
};

 

62. [LeetCode] Unique Paths 不同的路径

    dp[i][j]表示到当前位置不同的走法的个数,然后可以得到递推式为: dp[i][j] = dp[i - 1][j] + dp[i][j - 1]

解法: 我们用 f[i][j]  表示走到坐标 (i,j)有几种走法,转移方程为: f[i][j] = f[i-1][j] + f[i][j-1] 

初始化条件为: f[0][j] = f[i][0] = 1, f[0][0] = 0;

func uniquePaths(m int, n int) int {
	var dp [][]int
	for i := 0; i < m; i++ {
		temp := make([]int, n)
		for j := 0; j < n; j++ {
			temp[j] = 1
		}
		dp = append(dp, temp)
	}

	for i := 1; i < m; i++ {
		for j := 1; j < n; j++ {
			dp[i][j] = dp[i-1][j] + dp[i][j-1]
		}
	}
	return dp[m-1][n-1]
}

 

63. [LeetCode] Unique Paths II 不同的路径之二

路径中加了一些障碍物,当遇到为1的点,将该位置的dp数组中的值清零

func uniquePathsWithObstacles(obstacleGrid [][]int) int {
	var m = len(obstacleGrid)
	var n = len(obstacleGrid[0])

	if m == 0 || n == 0 || obstacleGrid[0][0] == 1 {
		return 0
	}

	var dp [][]int
	for i := 0; i < m; i++ {
		temp := make([]int, n)
		dp = append(dp, temp)
		for j := 0; j < n; j++ {
			temp[j] = 1
		}
	}

	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if obstacleGrid[i][j] == 1 {
				dp[i][j] = 0
			} else if i == 0 && j == 0 {
				dp[i][j] = 1
			} else if i == 0 && j > 0 {
				dp[i][j] = dp[i][j-1]
			} else if i > 0 && j == 0 {
				dp[i][j] = dp[i-1][j]
			} else {
				dp[i][j] = dp[i-1][j] + dp[i][j-1]
			}
		}
	}
	return dp[m-1][n-1]
}

 

64. [LeetCode] Minimum Path Sum 最小路径和

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.

Note: You can only move either down or right at any point in time.

Example:

Input:
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
Output: 7
Explanation: Because the path 1→3→1→1→1 minimizes the sum.

     dp[i][j]表示当前位置的最小路径和,递推式也容易写出来 dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j-1]

    初始化条件为:f[0][0] = grid[0][0], f[0][j] = f[0][j-1] + grid[0][j], f[i][0] = f[i-1][0] + grid[i][0]

func minPathSum(grid [][]int) int {
	var m = len(grid)
	if m <= 0 {
		return 0
	}
	var n = len(grid[0])
	if n <= 0 {
		return 0
	}

	var dp [][]int
	for i := 0; i < m; i++ {
		temp := make([]int, n)
		dp = append(dp, temp)
	}
	dp[0][0] = grid[0][0]

	for i := 1; i < m; i++ {
		dp[i][0] = grid[i][0] + dp[i-1][0]
	}
	for i := 1; i < n; i++ {
		dp[0][i] = grid[0][i] + dp[0][i-1]
	}

	for i := 1; i < m; i++ {
		for j := 1; j < n; j++ {
			var min = dp[i][j-1]
			if min > dp[i-1][j] {
				min = dp[i-1][j]
			}
			dp[i][j] = grid[i][j] + min
		}
	}
	return dp[m-1][n-1]
}

 

70. [LeetCode] Climbing Stairs 爬梯子

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

Note: Given n will be a positive integer.

Example 1:

Input: 2
Output: 2
Explanation: There are two ways to climb to the top.
1. 1 step + 1 step
2. 2 steps

Example 2:

Input: 3
Output: 3
Explanation: There are three ways to climb to the top.
1. 1 step + 1 step + 1 step
2. 1 step + 2 steps
3. 2 steps + 1 step

     状态转移方程其实就是Fibonacci数列,f[i]  表示爬 i 层楼梯有多少种爬法, 转移方程为:f[i] = f[i-1] + f[i-2]

    滚动数组优化,时间复杂度空间复杂度优化前都是O(n) 

class Solution {
public:
    int climbStairs(int n) {
        if (n <= 0) return 0;
        
        int first = 0; 
        int second = 1;
        int res;
        
        for (int i=1; i<=n; i++) {
            res = first + second;
            first = second;
            second = res;
        }
        
        return res;
    }
};

 

72. Edit Distance 编辑距离

  • 当word1[i] == word2[j]时,dp[i][j] = dp[i - 1][j - 1]
  • 其他情况时,dp[i][j]是其左,左上,上的三个值中的最小值加1
func minDistance(word1 string, word2 string) int {
	var n1, n2 = len(word1), len(word2)
	var res [][]int
	for i:=0; i<=n1; i++ {
		var temp = make([]int, n2+1)
		res = append(res, temp)
	}
	for i:=0; i<=n1; i++ {res[i][0] = i}
	for i:=0; i<=n2; i++ {res[0][i] = i}
	var min = func(a, b int) int {
		if a < b {return a}
		return b
	}
	for i:=1; i<=n1; i++ {
		for j:=1; j<=n2; j++ {
			if word1[i-1] == word2[j-1] {
				res[i][j] = res[i-1][j-1]
			} else {
				res[i][j] = min(res[i-1][j-1], min(res[i-1][j], res[i][j-1])) + 1
			}
		}
	}
	return res[n1][n2]
}

 

122. Best Time to Buy and Sell Stock II

       二维数组 local[N][M],global[N][M](全0),local[i][j],和global[i][j] 分别表示前i天最多进行j次交易的局部最优和全局最优。这里的局部最优指的是,在第i天卖出股票所得到的最大收入,全局最优指的是在第i天或第i天之前卖出股票所得的最大收入。

       设天数编号为1..N,从 i == 2 开始遍历数组,local[i][j] = max(  local[i-1][j] + price[i]-price[i-1] , global[i-1][j-1] + price[i] - price[i-1] ) ; global[i][j] = max(local[i][j], global [i][j-1] ); 最终结果存放在global[N][M] ;


class Solution {
public:
    int maxProfit(vector<int> &prices) {
        int size = prices.size();
        if( size <= 1 )
            return 0;
        int M = size / 2;
        vector<vector<int> > local ( size , vector<int>( M +1 , 0 ) );
        vector<vector<int> > global( size, vector<int>( M+1 , 0 ) );
        
        for( int i = 1 ;  i< size ; i++){
            for( int j = 1; j <= M; j++){
              	local[i][j] = max(local[i-1][j] + prices[i] - prices[i-1],
                           global[i-1][j-1] + prices[i] - prices[i-1]);
            	global[i][j] = max( global[i-1][j], local[i][j]);  
            }
        }
        return global[size-1][M];
    }

原文:https://blog.csdn.net/renjiewen1995/article/details/52442546 

 

97. Interleaving String 交织相错的字符串

  • 字符串s1和s2的长度和必须等于s3的长度,如果不等于,肯定返回false
  • 非边缘位置dp[i][j]时,它的左边或上边有可能为True或是False,两边都可以更新过来,只要有一条路通着,那么这个点就可以为True
  • 其中dp[i][j] 表示的是 s2 的前 i 个字符和 s1 的前 j 个字符是否匹配 s3 的前 i+j 个字符

递推公式为:

dp[i][j] = (dp[i - 1][j] && s1[i - 1] == s3[i - 1 + j]) || (dp[i][j - 1] && s2[j - 1] == s3[j - 1 + i]);

func isInterleave(s1 string, s2 string, s3 string) bool {
	var n1, n2, n3 = len(s1), len(s2), len(s3)
	if n1+n2 != n3 {
		return false
	}
	var dp = make([][]bool, n1+1)
	for i := 0; i <= n1; i++ {
		temp := make([]bool, n2+1)
		dp[i] = temp
	}
	dp[0][0] = true
	for i := 1; i <= n1; i++ {
		dp[i][0] = dp[i-1][0] && s1[i-1] == s3[i-1]
	}
	for j := 1; j <= n2; j++ {
		dp[0][j] = dp[0][j-1] && s2[j-1] == s3[j-1]
	}
	for i := 1; i <= n1; i++ {
		for j := 1; j <= n2; j++ {
			dp[i][j] = (dp[i][j-1] && s2[j-1] == s3[i+j-1]) || (dp[i-1][j] && s1[i-1] == s3[i+j-1])
		}
	}
	return dp[n1][n2]
}

 

 

123. Best Time to Buy and Sell Stock III

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

Example 1:

Input: [3,3,5,0,0,3,1,4]
Output: 6
Explanation: Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3.
             Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3.

Example 2:

Input: [1,2,3,4,5]
Output: 4
Explanation: Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4.
             Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are
             engaging multiple transactions at the same time. You must sell before buying again.

Example 3:

Input: [7,6,4,3,1]
Output: 0
Explanation: In this case, no transaction is done, i.e. max profit = 0.

     最多允许两次交易,k次交易的算法,然后最多进行两次我们只需要把k取成2

递推式为:

local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)

global[i][j] = max(local[i][j], global[i - 1][j])

class Solution {
public:
    int maxProfit(vector<int> &prices) {
        if (prices.empty()) return 0;
        int n = prices.size(), g[n][3] = {0}, l[n][3] = {0};
        for (int i = 1; i < prices.size(); ++i) {
            int diff = prices[i] - prices[i - 1];
            for (int j = 1; j <= 2; ++j) {
                l[i][j] = max(g[i - 1][j - 1] + max(diff, 0), l[i - 1][j] + diff);
                g[i][j] = max(l[i][j], g[i - 1][j]);
            }
        }
        return g[n - 1][2];
    }
};

参考: https://blog.csdn.net/linhuanmars/article/details/23236995

 

152. Maximum Product Subarray 求最大子数组乘积

    最小的与最大的数,比如最小的负数与负数有可能最大

func maxProduct(nums []int) int {
	var n = len(nums)
	if n <=0 {return 0}
	var min = func(a, b int) int {
		if a < b {return a}
		return b
	}
	var max = func(a, b int) int {
		if a > b {return a}
		return b
	}

	var global, local, res = make([]int, n+1), make([]int, n+1), nums[0]
	global[0], local[0] = nums[0], nums[0]
	for i:=1; i<n; i++ {
		global[i] = max(max(global[i-1]*nums[i], local[i-1]*nums[i]), nums[i])
		local[i] = min(min(global[i-1]*nums[i], local[i-1]*nums[i]), nums[i])
		res = max(res, global[i])
	}
	return res
}

 

一. 最大子序列(连续)如,5, -9, 4, 3   (结果为7)

for (int i=0; i<n; i++) {
    current += a[i];
    if (current > res) res = current;
    if (current < 0)  current = 0;
    return res;
}

 

二. 最长递增子序列(不连续) 

temp(j)以第J个元素结尾点递增字串长度
temp(j) = max(temp(i), i<j && a[i]<a[j]) + 1
temp[0] = 1;
for (int i=1; i<n; i++) {
    temp[i] = 1;
    for (int j=0; j<i; j++) {
         if (a[i] > a[j])
               temp[i] = max(temp[j], temp[i] + 1)
    】
     maxLen = max(maxLen, temp[i]);
     return maxLen;
}

 

三. 求两个字符串的最长的连续公共子串

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
//求公共子串(连续),注意跟求公共子序列有区别  
int lcstr( const char* s1,const char* s2)  
{  
//clen保存公共子串的最大长度,s1_Mindex保存 s1公共子串的最后一个元素的位置  
	int len1,len2,i,k,cLen=1,s1_Mindex=0;  
          int **c; 
		  if(s1==NULL || s2==NULL) return -1;  
		  len1=strlen(s1);  
		  len2=strlen(s2); 
		  if(len1< 1 || len2 < 1) return -1;  
		  c=malloc(sizeof(int*)*len1);  
		  for(i=0;i<len1;i++)  { 
			  c[i]=(int *)malloc(len2*sizeof(int));  
			  memset(c[i],0,len2*sizeof(int));  
		  }                                                          /**********init end!*************/  
		  
		  for(i=0;i<len1;i++)  
		  {  
			  for(k=0;k<len2;k++)  
			  {  
				  if(i==0 || k==0)  
				  {  
					  if(s1[i]==s2[k]) c[i][k]=1;  
					  else c[i][k]=0;  
				  } 
				  else  
				  {  
					  if (s1[i] == s2[k])  
					  {  
						  c[i][k] = c[i - 1][k - 1] + 1;  
						  if (cLen < c[i][k])  
						  {  
							  cLen = c[i][k]; 
							  s1_Mindex = i;
						  }  
					  }  
				  }
			  }  
		  }                                                                                         \
		  // printf the one of lcs 只是其中一条,如果存在多条。  
		  for(i=0;i<cLen;i++) 
		  {  
			  printf("%c",*(s1+s1_Mindex-cLen+1+i));  
		  } 
		  /*****free array*************/  
		  for(i=0;i<len1;i++)  
			  free(c[i]);  
		  free(c);  
		  
		  return cLen; 

}  
int main(void) {  
	char a[]="abcgooglecba";  
	char b[]="cbagoogleABVC";  
	printf("\nlcstr = %d\n",lcstr(a,b));  
	return 0;  
}


http://www.cnblogs.com/xwdreamer/archive/2011/06/21/2296995.html

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值