info5011 week 3 - lecture examples

3.1 target sum

target sum可能是求一个array里是否有多个元素可以凑到K.
如果不要求连续性, 可以转化成背包的问题.
如果要求连续性(subarray是否和为K), 那就是前缀和版的two sum问题.
Leetcode 560 里问的是一共有多少个subarray满足和为K.

思路:

  1. brute force range sum. 先求前缀和然后遍历开始和结束位. 复杂度O(n^2).
  2. 前缀和版two-sum, 复杂度O(n):
    rangeSum[i~j] = prefixSum[j] - prefixSum[i]
    如果我们要凑一共rangeSum[i~j] = K, 则可以去已知的prefixSum记录里找有没有值为prefixSum[j] - KprefixSum[i].
    搜索某个值需要O(1), 那就只有hashmap/dictionary, key为和, value为这个和出现的次数. 因为ij前面, 所以只用关心已经出现过的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

你可能感兴趣的:(info5011 week 3 - lecture examples)