A robber is planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
(a) Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
(b) What if all houses are arranged in a circle?
针对“Money Robbing”问题,可分为线性街道和环形街道的情况。
自然语言描述: 使用动态规划来解决这个问题。对于每个房子,强盗有两个选择:抢或不抢。如果他决定抢劫第i
个房子,那么他不能抢劫第i-1
个房子;如果他不抢劫第i
个房子,那么他可以从前i-1
个房子中获得的最大金额。
伪代码:
rob(houses):
# 如果没有房子,返回0
if houses is empty:
return 0
# 如果只有一个房子,返回该房子的金额
if length of houses is 1:
return houses[0]
# 初始化动态规划数组
dp[0] = houses[0] # 只有一个房子时的最大金额
dp[1] = max(houses[0], houses[1]) # 前两个房子中的最大金额
# 从第三个房子开始,计算每个房子的最大抢劫金额
for i from 2 to length of houses - 1:
# dp[i] 是前i个房子的最大金额,等于以下两者中的较大者:
# 1. 不抢第i个房子,金额为dp[i-1]
# 2. 抢第i个房子,金额为dp[i-2]加上第i个房子的金额
dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])
# 返回最后一个房子的最大抢劫金额
return dp[length of houses - 1]
i
个房子,最大抢劫金额是基于前i-1
个房子的最大抢劫金额决定的。dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])
i-1
个房子的最大金额我们已经知道,那么对于第i
个房子,我们可以选择不抢(保持dp[i-1]
),或者抢(dp[i-2] + houses[i]
)。这两种选择保证了我们总是得到最大金额。O(n)
,其中n
是房子的数量。O(n)
,用于存储每个房子的最大抢劫金额。自然语言描述: 在环形街道的情况下,问题变成了不能同时抢第一个和最后一个房子。我们可以把问题分成两个子问题:一个不包括第一个房子,另一个不包括最后一个房子。对这两个子问题分别使用上面的动态规划算法,然后取两者的最大值。
伪代码:
rob_circular(houses):
if houses is empty:
return 0
if length of houses is 1:
return houses[0]
return max(rob(houses[1:]), rob(houses[:-1]))
O(n)
,其中n
是房一个具体的例子并用python实现:
假设有一个街道,房屋中的金额分别为 [1, 2, 3, 1]。在这种情况下,强盗应该选择第二个和第三个房子来抢劫,以获得最大金额 2 + 3 = 5。
def rob_linear(houses):
if not houses:
return 0
if len(houses) == 1:
return houses[0]
dp = [0] * len(houses)
dp[0] = houses[0]
dp[1] = max(houses[0], houses[1])
for i in range(2, len(houses)):
dp[i] = max(dp[i - 1], dp[i - 2] + houses[i])
return dp[-1]
def rob_circular(houses):
if not houses:
return 0
if len(houses) == 1:
return houses[0]
return max(rob_linear(houses[:-1]), rob_linear(houses[1:]))
# 测试例子
houses = [1, 2, 3, 1]
print("Max amount in linear street:", rob_linear(houses))
print("Max amount in circular street:", rob_circular(houses))
An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5. Given an integer
n, return the nth ugly number.
(a) Using a brute-force algorithm to solve this problem, analyze the time complexity of your implemented brute-force algorithm and explain why the algorithm’s time complexity is O(n2), where n is the number of points.
(b) Propose an improved algorithm to solve this problem with a time complexity better than the brute-force algorithm. Describe the algorithm’s idea and analyze its time complexity.
丑数是只包含质因数2、3和5的正整数。给定一个整数n
,返回第n
个丑数。
n
个丑数。find_nth_ugly_number(n):
count = 0
number = 1
while count < n:
if is_ugly(number):
count += 1
number += 1
return number - 1
is_ugly(num):
for i in [2, 3, 5]:
while num % i == 0:
num /= i
return num == 1
improved_find_nth_ugly_number(n):
ugly_numbers = [1]
i2, i3, i5 = 0, 0, 0
while len(ugly_numbers) < n:
next2, next3, next5 = ugly_numbers[i2] * 2, ugly_numbers[i3] * 3, ugly_numbers[i5] * 5
next_ugly = min(next2, next3, next5)
ugly_numbers.append(next_ugly)
if next_ugly == next2:
i2 += 1
if next_ugly == next3:
i3 += 1
if next_ugly == next5:
i5 += 1
return ugly_numbers[-1]
n
个丑数依赖于之前的丑数。ugly_numbers[n] = min(ugly_numbers[i2] * 2, ugly_numbers[i3] * 3, ugly_numbers[i5] * 5)
。到列表的丑数是当前最小的丑数。通过乘以2、3和5并比较,我们可以保证按顺序生成丑数,并且不会遗漏任何丑数。
暴力算法简单直接,但效率低下,因为它需要为每个数都检查是否为丑数。改进的算法则通过维护三个队列,每次只计算可能成为下一个丑数的数,大大提高了效率。通过这种方法,我们可以按顺序生成丑数,并且保证每次添加的都是当前最小的丑数,从而有效地解决了这个问题。
Given n, how many structurally unique BST’s (binary search trees) that store values 1…n? Note: Given n = 3, there are a total of 5 unique BST’s:
给定一个整数n
,计算存储值1到n
的结构上唯一的二叉搜索树(BST)的数量。
n
,我们可以将每个数i
(1 到 n
)作为根节点,然后左子树由小于i
的数构成,右子树由大于i
的数构成。对于每个i
,其唯一的BST数量等于左子树的BST数量乘以右子树的BST数量。我们可以计算从1到n
的所有可能性,并将它们相加得到总数。unique_bst(n):
# 如果n小于等于1,直接返回1,因为没有或只有一个节点时只有一种BST
if n <= 1:
return 1
# 初始化动态规划数组,长度为n+1,初始值都设为0
dp = [0] * (n + 1)
# dp[0]和dp[1]都是1,因为没有节点或只有一个节点时只有一种情况
dp[0] = 1
dp[1] = 1
# 从2到n遍历,计算每个数i的唯一BST数量
for i in range(2, n + 1):
# 遍历所有可能的根节点j
for j in range(1, i + 1):
# dp[i]是以j为根节点的BST数量,等于左子树的BST数量乘以右子树的BST数量
# dp[j-1]是左子树的BST数量,dp[i-j]是右子树的BST数量
dp[i] += dp[j - 1] * dp[i - j]
# 返回存储值1到n的唯一BST的总数量
return dp[n]
dp[i] += dp[j - 1] * dp[i - j]
,其中dp[i]
是存储值1到i
的唯一BST的数量,j
是根节点的值。j
,其左子树由`[1,j-1]的数构成,右子树由
[j+1, i]`的数构成。
dp[j-1]
,右子树的唯一BST数量为dp[i-j]
。因此,以j
为根节点的唯一BST的总数为dp[j-1] * dp[i-j]
。j
的唯一BST数量,我们得到了dp[i]
的值。^2),其中n是给定的数字。这是因为我们需要两层循环来计算每个数i
(从2到n
)的唯一BST数量,内层循环对于每个i
遍历从1到i
的所有可能的根节点j
。
dp
,其中dp[i]
表示存储值1到i
的唯一BST的数量。总体而言,这个动态规划算法通过计算较小问题的解来有效地构建更大问题的解,从而避免了重复的计算工作,并能够准确地计算出给定n
的唯一二叉搜索树的数量。
Largest Divisible Subset
Given a set of distinct positive integers, find the largest subset such that every pair (Si, Sj) of elements in this subset satisfies: Si%Sj = 0 or Sj%Si = 0. Please return the largest size of the subset.
Note: Si%Sj = 0 means that Si is divisible by Sj.
给定一组不同的正整数,找出最大的子集,使得该子集中的每一对元素(Si, Sj)满足:Si%Sj = 0或Sj%Si = 0。返回这个子集的最大大小。
自然语言描述:首先,将数组排序。排序后,任何两个相邻的元素,较小的数能整除较大的数的可能性更大。然后,使用动态规划来找到最大可整除子集。对于每个元素,检查它能整除哪些之前的元素,并更新它能构成的最大子集的大小。最后,返回所有元素中最大子集的大小。
伪代码:
largest_divisible_subset(nums):
if not nums:
return 0
# 对数组进行排序
nums.sort()
# 初始化dp数组,每个位置表示以当前元素结尾的最大子集大小
dp = [1 for _ in nums]
# 遍历数组,更新每个位置的最大子集大小
for i in range(1, len(nums)):
for j in range(i):
if nums[i] % nums[j] == 0:
dp[i] = max(dp[i], dp[j] + 1)
# 返回最大的子集大小
return max(dp)
nums[i]
,它能构成的最大可整除子集取决于它之前的元素nums[j]
(j < i)所能构成的最大子集,并且nums[i]
能整除nums[j]
。dp[i] = max(dp[i], dp[j] + 1)
,其中j < i
且nums[i] % nums[j] == 0
。nums[j]
能整除nums[i]
,那么对于任何k < j
,nums[k]
也能整除nums[i]
。You are given an integer array nums and an integer target.
You want to build an expression out of nums by adding one of the symbols ’+’ and ’-’ before each integer in nums and then concatenate all the integers.
For example, if nums = [2, 1], you can add a ’+’ before 2 and a ’-’ before 1 and concatenate them to build the expression ”+2-1”.
Return the number of different expressions that you can build, which evaluates to target. Example:
Input: nums = [1,1,1,1,1], target = 3
Output: 5
Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
给定一个整数数组nums
和一个整数目标target
。你想通过在nums
中的每个整数前添加符号’+‘或’-',然后将所有整数串联起来来构建一个表达式。返回你可以构建的、计算结果为target
的不同表达式的数量。
自然语言描述:这个问题可以通过动态规划解决。我们可以将问题转换为子集求和问题。计算数组中元素能组成的所有可能的和,并统计其中等于target
的数量。对于数组中的每个元素,我们可以选择加上它或减去它。我们使用一个字典来存储中间结果,键为可能的和,值为达到这个和的方法数量。
伪代码:
find_target_sum_ways(nums, target):
dp = {0: 1} # 初始化,和为0的方法有1种
# 遍历数组中的每个元素
for num in nums:
temp = {}
# 遍历当前可能的和及其对应的方法数
for sum in dp:
# 计算加上或减去当前元素后的和
temp[sum + num] = temp.get(sum + num, 0) + dp[sum]
temp[sum - num] = temp.get(sum - num, 0) + dp[sum]
dp = temp
# 返回达到目标和的方法数
return dp.get(target, 0)
dp[sum + num] += dp[sum]
和 dp[sum - num] += dp[sum]
,这里dp[sum]
是在没有考虑当前元素时达到和为sum
的方法数。target
的方法数就是所有可能性的总和。nums
的长度,m是所有可能的和的数量。对于每个元素,我们都需要遍历目前所有可能的和并更新它们,所以时间复杂度取决于这两个因素的乘积。综上所述,这个动态规划算法通过考虑数组中每个元素对可能和的影响,有效地找到了达到目标和target
的所有可能方法的数量。