Leetcode(7)——动态规划和贪心算法

格式:

题号+题名+简单思路+code



动态规划




T53: 最大子序和

  • f ( n ) : f(n): f(n): 以包括索引n位置的子数组的最大和
  • f ( n ) = max ⁡ { f ( n − 1 ) , 0 } + n u m s [ n ] f(n)=\max\{f(n-1),0\}+nums[n] f(n)=max{ f(n1),0}+nums[n]
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        memo=0
        ans=-float("inf")
        for i in nums:
            memo=max(0,memo)+i
            if memo>ans:
                ans=memo
        return ans



进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
  • 使用线段树能够在O(logN)时间内解决任意区间内的该问题



T300: 最长递增子序列

  • f ( n ) : f(n): f(n): 以包括索引n位置的最长递增子序列长度
  • f ( n ) = max ⁡ n u m s [ n ] > n u m s [ i ] { f ( i 1 ) , f ( i 2 ) . . . f ( i m ) } + 1 f(n)=\max_{nums[n]>nums[i]}\{f(i_1),f(i_2)...f(i_m)\}+1 f(n)=maxnums[n]>nums[i]{ f(i1),f(i2)...f(im)}+1
func lengthOfLIS(nums []int) int {
     
    if len(nums)==0 {
     
        return 0
    }
    memo:=make([]int,len(nums))
    memo[0]=1
    ans:=1
    for i:=1;i<len(memo);i++ {
     
        tmp:=1
        for j:=0;j<i;j++ {
     
            if nums[j]<nums[i] {
     
                if memo[j]+1>tmp {
     
                    tmp=memo[j]+1
                }
            }
        }
        memo[i]=tmp
        if tmp>ans {
     
            ans=tmp
        }
    }
    return ans
}




T70: 爬楼梯

  • 两个变量
class Solution:
    def climbStairs(self, n: int) -> int:
        a=1
        b=1
        for i in range(2,n+1):
            c=a+b
            a=b
            b=c
        return b




T91: 解码方式

  • 有约束的爬楼梯
  • f ( n ) f(n) f(n): s [ : n ] s[:n] s[:n]位置的解码方式(包括n)
  • f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n1)+f(n2)
class Solution:
    def numDecodings(self, s: str) -> int:
        if len(s)==1:
            if s=="0":
                return 0
            return 1
        if s[0]=="0":
            return 0
        a=1
        tmp=s[:2]
        b=0
        if int(tmp)<=26 and int(s)>=11 and tmp[1]!="0":
            b=2
        elif tmp[1]=="0":
            if tmp[0]>="3":
                return 0
            b=1
        else:
            b=1
        t=b
        for i in range(2,len(s)):
            tmp=s[i-1:i+1]
            ti=int(tmp)
            if ti<=26 and ti>=11 and tmp[1]!="0":
                t=a+b
                a=b
                b=t
            elif ti==0:
                return 0
            elif tmp[1]=="0":
                if tmp[0]>="3":
                    return 0
                t=a
                a=b
                b=t
            else:
                a=b
                t=b
        return t




剑指46: 把数字翻译成字符串

class Solution:
    def translateNum(self, num: int) -> int:
        str_num=str(num)
        if len(str_num)==0:
            return 0
        if len(str_num)==1:
            return 1
        two=1
        one=1
        for i in range(1, len(str_num)):
            tmp=one
            if str_num[i-1]=="1" or (str_num[i-1]=="2" and int(str_num[i])<=5):    # 约束
                one=one+two
            two=tmp
        return one




T139: 单词拆分

  • f ( n ) f(n) f(n): s [ : n ] s[:n] s[:n]位置的拆分方式(包括n)
  • f ( n ) = f ( n − 1 ) ∣ ∣ f ( n − 2 ) ∣ ∣ . . . ∣ ∣ f ( 0 ) f(n) = f(n-1) || f(n-2)||...||f(0) f(n)=f(n1)f(n2)...f(0)
  • f ( 0 ) = T r u e f(0)=True f(0)=True
  • O ( N 2 ) O(N^2) O(N2)时间复杂度
  • DP
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        if s=="":
            return False
        dic=set(wordDict)
        memo=[False for i in range(len(s)+1)]
        memo[0]=True
        for i in range(len(s)):
            j=i
            while j>=0:
                if s[j:i+1] in dic and memo[j]:
                    memo[i+1]=True
                    break
                j-=1
        return memo[-1]
  • 记忆化递归
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        if s=="":
            return False
        self.dic=set(wordDict)
        self.memo=[None for i in range(len(s)+1)]
        self.memo[0]=True
        return self.assist(s,len(s)-1)
    
    def assist(self,s:str,end:int) -> bool:
        if self.memo[end+1]!=None:
            return self.memo[end+1]
        for i in range(end,-1,-1):
            if s[i:end+1] in self.dic and self.assist(s,i-1):
                self.memo[end+1]=True
                return True
        self.memo[end+1]=False
        return False




T343: 整数拆分

  • 记忆化递归
var memo map[int]int =map[int]int{
     1:1}

func integerBreak(n int) int {
     
    if v, ok:=memo[n];ok {
     
        return v
    }
    ans:=0
    for i:=1;i<n;i++ {
     
        tmp:=i*integerBreak(n-i)
        if tmp>ans {
     
            ans=tmp
        }
        if i*(n-i)>ans {
     
            ans=i*(n-i)
        }
    }
    memo[n]=ans
    return ans
}
class Solution:
    memo=[0 for i in range(59)]
    def integerBreak(self, n: int) -> int:
        if self.memo[n]!=0:
            return self.memo[n]
        if n==1:
            return 1
        tmp=0
        for i in range(1,n):
            tmp=max(tmp, i*self.integerBreak(n-i), i*(n-i))
        self.memo[n]=tmp
        return tmp




