python小练(3)

173 阅读9分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情


1. 求组成三角形的最大边长

给定由一些正整数(代表长度)组成的列表 Arr,返回由其中三个长度组成的、面积不为零的三角形的最大周长。

输入格式:

输入一个列表,包含n个整数。 3 ≤ n ≤ 10000

整数的范围:[1,106]

输出格式:

输出一个整数代表能组成的三角形的最大周长。

若不能形成任何面积不为零的三角形,返回 0。

输入样例:

[2,1,2]

输出样例:

5

输入样例:

[1,2,1]

输出样例:

0

输入样例:

[3,6,2,3]

输出样例:

8

分析🤔:

组成三角形的三条边的关系是

任意两边之和大于第三边,任意两边之差小于第三边。

假设三角形的边长满足 a ≤ b ≤ c,那么这三条边组成面积不为零的三角形 的充分必要条件为a + b > c

正常情况下,对于三条边长,判断能否组成三角形需要判断任何两条边长相加都大于其余的 一条边长,即: a + b > c && a + c > b && b + c > a

而如果已知 a <= b <= c,那么必然有:

  1. a + c > b,因为 c >= b,那 c 加上一个正数一定就比 b 大了。而题目里说所数都 >= 1,所以 c 加上 a 一定比 b 大。
  2. b + c > a,因为 b 和 c 至少跟 a 一样大(b >= a, c >= a),加起来的结果至少有 2a, 即 b + c >= 2a > a 所以最终只需要判断 a + b > c 即可。 所以本题我们可以采用 贪心+排序 算法,我们可以选择枚举三角形的最长边 c,而从贪心的角 度考虑,我们一定是选「小于 c 的最大的两个数」作为边长 a 和 b,此时最有可能满足 a + b>c,使得三条边能够组成一个三角形,且此时的三角形的周长是最大的。

因此,我们先对整个数组排序,倒序枚举 第 i 个数作为最长边,那么我们只要看其前两 个数 Arr[i−2] 和 Arr[i−1],判断 Arr[i−2] + Arr[i−1] 是否大于 Arr[i] 即可,如果能组 成三角形我们就找到了最大周长的三角形,返回答案 Arr[i−2] + Arr[i−1] + Arr[i] 即可。如 果对于任何数作为最长边都不存在面积不为零的三角形,则返回答案 0。

def GetMax_PM(li):
    li.sort()
    for i in range(len(li)-1,1,-1):
        if li[i-2] + li[i-1] > li[i]:
            return li[i-2] + li[i-1] + li[i]
    return 0
    
if __name__ == "__main__":
    li = eval(input())
    print(GetMax_PM(li))

时间复杂度O(nlogn),其中 n 是数组 A 的长度。

2. H指数

