LeetCode动态规划之二

467. 环绕字符串中的唯一子串

  • 思路是dp[i]代表着从0-i这一段中符合规则出现的子串数目。
  • j从i-1开始往前走,直到不满足相邻,可以提前break,我没写出来。
  • 如何判断相邻,我的办法是p[i] - p[i-1] == 1 and p[j+1] - p[j] == 1, 并且还要保证(p[i] - [j] + 26) % 26 == i - j

下面这段代码,在这种情况下失败了, 比如 za....zab. 最后一个条件不满足。所以样例只对了一部分。

 

 

class Solution:
    def findSubstringInWraproundString(self, p: str) -> int:
        if len(p) == 1:
            return 1
        if len(p) == 0:
            return 0
        dp = [0] * len(p)
        dp[0] = 1
        seen = set()
        seen.add(p[0])
        for i in range(1, len(p)):
            dp[i] += dp[i-1]
            for j in range(i-1, -1, -1):
                if ord(p[i]) - ord(p[i-1]) in [1, -25] and ord(p[j+1]) - ord(p[j]) in [1, -25] and (ord(p[i]) - ord(p[j])+26)%26 == i-j and not p[j:i+1] in seen:
                    dp[i] += 1
                    seen.add(p[j:i+1])
            if not p[i] in seen:
                dp[i] += 1
            seen.add(p[i])
        return dp[-1]

还可以把dp看成二维的。dp[i][j]代表着i-j这段是不是连续子序列。最后返回sum(dp)即可。

当然,实际上dp可以为一个数字来维护。

然后以上方向还是思考错了。将dp[i]是做,以字母i为结尾的最大子串长度。那么dp可以用len为26的字典实现。最后返回的是values的sum

class Solution:
    def findSubstringInWraproundString(self, p: str) -> int:
        if not p:
            return 0
        hashmap=[0]*26
        hashmap[ord(p[0])-ord("a")]=1
        prelen=1
        for i in range(1,len(p)):
            gap=ord(p[i])-ord(p[i-1])
            if gap==1 or gap==-25:
                prelen+=1
            else:
                prelen=1
            hashmap[ord(p[i])-ord("a")]=max(hashmap[ord(p[i])-ord("a")],prelen)
        return sum(hashmap)

作者:jasss
链接:https://leetcode-cn.com/problems/unique-substrings-in-wraparound-string/solution/python3-by-jasss-19/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

474. 1和0(背包问题)

题目:现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

思路:设dp[i][m'][n']的含义是:前i个字符中,用m'个0和n'个1, 能拼出字符集合中的字符的最大数目。

递归方程,状态就有三种,i,m,n,选择有两种,拼第i个字符还是不拼。

dp[i][m][n] = max(d[i-1][m-m1][n-n1] + 1, dp[i-1][m][n]) 。 m1和n1代表第i个字符中0和1的数目。

 

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        if len(strs) == 0:
            return 0
        l = len(strs)
        dp = [[[0]*(n+1) for _ in range(m+1)] for _ in range(l)]
        dp[0][0][0] = 0
        n0, n1 = self.count_zero_one(strs[0])
        for i in range(n0, m+1):
            for j in range(n1, n+1):
                dp[0][i][j] = 1
        for i in range(1, l):
            m1, n1 = self.count_zero_one(strs[i])
            for a in range(m+1):
                for b in range(n+1):
                    temp = 0
                    if a - m1 >=0 and b - n1 >= 0:
                        temp = dp[i-1][a-m1][b-n1] + 1 
                    # 从两个选择中选出最大的  
                    temp = max(temp, dp[i-1][a][b])
                    dp[i][a][b] = temp
        return dp[-1][-1][-1]

    def count_zero_one(self, s):
        m = s.count('0')
        n = s.count('1')
        return m, n

 

213. 打家劫舍||

这道题是在打家劫舍|的基础上做的修改,改动内容是,房屋成为一个圈。