T198: 打家劫舍

  • f ( n ) : f(n): f(n): 至包括索引n位置的最大利润
  • f ( n ) = max ⁡ { f ( n − 1 ) , f ( n − 2 ) + n u m s [ n ] } f(n)=\max\{f(n-1), f(n-2)+nums[n]\} f(n)=max{ f(n1),f(n2)+nums[n]}
  • DP
func rob(nums []int) int {
     
    a:=0
    b:=0
    for i:=0;i<len(nums);i++ {
     
        if a+nums[i]>b {
     
            tmp:=a
            a=b
            b=tmp+nums[i]
        } else {
     
            a=b
        }
    }
    return b
}




买卖股票

  • f ( i , k , 0 ) : f(i,k,0): f(i,k,0): 第i天,最多进行k次交易,当前状态为不持有
  • f ( i , k , 1 ) : f(i,k,1): f(i,k,1): 第i天,最多进行k次交易,当前状态为持有

基本公式

{ f ( i , k , 0 ) = max ⁡ { f ( i − 1 , k , 0 ) , f ( i − 1 , k , 1 ) + p r i c e s [ i ] } f ( i , k , 1 ) = max ⁡ { f ( i − 1 , k , 1 ) , f ( i − 1 , k − 1 , 0 ) − p r i c e s [ i ] } f ( − 1 , k , 0 ) = f ( i , 0 , 0 ) = 0 f ( − 1 , k , 1 ) = f ( i , 0 , 1 ) = − inf ⁡ \begin{cases} f(i,k,0)=\max\{f(i-1,k,0),f(i-1,k,1)+prices[i]\}\\ f(i,k,1)=\max\{f(i-1,k,1),f(i-1,k-1,0)-prices[i]\}\\ f(-1,k,0)=f(i,0,0)=0\\ f(-1,k,1)=f(i,0,1)=- \inf\\ \end{cases} f(i,k,0)=max{ f(i1,k,0),f(i1,k,1)+prices[i]}f(i,k,1)=max{ f(i1,k,1),f(i1,k1,0)prices[i]}f(1,k,0)=f(i,0,0)=0f(1,k,1)=f(i,0,1)=inf
a n s = f ( n , k , 0 ) ans=f(n,k,0) ans=f(n,k,0)

T121: 一笔交易

{ f ( i , 0 ) = max ⁡ { f ( i − 1 , 0 ) , f ( i − 1 , 1 ) + p r i c e s [ i ] } f ( i , 1 ) = max ⁡ { f ( i − 1 , 1 ) , − p r i c e s [ i ] } f ( 0 , 0 ) = 0 f ( 0 , 1 ) = − p r i c e s [ 0 ] \begin{cases} f(i,0)=\max\{f(i-1,0),f(i-1,1)+prices[i]\}\\ f(i,1)=\max\{f(i-1,1),-prices[i]\}\\ f(0,0)=0\\ f(0,1)=-prices[0]\\ \end{cases} f(i,0)=max{ f(i1,0),f(i1,1)+prices[i]}f(i,1)=max{ f(i1,1),prices[i]}f(0,0)=0f(0,1)=prices[0]

T122: 无限交易

{ f ( i , 0 ) = max ⁡ { f ( i − 1 , 0 ) , f ( i − 1 , 1 ) + p r i c e s [ i ] } f ( i , 1 ) = max ⁡ { f ( i − 1 , 1 ) , f ( i − 1 , 0 ) − p r i c e s [ i ] } f ( − 1 , 0 ) = 0 f ( − 1 , 1 ) = − inf ⁡ \begin{cases} f(i,0)=\max\{f(i-1,0),f(i-1,1)+prices[i]\}\\ f(i,1)=\max\{f(i-1,1),f(i-1,0)-prices[i]\}\\ f(-1,0)=0\\ f(-1,1)=-\inf\\ \end{cases} f(i,0)=max{ f(i1,0),f(i1,1)+prices[i]}f(i,1)=max{ f(i1,1),f(i1,0)prices[i]}f(1,0)=0f(1,1)=inf

T123: 最多两笔

{ f ( i , k , 0 ) = max ⁡ { f ( i − 1 , k , 0 ) , f ( i − 1 , k , 1 ) + p r i c e s [ i ] } f ( i , k , 1 ) = max ⁡ { f ( i − 1 , k , 1 ) , f ( i − 1 , k − 1 , 0 ) − p r i c e s [ i ] } f ( − 1 , k , 0 ) = f ( i , 0 , 0 ) = 0 f ( − 1 , k , 1 ) = f ( i , 0 , 1 ) = − inf ⁡ \begin{cases} f(i,k,0)=\max\{f(i-1,k,0),f(i-1,k,1)+prices[i]\}\\ f(i,k,1)=\max\{f(i-1,k,1),f(i-1,k-1,0)-prices[i]\}\\ f(-1,k,0)=f(i,0,0)=0\\ f(-1,k,1)=f(i,0,1)=- \inf\\ \end{cases} f(i,k,0)=max{ f(i1,k,0),f(i1,k,1)+prices[i]}f(i,k,1)=max{ f(i1,k,1),f(i1,k1,0)prices[i]}f(1,k,0)=f(i,0,0)=0f(1,k,1)=f(i,0,1)=inf

T188: 最多k笔

  • 同基本公式
import "math"

var MININT int=^(int(^uint32(0)>>1))

func maxProfit(k int, prices []int) int {
     
    if len(prices)==0 {
     
        return 0
    }
    max_k:=k
    if max_k>=len(prices)/2 {
     
        return maxProfitInf(prices)
    }
    memo:=make([][][2]int,len(prices))
    for i:=0;i<len(prices);i++ {
     
        memo[i]=make([][2]int,max_k+1)
    }
    for i:=0;i<len(prices);i++ {
     
        for k:=0;k<=max_k;k++ {
     
            if i==0 {
     
                memo[i][k][0]=0
                memo[i][k][1]=-prices[i]
            } else if k==0 {
     
                memo[i][k][0]=0
                memo[i][k][1]=MININT
            } else {
     
                memo[i][k][0]=int(math.Max(float64(memo[i-1][k][0]),float64(memo[i-1][k][1]+prices[i])))
                memo[i][k][1]=int(math.Max(float64(memo[i-1][k][1]),float64(memo[i-1][k-1][0]-prices[i])))
            }
        }
    }
    return memo[len(prices)-1][max_k][0]
}

func maxProfitInf(prices []int) int {
     
    if len(prices)<=1 {
     
        return 0
    }
    profit:=0
    for i:=1;i<len(prices);i++ {
     
        if prices[i]>prices[i-1] {
     
            profit+=prices[i]-prices[i-1]
        }
    }
    return profit
}

T309: 无限次交易,冷冻期

{ f ( i , 0 ) = max ⁡ { f ( i − 1 , 0 ) , f ( i − 1 , 1 ) + p r i c e s [ i ] } f ( i , 1 ) = max ⁡ { f ( i − 1 , 1 ) , f ( i − 2 , 0 ) − p r i c e s [ i ] } f ( − 1 , 0 ) = 0 f ( − 1 , 1 ) = − inf ⁡ \begin{cases} f(i,0)=\max\{f(i-1,0),f(i-1,1)+prices[i]\}\\ f(i,1)=\max\{f(i-1,1),f(i-2,0)-prices[i]\}\\ f(-1,0)=0\\ f(-1,1)=-\inf\\ \end{cases} f(i,0)=max{ f(i1,0),f(i1,1)+prices[i]}f(i,1)=max{ f(i1,1),f(i2,0)prices[i]}f(1,0)=0f(1,1)=inf

T714: 无限次交易,手续费

{ f ( i , 0 ) = max ⁡ { f ( i − 1 , 0 ) , f ( i − 1 , 1 ) + p r i c e s [ i ] } f ( i , 1 ) = max ⁡ { f ( i − 1 , 1 ) , f ( i − 1 , 0 ) − p r i c e s [ i ] − f e e } f ( − 1 , 0 ) = 0 f ( − 1 , 1 ) = − inf ⁡ \begin{cases} f(i,0)=\max\{f(i-1,0),f(i-1,1)+prices[i]\}\\ f(i,1)=\max\{f(i-1,1),f(i-1,0)-prices[i]-fee\}\\ f(-1,0)=0\\ f(-1,1)=-\inf\\ \end{cases} f(i,0)=max{ f(i1,0),f(i1,1)+prices[i]}f(i,1)=max{ f(i1,1),f(i1,0)prices[i]fee}f(1,0)=0f(1,1)=inf



T322: 零钱置换

  • f ( n ) : f(n): f(n): 总数为n的钱能兑换成多少枚零钱
  • f ( n ) = min ⁡ { f ( n − c 1 ) , f ( n − c 2 ) . . . f ( n − c m ) } + 1 f(n)=\min\{f(n-c_1),f(n-c_2)...f(n-c_m)\}+1 f(n)=min{ f(nc1),f(nc2)...f(ncm)}+1
  • O ( m N ) O(mN) O(mN)
var MAXINT int=int(^uint32(0)>>1)
func coinChange(coins []int, amount int) int {
     
    memo:=make([]int,amount+1)
    for i:=1;i<=amount;i++ {
     
        tmp:=MAXINT
        for _,c:=range coins {
     
            if i-c<0 || memo[i-c]==-1 {
     
                continue
            }
            if memo[i-c]+1<tmp {
     
                tmp=memo[i-c]+1
            }
        }
        if tmp==MAXINT {
     
            memo[i]=-1
        } else {
     
            memo[i]=tmp
        }
    }
    return memo[amount]
}




T64: 最小路径和

  • f ( i , j ) : f(i,j): f(i,j): 至grid[i,j]位置的最小路径和
  • f ( i , j ) = min ⁡ { f ( i − 1 , j ) , f ( i , j − 1 ) } + g r i d [ i , j ] f(i,j)=\min\{f(i-1,j),f(i,j-1)\}+grid[i,j] f(i,j)=min{ f(i1,j),f(i,j1)}+grid[i,j]
var MAXINT int=int(^uint32(0)>>1)
func minPathSum(grid [][]int) int {
     
    m:=len(grid)
    if m==0 {
     
        return 0
    }
    n:=len(grid[0])
    memo:=make([][]int,m)
    for i:=0;i<m;i++ {
     
        memo[i]=make([]int,n)
        for j:=0;j<n;j++ {
     
            memo[i][j]=MAXINT
        }
    }
    for i:=0;i<m;i++ {
     
        for j:=0;j<n;j++ {
     
            if i==0 && j==0 {
     
                memo[0][0]=grid[0][0]
            } else if i==0 {
     
                memo[0][j]=memo[0][j-1]+grid[0][j]
            } else if j==0 {
     
                memo[i][0]=memo[i-1][0]+grid[i][0]
            } else {
     
                if memo[i-1][j]>memo[i][j-1] {
     
                    memo[i][j]=memo[i][j-1]+grid[i][j]
                } else {
     
                    memo[i][j]=memo[i-1][j]+grid[i][j]
                }
            }
        }
    }
    return memo[m-1][n-1]
}




T72: 编辑距离

  • f ( i , j ) : w o r d 1 [ : i ] 和 w o r d 2 [ : j ] 的 最 小 编 辑 距 离 f(i,j): word1[:i]和word2[:j]的最小编辑距离 f(i,j):word1[:i]word2[:j]
    f ( i , j ) = { f ( i − 1 , j − 1 ) word[i]=word[j] min ⁡ { f ( i − 1 , j ) , f ( i , j − 1 ) , f ( i − 1 , j − 1 ) } + 1 else f(i,j)=\begin{cases} f(i-1,j-1) & \text{word[i]=word[j]}\\ \min\{f(i-1,j),f(i,j-1),f(i-1,j-1)\}+1& \text{else} \end{cases} f(i,j)={ f(i1,j1)min{ f(i1,j),f(i,j1),f(i1,j1)}+1word[i]=word[j]else
  • golang
import "math"
func minDistance(word1 string, word2 string) int {
     
    memo:=map[[2]int]int{
     }
    return assist(memo,[]byte(word1),[]byte(word2),len(word1)-1,len(word2)-1)
}

func assist(memo map[[2]int]int,word1 []byte,word2 []byte,i int,j int) int {
     
    if i==-1 {
     
        return j+1
    }
    if j==-1 {
     
        return i+1
    }
    if r,ok:=memo[[2]int{
     i,j}];ok {
     
        return r
    }
    r:=0
    if word1[i]==word2[j] {
     
        r=assist(memo,word1,word2,i-1,j-1)
    } else {
     
        i1:=assist(memo,word1,word2,i,j-1)+1
        i2:=assist(memo,word1,word2,i-1,j)+1
        i3:=assist(memo,word1,word2,i-1,j-1)+1
        r=int(math.Min(math.Min(float64(i1),float64(i2)),float64(i3)))
    }
    memo[[2]int{
     i,j}]=r
    return r
}
  • python3
class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        memo=[[0 for j in range(len(word1)+1)] for i in range(len(word2)+1)]
        for i in range(len(word1)+1):
            memo[0][i]=i
        for i in range(len(word2)+1):
            memo[i][0]=i
        for i in range(1,len(word2)+1):
            for j in range(1,len(word1)+1):
                if word1[j-1]==word2[i-1]:
                    memo[i][j]=memo[i-1][j-1]
                else:
                    memo[i][j]=min(memo[i][j-1],memo[i-1][j],memo[i-1][j-1])+1
        return memo[-1][-1]




T887: 鸡蛋掉落

  • f ( K , N ) : N 层 楼 , 持 有 K 个 鸡 蛋 , 此 时 的 最 小 尝 试 数 f(K,N): N层楼,持有K个鸡蛋,此时的最小尝试数 f(K,N):NK
    f ( K , N ) = { 0 N=0 N K=1 min ⁡ 1 ≤ i ≤ N max ⁡ { f ( K − 1 , i − 1 ) , f ( K , N − i ) } + 1 else f(K,N)=\begin{cases} 0 & \text{N=0}\\ N & \text{K=1}\\ \min_{1 \le i \le N}\max\{f(K-1,i-1),f(K,N-i)\}+1& \text{else} \end{cases} f(K,N)=0Nmin1iNmax{ f(K1,i1),f(K,Ni)}+1N=0K=1else
  • 二分搜索算法求 min ⁡ max ⁡ { f ( x ) , g ( x ) } \min \max\{f(x),g(x)\} minmax{ f(x),g(x)} f ( x ) , g ( x ) f(x),g(x) f(x),g(x)单调且单调性相反
var MAXINT int=int(^uint32(0)>>1)

func superEggDrop(K int, N int) int {
     
    memo:=make([][]int,K+1)
    for i:=0;i<K+1;i++ {
     
        memo[i]=make([]int,N+1)
        for j:=0;j<N+1;j++ {
     
            memo[i][j]=-1
        }
    }
    return assist(memo,K,N)
}

func assist(memo [][]int,K int,N int) int {
     
    if K==1 {
     
        return N
    }
    if N==0 {
     
        return 0
    }
    if memo[K][N]!=-1 {
     
        return memo[K][N]
    }
    tmp:=MAXINT
    start:=1
    end:=N
    for start<=end {
     
        mid:=(end-start)/2+start
        i1:=assist(memo,K-1,mid-1)
        i2:=assist(memo,K,N-mid)
        if i1-i2<0 {
     
            start=mid+1
            if i2+1<tmp {
     
                tmp=i2+1
            }
        } else {
     
            end=mid-1
            if i1+1<tmp {
     
                tmp=i1+1
            }
        }
    }
    memo[K][N]=tmp
    return tmp
}




T1143: 最长公共子序列

  • f ( i , j ) : t e x t 1 [ : i ] 和 t e x t 2 [ : j ] 的 最 长 公 共 子 序 列 长 度 f(i,j): text1[:i]和text2[:j]的最长公共子序列长度 f(i,j):text1[:i]text2[:j]
    f ( i , j ) = { f ( i − 1 , j − 1 ) + 1 text1[i]=text2[j] max ⁡ { f ( i − 1 , j ) , f ( i , j − 1 ) } else f(i,j)=\begin{cases} f(i-1,j-1)+1 & \text{text1[i]=text2[j]}\\ \max\{f(i-1,j),f(i,j-1)\}& \text{else} \end{cases} f(i,j)={ f(i1,j1)+1max{ f(i1,j),f(i,j1)}text1[i]=text2[j]else
import "math"
func longestCommonSubsequence(text1 string, text2 string) int {
     
    memo:=make([][]int,len(text1)+1)
    for i:=0;i<len(text1)+1;i++ {
     
        memo[i]=make([]int,len(text2)+1)
    }
    for i:=1;i<len(text1)+1;i++ {
     
        for j:=1;j<len(text2)+1;j++ {
     
            if text1[i-1]==text2[j-1] {
     
                memo[i][j]=memo[i-1][j-1]+1
            } else {
     
                memo[i][j]=int(math.Max(float64(memo[i-1][j]),float64(memo[i][j-1])))
            }
        }
    }
    return memo[len(text1)][len(text2)]
}




T718: 最长重复子数组

  • f ( i , j ) : 以 A [ i ] 和 B [ j ] 结 尾 的 最 长 重 复 子 数 组 长 度 f(i,j): 以A[i]和B[j]结尾的最长重复子数组长度 f(i,j):A[i]B[j]
    f ( i , j ) = { f ( i − 1 , j − 1 ) + 1 A[i]=B[j] 0 else f(i,j)=\begin{cases} f(i-1,j-1)+1 & \text{A[i]=B[j]}\\ 0& \text{else} \end{cases} f(i,j)={ f(i1,j1)+10A[i]=B[j]else
func findLength(A []int, B []int) int {
     
    memo:=make([][]int,len(A)+1)
    for i:=0;i<len(memo);i++ {
     
        memo[i]=make([]int,len(B)+1)
    }
    ans:=0
    for i:=1;i<=len(A);i++ {
     
        for j:=1;j<=len(B);j++ {
     
            if A[i-1]==B[j-1] {
     
                memo[i][j]=memo[i-1][j-1]+1
                if memo[i][j]>ans {
     
                    ans=memo[i][j]
                }
            }
        }
    }
    return ans
}




T516: 最长回文子序列

  • f ( i , j ) = t e x t [ i , j ] 中 的 最 长 回 文 子 序 列 长 度 f(i,j)=text[i,j]中的最长回文子序列长度 f(i,j)=text[i,j]
    f ( i , j ) = { f ( i + 1 , j − 1 ) + 2 text[i]=text[j] max ⁡ { f ( i + 1 , j ) , f ( i , j − 1 ) } text[i]!=text[j] 0 i>j 1 i==j f(i,j)=\begin{cases} f(i+1,j-1)+2 & \text{text[i]=text[j]}\\ \max\{f(i+1,j),f(i,j-1)\}& \text{text[i]!=text[j]}\\ 0 & \text{i>j}\\ 1 & \text{i==j} \end{cases} f(i,j)=f(i+1,j1)+2max{ f(i+1,j),f(i,j1)}01text[i]=text[j]text[i]!=text[j]i>ji==j
import "math"
func longestPalindromeSubseq(s string) int {
     
    memo:=make([][]int,len(s))
    for i:=0;i<len(s);i++ {
     
        memo[i]=make([]int,len(s))
    }
    for j:=0;j<len(s);j++ {
     
        for i:=j;i>=0;i-- {
     
            if i==j {
     
                memo[i][j]=1
            } else if s[i]==s[j] {
     
                memo[i][j]=memo[i+1][j-1]+2
            } else {
     
                memo[i][j]=int(math.Max(float64(memo[i+1][j]),float64(memo[i][j-1])))
            }
        }
    }
    return memo[0][len(s)-1]
}




T877: 石子游戏

  • f ( i , j , f i r s t ) : p i l e s [ i : j ] 先 手 获 得 的 总 石 头 数 f(i,j,first): piles[i:j]先手获得的总石头数 f(i,j,first):piles[i:j]
  • f ( i , j , s e c o n d ) : p i l e s [ i : j ] 后 手 获 得 的 总 石 头 数 f(i,j,second): piles[i:j]后手获得的总石头数 f(i,j,second):piles[i:j]
  • l e f t = p i l e s [ i ] + f ( i + 1 , j , s e c o n d ) left=piles[i]+f(i+1,j,second) left=piles[i]+f(i+1,j,second)
  • r i g h t = p i l e s [ j ] + f ( i , j − 1 , s e c o n d ) right=piles[j]+f(i,j-1,second) right=piles[j]+f(i,j1,second)
    f ( i , j , f i r s t ) = { p i l e s [ i ] i  =  j l e f t left  > right r i g h t left  ≤  right f(i,j,first)=\begin{cases} piles[i] & \text{i $=$ j}\\ left& \text{left $>$right}\\ right & \text{left $\le$ right}\\ \end{cases} f(i,j,first)=piles[i]leftright= jleft >rightleft  right

f ( i , j , s e c o n d ) = { 0 i = j f ( i + 1 , j , f i r s t ) left  > right f ( i , j − 1 , f i r s t ) left  ≤  right f(i,j,second)=\begin{cases} 0 & \text{i$=$j}\\ f(i+1,j,first)& \text{left $>$right}\\ f(i,j-1,first) & \text{left $\le$ right}\\ \end{cases} f(i,j,second)=0f(i+1,j,first)f(i,j1,first)i=jleft >rightleft  right

type Pair struct {
     
    first int
    second int
}

func stoneGame(piles []int) bool {
     
    memo:=make([][]Pair,len(piles))
    for i:=0;i<len(piles);i++ {
     
        memo[i]=make([]Pair,len(piles))
    }
    for j:=0;j<len(piles);j++ {
     
        for i:=j;i>=0;i-- {
     
            if i==j {
     
                memo[i][j].first=piles[i]
                memo[i][j].second=0
            } else {
     
                left:=piles[i]+memo[i+1][j].second
                right:=piles[j]+memo[i][j-1].second
                if left>right {
     
                    memo[i][j].first=left
                    memo[i][j].second=memo[i+1][j].first
                } else {
     
                    memo[i][j].first=right
                    memo[i][j].second=memo[i][j-1].first
                }
            }
        }
    }
    return memo[0][len(piles)-1].first-memo[0][len(piles)-1].second>0
}




T10: 正则表达式匹配

  • 普通匹配+“.”+"*"+记忆化递归
func isMatch(s string, p string) bool {
     
    memo:=make(map[[2]int]bool)
    return assist(memo,&s,&p,0,0)
}

func assist(memo map[[2]int]bool, s *string, p *string, point1 int, point2 int) bool {
     
    if point2==len(*p) {
     
        return point1==len(*s)
    }
    if b,ok:=memo[[2]int{
     point1,point2}];ok {
     
        return b
    }
    flag:=point1<len(*s) && ((*s)[point1]==(*p)[point2] || (*p)[point2]=='.')
    ans:=false
    if point2+1<len(*p) && (*p)[point2+1]=='*' {
     
        ans=assist(memo,s,p,point1,point2+2) || (flag && assist(memo,s,p,point1+1,point2))

    } else {
     
        ans=flag && assist(memo,s,p,point1+1,point2+1)
    }
    memo[[2]int{
     point1,point2}]=ans
    return ans
}



T818:赛车

  • f ( i ) f(i) f(i)表示在i位置上所需要的最少操作步骤
  • t a r g e t = 2 j − 1 ∈ [ 1 , 2 i ) 表 示 前 进 j 步 时 的 位 置 target=2^j-1 \in [1,2i)表示前进j步时的位置 target=2j1[1,2i)j
    f ( i ) = min ⁡ j , k { j i = target j + 1 ( R ) + k ∈ [ 0 , j ) + 1 ( R ) + f ( i − t a r g e t + 2 k − 1 ) i  > target j + 1 ( R ) + f ( t a r g e t − i ) i  <  target f(i)=\min_{j,k} \begin{cases} j & \text{i$=$target}\\ j+1(R)+k \in [0,j)+1(R)+f(i-target+2^k-1)& \text{i $>$target}\\ j+1(R)+f(target-i) & \text{i $<$ target}\\ \end{cases} f(i)=j,kminjj+1(R)+k[0,j)+1(R)+f(itarget+2k1)j+1(R)+f(targeti)i=target>target< target
// golang中全局变量;python3中全局变量需要加global
var memo []int
var MAXINT int = int(^uint32(0)>>1)

func buildMemo() {
     
    memo=make([]int,10001)
    for i:=1;i<len(memo);i++ {
     
        memo[i]=MAXINT
    }
    for i:=1;i<len(memo);i++ {
     
        for j:=1;1<<j-1 < 2*i;j++ {
     
            target:=1<<j-1
            if i==target {
     
                memo[i]=j
                break
            }
            if i<target {
     
                tmp:=j+1+memo[target-i]
                if tmp<memo[i] {
     
                    memo[i]=tmp
                }
            } else {
     
                for k:=0;k<j;k++ {
     
                    tmp:=j+2+k+memo[i-target+1<<k-1]
                    if tmp<memo[i] {
     
                        memo[i]=tmp
                    }
                }
            }
        }
    }
}

func racecar(target int) int {
     
    if memo==nil {
     
        buildMemo()
    }
    // fmt.Println(memo)
    return memo[target]
}





贪心算法




T435: 最大无重叠区间

  • 以最小右端点开始贪心遍历
class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        if len(intervals)==0:
            return 0
        s_intervals=sorted(intervals,key=lambda x:x[1])
        count=1
        min_end=s_intervals[0][1]
        for i in range(1,len(s_intervals)):
            if s_intervals[i][0]>=min_end:
                count+=1
                min_end=s_intervals[i][1]
        return len(intervals)-count




T452: 用最少数量的箭引爆气球

  • 同435
class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        if len(points)==0:
            return 0
        s_points=sorted(points,key=lambda x:x[1])
        count=1
        min_end=s_points[0][1]
        for i in range(1,len(s_points)):
            if s_points[i][0]>min_end:
                count+=1
                min_end=s_points[i][1]
        return count




※T406: 根据身高重建队列

  • 核心:由题意,高个看不见矮个;所以先安排高个的人,再安排矮个的人时首先能找到正确的位置,其次矮个对高个也没有影响。
import "sort"

func reconstructQueue(people [][]int) [][]int {
     
    sort.Slice(people, func(i int, j int) bool {
     
        if people[i][0]>people[j][0] {
     
            return true
        } else if people[i][0]<people[j][0] {
     
            return false
        } else {
     
            return people[i][1]<people[j][1]
        }
    })
    ans:=make([][]int, len(people))
    end:=-1
    for i := range people {
     
        idx:=people[i][1]
        for j:=end;j>=idx;j-- {
     
            ans[j+1]=ans[j]
        }
        end++
        ans[idx]=people[i]
    }
    return ans
}




T55: 跳跃游戏

  • 只要该位置可达,则更新最大可达距离
func canJump(nums []int) bool {
     
    maxL:=0
    for i:=0;i<len(nums);i++ {
     
        if i<=maxL && i+nums[i]>=maxL {
     
            maxL=i+nums[i]
            if maxL>=len(nums)-1 {
     
                return true
            }
        }
    }
    return false
}




※T45: 跳跃游戏Ⅱ

  • 反向贪心算法
  • 具有局部最优性质的DP
  • f ( i ) = m i n j < i   & &   n u m s [ j ] > = i − j { f ( j ) } + 1 f(i)=min_{j=i-j}\{f(j)\}+1 f(i)=minj<i&&nums[j]>=ij{ f(j)}+1
  • f ( i ) f(i) f(i)根据题意为单调增函数,因此只选第一个即可
    在这里插入图片描述
func jump(nums []int) int {
     
    pos:=len(nums)-1
    step:=0
    for pos>0 {
     
        for i:=0;i<pos;i++ {
     
            if pos-i<=nums[i] {
     
                pos=i
                break
            }
        }
        step++
    }
    return step
}
  • 正向贪心算法
  • 我们每次在每一步中选择下一步能达到最远的点
    在这里插入图片描述
func jump(nums []int) int {
     
    maxVal:=0
    end:=0
    step:=0
    for i:=0;i<len(nums)-1;i++ {
     
        if nums[i]+i>maxVal {
     
            maxVal=nums[i]+i
        }
        if i==end {
     
            step++
            end=maxVal
        }
    }
    return step
}




T765: 情侣牵手

  • 我们将每对牵手的两个人看作一个节点,情侣关系视为节点之间的边,从而构建一个图
  • 基于贪心思想,因为最终交换结果是图中共有 l e n ( r o w ) / / 2   len(row)//2\, len(row)//2个连通分量,而根据观察每次交换最多减少一个连通分量,因此最少交换数就是 l e n ( r o w ) / / 2   len(row)//2\, len(row)//2减去当前连通分量数
  • O(N)时间复杂度
class Solution:
    def minSwapsCouples(self, row: List[int]) -> int:
        N=len(row)//2
        couples=[[] for i in range(N)]
        for i,v in enumerate(row):
            couples[v//2].append(i//2)
        graph=[set() for i in range(N)]
        for i,j in couples:
            graph[i].add(j)
            graph[j].add(i)
        count=0    # dfs查找连通分量
        visited=[False for i in range(N)]
        for i in range(N):
            if visited[i]:
                continue
            visited[i]=True
            count+=1
            stack=[i]
            while len(stack)>0:
                item=stack.pop()
                for node in graph[item]:
                    if not visited[node]:
                        visited[node]=True
                        stack.append(node)
        return N-count
  • 很直观的贪心思想,如果某牵手的俩人第二个人不是第一个人的couple,则换成其couple
  • O(N)时间复杂度
class Solution:
    def minSwapsCouples(self, row: List[int]) -> int:
        site={
     }
        for idx, num in enumerate(row):
            site[num]=idx
        step=0
        for idx in range(0,len(row),2):
            couple=row[idx] ^ 1
            if row[idx+1]==couple:
                continue
            real_site=site[couple]
            row[idx+1], row[real_site]=row[real_site], row[idx+1]
            site[row[idx+1]]=idx+1
            site[row[real_site]]=real_site
            step+=1
        return step




※T767: 重构字符串

  • 如"aab"→"aba"
  • 回溯法;超时;O(N!)
class Solution:
    def reorganizeString(self, S: str) -> str:
        if S=="":
            return ""
        self.count={
     }
        for c in S:
            if c not in self.count:
                self.count[c]=1
            else:
                self.count[c]+=1
        self.ans=""
        self.assist("")
        return self.ans
    def assist(self,curr:str):
        if len(curr)==0:
            c=""
        else:
            c=curr[-1]
        tmp=0
        for word in self.count:
            tmp+=self.count[word]
        if tmp==0:
            self.ans=curr
            return
        for word in self.count:
            if self.ans!="":
                return
            if word!=c and self.count[word]>0:
                self.count[word]-=1
                self.assist(curr+word)
                self.count[word]+=1
  • 判断给定字符串能否成立
  • 交叉输出
  • O(NlogN)
import "sort"
func reorganizeString(S string) string {
     
    if len(S)==0 {
     
        return ""
    }
    n:=0
    if len(S)%2==0 {
     
        n=len(S)/2
    } else {
     
        n=len(S)/2+1
    }
    count:=map[rune]int{
     }
    for _,c := range S {
     
        count[c]++
        if count[c]>n {
     
            return ""
        }
    }
    source:=[]rune(S)
    sort.Slice(source,func(i int,j int) bool {
     
        tmp:=count[source[i]]-count[source[j]]
        if tmp>0 {
     
            return true
        } else if tmp<0 {
     
            return false
        } else {
     
            return source[i]-source[j]<0
        }
    })
    index:=0
    tmp:=make([]rune,len(S))
    if len(source)%2==1 {
     
        for i:=0;i<len(source);i++ {
     
            tmp[index]=source[i]
            index=(index+2)%len(source)
        }
    } else {
     
        for i:=0;i<len(source);i++ {
     
            tmp[index]=source[i]
            if index==len(source)-2 {
     
                index=1
                continue
            }
            index+=2
        }
    }
    return string(tmp)
}
  • 贪心堆
  • 最大堆
  • 每次弹出两个堆顶元素
  • O(NlogA)
  • 证明
import "container/heap"

type item struct {
     
    cha byte
    count int
}

type Stack []item

func (s Stack) Len() int{
     
    return len(s)
}

func (s Stack) Less(i int,j int) bool {
     
    return s[i].count-s[j].count>0
}

func (s Stack) Swap(i int,j int) {
     
    s[i],s[j]=s[j],s[i]
}

func (s *Stack) Push(i interface{
     }) {
     
    *s=append(*s,i.(item))
}

func (s *Stack) Pop() interface{
     } {
     
    tmp:=*s
    x:=tmp[len(tmp)-1]
    *s=tmp[:len(tmp)-1]
    return x
}

func reorganizeString(S string) string {
     
    count:=map[byte]int{
     }
    for i:=0;i<len(S);i++ {
     
        count[S[i]]++
        if 2*count[S[i]]>len(S)+1 {
     
            return ""
        }
    }
    stack:=&Stack{
     }
    for k,v:=range count {
     
        *stack=append(*stack,item{
     k,v})
    }
    result:=make([]byte,0,len(S))
    heap.Init(stack)
    for stack.Len()>=2 {
     
        item1:=heap.Pop(stack).(item)
        item2:=heap.Pop(stack).(item)
        result=append(result,item1.cha,item2.cha)
        if item1.count-1>0 {
     
            heap.Push(stack,item{
     item1.cha,item1.count-1})
        }
        if item2.count-1>0 {
     
            heap.Push(stack,item{
     item2.cha,item2.count-1})
        }
    }
    if stack.Len()==1 {
     
        result=append(result,(*stack)[0].cha)
    }
    return string(result)
}




T621: 任务调度

  • 贪心堆;与T767重构字符串类似,对于 l e n ( c o u n t ) = l e n ( t y p e   o f   t a s k s ) len(count)=len(type\,of\,tasks) len(count)=len(typeoftasks),如果大于等于 n + 1 n+1 n+1则先取前高频 n + 1 n+1 n+1先填满一个轮次,前 n + 1 n+1 n+1个频次减一;直至 l e n ( c o u n t ) len(count) len(count)小于 n + 1 n+1 n+1,此时由最高频任务决定等待时间
  • 可以看出相当于对 l e n ( t a s k s ) len(tasks) len(tasks)进行分配
  • O ( l e n ( t a s k s ) ) O(len(tasks)) O(len(tasks))时间复杂度,使用数组排序方式与最大堆复杂度相同
    在这里插入图片描述
import "container/heap"

type Item struct {
     
    name byte
    count int
}

type CPUHeap []*Item

func (p CPUHeap) Len() int {
     
    return len(p)
}

func (p CPUHeap) Less(i int, j int) bool {
     
    return p[i].count>p[j].count
}

func (p CPUHeap) Swap(i int, j int) {
     
    p[i],p[j]=p[j],p[i]
}

func (p *CPUHeap) Push(x interface{
     }) {
     
    *p=append(*p, x.(*Item))
}

func (p *CPUHeap) Pop() interface{
     } {
     
    x:=(*p)[len(*p)-1]
    *p=(*p)[:len(*p)-1]
    return x
}

func leastInterval(tasks []byte, n int) int {
     
    countMap:=map[byte]int{
     }
    cpuHeap:=&CPUHeap{
     }
    for i := range tasks {
     
        countMap[tasks[i]]++
    }
    for k,v := range countMap {
     
        heap.Push(cpuHeap, &Item{
     k, v})
    }
    ans:=0
    for cpuHeap.Len() >= n+1 {
     
        tmp:=[]*Item{
     }
        for i:=0;i<n+1;i++ {
     
            x:=heap.Pop(cpuHeap).(*Item)
            x.count--
            if x.count>0 {
     
                tmp=append(tmp, x)
            }
        }
        for i:=0;i<len(tmp);i++ {
     
            heap.Push(cpuHeap, tmp[i])
        }
        ans+=n+1
    }
    x:=0
    m:=1
    if cpuHeap.Len()>0 {
     
        m=heap.Pop(cpuHeap).(*Item).count
        x++
        for cpuHeap.Len()>0 {
     
            if heap.Pop(cpuHeap).(*Item).count==m {
     
                x++
            } else {
     
                break
            }
        }
    }
    ans+=(m-1)*(n+1)+x
    return ans
}
  • 数学法
  • 情况1:设等待时间time1为任务最高频次-1个轮次 × ( n + 1 ) \times (n+1) ×(n+1)+最高频次任务个数,显然大于等于 l e n ( t a s k s ) len(tasks) len(tasks)(设为time2),此时为下图( n = 2 n=2 n=2);
    Leetcode(7)——动态规划和贪心算法_第1张图片
  • 情况2:还有出现time2大于time1的情形,需要临时扩充某些 b a r r e l barrel barrel的容量,此时等待时间取**time2;此时为下图( n = 1 n=1 n=1);
    Leetcode(7)——动态规划和贪心算法_第2张图片
  • 综合两种情况,只要取 m a x { t i m e 1 , t i m e 2 } max\{time1, time2\} max{ time1,time2}即可
  • O ( l e n ( c o u n t ) ) O(len(count)) O(len(count))时间复杂度
    在这里插入图片描述
func leastInterval(tasks []byte, n int) int {
     
    count:=make([]int, 26)
    for i := range tasks {
     
        count[tasks[i]-'A']++
    }
    sort.Slice(count, func(i int, j int) bool {
     
        return count[i]-count[j]>0
    })
    barrelNum:=count[0]
    time1:=(barrelNum-1)*(n+1)
    lastBarrelNum:=1
    for i:=1;i<len(count);i++ {
     
        if count[i]==barrelNum {
     
            lastBarrelNum++
        } else {
     
            break
        }
    }
    time1+=lastBarrelNum
    time2:=len(tasks)
    if time1 > time2 {
     
        return time1
    } else {
     
        return time2
    }
}




T861: 翻转矩阵后的得分

  • 保证首列都为1,因为二进制中最高位是1均比最高位是0的大
  • 保证剩余列1的个数越多越好
func matrixScore(A [][]int) int {
     
    m:=len(A)
    if m==0 {
     
        return 0
    }
    n:=len(A[0])
    for i:=0;i<m;i++ {
     
        if A[i][0]==0 {
     
            for j:=0;j<n;j++ {
     
                A[i][j]^=1
            }
        }
    }
    ans:=0
    for j:=0;j<n;j++ {
     
        tmp:=0
        for i:=0;i<m;i++ {
     
            if A[i][j]==1 {
     
                tmp++
            }
        }
        if tmp<m/2+1 {
     
            tmp=m-tmp
        }
        ans+=(1<<(n-1-j))*tmp
    }
    return ans
}




T763: 划分字母区间

  • 确定每个字母的范围
  • 然后与T56一样进行区间合并
import "sort"
func partitionLabels(S string) []int {
     
    seen:=map[byte][]int{
     }
    for i:=0;i<len(S);i++ {
     
        if list,ok:=seen[S[i]];!ok {
     
            seen[S[i]]=[]int{
     i, i}
        } else {
     
            list[1]=i
        }
    }
    lists:=make([][]int, 0, len(seen))
    for key := range seen {
     
        lists=append(lists, seen[key])
    }
    sort.Slice(lists, func(i int, j int) bool {
     
        return lists[i][0]<lists[j][0]
    })
    point:=0
    minEnd:=lists[0][1]
    ans:=[]int{
     }
    for i:=1;i<len(lists);i++ {
     
        if lists[i][0]<minEnd {
     
            if lists[i][1]>minEnd {
     
                minEnd=lists[i][1]
            }
        } else {
     
            ans=append(ans, minEnd+1-point)
            point=minEnd+1
            minEnd=lists[i][1]
        }
    }
    ans=append(ans, minEnd+1-point)
    return ans
}
  • 更快的一种解法,不需要排序
func partitionLabels(S string) []int {
     
    seen:=[26]int{
     }
    for i:=0;i<len(S);i++ {
     
        seen[S[i]-'a']=i
    }
    start:=0
    end:=0
    ans:=[]int{
     }
    for i:=0;i<len(S);i++ {
     
        if seen[S[i]-'a']>end {
     
            end=seen[S[i]-'a']
        }
        if i==end {
     
            ans=append(ans, end-start+1)
            start=i+1
        }
    }
    return ans
}

你可能感兴趣的:(leetcode,leetcode,算法,python)