目录
279. 完全平方数(待完善)
https://leetcode-cn.com/problems/perfect-squares/
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:输入: n = 12,输出: 3 ,解释: 12 = 4 + 4 + 4.
示例 2:输入: n = 13,输出: 2,解释: 13 = 4 + 9.
题解
一:动态规划, 时间复杂度O(n^2),用dp[i]表示数字i被拆成的最少的完全平方数的个数。
class Solution(object):
def numSquares(self, n):
"""
:type n: int
:rtype: int
"""
dp = [i for i in xrange(n + 1)]
for i in xrange(2, n + 1):
for j in xrange(i):
if j * j == i:
dp[i] = 1
break
else:
dp[i] = min(dp[i], dp[j] + dp[i - j])
return dp[-1]
二:动态规划, 时间复杂度,用dp[i]表示数字i被拆成的最少的完全平方数的个数,不过优化了一下,我们内层循环不再遍历i前的所有数字,而是只遍历那些完全平方数,因为只有完全平方数才能减少个数。
class Solution(object):
def numSquares(self, n):
res = [i for i in range(n + 1)]
for i in range(3, n + 1):
j = 0
while i - j * j >= 0:
res[i] = min(res[i - j * j] + 1, res[i])
j += 1
return res[-1]
三:mark一下,官方题解https://leetcode-cn.com/problems/perfect-squares/solution/wan-quan-ping-fang-shu-by-leetcode/,等刷一部分递归再来。
276. 栅栏涂色
https://leetcode-cn.com/problems/paint-fence/
有 k 种颜色的涂料和一个包含 n 个栅栏柱的栅栏,每个栅栏柱可以用其中一种颜色进行上色。你需要给所有栅栏柱上色,并且保证其中相邻的栅栏柱 最多连续两个 颜色相同。然后,返回所有有效涂色的方案数。
注意:n 和 k 均为非负的整数。
示例:输入: n = 3,k = 2,输出: 6,解析: 用 c1 表示颜色 1,c2 表示颜色 2,所有可能的涂色方案有:
题解
一:递推公式,考虑i下标的栅栏,那么只有两种情况:
- 下标i的栅栏与前面的栅栏不同色,那么前面的栅栏没限制,目前的栅栏有k-1种选择
- 下标i的栅栏与前面的栅栏同色,那么前面的栅栏有限制(不能与他前面的同色),因为之前的都定下了,故该下标与前面同色,只有一种选择。
此处用same来记录一个栅栏与前面栅栏同色的方法数,diff来记录一个栅栏与前面栅栏不同色的方法数。
class Solution(object):
def numWays(self, n, k):
"""
:type n: int
:type k: int
:rtype: int
"""
if n <= 0 or k <= 0 or ( k == 1 and n > 2):
return 0
if n == 1:
return k
diff, same = [0] * (n + 1), [0] * (n + 1)
diff[1], same[1] = k, 0
for i in range(2, n + 1):
same[i] = diff[i - 1]
diff[i] = (diff[i - 1] + same[i - 1]) * (k - 1)
return same[-1] + diff[-1]
152. 乘积最大子数组
https://leetcode-cn.com/problems/maximum-product-subarray/
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:输入: [2,3,-2,4],输出: 6,解释: 子数组 [2,3] 有最大乘积 6。
示例 2:输入: [-2,0,-1],输出: 0,解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
题解
一:这一题乍一看与最大子序和差不多,不过有一点不一样。求和的时候只要前面的求和大于等于0,该值加上去,就是对该值的加强,但是乘积不一样,当该值是正值时,自然前面是正值乘上会最大,如果该值是负值,最大的反而不是和前面最大的一起得到的,此时期待前面是负值,这样才会最大,当然0要单独讨论,因为任何子序只要有0,值必为0。start记录的是非0元素的下标,即一段开始的位置,product记录的是这段的乘积和,若product>0,看情况更新res,若是负值,我们则证明这一段有一个多处的负值,我们把这一段的第一个负值和他前面的均剔除掉,则必定乘积是正值,看情况更新res。
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
res = max(nums)
start, neg, product = 0, 1, 1
for i in xrange(len(nums)):
if nums[i] == 0:
start = i + 1
product = 1
else:
product *= nums[i]
if product > 0:
res = max(res, product)
if product < 0:
j, pos_product = start, product
while j < i and nums[j] > 0:
pos_product /= nums[j]
j += 1
if j != i:
pos_product /= nums[j]
re
二:动态规划,转自leetcode官方题解,https://leetcode-cn.com/problems/maximum-product-subarray/solution/cheng-ji-zui-da-zi-shu-zu-by-leetcode-solution/,
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
local_min, local_max, res = nums[0], nums[0], nums[0]
for i in xrange(1, len(nums)):
tmp_min = min([local_min * nums[i], local_max * nums[i], nums[i]])
local_max = max([local_max * nums[i], local_min * nums[i], nums[i]])
local_min = tmp_min
res = max(local_max, res)
return res
740. 删除与获得点数
https://leetcode-cn.com/problems/delete-and-earn/
给定一个整数数组 nums ,你可以对它进行一些操作。每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除每个等于 nums[i] - 1 或 nums[i] + 1 的元素。开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。
示例 1:输入: nums = [3, 4, 2],输出: 6,解释: 删除 4 来获得 4 个点数,因此 3 也被删除。之后,删除 2 来获得 2 个点数。总共获得 6 个点数。
示例 2:输入: nums = [2, 2, 3, 3, 3, 4],输出: 9,解释: 删除 3 来获得 3 个点数,接着要删除两个 2 和 4 。之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。总共获得 9 个点数。
注意:nums的长度最大为20000。每个整数nums[i]的大小都在[1, 10000]范围内。
题解
一:rec[i]记录每个值i出现了几次,dp[i]表示截止i,目前能获得的最大的点数,那么对rec进行动态规划,其实就是打家劫舍的一种变种。考虑是否选择添加点数i时,综合比较添加i-1,和i-2以及i的值。
class Solution(object):
def deleteAndEarn(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
rec = [0] * 10001
dp = [0] * 10001
for num in nums:
rec[num] += 1
dp[1] = rec[1] * 1
for i in xrange(2, len(rec)):
dp[i] = max(dp[i - 2] + rec[i] * i, dp[i - 1])
return dp[-1]
二:其实和思路一一样,只不过借用了python的结构,其中avoid表示不添加当前点数,using表示添加当前的点数。
class Solution(object):
def deleteAndEarn(self, nums):
count = collections.Counter(nums)
prev = None
avoid = using = 0
for k in sorted(count):
if k - 1 != prev:
avoid, using = max(avoid, using), k * count[k] + max(avoid, using)
else:
avoid, using = max(avoid, using), k * count[k] + avoid
prev = k
return max(avoid, using)
三:把pythonic改写一下,其实用的是未变化的avoid
avoid, using = max(avoid, using), k * count[k] + max(avoid, using)
相当于,如下:
class Solution(object):
def deleteAndEarn(self, nums):
count = collections.Counter(nums)
prev = None
avoid = using = 0
for k in sorted(count):
if k - 1 != prev:
tmp = max(avoid, using)
using = k * count[k] + max(avoid, using)
avoid = tmp
else:
tmp = max(avoid, using)
using = k * count[k] + avoid
avoid = tmp
prev = k
return max(avoid, using)
121. 买卖股票的最佳时机
https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
题解
一:其实目前的最大值cur_max减去他前面的最小值cur_min中的最大就是解。因为必须先买再卖,所以如果当前值比最小值还小,就得看他后面的峰值,此时cur_max必须更新成现在的值,因为卖出必须先买入。
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if not prices:
return 0
res, cur_min, cur_max = 0, prices[0], prices[0]
for i in xrange(len(prices)):
if prices[i] > cur_max:
cur_max = prices[i]
res = max(cur_max - cur_min, res)
elif prices[i] < cur_min:
cur_min, cur_max = prices[i], prices[i]
return res
337. 打家劫舍 III
https://leetcode-cn.com/problems/house-robber-iii/
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
题解
一:打家劫舍的1和2,可以用一个列表来表示截止目前房子可盗取的最高金额。
- 当不盗取当前屋子可以直接用截止上一个屋子盗取的最高金额。由于前两题房子可以用下标表示,而树结构不便于用下标表示,这边可以用字典表示。这一题在这种情况下也很好解决,因为可以直接拿到截止子节点盗取的最高金额。
- 当盗取当前屋子,那么就是当前屋子加上截止上上一个屋子能盗取的最高金额,这时候用列表很好找到。但是本题不好找,因为找到孙子节点还是要费点事的。我们可以将截止孙节点的最高金额记到其父节点(当前节点的子节点)上。那么每个节点及两种状态,不取当前节点,则最高金额是其子节点(子节点可取可不取)的的最高金额。取当前节点,则最高金额是不取子节点的金额加上当前节点的金额。
字典rec的记当前节点的两种状态,下标0表示不取当前节点,则对子节点无限制,可取子节点两种状态的最大值。下标1表示取当前节点,则对子节点有限制(不能取子节点)。
class Solution(object):
def __init__(self):
self.rec = {}
def rob(self, root):
"""
:type root: TreeNode
:rtype: int
"""
if not root:
return 0
self.helper(root)
return max(self.rec[root])
def helper(self, root):
if not root:
return [0, 0]
if root in self.rec:
return self.rec[root]
left_child = self.helper(root.left)
right_child = self.helper(root.right)
self.rec[root] = [0, 0]
self.rec[root][0] = (max(left_child[0], left_child[1])
+ max(right_child[0], right_child[1]))
self.rec[root][1] = left_child[0] + right_child[0] + root.val
return self.rec[root]
416. 分割等和子集
https://leetcode-cn.com/problems/partition-equal-subset-sum/
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:每个数组中的元素不会超过 100。数组的大小不会超过 200
示例 1:输入: [1, 5, 11, 5],输出: true。解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:输入: [1, 2, 3, 5],输出: false。解释: 数组不能分割成两个元素和相等的子集.
题解
一:首先列表元素小于2的一定不符合,列表元素和不为偶数的也是False。剩下的我们可以完全当作0-1背包的问题,即是容量为target的背包,用nums中的元素(体积),能达到的最大价值(每个元素的价值是其本身)是多少。最后我们只要看这个最大价值是不是target。
class Solution(object):
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
n = len(nums)
if n == 0:
return True
if n == 1:
return False
target = sum(nums)
if target % 2:
return False
target //= 2
dp = [0] * (target + 1)
for i in range(n):
for j in range(target, -1, -1):
if j >= nums[i]:
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])
if dp[-1] == target:
return True
return False
法二:典型的背包问题,在n个物品中选出一定物品,填满target的背包,F(n,C)考虑将n个物品填满容量为C的背包。。第一种情况,我们先看i-1个物品能否填满c的空间,如果能,那么多一种选择也能;第二种情况,我们考虑前i-1个物品能否填满c-w(i)的空间,若能,则放入第i个物品即可填满c的空间。
class Solution(object):
def __init__(self):
self.rec = None
def helper(self, capacity, idx, nums):
if capacity == 0:
return True
if idx < 0 or capacity < 0:
return False
if self.rec[idx][capacity] != -1:
return self.rec[idx][capacity]
res = (self.helper(capacity, idx - 1, nums)
or self.helper(capacity - nums[idx], idx - 1, nums))
self.rec[idx][capacity] = res
return res
def canPartition(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
n = len(nums)
if n == 0:
return True
if n == 1:
return False
target = sum(nums)
if target % 2:
return False
target //= 2
self.rec = [[-1] * (target + 1) for _ in range(n)]
return self.helper(target, n - 1, nums) == 1
法三:法二的动态规划版本。
class Solution(object):
def canPartition(self, nums):
n = len(nums)
if n == 0:
return True
if n == 1:
return False
target = sum(nums)
if target % 2:
return False
target //= 2
rec = [False] * (target + 1)
for c in range(target + 1):
rec[c] = (c == nums[0])
for i in range(1, n):
for c in range(target, nums[i] - 1, -1):
rec[c] = (rec[c] or rec[c - nums[i]])
return rec[-1]
322. 零钱兑换
https://leetcode-cn.com/problems/coin-change/submissions/
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:输入: coins = [1, 2, 5], amount = 11。输出: 3 。解释: 11 = 5 + 5 + 1
示例 2:输入: coins = [2], amount = 3。输出: -1
说明:你可以认为每种硬币的数量是无限的。
题解
一:动态规划。这种做法和硬币只能用一次并没有太多的区别。
class Solution(object):
def coinChange(self, coins, amount):
"""
:type coins: List[int]
:type amount: int
:rtype: int
"""
n = len(coins)
if amount == 0:
return 0
if amount < 0 or n == 0:
return -1
dp = [[amount + 1] * (amount + 1) for _ in range(n)]
dp[0][0] = 0
for j in range(amount + 1):
if j % coins[0] == 0:
dp[0][j] = j // coins[0]
for i in range(1, n):
dp[i][0] = 0
for j in range(amount + 1):
dp[i][j] = dp[i - 1][j]
cnt = j // coins[i]
for k in range(cnt + 1):
dp[i][j] = min(min(dp[i - 1][j],dp[i][j]),
dp[i - 1][j - k * coins[i]] + k)
return dp[-1][-1] if dp[-1][-1] != (amount + 1) else -1
二:对一的空间优化,顺带也稍微优化了一下时间。注意这种做法与只能取一次的区别,即0-1背包问题。区别在于,这边的内层循环是从左往右的,即由小到大(而不是0-1背包的由大到小)
0-1背包内层循环从右往左是因为,真正的递推公式是,即每次用到的都是截止上一个硬币的值,当作空间优化后,只有一行,我们从后面往前更新,可以理解为每次用到的都是未更新前的值,也即截止上一个硬币的相关值。参见https://blog.csdn.net/qq_xuanshuang/article/details/104025707。
本题:硬币可以无限次使用,当作了空间优化后,同样只有一行,当时我们希望每次的值都是用的截止当前硬币的值,因为在本次用该硬币时,之前可能用过相同值的硬币,即我们期待拿到的是更新后的值,这种适合从左往右更新。
class Solution(object):
def coinChange(self, coins, amount):
n = len(coins)
if amount == 0:
return 0
if amount < 0 or n == 0:
return -1
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for j in range(amount + 1):
if j % coins[0] == 0:
dp[j] = j // coins[0]
for i in range(1, n):
for j in range(coins[i], amount + 1):
dp[j] = min(dp[j], dp[j - coins[i]] + 1)
return -1 if dp[-1] == (amount + 1) else dp[-1]
1027. 最长等差数列
给定一个整数数组 A,返回 A 中最长等差子序列的长度。回想一下,A 的子序列是列表 A[i_1], A[i_2], ..., A[i_k] 其中 0 <= i_1 < i_2 < ... < i_k <= A.length - 1。并且如果 B[i+1] - B[i]( 0 <= i < B.length - 1) 的值都相同,那么序列 B 是等差的。
提示:2 <= A.length <= 2000。0 <= A[i] <= 10000
题解
一:这题和873. 最长的斐波那契子序列的长度(https://leetcode-cn.com/problems/length-of-longest-fibonacci-subsequence/),https://blog.csdn.net/qq_xuanshuang/article/details/105023133一样。
from collections import defaultdict
class Solution(object):
def longestArithSeqLength(self, A):
"""
:type A: List[int]
:rtype: int
"""
index = {}
for i in range(len(A)):
if A[i] not in index:
index[A[i]] = set()
index[A[i]].add(i)
longest = defaultdict(lambda: 2)
res = max(collections.Counter(A).values())
for i in range(len(A)):
for j in range(i):
delta = A[i] - A[j]
idx_set = index.get(A[j] - delta)
if idx_set is None:
continue
for idx in idx_set:
if idx is not None and idx < j:
longest[j, i] = max(longest[j, i], longest[idx, j] + 1)
res = max(res, longest[j, i])
return max(res, min(2, len(A)))
二:也可以先确定,等差数列的最后两个数字,这样他前面的数字也出现了,类似斐波那契题目一样,这边也涉及到两个数之间的关系,所以用二维数组,字典表示longest[i,j]表示以i和j结尾的最长的等差数列的长度。
from collections import defaultdict
class Solution(object):
def longestArithSeqLength(self, A):
res = 2
rec = defaultdict(int)
longest = defaultdict(lambda: 2)
for i in range(len(A)):
for j in range(i + 1, len(A)):
target = 2 * A[i] - A[j]
if target in rec:
longest[i, j] = longest[rec[target], i] + 1
res = max(res, longest[i,j])
rec[A[i]] = i
return max(res, min(2, len(A)))