leetcode之动态规划刷题总结3
1-打家劫舍
题目链接:题目链接戳这里!!!
思路:这题思路不难,但是需要注意细节。
递推式:dp[i]=max(dp[i-1],dp[i-2]+nums[i])
class Solution {
public int rob(int[] nums) {
if(nums.length==1){
return nums[0] ;
}
if(nums.length==2){
return Math.max(nums[0],nums[1]) ;
}
int [] dp = new int [nums.length] ;
dp[0] = nums[0] ;
dp[1] = Math.max(nums[0],nums[1]) ;
for(int i=2; i<nums.length; i++){
dp[i] = Math.max(dp[i-1], nums[i]+dp[i-2]) ;
}
return dp[nums.length-1];
}
}
2-打家劫舍II
题目链接:题目链接戳这里!!!
思路:这题与第一题的区别在于防盗系统变成一个环了,递推式还是不变的,只不过第一个和最后一个只能选其一。
class Solution {
public int rob(int[] nums) {
if(nums.length==1){
return nums[0] ;
}
if(nums.length==2){
return Math.max(nums[0],nums[1]) ;
}
return Math.max(f(nums,0,nums.length-1),f(nums,1,nums.length)) ;
}
public int f(int []nums, int begin, int end){
int [] dp = new int [end-begin+1] ;
dp[begin] = nums[begin];
dp[begin+1] = Math.max(nums[begin],nums[begin+1]) ;
for(int i=begin+2; i<end; i++){
dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i]) ;
}
return dp[end-1] ;
}
}
3-比特位计数
题目链接:题目链接戳这里!!!
思路:主要式递推式找到,如下所示:
dp[2i] = dp[i] ;
dp[2i+1] = dp[i]+1;
class Solution {
public int[] countBits(int n) {
if(n==0){
return new int []{0} ;
}
int [] dp = new int [n+1] ;
dp[0] = 0 ;
dp[1] = 1 ;
for(int i=0; i<=n/2; i++){
dp[2*i] = dp[i] ;
if(2*i+1<=n)
dp[2*i+1] = dp[i]+1;
}
return dp ;
}
}
4-最佳买卖股票时期含冷冻期
题目链接:题目链接戳这里!!!
思路:sell[i]表示截至第i天,最后一个操作是卖时的最大收益;
buy[i]表示截至第i天,最后一个操作是买时的最大收益;
cool[i]表示截至第i天,最后一个操作是冷冻期时的最大收益;
递推公式:
sell[i] = max(buy[i-1]+prices[i], sell[i-1]) (第一项表示第i天卖出,第二项表示第i天冷冻)
buy[i] = max(cool[i-1]-prices[i], buy[i-1]) (第一项表示第i天买进,第二项表示第i天冷冻)
cool[i] = max(sell[i-1], buy[i-1], cool[i-1])
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==1){
return 0 ;
}
int [] sale = new int [prices.length] ;
int [] buy = new int [prices.length] ;
int [] coll = new int [prices.length] ;
buy[0] = -prices[0] ;
for(int i=1;i<prices.length; i++){
sale[i] = Math.max(sale[i-1],buy[i-1]+prices[i]) ;
buy[i] = Math.max(buy[i-1],coll[i-1]-prices[i]) ;
coll[i] = Math.max(Math.max(coll[i-1],buy[i-1]),sale[i-1]) ;
}
return sale[prices.length-1] ;
}
}
思路2:
f[i][0]:代表持有股票的最大利润
f[i][1]:代表不持有股票,但是处于冷冻期的最大利润
f[i][2]:代表不持有股票,且不处于冷冻期的最大利润
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==1){
return 0 ;
}
int [][] f = new int [prices.length][3] ;
f[0][0] = -prices[0] ;
for(int i=1;i<prices.length; i++){
f[i][0] = Math.max(f[i-1][0],f[i-1][2]-prices[i]) ;
f[i][1] = f[i-1][0] + prices[i] ;
f[i][2] = Math.max(f[i-1][2],f[i-1][1]) ;
}
return Math.max(f[prices.length-1][1],f[prices.length-1][2]) ;
}
}
5-为运算表达式设计优先级
题目链接:题目链接戳这里!!!
思路:分治法:如果当前字符串全部为数字,则返回数字即可,不需要运算。
否则需要找出当前的符号,递归计算左半部分和右半部分,再根据符号计算最终结果。
class Solution {
public List<Integer> diffWaysToCompute(String expression) {
List<Integer> list = new ArrayList<>() ;
if(isDigits(expression)){
list.add(Integer.parseInt(expression)) ;
return list ;
}
for(int i=0; i<expression.length(); i++){
if(expression.charAt(i)=='+' || expression.charAt(i)=='-' || expression.charAt(i)=='*'){
List<Integer> left = diffWaysToCompute(expression.substring(0,i)) ;
List<Integer> right = diffWaysToCompute(expression.substring(i+1)) ;
char op = expression.charAt(i) ;
for(int j : left){
for(int k : right){
if(op=='+'){
list.add(j+k) ;
}else if(op=='-'){
list.add(j-k) ;
}else if(op=='*'){
list.add(j*k) ;
}
}
}
}
}
return list ;
}
public boolean isDigits(String expression){
for(int i=0; i<expression.length(); i++){
if(!Character.isDigit(expression.charAt(i))){
return false ;
}
}
return true ;
}
}
6-整数拆分
题目链接:题目链接戳这里!!!
思路:动态规划
dp[i]:表示将正整数i拆分成至少两个正整数和之后,这些正整数的最大乘积。
有两种情况:
第一种,i拆分成i和i-j并且i-j不继续拆分,则当前值为j*(i-j)
第二种,i拆分成i和i-j并且i-j继续拆分,则当前值为j*dp[i-j]
每一轮找出第j个位置拆分的最大值即可。
dp[n]表示将正整数n拆分成至少两个正整数和之后,这些正整数的最大乘积。
class Solution {
public int integerBreak(int n) {
int [] dp = new int [n+1] ;
for(int i=2; i<=n; i++){
int curMax = 0;
for(int j=1; j<i; j++){
curMax = Math.max(curMax,Math.max(j*(i-j),j*dp[i-j])) ;
}
dp[i] = curMax ;
}
return dp[n] ;
}
}
7-计算各个位数不同的数字个数
题目链接:题目链接戳这里!!!
思路1:递归法。
所有位不相同
即第一位9种选择
第二位9种选择(因为多一个0,所有有9种)
第三位8种…接下来依次递减
所有n位数有多少个不同即可获得
在返回count加上小他一位的即可
return count+countNumbersWithUniqueDigits(n-1);
class Solution {
public int countNumbersWithUniqueDigits(int n) {
if(n==0){
return 1 ;
}
if(n==1){
return 10 ;
}
int j = 9 ;
int cnt = 9 ;
for(int i=1;i<n; i++){
cnt *= j ;
j -- ;
}
return countNumbersWithUniqueDigits(n-1) + cnt ;
}
}
思路2:动态规划
class Solution {
public int countNumbersWithUniqueDigits(int n) {
if(n==0){
return 1;
}
int [] dp = new int [n+1] ;
dp[0] = 1 ;
dp[1] = 10;
for(int i=2; i<=n; i++){
dp[i] = dp[i-1] + (dp[i-1]-dp[i-2])*(10-(i-1));
}
return dp[n] ;
}
}
8-判断子序列
题目链接:题目链接戳这里!!!
思路1:双指针法。
class Solution {
public boolean isSubsequence(String s, String t) {
int ls = 0, lt = 0 ;
while(ls<s.length() && lt<t.length()){
if(s.charAt(ls)==t.charAt(lt)){
ls ++ ;
}
lt ++ ;
}
return ls==s.length() ;
}
}
思路2:调库法,哈哈哈
class Solution {
public boolean isSubsequence(String s, String t) {
int idx = -1 ;
for(char c : s.toCharArray()){
idx = t.indexOf(c,idx+1) ;
if(idx==-1){
return false ;
}
}
return true ;
}
}
9-等差数列划分
题目链接:题目链接戳这里!!!
思路:规律题。
这道题主要是需要找到其规律,从小的例子出发,仔细观察,会发现当整个数组为(1, 2, 3, 4, 5, 6)的时候,我们先取出前三个,(1, 2, 3)的等差数列的个数为1,(1, 2, 3, 4)的等差数列的个数为3,(1, 2, 3, 4, 5)的等差数列的个数为6,(1, 2, 3, 4, 5, 6)的等差数列个数为10,以此类推我们可以很容易的发现在一个等差数列中加入一个数字,如果还保持着等差数列的特性,每次的增量都会加1,如果刚加进来的数字与原先的序列构不成等差数列,就将增量置为0,接下来继续循环,执行以上的逻辑即可.可以发现,这道题只要找到规律还是相当的简单。
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int add = 0 ;
int res = 0 ;
for(int i=2; i<nums.length; i++){
if(nums[i]-nums[i-1]==nums[i-1]-nums[i-2]){
res += (++add) ;
}else{
add = 0 ;
}
}
return res ;
}
}
10-分割等和子集
题目链接:题目链接戳这里!!!
思路:这是典型的0-1背包的变形题,我们其实要求的就是从集合中取出一些数字,求和等于所有集合元素和的一半,则返回为true,否则返回为false .这道题与传统的「0-1 背包问题」的区别在于,传统的「0-−1 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。
根据数组的长度 n判断数组是否可以被划分。如果 n<2,则不可能将数组分割成元素和相等的两个子集,因此直接返回 false。
计算整个数组的元素和sum 以及最大元素 max,若sum是奇数,则肯定不可能将数组分割成两个相等的子集,返回false,如果最大值max大于sum/2,则返回false .
接下来定义dp数组,dp[i][j]表示从数组的 [0,i]下标范围内选取若干个正整数(可以是 0 个),是否存在一种选取方案使得被选取的正整数的和等于 j。初始时,dp 中的全部元素都是 false。
初始化dp数组
dp[i][0]=true
dp[0][nums[0]]=true
递推表达式如下:
class Solution {
public boolean canPartition(int[] nums) {
int n = nums.length ;
if(n<2){
return false ;
}
int max = 0,sum=0 ;
for(int i=0; i<n; i++){
max = Math.max(max,nums[i]) ;
sum += nums[i] ;
}
int target = sum / 2 ;
if(sum%2!=0 || max>target){
return false ;
}
boolean [][] dp = new boolean [n][target+1] ;
dp[0][nums[0]] = true ;
for(int i=0; i<n; i++){
dp[i][0] = true ;
}
for(int i=1; i<n; i++){
int num = nums[i] ;
for(int j=1; j<=target; j++){
if(j>=num){
dp[i][j] = (dp[i-1][j] | dp[i-1][j-num]) ;
}else{
dp[i][j] = dp[i-1][j] ;
}
}
}
return dp[n-1][target] ;
}
}