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 范围中的每个数字 i ,计算其二进制数中的 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和余数之间的差去掉,但为什么这样子做呢,有两个好处