思路,如果偷第一家,就不能偷最后一家,那么实际范围就是0- n-2; 如果不偷第一家,就可以偷最后一家,那么实际范围是1- n-1。那只要用第一次的解法,在两个范围内都求一次,就完事了。

打家劫舍|的思路: 递归方程是  cur = max(previous_one, previous_two + cur_value)

 

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums)<=2:
            return max(nums)
        def cal(s):
            pre=cur=0
            for i in range(len(s)):
                temp=max(pre+s[i],cur)
                cur, pre= temp,cur
            return cur
        return max(cal(nums[:-1]), cal(nums[1:]))

 

221. 最大正方形

这道题是求一个二维矩阵里面, 一个正方形区域全部为1的最大面积

思路: 某个位置(i,j)是正方形的右下角的条件是:(i-1,j-1)是某个正方形的右下角 && (i-1,j)是1 && (i, j-1)是1。 所以递归方程为

dp[i][j] = min(dp[i-1][j-1], dp[i-1, j] , dp[i][j-1]) +1

注意到递归方程仅用了第i-1行和第i行,根据dp问题的经典优化方式,我们可以用两个数组代替整个矩阵。更近一步,其实只用一个数组就行了。

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        if not matrix:
            return 0
        m,n=len(matrix),len(matrix[0])
        dp=[0]*(n+1)
        res=0
        for i in range(m):
            pre=0
            for j in range(1, n+1):
                temp=dp[j]
                if matrix[i][j-1]=='0':
                    dp[j]=0
                else:
                    dp[j]=min(dp[j-1],dp[j],pre)+1
                    res=max(dp[j],res)
                pre=temp
        return res**2

用pre记录dp[i-1][j-1]的数值,那么整个dp可以压缩为一个数组。temp是dp[i-1][j], 先取出来保存,因为j的位置要被代替成dp[i][j]了。


322. 零钱兑换(背包问题)

题目:给定一个总金额和不同面额的硬币集合,求用硬币组合成总金额的最少硬币数目,硬币可以复用。如果不能组合,则返回-1.

这道题和之前的279. 完全平方数有点类似,都是背包问题的变种

思路:dp[i]代表了金额i能用这些硬币组合的最小数目,如果记硬币集合为{2,4,6}. 则i是否可以被组合取决于 i-2, i-4 , i-6这些金额是否能被组合。递归方程为 dp[i] = min(dp[i-2], dp[i-4], dp[i-6]) + 1

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0
        for i in range(1, amount + 1):
            for coin in coins:
                if i - coin <0:
                    continue
                dp[i] = min(dp[i], dp[i - coin] + 1)
        return dp[-1] if dp[-1] !=float('inf') else -1

而279.完全平方数的解法,求一个数字n能被完全平方数组合的最小个数。我们需要从i出发,i- cnt**2 +1来求, cnt逐渐增加1.

 

338. 比特位计数

题目:给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 ,计算其二进制数中的 1 的数目并将它们作为数组返回。

思路: 暴力法的复杂度是O(n*size(int))。如果用DP解,dp理应是一个一维数组,那进而设dp[i]的含义是,数组i的二进制表示中1的数目。那dp[i]和之前的dp[k],k {0,...,i-1}有什么联系呢。一个数字A左移1位得到B,最低位消失,最高位补0。那么A的1的个数应该为B+A&1。

