左神算法-初级8(python)

左神算法-初级8

  • 贪心策略:累加
    • 1、 金条和铜板
    • 2、 IPO
    • 3、 会议室项目宣讲
  • 递归和动态规划
    • 1、 汉诺塔问题
    • 2、打印一个字符串的所有子序列
    • 3、打印一个字符串的所有子串
    • 4、打印一个字符串的所有全排列
    • 5、母牛生子
    • 6、最小路径和
    • 7、一个数是否是数组中任意个数的和

贪心策略:累加

1、 金条和铜板

一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?

代价=金条长度
分割金条,返回最小分割代价:[60, [10,20,30]]

哈夫曼编码问题,结果为所有非叶节点的和;
构建小根堆:每次拿出最小的两个数,计算和,重新放入堆,直到只剩一个数即为结果(贪心算法)

# 自己构建小根堆
def little_heap(array):
    def heap(root, length):
        smallest = root
        left = root*2
        right = left + 1
        if left<length and array[left]<array[smallest]:
            smallest = left
        if right<length and array[right]<array[smallest]:
            smallest = right
        if smallest != root:
            array[root], array[smallest] = array[smallest], array[root]
            heap(smallest, length)
        
    for i in range(len(array)//2 + 1):
        heap(i, len(array))
    for i in range(len(array)-1, -1, -1):
        array[0], array[i] = array[i], array[0]
        heap(0, i)
    return array

array = [1,3,2,6,7,8,3,11]
print(little_heap(array))

def split_gold(arr):
    n = len(arr)
    while n>=2:
        tmp = little_heap(arr)
        a = tmp.pop() + tmp.pop()
        tmp.append(a)
        n = len(tmp)
    return tmp[0]

arr = [10, 20, 30]
print(split_gold(arr))


# 或者使用 heapq模块实现小根堆
def split_gold2(arr):
    import heapq
    
    heapq.heapify(arr)
    n = len(arr)
    while n>=2:
        a = arr.pop() + arr.pop()
        heapq.heappush(arr, a)
        n = len(arr)
    return arr[0]

arr = [10, 20, 30, 40]
print(split_gold2(arr))

# 其实可以直接用列表代替小根堆

2、 IPO

输入: 正数数组costs;正数数组profits;正数k;正数w。
costs[i]表示i号项目的花费(成本),profits[i]表示i号项目做完后在扣除花费之后还能挣到的钱(利润),k表示你不能并行,只能串行的最多做k个项目, w表示你初始的资金。

说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
输出: 你最后获得的最大钱数。

每次只能作一个项目,初始资金为w,求作k个项目后的最大资金

'''
思路:
构建一个小根堆,一个大根堆
小根堆按花费存放,花费最少的放入根部,大根堆按收益存放,收益高的放入根部
首先把花费小于初始资金w的放入大根堆(解锁项目),花费大于w的放入小根堆(未解锁项目),
完成一个项目后更新两个堆,直到完成k个项目或大根堆为空。
'''
def max_profit(k, w, profit, cost):
    node = [[0]*2]*len(profit)
    for i in range(len(profit)):
        node[i] = [cost[i], profit[i]]
    print(node)
#    # 不晓得如何用比较器去构建堆,可以用列表代替
#    def compare_cost(node1, node2):
#        return -(node1[0] - node2[0])
#    def compare_profit(node1, node2):
#        return node1[1] - node2[1]
    # key只能接受一个参数为一个的函数,所以使用lambda 或 functools.cmp_to_key()
    node.sort(key = lambda x:x[0]) # 按花费升序
    print(node)
    node2 = []
    for i in range(k):
        while node and node[0][0] <=:
            node2.append(node.pop(0))
        node2.sort(key = lambda x:x[1], reverse=True) # 按收益降序
        print('node2', node2)
        if not node2:
            return w
        w += node2.pop(0)[1]
        print('w', w)
    return w

k = 3
w = 10
cost = [5, 8, 15, 12]
profit = [5, 3, 10, 4]

print(max_profit(k, w, profit, cost))

3、 会议室项目宣讲

一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。
给你每一个项目开始的时间和结束的时间(给你一个数组,里面 是一个个具体的项目),
你来安排宣讲的日程,要求会议室进行 的宣讲的场次最多。返回这个最多的宣讲场次。

'''
思路:
贪心策略:
1、开始时间最早的项目先安排。反例:开始时间最早,但持续时间占了一整天,其他项目无法安排。
2、持续时间最短的项目先安排。反例:这样安排会导致结束时间在此期间和开始时间在此期间的所有项目不能安排。
3、最优策略:最先结束的项目先安排。
'''

# 输入会议时间数组:arr[i] = [i.start_time, i.end_time], 当前时间cur
# 输出可以安排的会议

def Schedule(arr, cur):
    arr.sort(key = lambda x:x[1]) # 按照结束时间升序
    res = []
    for i in range(len(arr)):
        if cur <= arr[i][0]:
            res.append(arr[i])
            cur = arr[i][1]
    return res

cur = 9
arr = [[8, 10],[9, 10],[9, 11],[10, 12]]
print(Schedule(arr, cur))

递归和动态规划

暴力递归:
1、把问题转化为规模缩小了的同类问题的子问题
2、有明确的不需要继续进行递归的条件(base case)
3、有当得到了子问题的结果之后的决策过程
4、不记录每一个子问题的解

动态规划:
1、从暴力递归中来
2、将每一个子问题的解记录下来,避免重复计算
3、把暴力递归的过程,抽象成了状态表达
4、并且存在化简状态表达,使其更加简洁的可能

P和NP问题
P指的是我明确地知道怎么算,计算的流程很清楚;而NP问题指的是我不知道怎么算,但我知道怎么尝试(暴力递归)。

1、 汉诺塔问题

leetcode 面试题 08.06. 汉诺塔问题

'''
思路:
三个杆:from,to,help
从from到to,可以分解为三步:
① 1 - n-1     from - help
② 单独的n     from - to
③ 1 - n-1     help - to   此时from成为辅助

更具体的:
n = 1 时,直接把盘子从 A 移到 C;
n > 1 时,
先把上面 n - 1 个盘子从 A 移到 B(子问题,递归);
再将最大的盘子从 A 移到 C;
再将 B 上 n - 1 个盘子从 B 移到 C(子问题,递归)。
注意:递归过程中,哪个是A or B or C要看传入时的位置,容易混肴


时间复杂度:O(2^n)
T(n) = T(n-1) + 1 + T(n-1) = 2T(n-1) + 1
等比数列,共 2^n - 1 步
''' 
def process(n, right, left, mid):
    # ② 单独的n     from - to
    if n == 1:
        print('move 1 from ' + right + ' to ' + left)
    else:
        # ① 1 - n-1     from - help
        process(n-1, right, mid, left)
        print('move ' + str(n) + ' from ' + right + ' to ' + left)
        # ③ 1 - n-1     help - to
        process(n-1, mid, left, right)
        
print(process(3, 'right', 'left', 'mid'))

2、打印一个字符串的所有子序列

'''
子串指串中相邻的任意个字符组成的串,而子序列可以是串中任意个不同字符组成的串
'''
# 递归实现
# 思路:取当前值或不取当前值,当游标达到最后时,返回取值结果即可
def all_son(s, i, res):
    if i == len(s):
        print(res)
        return 
    all_son(s, i+1, res)
    all_son(s, i+1, res+s[i])
    
s = 'abc'
print(all_son(s, 0, ''))

3、打印一个字符串的所有子串

# 非递归
def cut(s):
    results = []
    # x + 1 表示子字符串长度
    for x in range(len(s)):
        # i 表示偏移量(一个地方是起点,距离这个起点的距离,就是偏移量)
        for i in range(len(s) - x):
            results.append(s[i:i + x + 1])
    return results
 
print(cut('abc'))

4、打印一个字符串的所有全排列

#无论输入的字符串有多长,其排列出来的组合式样式均可分为“第一个字符串+剩下的字符串”的样式,
#可以通过遍历赋予第一位上不同的字符。那剩下的字符串又可以如上分解
def all_brother(ss):
    ret = []
    if len(ss) <= 1:
        return ss
    for i in range(len(ss)):
        for j in map(lambda x:ss[i] + x, all_brother(ss[:i]+ss[i+1:])):
            if j not in ret:
                ret.append(j)
    return ret
ss = 'abc'        
print(all_brother(ss))

# 借助内置模块
def all_brother2(ss):
    from itertools import permutations
    res = []
    for i in permutations(ss, len(ss)):
        res.append(i)
    return res

print(all_brother2(ss))

# 或者
def all_brother3(ss):
    if len(ss) <=1 :
        return ss
    res = []
    def dfs(tmp, ss):
        if not ss:
            res.append(tmp)
            return
        for i in range(len(ss)):
            dfs(tmp+[ss[i]], ss[:i]+ss[i+1:])
            
    dfs([], ss)
    # 到这里得到的结果是列表形式
    # 下面把列表变成字符串
    result = []
    for i in res:
        tmp = ''
        for a in i:
            tmp += str(a)
        result.append(tmp)
    return result

print(all_brother3(ss))

5、母牛生子

'''
母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。求N年后,母牛的数量。
前四年:1、2、3、4
第5年开始:f(n) = f(n-1) + f(n-3)
第n年的牛=去年的牛+新生牛, 3年前的牛都可以生小牛了f(n-3), 去年的牛都活着f(n-1)
'''
很简单,就不写了
剑指offer里面的青蛙跳台阶一样

6、最小路径和

'''
给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,
每一步只能向右或者向下。沿途经过的数字要累加起来。返回最小的路径和。
'''

#暴力递归
def minPathSum(matrix, i, j):
    if i == len(matrix)-1 and j == len(matrix[0])-1:
        return matrix[i][j]
    if i == len(matrix)-1:
        return matrix[i][j] + minPathSum(matrix, i, j+1)
    if j == len(matrix[0])-1:
        return matrix[i][j] + minPathSum(matrix, i+1, j)
    right = minPathSum(matrix, i, j+1)
    down = minPathSum(matrix, i+1, j)
    return matrix[i][j] + min(right, down)


matrix = [[9, 1, 0, 1],
          [4, 8, 1, 0],
          [1, 4, 2, 3]]

print(minPathSum(matrix, 0, 0))


#动态规划
def minPathSum2(matrix, i, j):
    if i == len(matrix)-1 and j == len(matrix[0])-1:
        return matrix[i][j]
    dp = [[0 for i in range(len(matrix[0])+1)] for j in range(len(matrix)+1)]
    for i in range(len(matrix)-1, -1, -1):
        dp[i][len(matrix[0])-1] = matrix[i][len(matrix[0])-1] + dp[i+1][len(matrix[0])-1]
        
    for j in range(len(matrix[0])-1, -1, -1):
        dp[len(matrix)-1][j] = matrix[len(matrix)-1][j] + dp[len(matrix)-1][j+1]
        
        
    for i in range(len(matrix)-2, -1, -1):
        for j in range(len(matrix[0])-2, -1, -1):
            dp[i][j] = min(dp[i+1][j], dp[i][j+1]) + matrix[i][j]
    return dp[0][0]

matrix = [[9, 1, 0, 1],
          [4, 8, 1, 0],
          [1, 4, 2, 3]]

print(minPathSum2(matrix, 0, 0))

7、一个数是否是数组中任意个数的和

'''
给你一个数组arr,和一个整数aim。如果可以任意选择arr中的数字,能不能累加得到aim,返回true或者false。
'''
# 穷举法
def isSum(arr, aim):
    # 穷举出数组中所有子序列组合
    def all_son(arr, i, tmp):
        if i == len(arr):
            res.append(tmp)
            return 
        all_son(arr, i+1, tmp)
        all_son(arr, i+1, tmp+[arr[i]])
    res = []
    all_son(arr, 0, [])
    print(res)
    # 遍历子序列组合的和
    for i in res:
        print(i)
        if sum(i) == aim:
            
            return True
            break
    return False

# 简化一下
def isSum2(arr, aim):
    def all_son(arr, i, tmp):
        if i == len(arr):
            return tmp == aim
        return all_son(arr, i+1, tmp) or all_son(arr, i+1, tmp+arr[i])
        
    return all_son(arr, 0, 0)

aim = 9
arr = [1, 3, 6]
print(isSum(arr, aim))
print(isSum2(arr, aim))
    
# 动态规划
'''
使用二维表存放状态值,横坐标为 m 的值,m=range(sum(arr)+1),
纵坐标为i的值,i=range(len(arr)+1)
二维表中最后一行的值是可以确定的,最后一行是数组中所有组合的加和,并且还包括了一些不存在的加和(没有子序列的和是该值)
从最后一行往上推,一直到(0,0)(到(0,0)也就是说考虑了所有可能的结果),如果为True,则返回True

dp[i][sum] 为 true 有两种情况:dp[i + 1][sum] 和 dp[i+1][sum+arr[i]]为 ture,两个有一个为 true 就可以了。
也就是加当前值或者不加当前值,和递归是一样的
'''
def isSum3(arr, aim):
    if sum(arr) < aim:
        return False
    n = len(arr)
    sum_arr = sum(arr)
    # 填好dp的最后一行,上面每一行的dp值都依赖下面一行的dp值得出
    dp = [[0 for i in range(sum_arr+1)] for i in range(n+1)]
    for j in range(sum_arr+1):
        if j == aim:
            dp[n][j] = True
        else:
            dp[n][j] = False
    for i in range(n-1, -1, -1):
        # 只需要关心 aim 之前的累加和就好了
        for j in range(aim, -1, -1):
            # 填充最后一列的 dp 值,其实一个为 true,正上方的位置都为 true
            # 这一步很有必要,否则j+arr[i]可能会大于sum,
            if j+arr[i] > aim:
                dp[i][j] = dp[i+1][j]
            else:
                dp[i][j] = dp[i+1][j] or dp[i+1][j+arr[i]]
    return dp[0][0] == True

aim = 9
arr = [1, 3, 6]
print(isSum3(arr, aim))

你可能感兴趣的:(左神算法-初级,Python,算法)