3.1 target sum
target sum可能是求一个array里是否有多个元素可以凑到K.
如果不要求连续性, 可以转化成背包的问题.
如果要求连续性(subarray是否和为K), 那就是前缀和版的two sum问题.
Leetcode 560 里问的是一共有多少个subarray满足和为K.
思路:
- brute force range sum. 先求前缀和然后遍历开始和结束位. 复杂度
O(n^2)
. - 前缀和版two-sum, 复杂度
O(n)
:
rangeSum[i~j] = prefixSum[j] - prefixSum[i]
如果我们要凑一共rangeSum[i~j] = K
, 则可以去已知的prefixSum记录里找有没有值为prefixSum[j] - K
的prefixSum[i]
.
搜索某个值需要O(1)
, 那就只有hashmap/dictionary,key
为和,value
为这个和出现的次数. 因为i
在j
前面, 所以只用关心已经出现过的prefixSum.
在计算prefixSum的时候, 需要累计某个和出现的次数, 因为可能有负数会产生值相同的和, 可以算作不同的组合.
#target sum is not knapsack because it needs to be subarray
# OJ: leetcode 560 - find total number of subarray number that meet the requirement
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
records = {0:1} # empty subarray to subtract from.
N = len(nums)
localSum = 0
count = 0
for i in range(0, len(nums)):
localSum +=nums[i]
if(localSum - k in records):
count+= records[localSum-k]
records[localSum] = records.get(localSum, 0) + 1
return count
3.2 2018 SPP Divisional H: Holiday
题目大意: 给定一个array, 选最小长度k 使无论从哪天开始的之后k天里都有array里所有的元素
思路
滑动窗口.
非常类似很多经典的string相关的滑动窗口的题.
#读input
L = int(input()) #array长度
arr = [int(i) for i in input().split(" ")] #array数字内容
K = len(set(arr)) #array里一共有几个不同元素
min_len = 1 #本题答案记在min_len里, 至少需要一天, 哪怕全部相同.
s = 0 #慢指针
f = 0 #快指针
local_set = {} #滑动窗口的local hashmap
local_set_elements_counter = 0 #记录当前滑动窗口里有几个不同元素的频率>0
#重点部分
for s in range(L): #移动慢指针
while(f
3.3 Nearest Smaller Elements
思路
单调栈 Monotonic Stack
从左到右Maintain一个单调递增栈 - 对于栈内每个元素, 它左边的元素是左方向离它最近的比它小的数字.
单调栈模板
单调递增:
for i in arr:
while(len(stk) > 0 and stk[-1]>=arr[i]):
stk.pop()
#do something
stk.push(arr[i])
单调递减:
for i in arr:
while(len(stk) > 0 and stk[-1]<=arr[i]):
stk.pop()
#do something
stk.push(arr[i])
desk check 过程:
初始:
arr = [1,3,4,2,5]
stk = []
res = ['_','_','_','_','_']
1入栈:
arr = [1,3,4,2,5]
stk = [1]
res = ['_','_','_','_','_']
3入栈之前先记录stk[-1]
(是1), 然后3入栈
arr = [1,3,4,2,5]
stk = [1,3]
res = ['_',1,'_','_','_']
4入栈之前先记录stk[-1]
(是3), 然后4入栈
arr = [1,3,4,2,5]
stk = [1,3,4]
res = ['_',1,3,'_','_']
因为2比栈顶的4小, 触发while loop.
2入栈之前先pop直到stk[-1]
比自己小为止结束.
此时stk变成只剩下[1]
, 此时在res里记录.
然后2入栈
arr = [1,3,4,2,5]
stk = [1,2]
res = ['_',1,3,1,'_']
最后5入栈, stk[-1]
是2. 结束`.
arr = [1,3,4,2,5]
stk = [1,2]
res = ['_',1,3,1,2]
完整代码
arr = [1,3,4,2,5,3,4,2]
stk = []
res = ['_' for i in arr]
for i in range(0, len(arr)):
while(len(stk) > 0 and stk[-1]>=arr[i]):
stk.pop()
if len(stk)>0:
res[i] = stk[-1]
stk.append(arr[i])
print(res)
3.4 Sliding Window Minimum
思路
单调队列 Monotonic Queue
单调队列的内部就是单调Stack, 这里因为是滑动窗口, 需要不断关注窗口左端排出的位置和窗口右端进来的位置.
这里是一个单调递增stack, 因为最小元素在list的0位而不是最后位.
这题还有DP解法.
class MonoQueue:
l = []
def __init__(self):
self.l = []
def push(self, e):
while(len(self.l)>0 and self.l[-1] > e): #以此保持最小位在list左端.
self.l.pop()
self.l.append(e)
def pop(self):
return self.l.pop(0) #左端排出.
def getMin(self): #最小位在list左端.
return self.l[0]
arr = [2,1,4,5,3,4,1,2]
k = 4
mq = MonoQueue()
ans = []
for i in range(len(arr)):
mq.push(arr[i])
if(i-k+1>=0): #当index i>k的时候, 第一次开始从队列左端(index i-k+1) 排出数字并记录答案.
ans.append(mq.getMin())
if(arr[i-k+1]==mq.getMin()): #如果当左端点就是MQ里最小的数字, 需要去除.
mq.pop()
print(ans)
3.5 & 3.6: 0/1 Knapsack
0/1背包的模板, 省去了一维
for i = [0....N):
for j = [V...cost[i]], j--: //注意j--
dp[j] = max(dp[j], dp[j - cost[i]] + value[i])
3.7: Complete Knapsack
完全背包的模板
for i = [0....N):
for j = [cost[i]...V], j++: //注意此处j++
dp[j] = max(dp[j], dp[j - cost[i]] + value[i])
3.8: Multiple Knapsack
多重背包的模板
背包求解部分就是0/1背包, 预处理部分重要.
for i=[0~N):
//获取第i个商品的消耗, 价值, 数量信息
itemCost = input
itemValue = input
itemNumber = input
int k = 1;
while k <= itemNumber :
// !! 以下这段重要
value[item] = itemValue * k
cost[item] = itemCost * k
itemNumber -= k
k = k*2
item += 1
//把转化过的商品当成普通商品存进value和cost, 方便之后使用正常0/1背包
if itemNumber>0:
value[item] = itemValue * itemNumber
cost[item] = itemCost * itemNumber
item +=1
3.9 2018 SPP Divisional J: Juice Machine
是leetcode里的coin change 1, USYD COMP3X27 tutorial里也出现过.
以往这题会问用至少几个硬币可以凑到某个数值, 硬币面值是整数. 这比赛题里硬币面值别出心裁的是fractional.
不过还好给的分子分母范围是1~10之间, 可以把1~10的最小公倍数乘到硬币面值和需要凑的数目上去, 变成整数的coin change 1.
M = 2520 #1~10的最小公倍数
l = input().split(" ")
target = int(l[0]) * M #需要凑的面值乘以公倍数
nCoin = int(l[1]) #一共有几个硬币
coins = [] #储存转化后的硬币面值
for i in range(nCoin):
l = input().split(" ")
coins.append(int(int(l[0])*M/int(l[1]))) #把最小公倍数乘到硬币面值上去, 让硬币面值变成整数.
'''
经典coin change 1
'''
def coinChange(coins, amount):
table = {}
x = amount
if x == 0:
return 0
coins.sort()
table[0] = 0
if x0:
table[i] = min(possible)
if amount not in table.keys():
return -1
return table[amount]
print(coinChange(coins, target))
3.10 POJ2559 Largest Rectangle in a Histogram
同leetcode原题, 也使用单调栈
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
heights.append(0)
stk = []
N = len(heights)
max_size = 0
for i in range(0, N):
while(len(stk)>0 and heights[stk[-1]]>=heights[i]):
index = stk.pop()
height = heights[index]
if(len(stk)==0):
max_size = max(max_size, height*i)
else:
max_size = max(max_size, height*(i - stk[-1] - 1))
stk.append(i)
return max_size