递归方程: dp[i] = dp[i // 2] + i&1.

class Solution:
    def countBits(self, num: int) -> List[int]:
        dp = [0] * (num + 1)
        for i in range(1, num + 1):
            dp[i] = dp[i // 2] + i%2
        return dp        

343. 整数拆分(剑指offer的剪绳子题目)

357. 计算各个位数不同的数字个数

题目:给定一个非负整数n, 计算各位数字都不同的数字x的个数, 0<=x<10^n

思路: 一共就0-9 10个数字 所以11位数以上的数字肯定是存在重复。只考虑10位及以下的数字。接下来考虑dp的含义。状态有1个,就是位数,所以dp为1维数组,dp[i]的含义就是i位数存在每一位不重复的数字数目。接下来看有什么选择,这道题只有一种选择,那就寻找dp[i]和之前的关系。初始状态dp[0]=1 dp[1] = 9.  对于n为2的情况,1-9这9个数字分别有其他9个数字,可以加在后面组成一个两位数,而且是不重复的。那么递归方程可以推出: dp[i] = dp[i-1] * (10-i +1)。 返回sum(dp)

class Solution:
    def countNumbersWithUniqueDigits(self, n: int) -> int:
        if n == 0:
            return 1
        dp = [0] * (n + 1)
        dp[0] = 1
        dp[1] = 9
        for i in range(2, n + 1):
            dp[i] = dp[i-1] * (10 - (i - 1))
        return sum(dp)

 

413. 等差数列划分

寻找数组中等差数列的最大个数

思路:设dp[i]为,以i为起点的等差数列的数目。 第一层循环倒着遍历,第二层循环,从第一层的i为起点 正向遍历。返回sum(dp)。 如果A[i] 和A[j]之间组成等差,那就dp[i] +=1, 意味找到一个以i为起点的等差数列。如果不是,直接break跳出第二层循环。

class Solution:
    def numberOfArithmeticSlices(self, A: List[int]) -> int:
        m = len(A)
        dp = [0] * m
        if m <3:
            return 0
        for i in range(m-3, -1, -1):
            for j in range(i, m):
                if j - i < 2:
                    continue
                if A[i] - A[i+1] == A[j-1] - A[j] and A[j]-A[i] ==(j - i) * (A[i+1] - A[i]):
                    dp[i] += 1
                else:
                    break
        return sum(dp)

486. 预测赢家

一个数组代表分数,玩家1和2, 玩家1先手,只能选择拿开头和末尾的数组,如果一方拿了开头,另一方只能拿末尾。预测玩家1是否必赢。

思路: 对于玩家1来说有三种状态,拿分的区间, 从左端拿还是右端拿。 dp[i][k][k]. k 为0或者1. 然后需要两个dp数组分别为玩家1和2记录中间结果。但实际上,我们只需一个数组dp就可以,这个数组dp为dp[i][j][k],k也是0,1。但是意义不一样了。i和j代表在区间[i,j]之间拿, 0是玩家1先手的分数,1是玩家2后手的分。dp的初始化是当区间内仅为1个数字的情况。选择有从左拿还是从右边拿。

递归方程 玩家1的分数 dp[i][j][0] = max(nums[i] + dp[i+1][j][1], dp[nums[j]+dp[i][j-1] );

玩家2的分数 dp[i][j][1] = sum(nums[i:j+1]) - dp[i][j][0]

class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        n = len(nums)
        dp = [[[0,0] for _ in range(n)] for _ in range(n)]
        for i in range(n):
            dp[i][i][0] = nums[i]
            dp[i][i][1] = 0
        for i in range(n-1, -1, -1):
            for j in range(i+1, n):
                dp[i][j][0] = max(dp[i+1][j][1]+nums[i], dp[i][j-1][1]+nums[j])
                dp[i][j][1] = sum(nums[i:j+1]) - dp[i][j][0]
        print(dp)
        if dp[0][-1][0] >= dp[0][-1][1]:
            return True
        else:
            return False

 

523. 连续的子数组和

题目:

给定一个包含非负数的数组和一个目标整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

思路:这道题有点求二维区域面积的意思。首先暴力法就不说了。优化的暴力法:可以用dp[i]代表以i为末尾的前缀和。那i和j的和可以记作dp[j]-dp[i],不过复杂度是On2。一种On的思路是:利用哈希表,一次遍历得到答案。假设遍历到cur这个位置,其对应的前缀和是sum, 那么往字典中insert({sum%k:cur}),如果字典中本身有sum%K这个映射,则比较cur和哈希表记录的index的差。

之所以这样有效的原因是:

  • 两个不同的前缀和的余数相等,意味着这两个前缀和之差就是k的倍数
  • sum=sum%k对求解前缀和的余数没有影响,他只是让sum和余数之间的差去掉,但为什么这样子做呢,有两个好处

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值