H 指数的定义:H 代表“高引用次数”(high citations),一名科研人员的 H指数是指他(她)的 (N 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。且其余的 N - h 篇论文每篇被引用次数 不超过 h 次。

给定一位研究者论文被引用次数的列表(被引用次数是非负整数)。请你编写一个方法,帮助计算出研究者的 H 指数。

eg:某人的 H 指数是 20,这表示他已发表的论文中,每篇被引用了至少 20 次的论文总共有 20 篇。

输入格式:

输入一个列表,代表该研究者的论文被引用的次数。列表长度是该研究者的论文数量。

输出格式:

输出该研究者的 H指数 。

输入样例:

[3, 0, 6, 1, 5]

输出样例:

3

样例说明:

给定列表表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。
由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。

输入样例:

[1, 3, 3, 9, 5, 2, 10]

输出样例:

3

代码长度限制 16 KB

时间限制 100 ms

内存限制 64 MB

分析🤔:

排序算法

首先我们将引用次数降序排序,在排完序的数组 Arr 中,如果 Arr[i] > i,那么说明第 0 到 i 篇论文都有至少 i + 1 次引用。因此我们只要找到最大的 i 满足 Arr[i] > i,那么 h 指数即为 i+1。例如:

i0123456
引用次数10953321
citations[i]truetruetruefalsefalsefalsefalse

其中最大的满足 Arr[i] > i 的 i 值为 2,因此 h = i + 1 = 3。

def H_temp(h):
    h.sort()
    i = 0
    while (i < len(h) and i < h[len(h) - 1 - i]):
        i += 1
    return i
    
    
if __name__ == "__main__":
    h = eval(input())
    print(H_temp(h))

时间复杂度O(nlogn)

3. 买卖股票含手续费

股票有交易手续费,并且股票一般不限交易次数。

也就是说:你可以无限次地完成交易,但是你每笔交易都需要付手续费。你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

Alan想请你帮助算一下获得利润的最大值。

注意 :这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

输入格式:

给定一个整数列表 prices,其中第 i 个元素代表了第 i 天的股票价格 ;

第二行一个非负整数 fee 代表了交易股票的手续费用。

  • 0 < prices.length <= 50000.
  • 0 < prices[i] < 50000.
  • 0 <= fee < 50000.

输出格式:

输出一个整数代表最大利润。

输入样例:

[1, 3, 2, 8, 4, 9]
2

输出样例:

8

样例说明:

能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.

输入样例:

[1, 3, 7, 5, 10, 3]
3

输出样例:

6

样例说明:

能够达到的最大利润:  
在此处买入 prices[0] = 1
在此处卖出 prices[4] = 10
最大利润: (10 - 1) - 3 = 6

代码长度限制 16 KB

时间限制 400 ms

内存限制 64 MB

分析🤔:

3.1 动态规划解法

1. 定义状态转移方程

定义二维数组 dp[n][2]:

  • dp[i][0] 表示第 i 天不持有可获得的最大利润;
  • dp[i][1] 表示第 i 天持有可获得的最大利润(注意是第 i 天持有,而不是第 i 天买入)。

定义状态转移方程:

  • 不持有:dp[i][0] = max(dp[i−1][0], dp[i−1][1] + prices[i] − fee)

对于今天不持有,可以从两个状态转移过来:

  1. 昨天也不持有;2. 昨天持有,今天卖出。两者取较大值。 持有:dp[i][1] = max( dp[i−1][1], dp[i−1][0] − prices[i]) 对于今天持有,可以从两个状态转移过来:
  2. 昨天也持有;2. 昨天不持有,今天买入。两者取较大值。
  • 持有:dp[i][1] = max( dp[i−1][1], dp[i−1][0] − prices[i])

对于今天持有,可以从两个状态转移过来:

  1. 昨天也持有;
  2. 昨天不持有,今天买入。两者取较大值。

2. 给定转移方程初始值

对于第 0 天:

  • 不持有: dp[0][0]=0
  • 持有(即花了 price[0] 的钱买入):dp[0][1] = −prices[0]

代码如下:

def maxProfit(prices, fee):
    n = len(prices)
    dp = [[0, -prices[0]]] + [[0, 0] for _ in range(n - 1)]
    for i in range(1, n):
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
    return dp[n - 1][0]


if __name__ == "__main__":
    p = eval(input())
    f = int(input())
    print(maxProfit(p, f))

以上动态规划还可以优化:转移的时候,dp[i] 只会从 dp[i−1] 转移得来,因此第一维可 以去掉:

def max_profit(prices, fee):
    n = len(prices)
    dp = [0] * 2
    dp[0] = 0
    dp[1] = -prices[0]
    for i in range(1, n):
        dp[0] = max(dp[0], dp[1] + prices[i] - fee)
        dp[1] = max(dp[1], dp[0] - prices[i])
    return dp[0]


if __name__ == "__main__":
    prices = eval(input())
    f = int(input())
    print(max_profit(prices, f))

时间复杂度O(n),遍历一遍即可。

3.2 贪心算法

上面我们将手续费放在卖出时考虑,我们换一个思路,将手续费放在买入时,此时我们就可 以得到基于贪心算法的一种解题方案:当我们卖出一支股票时,我们就立即获得了以相同价 格并且免除手续费买入一支股票的权利。在遍历完整个数组 prices 之后之后,我们就得到了 最大的总收益。

我们用 buy 表示在最大化收益的前提下,如果我们手上拥有一支股票,那么它的最低 买入价格是多少。在初始时,buy 的值为 prices[0] 加上手续费 fee。那么当我们遍历到第 i (i>0) 天时:

  • 如果当前的股票价格 prices[i] 加上手续费 fee 小于 buy,那么与其使用 buy 的价格购买股票,我们不如以 prices[i] + fee 的价格购买股票,因此我们将 buy 更新为prices[i] + fee;
  • 如果当前的股票价格 prices[i] 大于 buy,那么我们直接卖出股票并且获得 prices[i] − buy 的收益。但实际上,我们此时卖出股票可能并不是全局最优的(例如下一天股票价格继续上升),因此我们可以提供一个反悔操作,看成当前手上拥有一支买入价格为prices[i] 的股票,将 buy 更新为 prices[i]。这样一来,如果下一天股票价格继续上升,我们会获得 prices[i+1] − prices[i] 的收益,加上这一天 prices[i] − buy 的收益,恰好就等于在这一天不进行任何操作,而在下一天卖出股票的收益;
  • 对于其余的情况,prices[i] 落在区间 [buy−fee, buy] 内,它的价格没有低到我们放弃手上的股票去选择它,也没有高到我们可以通过卖出获得收益,因此我们不进行任何操作。

代码如下:

def max_profit(prices, fee):
    n = len(prices)
    buy = prices[0] + fee
    sum_profit = 0
    for i in range(1, n):
        if prices[i] + fee < buy:
        buy = prices[i] + fee
        if prices[i] > buy:
            sum_profit += prices[i] - buy
            buy = prices[i]
    return sum_profit
    
    
if __name__ == "__main__":
    prices = eval(input())
    f = int(input())
    print(max_profit(prices, f))

时间复杂度O(n),其中 n 为数组的长度。